diff options
Diffstat (limited to 'drivers/input/touchscreen')
45 files changed, 31700 insertions, 1 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index ae33da7ab51f..2d564aabbc74 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -11,7 +11,9 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN -config TOUCHSCREEN_PROPERTIES +source "drivers/input/touchscreen/synaptics_dsx/Kconfig" + +config OF_TOUCHSCREEN def_tristate INPUT depends on INPUT @@ -115,6 +117,18 @@ config TOUCHSCREEN_ATMEL_MXT To compile this driver as a module, choose M here: the module will be called atmel_mxt_ts. +config TOUCHSCREEN_ATMEL_MAXTOUCH_TS + tristate "Atmel Maxtouch Touchscreen Family" + depends on I2C + help + Say Y here if you have Atmel MaXTouch Touchscreen + using i2c connected to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called atmel_maxtouch_ts. + config TOUCHSCREEN_AUO_PIXCIR tristate "AUO in-cell touchscreen using Pixcir ICs" depends on I2C @@ -1095,6 +1109,34 @@ config TOUCHSCREEN_COLIBRI_VF50 To compile this driver as a module, choose M here: the module will be called colibri_vf50_ts. +config TOUCHSCREEN_FT5X06_PSENSOR + tristate "FocalTech proximity feature support" + depends on TOUCHSCREEN_FT5X06 && SENSORS + help + Say Y here if you want to support ft5x06's proximity + feature. + + If unsure, say N. + +config TOUCHSCREEN_FT5X06_GESTURE + tristate "FocalTech gesture feature support" + depends on TOUCHSCREEN_FT5X06 + help + Say Y here if you want to support ft5x06's gesture + feature. + + If unsure, say N. + +config TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + bool "Synaptics DSX firmware update extra sysfs attributes" + depends on TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE + help + Say Y here to enable support for extra sysfs attributes + supporting firmware update in a development environment. + This does not affect the core or other subsystem attributes. + + If unsure, say N. + config TOUCHSCREEN_ROHM_BU21023 tristate "ROHM BU21023/24 Dual touch support resistive touchscreens" depends on I2C @@ -1106,4 +1148,81 @@ config TOUCHSCREEN_ROHM_BU21023 To compile this driver as a module, choose M here: the module will be called bu21023_ts. +config TOUCHSCREEN_MAXIM_STI + tristate "Maxim based STI touchscreens" + depends on SPI_MASTER + help + Say Y here if you have a touchscreen interface using the + Maxim STI based touch controller. + + If unsure, say N + + To compile this driver as a module, choose M here: the + module will be called maxim_sti. + +config SECURE_TOUCH + bool "Secure Touch" + depends on (TOUCHSCREEN_SYNAPTICS_I2C_RMI4 || \ + TOUCHSCREEN_SYNAPTICS_DSX_I2C_v21) + help + Say Y here to enable Secure Touch in supported drivers. + + If unsure, say N. + +config TOUCHSCREEN_GEN_VKEYS + tristate "Touchscreen Virtual Keys Driver" + help + Say Y here if you want to generate a sysfs entry for virtual + keys on Android. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called gen_vkeys. + +config TOUCHSCREEN_FT5X06 + tristate "FocalTech touchscreens" + depends on I2C + help + Say Y here if you have a ft5X06 touchscreen. + Ft5x06 controllers are multi touch controllers which can + report 5 touches at a time. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ft5x06_ts. + +config FT_SECURE_TOUCH + bool "Secure Touch support for Focaltech Touchscreen" + depends on TOUCHSCREEN_FT5X06 + help + Say Y here + -Focaltech touch driver is connected + -To enable secure touch for Focaltech touch driver + + If unsure, say N. + +config TOUCHSCREEN_IT7260_I2C + tristate "IT7260 Touchscreen Driver" + depends on I2C + help + Say Y here if you have a IT7260 Touchscreen Driver + connected to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called it7258_ts_i2c. + +config TOUCHSCREEN_ST + bool "STMicroelectronics Touchscreen Driver" + depends on I2C + help + Say Y here if you have a STMicroelectronics Touchscreen. + + If unsure, say N. + +source "drivers/input/touchscreen/st/Kconfig" + endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index cbaa6abb08da..f5be6fc19751 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_TOUCHSCREEN_AD7879_SPI) += ad7879-spi.o obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o obj-$(CONFIG_TOUCHSCREEN_AR1021_I2C) += ar1021_i2c.o obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.o +obj-$(CONFIG_TOUCHSCREEN_ATMEL_MAXTOUCH_TS) += atmel_maxtouch_ts.o obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR) += auo-pixcir-ts.o obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8318) += chipone_icn8318.o @@ -36,15 +37,19 @@ obj-$(CONFIG_TOUCHSCREEN_ELAN) += elants_i2c.o obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o obj-$(CONFIG_TOUCHSCREEN_FT6236) += ft6236.o +obj-$(CONFIG_TOUCHSCREEN_FT5X06) += ft5x06_ts.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o +obj-$(CONFIG_TOUCHSCREEN_GEN_VKEYS) += gen_vkeys.o obj-$(CONFIG_TOUCHSCREEN_ILI210X) += ili210x.o obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC) += imx6ul_tsc.o obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o obj-$(CONFIG_TOUCHSCREEN_INTEL_MID) += intel-mid-touch.o +obj-$(CONFIG_TOUCHSCREEN_IT7260_I2C) += it7258_ts_i2c.o obj-$(CONFIG_TOUCHSCREEN_IPROC) += bcm_iproc_tsc.o obj-$(CONFIG_TOUCHSCREEN_LPC32XX) += lpc32xx_ts.o obj-$(CONFIG_TOUCHSCREEN_MAX11801) += max11801_ts.o +obj-$(CONFIG_TOUCHSCREEN_MAXIM_STI) += maxim_sti.o obj-$(CONFIG_TOUCHSCREEN_MC13783) += mc13783_ts.o obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o @@ -64,6 +69,7 @@ obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_v21) += synaptics_dsx/ obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o @@ -91,3 +97,4 @@ obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o obj-$(CONFIG_TOUCHSCREEN_ZFORCE) += zforce_ts.o obj-$(CONFIG_TOUCHSCREEN_COLIBRI_VF50) += colibri-vf50-ts.o obj-$(CONFIG_TOUCHSCREEN_ROHM_BU21023) += rohm_bu21023.o +obj-$(CONFIG_TOUCHSCREEN_ST) += st/ diff --git a/drivers/input/touchscreen/atmel_maxtouch_ts.c b/drivers/input/touchscreen/atmel_maxtouch_ts.c new file mode 100644 index 000000000000..d07ca3dc7e46 --- /dev/null +++ b/drivers/input/touchscreen/atmel_maxtouch_ts.c @@ -0,0 +1,4210 @@ +/* + * Atmel maXTouch Touchscreen driver + * + * Copyright (c) 2014-2015, 2018 The Linux Foundation. All rights reserved. + * + * Linux foundation chooses to take subject only to the GPLv2 license terms, + * and distributes only under these terms. + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Copyright (C) 2011-2012 Atmel Corporation + * Copyright (C) 2012 Google, Inc. + * + * Author: Joonyoung Shim <jy0922.shim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/input/atmel_maxtouch_ts.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> + +#if defined(CONFIG_FB) +#include <linux/notifier.h> +#include <linux/fb.h> + +#elif defined(CONFIG_HAS_EARLYSUSPEND) +#include <linux/earlysuspend.h> +/* Early-suspend level */ +#define MXT_SUSPEND_LEVEL 1 +#endif + +#if defined(CONFIG_SECURE_TOUCH) +#include <linux/completion.h> +#include <linux/pm_runtime.h> +#include <linux/errno.h> +#include <linux/atomic.h> +#include <linux/clk.h> +#endif + +/* Configuration file */ +#define MXT_CFG_MAGIC "OBP_RAW V1" + +/* Registers */ +#define MXT_OBJECT_START 0x07 +#define MXT_OBJECT_SIZE 6 +#define MXT_INFO_CHECKSUM_SIZE 3 +#define MXT_MAX_BLOCK_WRITE 256 + +/* Object types */ +#define MXT_DEBUG_DIAGNOSTIC_T37 37 +#define MXT_GEN_MESSAGE_T5 5 +#define MXT_GEN_COMMAND_T6 6 +#define MXT_GEN_POWER_T7 7 +#define MXT_GEN_ACQUIRE_T8 8 +#define MXT_GEN_DATASOURCE_T53 53 +#define MXT_TOUCH_MULTI_T9 9 +#define MXT_TOUCH_KEYARRAY_T15 15 +#define MXT_TOUCH_PROXIMITY_T23 23 +#define MXT_TOUCH_PROXKEY_T52 52 +#define MXT_PROCI_GRIPFACE_T20 20 +#define MXT_PROCG_NOISE_T22 22 +#define MXT_PROCI_ONETOUCH_T24 24 +#define MXT_PROCI_TWOTOUCH_T27 27 +#define MXT_PROCI_GRIP_T40 40 +#define MXT_PROCI_PALM_T41 41 +#define MXT_PROCI_TOUCHSUPPRESSION_T42 42 +#define MXT_PROCI_STYLUS_T47 47 +#define MXT_PROCG_NOISESUPPRESSION_T48 48 +#define MXT_SPT_COMMSCONFIG_T18 18 +#define MXT_SPT_GPIOPWM_T19 19 +#define MXT_SPT_SELFTEST_T25 25 +#define MXT_SPT_CTECONFIG_T28 28 +#define MXT_SPT_USERDATA_T38 38 +#define MXT_SPT_DIGITIZER_T43 43 +#define MXT_SPT_MESSAGECOUNT_T44 44 +#define MXT_SPT_CTECONFIG_T46 46 +#define MXT_PROCI_ACTIVE_STYLUS_T63 63 +#define MXT_TOUCH_MULTITOUCHSCREEN_T100 100 + +/* MXT_GEN_MESSAGE_T5 object */ +#define MXT_RPTID_NOMSG 0xff + +/* MXT_GEN_COMMAND_T6 field */ +#define MXT_COMMAND_RESET 0 +#define MXT_COMMAND_BACKUPNV 1 +#define MXT_COMMAND_CALIBRATE 2 +#define MXT_COMMAND_REPORTALL 3 +#define MXT_COMMAND_DIAGNOSTIC 5 + +/* Define for T6 status byte */ +#define MXT_T6_STATUS_RESET (1 << 7) +#define MXT_T6_STATUS_OFL (1 << 6) +#define MXT_T6_STATUS_SIGERR (1 << 5) +#define MXT_T6_STATUS_CAL (1 << 4) +#define MXT_T6_STATUS_CFGERR (1 << 3) +#define MXT_T6_STATUS_COMSERR (1 << 2) + +/* MXT_GEN_POWER_T7 field */ +struct t7_config { + u8 idle; + u8 active; +} __packed; + +#define MXT_POWER_CFG_RUN 0 +#define MXT_POWER_CFG_DEEPSLEEP 1 + +/* MXT_TOUCH_MULTI_T9 field */ +#define MXT_T9_ORIENT 9 +#define MXT_T9_RANGE 18 + +/* MXT_TOUCH_MULTI_T9 status */ +#define MXT_T9_UNGRIP (1 << 0) +#define MXT_T9_SUPPRESS (1 << 1) +#define MXT_T9_AMP (1 << 2) +#define MXT_T9_VECTOR (1 << 3) +#define MXT_T9_MOVE (1 << 4) +#define MXT_T9_RELEASE (1 << 5) +#define MXT_T9_PRESS (1 << 6) +#define MXT_T9_DETECT (1 << 7) + +struct t9_range { + u16 x; + u16 y; +} __packed; + +/* MXT_TOUCH_MULTI_T9 orient */ +#define MXT_T9_ORIENT_SWITCH (1 << 0) + +/* MXT_SPT_COMMSCONFIG_T18 */ +#define MXT_COMMS_CTRL 0 +#define MXT_COMMS_CMD 1 +#define MXT_COMMS_RETRIGEN (1 << 6) + +/* Define for MXT_GEN_COMMAND_T6 */ +#define MXT_BOOT_VALUE 0xa5 +#define MXT_RESET_VALUE 0x01 +#define MXT_BACKUP_VALUE 0x55 + +/* Define for MXT_PROCI_TOUCHSUPPRESSION_T42 */ +#define MXT_T42_MSG_TCHSUP (1 << 0) + +/* T47 Stylus */ +#define MXT_TOUCH_MAJOR_T47_STYLUS 1 + +/* T63 Stylus */ +#define MXT_T63_STYLUS_PRESS (1 << 0) +#define MXT_T63_STYLUS_RELEASE (1 << 1) +#define MXT_T63_STYLUS_MOVE (1 << 2) +#define MXT_T63_STYLUS_SUPPRESS (1 << 3) + +#define MXT_T63_STYLUS_DETECT (1 << 4) +#define MXT_T63_STYLUS_TIP (1 << 5) +#define MXT_T63_STYLUS_ERASER (1 << 6) +#define MXT_T63_STYLUS_BARREL (1 << 7) + +#define MXT_T63_STYLUS_PRESSURE_MASK 0x3F + +/* T100 Multiple Touch Touchscreen */ +#define MXT_T100_CTRL 0 +#define MXT_T100_CFG1 1 +#define MXT_T100_TCHAUX 3 +#define MXT_T100_XRANGE 13 +#define MXT_T100_YRANGE 24 + +#define MXT_T100_CFG_SWITCHXY (1 << 5) + +#define MXT_T100_TCHAUX_VECT (1 << 0) +#define MXT_T100_TCHAUX_AMPL (1 << 1) +#define MXT_T100_TCHAUX_AREA (1 << 2) + +#define MXT_T100_DETECT (1 << 7) +#define MXT_T100_TYPE_MASK 0x70 +#define MXT_T100_TYPE_STYLUS 0x20 + +/* Delay times */ +#define MXT_BACKUP_TIME 50 /* msec */ +#define MXT_RESET_TIME 200 /* msec */ +#define MXT_RESET_TIMEOUT 3000 /* msec */ +#define MXT_CRC_TIMEOUT 1000 /* msec */ +#define MXT_FW_RESET_TIME 3000 /* msec */ +#define MXT_FW_CHG_TIMEOUT 300 /* msec */ +#define MXT_WAKEUP_TIME 25 /* msec */ +#define MXT_REGULATOR_DELAY 150 /* msec */ +#define MXT_POWERON_DELAY 150 /* msec */ + +/* Command to unlock bootloader */ +#define MXT_UNLOCK_CMD_MSB 0xaa +#define MXT_UNLOCK_CMD_LSB 0xdc + +/* Bootloader mode status */ +#define MXT_WAITING_BOOTLOAD_CMD 0xc0 /* valid 7 6 bit only */ +#define MXT_WAITING_FRAME_DATA 0x80 /* valid 7 6 bit only */ +#define MXT_FRAME_CRC_CHECK 0x02 +#define MXT_FRAME_CRC_FAIL 0x03 +#define MXT_FRAME_CRC_PASS 0x04 +#define MXT_APP_CRC_FAIL 0x40 /* valid 7 8 bit only */ +#define MXT_BOOT_STATUS_MASK 0x3f +#define MXT_BOOT_EXTENDED_ID (1 << 5) +#define MXT_BOOT_ID_MASK 0x1f + +/* Touchscreen absolute values */ +#define MXT_MAX_AREA 0xff + +#define MXT_PIXELS_PER_MM 20 + +#define DEBUG_MSG_MAX 200 + +#define MXT_COORDS_ARR_SIZE 4 + +/* Orient */ +#define MXT_NORMAL 0x0 +#define MXT_DIAGONAL 0x1 +#define MXT_HORIZONTAL_FLIP 0x2 +#define MXT_ROTATED_90_COUNTER 0x3 +#define MXT_VERTICAL_FLIP 0x4 +#define MXT_ROTATED_90 0x5 +#define MXT_ROTATED_180 0x6 +#define MXT_DIAGONAL_COUNTER 0x7 + +/* MXT_TOUCH_KEYARRAY_T15 */ +#define MXT_KEYARRAY_MAX_KEYS 32 + +/* Bootoader IDs */ +#define MXT_BOOTLOADER_ID_224 0x0A +#define MXT_BOOTLOADER_ID_224E 0x06 +#define MXT_BOOTLOADER_ID_336S 0x1A +#define MXT_BOOTLOADER_ID_1386 0x01 +#define MXT_BOOTLOADER_ID_1386E 0x10 +#define MXT_BOOTLOADER_ID_1664S 0x14 + +/* recommended voltage specifications */ +#define MXT_VDD_VTG_MIN_UV 1800000 +#define MXT_VDD_VTG_MAX_UV 1800000 +#define MXT_AVDD_VTG_MIN_UV 2700000 +#define MXT_AVDD_VTG_MAX_UV 3300000 +#define MXT_XVDD_VTG_MIN_UV 2700000 +#define MXT_XVDD_VTG_MAX_UV 10000000 + +#define MXT_GEN_CFG "maxtouch_generic_cfg.raw" +#define MXT_NAME_MAX_LEN 100 + +struct mxt_info { + u8 family_id; + u8 variant_id; + u8 version; + u8 build; + u8 matrix_xsize; + u8 matrix_ysize; + u8 object_num; +}; + +struct mxt_object { + u8 type; + u16 start_address; + u8 size_minus_one; + u8 instances_minus_one; + u8 num_report_ids; +} __packed; + +/* Each client has this additional data */ +struct mxt_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct pinctrl *ts_pinctrl; + struct pinctrl_state *gpio_state_active; + struct pinctrl_state *gpio_state_suspend; + char phys[64]; /* device physical location */ + struct mxt_platform_data *pdata; + struct mxt_object *object_table; + struct mxt_info *info; + void *raw_info_block; + unsigned int irq; + unsigned int max_x; + unsigned int max_y; + bool in_bootloader; + u16 mem_size; + u8 t100_aux_ampl; + u8 t100_aux_area; + u8 t100_aux_vect; + struct bin_attribute mem_access_attr; + bool debug_enabled; + bool debug_v2_enabled; + u8 *debug_msg_data; + u16 debug_msg_count; + struct bin_attribute debug_msg_attr; + struct mutex debug_msg_lock; + u8 max_reportid; + u32 config_crc; + u32 info_crc; + u8 bootloader_addr; + struct t7_config t7_cfg; + u8 *msg_buf; + u8 t6_status; + bool update_input; + u8 last_message_count; + u8 num_touchids; + u8 num_stylusids; + unsigned long t15_keystatus; + bool use_retrigen_workaround; + bool use_regulator; + struct regulator *reg_vdd; + struct regulator *reg_avdd; + struct regulator *reg_xvdd; + char fw_name[MXT_NAME_MAX_LEN]; + char cfg_name[MXT_NAME_MAX_LEN]; + u8 cfg_version[3]; + bool fw_w_no_cfg_update; + +#if defined(CONFIG_FB) + struct notifier_block fb_notif; +#elif defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + /* Cached parameters from object table */ + u16 T5_address; + u8 T5_msg_size; + u8 T6_reportid; + u16 T6_address; + u16 T7_address; + u8 T9_reportid_min; + u8 T9_reportid_max; + u8 T15_reportid_min; + u8 T15_reportid_max; + u16 T18_address; + u8 T19_reportid; + u8 T42_reportid_min; + u8 T42_reportid_max; + u16 T44_address; + u8 T48_reportid; + u8 T63_reportid_min; + u8 T63_reportid_max; + u8 T100_reportid_min; + u8 T100_reportid_max; + + /* for fw update in bootloader */ + struct completion bl_completion; + + /* for reset handling */ + struct completion reset_completion; + + /* for reset handling */ + struct completion crc_completion; + + /* Enable reporting of input events */ + bool enable_reporting; + + /* Indicates whether device is in suspend */ + bool suspended; + +#if defined(CONFIG_SECURE_TOUCH) + atomic_t st_enabled; + atomic_t st_pending_irqs; + bool st_initialized; + struct completion st_powerdown; + struct clk *core_clk; + struct clk *iface_clk; +#endif +}; + +static inline unsigned int mxt_obj_size(const struct mxt_object *obj) +{ + return obj->size_minus_one + 1; +} + +static inline unsigned int mxt_obj_instances(const struct mxt_object *obj) +{ + return obj->instances_minus_one + 1; +} + +static bool mxt_object_readable(unsigned int type) +{ + switch (type) { + case MXT_GEN_COMMAND_T6: + case MXT_GEN_POWER_T7: + case MXT_GEN_ACQUIRE_T8: + case MXT_GEN_DATASOURCE_T53: + case MXT_TOUCH_MULTI_T9: + case MXT_TOUCH_KEYARRAY_T15: + case MXT_TOUCH_PROXIMITY_T23: + case MXT_TOUCH_PROXKEY_T52: + case MXT_PROCI_GRIPFACE_T20: + case MXT_PROCG_NOISE_T22: + case MXT_PROCI_ONETOUCH_T24: + case MXT_PROCI_TWOTOUCH_T27: + case MXT_PROCI_GRIP_T40: + case MXT_PROCI_PALM_T41: + case MXT_PROCI_TOUCHSUPPRESSION_T42: + case MXT_PROCI_STYLUS_T47: + case MXT_PROCG_NOISESUPPRESSION_T48: + case MXT_SPT_COMMSCONFIG_T18: + case MXT_SPT_GPIOPWM_T19: + case MXT_SPT_SELFTEST_T25: + case MXT_SPT_CTECONFIG_T28: + case MXT_SPT_USERDATA_T38: + case MXT_SPT_DIGITIZER_T43: + case MXT_SPT_CTECONFIG_T46: + return true; + default: + return false; + } +} + +static void mxt_dump_message(struct mxt_data *data, u8 *message) +{ + print_hex_dump(KERN_DEBUG, "MXT MSG:", DUMP_PREFIX_NONE, 16, 1, + message, data->T5_msg_size, false); +} + +static void mxt_debug_msg_enable(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + + if (data->debug_v2_enabled) + return; + + mutex_lock(&data->debug_msg_lock); + + data->debug_msg_data = kcalloc(DEBUG_MSG_MAX, + data->T5_msg_size, GFP_KERNEL); + if (!data->debug_msg_data) { + dev_err(&data->client->dev, "Failed to allocate buffer\n"); + return; + } + + data->debug_v2_enabled = true; + mutex_unlock(&data->debug_msg_lock); + + dev_info(dev, "Enabled message output\n"); +} + +static void mxt_debug_msg_disable(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + + if (!data->debug_v2_enabled) + return; + + dev_info(dev, "disabling message output\n"); + data->debug_v2_enabled = false; + + mutex_lock(&data->debug_msg_lock); + kfree(data->debug_msg_data); + data->debug_msg_data = NULL; + data->debug_msg_count = 0; + mutex_unlock(&data->debug_msg_lock); + dev_info(dev, "Disabled message output\n"); +} + +static void mxt_debug_msg_add(struct mxt_data *data, u8 *msg) +{ + struct device *dev = &data->client->dev; + + mutex_lock(&data->debug_msg_lock); + + if (!data->debug_msg_data) { + dev_err(dev, "No buffer!\n"); + return; + } + + if (data->debug_msg_count < DEBUG_MSG_MAX) { + memcpy(data->debug_msg_data + data->debug_msg_count * data->T5_msg_size, + msg, + data->T5_msg_size); + data->debug_msg_count++; + } else { + dev_dbg(dev, "Discarding %u messages\n", data->debug_msg_count); + data->debug_msg_count = 0; + } + + mutex_unlock(&data->debug_msg_lock); + + sysfs_notify(&data->client->dev.kobj, NULL, "debug_notify"); +} + +static ssize_t mxt_debug_msg_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, + size_t count) +{ + return -EIO; +} + +static ssize_t mxt_debug_msg_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t bytes) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct mxt_data *data = dev_get_drvdata(dev); + int count; + size_t bytes_read; + + if (!data->debug_msg_data) { + dev_err(dev, "No buffer!\n"); + return 0; + } + + count = bytes / data->T5_msg_size; + + if (count > DEBUG_MSG_MAX) + count = DEBUG_MSG_MAX; + + mutex_lock(&data->debug_msg_lock); + + if (count > data->debug_msg_count) + count = data->debug_msg_count; + + bytes_read = count * data->T5_msg_size; + + memcpy(buf, data->debug_msg_data, bytes_read); + data->debug_msg_count = 0; + + mutex_unlock(&data->debug_msg_lock); + + return bytes_read; +} + +static int mxt_debug_msg_init(struct mxt_data *data) +{ + int ret; + + sysfs_bin_attr_init(&data->debug_msg_attr); + data->debug_msg_attr.attr.name = "debug_msg"; + data->debug_msg_attr.attr.mode = 0666; + data->debug_msg_attr.read = mxt_debug_msg_read; + data->debug_msg_attr.write = mxt_debug_msg_write; + data->debug_msg_attr.size = data->T5_msg_size * DEBUG_MSG_MAX; + + ret = sysfs_create_bin_file(&data->client->dev.kobj, + &data->debug_msg_attr); + if (ret < 0) { + if (ret == -EEXIST) { + dev_info(&data->client->dev, + "Debugfs already exists\n"); + ret = 0; + } else { + dev_err(&data->client->dev, + "Failed to create 'debug_msg' file\n"); + } + } + + return ret; +} + +static void mxt_debug_msg_remove(struct mxt_data *data) +{ + if (data->debug_msg_attr.attr.name) + sysfs_remove_bin_file(&data->client->dev.kobj, + &data->debug_msg_attr); +} + +static int mxt_wait_for_completion(struct mxt_data *data, + struct completion *comp, unsigned int timeout_ms) +{ + struct device *dev = &data->client->dev; + unsigned long timeout = msecs_to_jiffies(timeout_ms); + long ret; + + ret = wait_for_completion_interruptible_timeout(comp, timeout); + if (ret < 0) { + dev_err(dev, "Wait for completion interrupted.\n"); + return -EINTR; + } else if (ret == 0) { + dev_err(dev, "Wait for completion timed out.\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int mxt_bootloader_read(struct mxt_data *data, + u8 *val, unsigned int count) +{ + int ret; + struct i2c_msg msg; + + msg.addr = data->bootloader_addr; + msg.flags = data->client->flags & I2C_M_TEN; + msg.flags |= I2C_M_RD; + msg.len = count; + msg.buf = val; + + ret = i2c_transfer(data->client->adapter, &msg, 1); + + if (ret == 1) { + ret = 0; + } else { + ret = (ret < 0) ? ret : -EIO; + dev_err(&data->client->dev, "%s: i2c recv failed (%d)\n", + __func__, ret); + } + + return ret; +} + +static int mxt_bootloader_write(struct mxt_data *data, + const u8 * const val, unsigned int count) +{ + int ret; + struct i2c_msg msg; + + msg.addr = data->bootloader_addr; + msg.flags = data->client->flags & I2C_M_TEN; + msg.len = count; + msg.buf = (u8 *)val; + + ret = i2c_transfer(data->client->adapter, &msg, 1); + if (ret == 1) { + ret = 0; + } else { + ret = (ret < 0) ? ret : -EIO; + dev_err(&data->client->dev, "%s: i2c send failed (%d)\n", + __func__, ret); + } + + return ret; +} + +static int mxt_lookup_bootloader_address(struct mxt_data *data, bool retry) +{ + u8 appmode = data->client->addr; + u8 bootloader; + u8 family_id = 0; + + if (data->pdata->bl_addr) { + data->bootloader_addr = data->pdata->bl_addr; + return 0; + } + + if (data->info) + family_id = data->info->family_id; + + switch (appmode) { + case 0x4a: + case 0x4b: + /* Chips after 1664S use different scheme */ + if (retry || family_id >= 0xa2) { + bootloader = appmode - 0x24; + break; + } + /* Fall through for normal case */ + case 0x4c: + case 0x4d: + case 0x5a: + case 0x5b: + bootloader = appmode - 0x26; + break; + default: + dev_err(&data->client->dev, + "Appmode i2c address 0x%02x not found\n", + appmode); + return -EINVAL; + } + + data->bootloader_addr = bootloader; + return 0; +} + +static int mxt_probe_bootloader(struct mxt_data *data, bool retry) +{ + struct device *dev = &data->client->dev; + int ret; + u8 val; + bool crc_failure; + + ret = mxt_lookup_bootloader_address(data, retry); + if (ret) + return ret; + + ret = mxt_bootloader_read(data, &val, 1); + if (ret) + return ret; + + /* Check app crc fail mode */ + crc_failure = (val & ~MXT_BOOT_STATUS_MASK) == MXT_APP_CRC_FAIL; + + dev_err(dev, "Detected bootloader, status:%02X%s\n", + val, crc_failure ? ", APP_CRC_FAIL" : ""); + + return 0; +} + +static u8 mxt_get_bootloader_version(struct mxt_data *data, u8 val) +{ + struct device *dev = &data->client->dev; + u8 buf[3]; + + if (val & MXT_BOOT_EXTENDED_ID) { + if (mxt_bootloader_read(data, &buf[0], 3) != 0) { + dev_err(dev, "%s: i2c failure\n", __func__); + return -EIO; + } + + dev_info(dev, "Bootloader ID:%d Version:%d\n", buf[1], buf[2]); + + return buf[0]; + } else { + dev_info(dev, "Bootloader ID:%d\n", val & MXT_BOOT_ID_MASK); + + return val; + } +} + +static int mxt_check_bootloader(struct mxt_data *data, unsigned int state, + bool wait) +{ + struct device *dev = &data->client->dev; + u8 val; + int ret; + +recheck: + if (wait) { + /* + * In application update mode, the interrupt + * line signals state transitions. We must wait for the + * CHG assertion before reading the status byte. + * Once the status byte has been read, the line is deasserted. + */ + ret = mxt_wait_for_completion(data, &data->bl_completion, + MXT_FW_CHG_TIMEOUT); + if (ret) { + /* + * TODO: handle -EINTR better by terminating fw update + * process before returning to userspace by writing + * length 0x000 to device (iff we are in + * WAITING_FRAME_DATA state). + */ + dev_err(dev, "Update wait error %d\n", ret); + return ret; + } + } + + ret = mxt_bootloader_read(data, &val, 1); + if (ret) + return ret; + + if (state == MXT_WAITING_BOOTLOAD_CMD) + val = mxt_get_bootloader_version(data, val); + + switch (state) { + case MXT_WAITING_BOOTLOAD_CMD: + case MXT_WAITING_FRAME_DATA: + case MXT_APP_CRC_FAIL: + val &= ~MXT_BOOT_STATUS_MASK; + break; + case MXT_FRAME_CRC_PASS: + if (val == MXT_FRAME_CRC_CHECK) { + goto recheck; + } else if (val == MXT_FRAME_CRC_FAIL) { + dev_err(dev, "Bootloader CRC fail\n"); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + if (val != state) { + dev_err(dev, "Invalid bootloader state %02X != %02X\n", + val, state); + return -EINVAL; + } + + return 0; +} + +static int mxt_send_bootloader_cmd(struct mxt_data *data, bool unlock) +{ + int ret; + u8 buf[2]; + + if (unlock) { + buf[0] = MXT_UNLOCK_CMD_LSB; + buf[1] = MXT_UNLOCK_CMD_MSB; + } else { + buf[0] = 0x01; + buf[1] = 0x01; + } + + ret = mxt_bootloader_write(data, buf, 2); + if (ret) + return ret; + + return 0; +} + +static int __mxt_read_reg(struct i2c_client *client, + u16 reg, u16 len, void *val) +{ + struct i2c_msg xfer[2]; + u8 buf[2]; + int ret; + bool retry = false; + + buf[0] = reg & 0xff; + buf[1] = (reg >> 8) & 0xff; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 2; + xfer[0].buf = buf; + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = len; + xfer[1].buf = val; + +retry_read: + ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret != ARRAY_SIZE(xfer)) { + if (!retry) { + dev_dbg(&client->dev, "%s: i2c retry\n", __func__); + msleep(MXT_WAKEUP_TIME); + retry = true; + goto retry_read; + } else { + dev_err(&client->dev, "%s: i2c transfer failed (%d)\n", + __func__, ret); + return -EIO; + } + } + + return 0; +} + +static int __mxt_write_reg(struct i2c_client *client, u16 reg, u16 len, + const void *val) +{ + u8 *buf; + int count; + int ret; + bool retry = false; + + count = len + 2; + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = reg & 0xff; + buf[1] = (reg >> 8) & 0xff; + memcpy(&buf[2], val, len); + +retry_write: + ret = i2c_master_send(client, buf, count); + if (ret != count) { + if (!retry) { + dev_dbg(&client->dev, "%s: i2c retry\n", __func__); + msleep(MXT_WAKEUP_TIME); + retry = true; + goto retry_write; + } else { + dev_err(&client->dev, "%s: i2c send failed (%d)\n", + __func__, ret); + ret = -EIO; + } + } else { + ret = 0; + } + + kfree(buf); + return ret; +} + +static int mxt_write_reg(struct i2c_client *client, u16 reg, u8 val) +{ + return __mxt_write_reg(client, reg, 1, &val); +} + +static struct mxt_object * +mxt_get_object(struct mxt_data *data, u8 type) +{ + struct mxt_object *object; + int i; + + for (i = 0; i < data->info->object_num; i++) { + object = data->object_table + i; + if (object->type == type) + return object; + } + + dev_warn(&data->client->dev, "Invalid object type T%u\n", type); + return NULL; +} + +static void mxt_proc_t6_messages(struct mxt_data *data, u8 *msg) +{ + struct device *dev = &data->client->dev; + u8 status = msg[1]; + u32 crc = msg[2] | (msg[3] << 8) | (msg[4] << 16); + + if (crc != data->config_crc) { + data->config_crc = crc; + dev_dbg(dev, "T6 Config Checksum: 0x%06X\n", crc); + complete(&data->crc_completion); + } + + /* Detect transition out of reset */ + if ((data->t6_status & MXT_T6_STATUS_RESET) && + !(status & MXT_T6_STATUS_RESET)) + complete(&data->reset_completion); + + /* Output debug if status has changed */ + if (status != data->t6_status) + dev_dbg(dev, "T6 Status 0x%02X%s%s%s%s%s%s%s\n", + status, + (status == 0) ? " OK" : "", + (status & MXT_T6_STATUS_RESET) ? " RESET" : "", + (status & MXT_T6_STATUS_OFL) ? " OFL" : "", + (status & MXT_T6_STATUS_SIGERR) ? " SIGERR" : "", + (status & MXT_T6_STATUS_CAL) ? " CAL" : "", + (status & MXT_T6_STATUS_CFGERR) ? " CFGERR" : "", + (status & MXT_T6_STATUS_COMSERR) ? " COMSERR" : ""); + + /* Save current status */ + data->t6_status = status; +} + +static void mxt_input_button(struct mxt_data *data, u8 *message) +{ + struct input_dev *input = data->input_dev; + const struct mxt_platform_data *pdata = data->pdata; + bool button; + int i; + + /* do not report events if input device not yet registered */ + if (!data->enable_reporting) + return; + + /* Active-low switch */ + for (i = 0; i < pdata->t19_num_keys; i++) { + if (pdata->t19_keymap[i] == KEY_RESERVED) + continue; + button = !(message[1] & (1 << i)); + input_report_key(input, pdata->t19_keymap[i], button); + } +} + +static void mxt_input_sync(struct input_dev *input_dev) +{ + input_mt_report_pointer_emulation(input_dev, false); + input_sync(input_dev); +} + +static void mxt_proc_t9_message(struct mxt_data *data, u8 *message) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev = data->input_dev; + int id; + u8 status; + int x; + int y; + int area; + int amplitude; + u8 vector; + int tool; + + /* do not report events if input device not yet registered */ + if (!data->enable_reporting) + return; + + id = message[0] - data->T9_reportid_min; + status = message[1]; + x = (message[2] << 4) | ((message[4] >> 4) & 0xf); + y = (message[3] << 4) | ((message[4] & 0xf)); + + /* Handle 10/12 bit switching */ + if (data->max_x < 1024) + x >>= 2; + if (data->max_y < 1024) + y >>= 2; + + area = message[5]; + + amplitude = message[6]; + vector = message[7]; + + dev_dbg(dev, + "[%u] %c%c%c%c%c%c%c%c x: %5u y: %5u area: %3u amp: %3u vector: %02X\n", + id, + (status & MXT_T9_DETECT) ? 'D' : '.', + (status & MXT_T9_PRESS) ? 'P' : '.', + (status & MXT_T9_RELEASE) ? 'R' : '.', + (status & MXT_T9_MOVE) ? 'M' : '.', + (status & MXT_T9_VECTOR) ? 'V' : '.', + (status & MXT_T9_AMP) ? 'A' : '.', + (status & MXT_T9_SUPPRESS) ? 'S' : '.', + (status & MXT_T9_UNGRIP) ? 'U' : '.', + x, y, area, amplitude, vector); + + input_mt_slot(input_dev, id); + + if (status & MXT_T9_DETECT) { + /* Multiple bits may be set if the host is slow to read the + * status messages, indicating all the events that have + * happened */ + if (status & MXT_T9_RELEASE) { + input_mt_report_slot_state(input_dev, + MT_TOOL_FINGER, 0); + mxt_input_sync(input_dev); + } + + /* A reported size of zero indicates that the reported touch + * is a stylus from a linked Stylus T47 object. */ + if (area == 0) { + area = MXT_TOUCH_MAJOR_T47_STYLUS; + tool = MT_TOOL_PEN; + } else { + tool = MT_TOOL_FINGER; + } + + /* Touch active */ + input_mt_report_slot_state(input_dev, tool, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(input_dev, ABS_MT_PRESSURE, amplitude); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, area); + input_report_abs(input_dev, ABS_MT_ORIENTATION, vector); + } else { + /* Touch no longer active, close out slot */ + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); + } + + data->update_input = true; +} + +static void mxt_proc_t100_message(struct mxt_data *data, u8 *message) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev = data->input_dev; + int id; + u8 status; + int x; + int y; + int tool; + + /* do not report events if input device not yet registered */ + if (!data->enable_reporting) + return; + + id = message[0] - data->T100_reportid_min - 2; + + /* ignore SCRSTATUS events */ + if (id < 0) + return; + + status = message[1]; + x = (message[3] << 8) | message[2]; + y = (message[5] << 8) | message[4]; + + dev_dbg(dev, + "[%u] status:%02X x:%u y:%u area:%02X amp:%02X vec:%02X\n", + id, + status, + x, y, + (data->t100_aux_area) ? message[data->t100_aux_area] : 0, + (data->t100_aux_ampl) ? message[data->t100_aux_ampl] : 0, + (data->t100_aux_vect) ? message[data->t100_aux_vect] : 0); + + input_mt_slot(input_dev, id); + + if (status & MXT_T100_DETECT) { + /* A reported size of zero indicates that the reported touch + * is a stylus from a linked Stylus T47 object. */ + if ((status & MXT_T100_TYPE_MASK) == MXT_T100_TYPE_STYLUS) + tool = MT_TOOL_PEN; + else + tool = MT_TOOL_FINGER; + + /* Touch active */ + input_mt_report_slot_state(input_dev, tool, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + + if (data->t100_aux_ampl) + input_report_abs(input_dev, ABS_MT_PRESSURE, + message[data->t100_aux_ampl]); + else + input_report_abs(input_dev, ABS_MT_PRESSURE, 255); + + if (data->t100_aux_area) { + if (tool == MT_TOOL_PEN) + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, + MXT_TOUCH_MAJOR_T47_STYLUS); + else + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, + message[data->t100_aux_area]); + } + + if (data->t100_aux_vect) + input_report_abs(input_dev, ABS_MT_ORIENTATION, + message[data->t100_aux_vect]); + } else { + /* Touch no longer active, close out slot */ + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); + } + + data->update_input = true; +} + + +static void mxt_proc_t15_messages(struct mxt_data *data, u8 *msg) +{ + struct input_dev *input_dev = data->input_dev; + struct device *dev = &data->client->dev; + int key; + bool curr_state, new_state; + bool sync = false; + unsigned long keystates = le32_to_cpu(msg[2]); + + /* do not report events if input device not yet registered */ + if (!data->enable_reporting) + return; + + for (key = 0; key < data->pdata->t15_num_keys; key++) { + curr_state = test_bit(key, &data->t15_keystatus); + new_state = test_bit(key, &keystates); + + if (!curr_state && new_state) { + dev_dbg(dev, "T15 key press: %u\n", key); + __set_bit(key, &data->t15_keystatus); + input_event(input_dev, EV_KEY, + data->pdata->t15_keymap[key], 1); + sync = true; + } else if (curr_state && !new_state) { + dev_dbg(dev, "T15 key release: %u\n", key); + __clear_bit(key, &data->t15_keystatus); + input_event(input_dev, EV_KEY, + data->pdata->t15_keymap[key], 0); + sync = true; + } + } + + if (sync) + input_sync(input_dev); +} + +static void mxt_proc_t42_messages(struct mxt_data *data, u8 *msg) +{ + struct device *dev = &data->client->dev; + u8 status = msg[1]; + + if (status & MXT_T42_MSG_TCHSUP) + dev_info(dev, "T42 suppress\n"); + else + dev_info(dev, "T42 normal\n"); +} + +static int mxt_proc_t48_messages(struct mxt_data *data, u8 *msg) +{ + struct device *dev = &data->client->dev; + u8 status, state; + + status = msg[1]; + state = msg[4]; + + dev_dbg(dev, "T48 state %d status %02X %s%s%s%s%s\n", + state, + status, + (status & 0x01) ? "FREQCHG " : "", + (status & 0x02) ? "APXCHG " : "", + (status & 0x04) ? "ALGOERR " : "", + (status & 0x10) ? "STATCHG " : "", + (status & 0x20) ? "NLVLCHG " : ""); + + return 0; +} + +static void mxt_proc_t63_messages(struct mxt_data *data, u8 *msg) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev = data->input_dev; + u8 id; + u16 x, y; + u8 pressure; + + /* do not report events if input device not yet registered */ + if (!data->enable_reporting) + return; + + /* stylus slots come after touch slots */ + id = data->num_touchids + (msg[0] - data->T63_reportid_min); + + if (id < 0 || id > (data->num_touchids + data->num_stylusids)) { + dev_err(dev, "invalid stylus id %d, max slot is %d\n", + id, data->num_stylusids); + return; + } + + x = msg[3] | (msg[4] << 8); + y = msg[5] | (msg[6] << 8); + pressure = msg[7] & MXT_T63_STYLUS_PRESSURE_MASK; + + dev_dbg(dev, + "[%d] %c%c%c%c x: %d y: %d pressure: %d stylus:%c%c%c%c\n", + id, + (msg[1] & MXT_T63_STYLUS_SUPPRESS) ? 'S' : '.', + (msg[1] & MXT_T63_STYLUS_MOVE) ? 'M' : '.', + (msg[1] & MXT_T63_STYLUS_RELEASE) ? 'R' : '.', + (msg[1] & MXT_T63_STYLUS_PRESS) ? 'P' : '.', + x, y, pressure, + (msg[2] & MXT_T63_STYLUS_BARREL) ? 'B' : '.', + (msg[2] & MXT_T63_STYLUS_ERASER) ? 'E' : '.', + (msg[2] & MXT_T63_STYLUS_TIP) ? 'T' : '.', + (msg[2] & MXT_T63_STYLUS_DETECT) ? 'D' : '.'); + + input_mt_slot(input_dev, id); + + if (msg[2] & MXT_T63_STYLUS_DETECT) { + input_mt_report_slot_state(input_dev, MT_TOOL_PEN, 1); + input_report_abs(input_dev, ABS_MT_POSITION_X, x); + input_report_abs(input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(input_dev, ABS_MT_PRESSURE, pressure); + } else { + input_mt_report_slot_state(input_dev, MT_TOOL_PEN, 0); + } + + input_report_key(input_dev, BTN_STYLUS, + (msg[2] & MXT_T63_STYLUS_ERASER)); + input_report_key(input_dev, BTN_STYLUS2, + (msg[2] & MXT_T63_STYLUS_BARREL)); + + mxt_input_sync(input_dev); +} + +static int mxt_proc_message(struct mxt_data *data, u8 *message) +{ + u8 report_id = message[0]; + bool dump = data->debug_enabled; + + if (report_id == MXT_RPTID_NOMSG) + return 0; + + if (report_id == data->T6_reportid) { + mxt_proc_t6_messages(data, message); + } else if (report_id >= data->T9_reportid_min + && report_id <= data->T9_reportid_max) { + mxt_proc_t9_message(data, message); + } else if (report_id >= data->T100_reportid_min + && report_id <= data->T100_reportid_max) { + mxt_proc_t100_message(data, message); + } else if (report_id == data->T19_reportid) { + mxt_input_button(data, message); + data->update_input = true; + } else if (report_id >= data->T63_reportid_min + && report_id <= data->T63_reportid_max) { + mxt_proc_t63_messages(data, message); + } else if (report_id >= data->T42_reportid_min + && report_id <= data->T42_reportid_max) { + mxt_proc_t42_messages(data, message); + } else if (report_id == data->T48_reportid) { + mxt_proc_t48_messages(data, message); + } else if (report_id >= data->T15_reportid_min + && report_id <= data->T15_reportid_max) { + mxt_proc_t15_messages(data, message); + } else { + dump = true; + } + + if (dump) + mxt_dump_message(data, message); + + if (data->debug_v2_enabled) + mxt_debug_msg_add(data, message); + + return 1; +} + +static int mxt_read_and_process_messages(struct mxt_data *data, u8 count) +{ + struct device *dev = &data->client->dev; + int ret; + int i; + u8 num_valid = 0; + + /* Safety check for msg_buf */ + if (count > data->max_reportid) + return -EINVAL; + + /* Process remaining messages if necessary */ + ret = __mxt_read_reg(data->client, data->T5_address, + data->T5_msg_size * count, data->msg_buf); + if (ret) { + dev_err(dev, "Failed to read %u messages (%d)\n", count, ret); + return ret; + } + + for (i = 0; i < count; i++) { + ret = mxt_proc_message(data, + data->msg_buf + data->T5_msg_size * i); + + if (ret == 1) + num_valid++; + } + + /* return number of messages read */ + return num_valid; +} + +static irqreturn_t mxt_process_messages_t44(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + u8 count, num_left; + + /* Read T44 and T5 together */ + ret = __mxt_read_reg(data->client, data->T44_address, + data->T5_msg_size + 1, data->msg_buf); + if (ret) { + dev_err(dev, "Failed to read T44 and T5 (%d)\n", ret); + return IRQ_NONE; + } + + count = data->msg_buf[0]; + + if (count == 0) { + dev_warn(dev, "Interrupt triggered but zero messages\n"); + return IRQ_NONE; + } else if (count > data->max_reportid) { + dev_err(dev, "T44 count %d exceeded max report id\n", count); + count = data->max_reportid; + } + + /* Process first message */ + ret = mxt_proc_message(data, data->msg_buf + 1); + if (ret < 0) { + dev_warn(dev, "Unexpected invalid message\n"); + return IRQ_NONE; + } + + num_left = count - 1; + + /* Process remaining messages if necessary */ + if (num_left) { + ret = mxt_read_and_process_messages(data, num_left); + if (ret < 0) + goto end; + else if (ret != num_left) + dev_warn(dev, "Unexpected invalid message\n"); + } + +end: + if (data->update_input) { + mxt_input_sync(data->input_dev); + data->update_input = false; + } + + return IRQ_HANDLED; +} + +static int mxt_process_messages_until_invalid(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int count, read; + u8 tries = 2; + + count = data->max_reportid; + + /* Read messages until we force an invalid */ + do { + read = mxt_read_and_process_messages(data, count); + if (read < count) + return 0; + } while (--tries); + + if (data->update_input) { + mxt_input_sync(data->input_dev); + data->update_input = false; + } + + dev_err(dev, "CHG pin isn't cleared\n"); + return -EBUSY; +} + +static irqreturn_t mxt_process_messages(struct mxt_data *data) +{ + int total_handled, num_handled; + u8 count = data->last_message_count; + + if (count < 1 || count > data->max_reportid) + count = 1; + + /* include final invalid message */ + total_handled = mxt_read_and_process_messages(data, count + 1); + if (total_handled < 0) + return IRQ_NONE; + /* if there were invalid messages, then we are done */ + else if (total_handled <= count) + goto update_count; + + /* read two at a time until an invalid message or else we reach + * reportid limit */ + do { + num_handled = mxt_read_and_process_messages(data, 2); + if (num_handled < 0) + return IRQ_NONE; + + total_handled += num_handled; + + if (num_handled < 2) + break; + } while (total_handled < data->num_touchids); + +update_count: + data->last_message_count = total_handled; + + if (data->enable_reporting && data->update_input) { + mxt_input_sync(data->input_dev); + data->update_input = false; + } + + return IRQ_HANDLED; +} + +#if defined(CONFIG_SECURE_TOUCH) +static void mxt_secure_touch_notify(struct mxt_data *data) +{ + sysfs_notify(&data->client->dev.kobj, NULL, "secure_touch"); +} + +static irqreturn_t mxt_filter_interrupt(struct mxt_data *data) +{ + if (atomic_read(&data->st_enabled)) { + if (atomic_cmpxchg(&data->st_pending_irqs, 0, 1) == 0) + mxt_secure_touch_notify(data); + return IRQ_HANDLED; + } + return IRQ_NONE; +} +#else +static irqreturn_t mxt_filter_interrupt(struct mxt_data *data) +{ + return IRQ_NONE; +} +#endif + +static irqreturn_t mxt_interrupt(int irq, void *dev_id) +{ + struct mxt_data *data = dev_id; + + if (data->in_bootloader) { + /* bootloader state transition completion */ + complete(&data->bl_completion); + return IRQ_HANDLED; + } + + if (IRQ_HANDLED == mxt_filter_interrupt(data)) + return IRQ_HANDLED; + + if (!data->object_table) + return IRQ_NONE; + + if (data->T44_address) { + return mxt_process_messages_t44(data); + } else { + return mxt_process_messages(data); + } +} + +static int mxt_t6_command(struct mxt_data *data, u16 cmd_offset, + u8 value, bool wait) +{ + u16 reg; + u8 command_register = 0; + int timeout_counter = 0; + int ret; + + reg = data->T6_address + cmd_offset; + + ret = mxt_write_reg(data->client, reg, value); + if (ret) + return ret; + + if (!wait) + return 0; + + do { + msleep(20); + ret = __mxt_read_reg(data->client, reg, 1, &command_register); + if (ret) + return ret; + } while ((command_register != 0) && (timeout_counter++ <= 100)); + + if (timeout_counter > 100) { + dev_err(&data->client->dev, "Command failed!\n"); + return -EIO; + } + + return 0; +} + +static int mxt_soft_reset(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int ret = 0; + + dev_info(dev, "Resetting chip\n"); + + reinit_completion(&data->reset_completion); + + ret = mxt_t6_command(data, MXT_COMMAND_RESET, MXT_RESET_VALUE, false); + if (ret) + return ret; + + ret = mxt_wait_for_completion(data, &data->reset_completion, + MXT_RESET_TIMEOUT); + if (ret) + return ret; + + return 0; +} + +static void mxt_update_crc(struct mxt_data *data, u8 cmd, u8 value) +{ + /* on failure, CRC is set to 0 and config will always be downloaded */ + data->config_crc = 0; + reinit_completion(&data->crc_completion); + + mxt_t6_command(data, cmd, value, true); + + /* Wait for crc message. On failure, CRC is set to 0 and config will + * always be downloaded */ + mxt_wait_for_completion(data, &data->crc_completion, MXT_CRC_TIMEOUT); +} + +static void mxt_calc_crc24(u32 *crc, u8 firstbyte, u8 secondbyte) +{ + static const unsigned int crcpoly = 0x80001B; + u32 result; + u32 data_word; + + data_word = (secondbyte << 8) | firstbyte; + result = ((*crc << 1) ^ data_word); + + if (result & 0x1000000) + result ^= crcpoly; + + *crc = result; +} + +static u32 mxt_calculate_crc(u8 *base, off_t start_off, off_t end_off) +{ + u32 crc = 0; + u8 *ptr = base + start_off; + u8 *last_val = base + end_off - 1; + + if (end_off < start_off) + return -EINVAL; + + while (ptr < last_val) { + mxt_calc_crc24(&crc, *ptr, *(ptr + 1)); + ptr += 2; + } + + /* if len is odd, fill the last byte with 0 */ + if (ptr == last_val) + mxt_calc_crc24(&crc, *ptr, 0); + + /* Mask to 24-bit */ + crc &= 0x00FFFFFF; + + return crc; +} + +static int mxt_check_retrigen(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + int val = 0; + + if (data->pdata->irqflags & IRQF_TRIGGER_LOW) + return 0; + + if (data->T18_address) { + error = __mxt_read_reg(client, + data->T18_address + MXT_COMMS_CTRL, + 1, &val); + if (error) + return error; + + if (val & MXT_COMMS_RETRIGEN) + return 0; + } + + dev_warn(&client->dev, "Enabling RETRIGEN workaround\n"); + data->use_retrigen_workaround = true; + return 0; +} + +static int mxt_init_t7_power_cfg(struct mxt_data *data); + +static int mxt_update_cfg_version(struct mxt_data *data) +{ + struct mxt_object *object; + int error; + + object = mxt_get_object(data, MXT_SPT_USERDATA_T38); + if (!object) + return -EINVAL; + + error = __mxt_read_reg(data->client, object->start_address, + sizeof(data->cfg_version), &data->cfg_version); + if (error) + return error; + + return 0; +} + +static int mxt_update_t100_resolution(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + struct mxt_object *object; + u16 range_x = 0; + u16 range_y = 0; + u16 temp; + u8 cfg = 0; + u8 tchaux = 0; + u8 aux; + bool update = false; + + object = mxt_get_object(data, MXT_TOUCH_MULTITOUCHSCREEN_T100); + if (!object) + return -EINVAL; + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_XRANGE, + sizeof(range_x), &range_x); + if (error) + return error; + + le16_to_cpus(range_x); + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_YRANGE, + sizeof(range_y), &range_y); + if (error) + return error; + + le16_to_cpus(range_y); + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_CFG1, + 1, &cfg); + if (error) + return error; + + error = __mxt_read_reg(client, + object->start_address + MXT_T100_TCHAUX, + 1, &tchaux); + if (error) + return error; + + /* Handle default values */ + if (range_x == 0) + range_x = 1023; + + if (range_y == 0) + range_y = 1023; + + dev_dbg(&client->dev, "initial x=%d y=%d\n", range_x, range_y); + + if (cfg & MXT_T100_CFG_SWITCHXY) { + dev_dbg(&client->dev, "flip x and y\n"); + temp = range_y; + range_y = range_x; + range_x = temp; + } + + if (data->pdata->panel_maxx != range_x) { + if (cfg & MXT_T100_CFG_SWITCHXY) + range_x = data->pdata->panel_maxy; + else + range_x = data->pdata->panel_maxx; + cpu_to_le16s(range_x); + error = __mxt_write_reg(client, + object->start_address + MXT_T100_XRANGE, + sizeof(range_x), &range_x); + if (error) + return error; + dev_dbg(&client->dev, "panel maxx mismatch. update\n"); + update = true; + } + + if (data->pdata->panel_maxy != range_y) { + if (cfg & MXT_T100_CFG_SWITCHXY) + range_y = data->pdata->panel_maxx; + else + range_y = data->pdata->panel_maxy; + + cpu_to_le16s(range_y); + error = __mxt_write_reg(client, + object->start_address + MXT_T100_YRANGE, + sizeof(range_y), &range_y); + if (error) + return error; + dev_dbg(&client->dev, "panel maxy mismatch. update\n"); + update = true; + } + + if (update) { + mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); + + mxt_soft_reset(data); + } + + /* allocate aux bytes */ + aux = 6; + + if (tchaux & MXT_T100_TCHAUX_VECT) + data->t100_aux_vect = aux++; + + if (tchaux & MXT_T100_TCHAUX_AMPL) + data->t100_aux_ampl = aux++; + + if (tchaux & MXT_T100_TCHAUX_AREA) + data->t100_aux_area = aux++; + + dev_info(&client->dev, + "T100 Touchscreen size X%uY%u\n", range_x, range_y); + + return 0; +} + +static int mxt_update_t9_resolution(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + struct t9_range range = {0}; + unsigned char orient = 0; + struct mxt_object *object; + u16 temp; + bool update = false; + + object = mxt_get_object(data, MXT_TOUCH_MULTI_T9); + if (!object) + return -EINVAL; + + error = __mxt_read_reg(client, + object->start_address + MXT_T9_RANGE, + sizeof(range), &range); + if (error) + return error; + + le16_to_cpus(range.x); + le16_to_cpus(range.y); + + error = __mxt_read_reg(client, + object->start_address + MXT_T9_ORIENT, + 1, &orient); + if (error) + return error; + + /* Handle default values */ + if (range.x == 0) + range.x = 1023; + + if (range.y == 0) + range.y = 1023; + + dev_dbg(&client->dev, "initial x=%d y=%d\n", range.x, range.y); + + if (orient & MXT_T9_ORIENT_SWITCH) { + dev_dbg(&client->dev, "flip x and y\n"); + temp = range.y; + range.y = range.x; + range.x = temp; + } + + if (data->pdata->panel_maxx != range.x) { + if (orient & MXT_T9_ORIENT_SWITCH) + range.x = data->pdata->panel_maxy; + else + range.x = data->pdata->panel_maxx; + cpu_to_le16s(range.x); + error = __mxt_write_reg(client, + object->start_address + MXT_T100_XRANGE, + sizeof(range.x), &range.x); + if (error) + return error; + dev_dbg(&client->dev, "panel maxx mismatch. update\n"); + update = true; + } + + if (data->pdata->panel_maxy != range.y) { + if (orient & MXT_T9_ORIENT_SWITCH) + range.y = data->pdata->panel_maxx; + else + range.y = data->pdata->panel_maxy; + + cpu_to_le16s(range.y); + error = __mxt_write_reg(client, + object->start_address + MXT_T100_YRANGE, + sizeof(range.y), &range.y); + if (error) + return error; + dev_dbg(&client->dev, "panel maxy mismatch. update\n"); + update = true; + } + + if (update) { + mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); + + mxt_soft_reset(data); + } + + dev_info(&client->dev, + "Touchscreen size X%uY%u\n", range.x, range.y); + + return 0; +} + +/* + * mxt_load_cfg - download configuration to chip + * + * Atmel Raw Config File Format + * + * The first four lines of the raw config file contain: + * 1) Version + * 2) Chip ID Information (first 7 bytes of device memory) + * 3) Chip Information Block 24-bit CRC Checksum + * 4) Chip Configuration 24-bit CRC Checksum + * + * The rest of the file consists of one line per object instance: + * <TYPE> <INSTANCE> <SIZE> <CONTENTS> + * + * <TYPE> - 2-byte object type as hex + * <INSTANCE> - 2-byte object instance number as hex + * <SIZE> - 2-byte object size as hex + * <CONTENTS> - array of <SIZE> 1-byte hex values + */ +static int mxt_load_cfg(struct mxt_data *data, bool force) +{ + struct device *dev = &data->client->dev; + struct mxt_info cfg_info; + struct mxt_object *object; + const struct firmware *cfg = NULL; + int ret = 0; + int offset; + int data_pos; + int byte_offset; + int i; + int cfg_start_ofs; + u32 info_crc, config_crc, calculated_crc; + u8 *config_mem; + unsigned int config_mem_size; + unsigned int type, instance, size; + u8 val; + int ver[3]; + u16 reg; + + ret = request_firmware(&cfg, data->cfg_name, dev); + if (ret < 0) { + dev_err(dev, "Failure to request config file %s\n", + data->cfg_name); + goto report_enable; + } + + mxt_update_crc(data, MXT_COMMAND_REPORTALL, 1); + + if (strncmp(cfg->data, MXT_CFG_MAGIC, strlen(MXT_CFG_MAGIC))) { + dev_err(dev, "Unrecognised config file\n"); + ret = -EINVAL; + goto release; + } + + data_pos = strlen(MXT_CFG_MAGIC); + + /* Load information block and check */ + for (i = 0; i < sizeof(struct mxt_info); i++) { + ret = sscanf(cfg->data + data_pos, "%hhx%n", + (unsigned char *)&cfg_info + i, + &offset); + if (ret != 1) { + dev_err(dev, "Bad format\n"); + ret = -EINVAL; + goto release; + } + + data_pos += offset; + } + + if (cfg_info.family_id != data->info->family_id) { + dev_err(dev, "Family ID mismatch!\n"); + ret = -EINVAL; + goto release; + } + + if (cfg_info.variant_id != data->info->variant_id) { + dev_err(dev, "Variant ID mismatch!\n"); + ret = -EINVAL; + goto release; + } + + /* Read CRCs */ + ret = sscanf(cfg->data + data_pos, "%x%n", &info_crc, &offset); + if (ret != 1) { + dev_err(dev, "Bad format: failed to parse Info CRC\n"); + ret = -EINVAL; + goto release; + } + data_pos += offset; + + ret = sscanf(cfg->data + data_pos, "%x%n", &config_crc, &offset); + if (ret != 1) { + dev_err(dev, "Bad format: failed to parse Config CRC\n"); + ret = -EINVAL; + goto release; + } + data_pos += offset; + + /* Malloc memory to store configuration */ + cfg_start_ofs = MXT_OBJECT_START + + data->info->object_num * sizeof(struct mxt_object) + + MXT_INFO_CHECKSUM_SIZE; + config_mem_size = data->mem_size - cfg_start_ofs; + config_mem = kzalloc(config_mem_size, GFP_KERNEL); + if (!config_mem) { + dev_err(dev, "Failed to allocate memory\n"); + ret = -ENOMEM; + goto release; + } + + while (data_pos < cfg->size) { + /* Read type, instance, length */ + ret = sscanf(cfg->data + data_pos, "%x %x %x%n", + &type, &instance, &size, &offset); + if (ret == 0) { + /* EOF */ + break; + } else if (ret != 3) { + dev_err(dev, "Bad format: failed to parse object\n"); + ret = -EINVAL; + goto release_mem; + } + data_pos += offset; + + if (type == MXT_SPT_USERDATA_T38) { + ret = sscanf(cfg->data + data_pos, "%x %x %x", + &ver[0], &ver[1], &ver[2]); + dev_info(dev, "controller version:%d.%d.%d file version:%d.%d.%d", + data->cfg_version[0], data->cfg_version[1], + data->cfg_version[2], ver[0], ver[1], ver[2]); + + if (force || data->fw_w_no_cfg_update) { + dev_info(dev, "starting force cfg update\n"); + } else if (data->cfg_version[0] != ver[0]) { + dev_info(dev, "cfg major versions do not match\n"); + ret = -EINVAL; + goto release_mem; + } else if (data->cfg_version[1] > ver[1]) { + dev_info(dev, "configuration is up-to-date\n"); + ret = -EINVAL; + goto release_mem; + } else if (data->cfg_version[1] == ver[1]) { + if (data->cfg_version[2] >= ver[2]) { + dev_info(dev, "configuration is up-to-date\n"); + ret = -EINVAL; + goto release_mem; + } + } else { + dev_info(dev, "starting cfg update\n"); + } + } + + object = mxt_get_object(data, type); + if (!object) { + /* Skip object */ + for (i = 0; i < size; i++) { + ret = sscanf(cfg->data + data_pos, "%hhx%n", + &val, + &offset); + data_pos += offset; + } + continue; + } + + if (size > mxt_obj_size(object)) { + /* Either we are in fallback mode due to wrong + * config or config from a later fw version, + * or the file is corrupt or hand-edited */ + dev_warn(dev, "Discarding %u byte(s) in T%u\n", + size - mxt_obj_size(object), type); + } else if (mxt_obj_size(object) > size) { + /* If firmware is upgraded, new bytes may be added to + * end of objects. It is generally forward compatible + * to zero these bytes - previous behaviour will be + * retained. However this does invalidate the CRC and + * will force fallback mode until the configuration is + * updated. We warn here but do nothing else - the + * malloc has zeroed the entire configuration. */ + dev_warn(dev, "Zeroing %u byte(s) in T%d\n", + mxt_obj_size(object) - size, type); + } + + if (instance >= mxt_obj_instances(object)) { + dev_err(dev, "Object instances exceeded!\n"); + ret = -EINVAL; + goto release_mem; + } + + reg = object->start_address + mxt_obj_size(object) * instance; + + for (i = 0; i < size; i++) { + ret = sscanf(cfg->data + data_pos, "%hhx%n", + &val, + &offset); + if (ret != 1) { + dev_err(dev, "Bad format in T%d\n", type); + ret = -EINVAL; + goto release_mem; + } + data_pos += offset; + + if (i > mxt_obj_size(object)) + continue; + + byte_offset = reg + i - cfg_start_ofs; + + if ((byte_offset >= 0) + && (byte_offset <= config_mem_size)) { + *(config_mem + byte_offset) = val; + } else { + dev_err(dev, "Bad object: reg:%d, T%d, ofs=%d\n", + reg, object->type, byte_offset); + ret = -EINVAL; + goto release_mem; + } + } + } + + /* calculate crc of the received configs (not the raw config file) */ + if (data->T7_address < cfg_start_ofs) { + dev_err(dev, "Bad T7 address, T7addr = %x, config offset %x\n", + data->T7_address, cfg_start_ofs); + ret = 0; + goto release_mem; + } + + calculated_crc = mxt_calculate_crc(config_mem, + data->T7_address - cfg_start_ofs, + config_mem_size); + + if (config_crc > 0 && (config_crc != calculated_crc)) + dev_warn(dev, "Config CRC error, calculated=%06X, file=%06X\n", + calculated_crc, config_crc); + + /* Write configuration as blocks */ + byte_offset = 0; + while (byte_offset < config_mem_size) { + size = config_mem_size - byte_offset; + + if (size > MXT_MAX_BLOCK_WRITE) + size = MXT_MAX_BLOCK_WRITE; + + ret = __mxt_write_reg(data->client, + cfg_start_ofs + byte_offset, + size, config_mem + byte_offset); + if (ret != 0) { + dev_err(dev, "Config write error, ret=%d\n", ret); + goto release_mem; + } + + byte_offset += size; + } + + mxt_update_crc(data, MXT_COMMAND_BACKUPNV, MXT_BACKUP_VALUE); + + ret = mxt_check_retrigen(data); + if (ret) + goto release_mem; + + ret = mxt_soft_reset(data); + if (ret) + goto release_mem; + + dev_info(dev, "Config written\n"); + + /* T7 config may have changed */ + mxt_init_t7_power_cfg(data); + + mxt_update_cfg_version(data); + + /* update resolution if needed */ + if (data->T9_reportid_min) { + ret = mxt_update_t9_resolution(data); + if (ret) + goto release_mem; + } else if (data->T100_reportid_min) { + ret = mxt_update_t100_resolution(data); + if (ret) + goto release_mem; + } else { + dev_warn(dev, "No touch object detected\n"); + } + +release_mem: + kfree(config_mem); +release: + release_firmware(cfg); +report_enable: + data->enable_reporting = true; + + return ret; +} + +static int mxt_set_t7_power_cfg(struct mxt_data *data, u8 sleep) +{ + struct device *dev = &data->client->dev; + int error; + struct t7_config *new_config; + struct t7_config deepsleep = { .active = 0, .idle = 0 }; + + if (sleep == MXT_POWER_CFG_DEEPSLEEP) + new_config = &deepsleep; + else + new_config = &data->t7_cfg; + + error = __mxt_write_reg(data->client, data->T7_address, + sizeof(data->t7_cfg), + new_config); + if (error) + return error; + + dev_dbg(dev, "Set T7 ACTV:%d IDLE:%d\n", + new_config->active, new_config->idle); + + return 0; +} + +static int mxt_init_t7_power_cfg(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + int error; + bool retry = false; + +recheck: + error = __mxt_read_reg(data->client, data->T7_address, + sizeof(data->t7_cfg), &data->t7_cfg); + if (error) + return error; + + if (data->t7_cfg.active == 0 || data->t7_cfg.idle == 0) { + if (!retry) { + dev_info(dev, "T7 cfg zero, resetting\n"); + mxt_soft_reset(data); + retry = true; + goto recheck; + } else { + dev_dbg(dev, "T7 cfg zero after reset, overriding\n"); + data->t7_cfg.active = 20; + data->t7_cfg.idle = 100; + return mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN); + } + } else { + dev_info(dev, "Initialised power cfg: ACTV %d, IDLE %d\n", + data->t7_cfg.active, data->t7_cfg.idle); + return 0; + } +} + +static int mxt_acquire_irq(struct mxt_data *data) +{ + int error; + + enable_irq(data->irq); + + if (data->use_retrigen_workaround) { + error = mxt_process_messages_until_invalid(data); + if (error) + return error; + } + + return 0; +} + +static void mxt_free_input_device(struct mxt_data *data) +{ + if (data->input_dev) { + input_unregister_device(data->input_dev); + data->input_dev = NULL; + } +} + +static void mxt_free_object_table(struct mxt_data *data) +{ + mxt_debug_msg_remove(data); + + kfree(data->raw_info_block); + data->object_table = NULL; + data->info = NULL; + data->raw_info_block = NULL; + kfree(data->msg_buf); + data->msg_buf = NULL; + + mxt_free_input_device(data); + + data->enable_reporting = false; + data->T5_address = 0; + data->T5_msg_size = 0; + data->T6_reportid = 0; + data->T7_address = 0; + data->T9_reportid_min = 0; + data->T9_reportid_max = 0; + data->T15_reportid_min = 0; + data->T15_reportid_max = 0; + data->T18_address = 0; + data->T19_reportid = 0; + data->T42_reportid_min = 0; + data->T42_reportid_max = 0; + data->T44_address = 0; + data->T48_reportid = 0; + data->T63_reportid_min = 0; + data->T63_reportid_max = 0; + data->T100_reportid_min = 0; + data->T100_reportid_max = 0; + data->max_reportid = 0; +} + +static int mxt_parse_object_table(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int i; + u8 reportid; + u16 end_address; + + /* Valid Report IDs start counting from 1 */ + reportid = 1; + data->mem_size = 0; + for (i = 0; i < data->info->object_num; i++) { + struct mxt_object *object = data->object_table + i; + u8 min_id, max_id; + + le16_to_cpus(&object->start_address); + + if (object->num_report_ids) { + min_id = reportid; + reportid += object->num_report_ids * + mxt_obj_instances(object); + max_id = reportid - 1; + } else { + min_id = 0; + max_id = 0; + } + + dev_dbg(&data->client->dev, + "T%u Start:%u Size:%u Instances:%u Report IDs:%u-%u\n", + object->type, object->start_address, + mxt_obj_size(object), mxt_obj_instances(object), + min_id, max_id); + + switch (object->type) { + case MXT_GEN_MESSAGE_T5: + if (data->info->family_id == 0x80) { + /* On mXT224 read and discard unused CRC byte + * otherwise DMA reads are misaligned */ + data->T5_msg_size = mxt_obj_size(object); + } else { + /* CRC not enabled, so skip last byte */ + data->T5_msg_size = mxt_obj_size(object) - 1; + } + data->T5_address = object->start_address; + case MXT_GEN_COMMAND_T6: + data->T6_reportid = min_id; + data->T6_address = object->start_address; + break; + case MXT_GEN_POWER_T7: + data->T7_address = object->start_address; + break; + case MXT_TOUCH_MULTI_T9: + /* Only handle messages from first T9 instance */ + data->T9_reportid_min = min_id; + data->T9_reportid_max = min_id + + object->num_report_ids - 1; + data->num_touchids = object->num_report_ids; + break; + case MXT_TOUCH_KEYARRAY_T15: + data->T15_reportid_min = min_id; + data->T15_reportid_max = max_id; + break; + case MXT_SPT_COMMSCONFIG_T18: + data->T18_address = object->start_address; + break; + case MXT_PROCI_TOUCHSUPPRESSION_T42: + data->T42_reportid_min = min_id; + data->T42_reportid_max = max_id; + break; + case MXT_SPT_MESSAGECOUNT_T44: + data->T44_address = object->start_address; + break; + case MXT_SPT_GPIOPWM_T19: + data->T19_reportid = min_id; + break; + case MXT_PROCG_NOISESUPPRESSION_T48: + data->T48_reportid = min_id; + break; + case MXT_PROCI_ACTIVE_STYLUS_T63: + /* Only handle messages from first T63 instance */ + data->T63_reportid_min = min_id; + data->T63_reportid_max = min_id; + data->num_stylusids = 1; + break; + case MXT_TOUCH_MULTITOUCHSCREEN_T100: + data->T100_reportid_min = min_id; + data->T100_reportid_max = max_id; + /* first two report IDs reserved */ + data->num_touchids = object->num_report_ids - 2; + break; + } + + end_address = object->start_address + + mxt_obj_size(object) * mxt_obj_instances(object) - 1; + + if (end_address >= data->mem_size) + data->mem_size = end_address + 1; + } + + /* Store maximum reportid */ + data->max_reportid = reportid; + + /* If T44 exists, T5 position has to be directly after */ + if (data->T44_address && (data->T5_address != data->T44_address + 1)) { + dev_err(&client->dev, "Invalid T44 position\n"); + return -EINVAL; + } + + data->msg_buf = kcalloc(data->max_reportid, + data->T5_msg_size, GFP_KERNEL); + if (!data->msg_buf) { + dev_err(&client->dev, "Failed to allocate message buffer\n"); + return -ENOMEM; + } + + return 0; +} + +static int mxt_read_info_block(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + u16 size; + void *buf; + uint8_t num_objects; + u32 calculated_crc; + u8 *crc_ptr; + + /* If info block already allocated, free it */ + if (data->raw_info_block != NULL) + mxt_free_object_table(data); + + /* Read 7-byte ID information block starting at address 0 */ + size = sizeof(struct mxt_info); + buf = kzalloc(size, GFP_KERNEL); + if (!buf) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + error = __mxt_read_reg(client, 0, size, buf); + if (error) + goto err_free_mem; + + /* Resize buffer to give space for rest of info block */ + num_objects = ((struct mxt_info *)buf)->object_num; + size += (num_objects * sizeof(struct mxt_object)) + + MXT_INFO_CHECKSUM_SIZE; + + buf = krealloc(buf, size, GFP_KERNEL); + if (!buf) { + dev_err(&client->dev, "Failed to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + /* Read rest of info block */ + error = __mxt_read_reg(client, MXT_OBJECT_START, + size - MXT_OBJECT_START, + buf + MXT_OBJECT_START); + if (error) + goto err_free_mem; + + /* Extract & calculate checksum */ + crc_ptr = buf + size - MXT_INFO_CHECKSUM_SIZE; + data->info_crc = crc_ptr[0] | (crc_ptr[1] << 8) | (crc_ptr[2] << 16); + + calculated_crc = mxt_calculate_crc(buf, 0, + size - MXT_INFO_CHECKSUM_SIZE); + + /* CRC mismatch can be caused by data corruption due to I2C comms + * issue or else device is not using Object Based Protocol */ + if ((data->info_crc == 0) || (data->info_crc != calculated_crc)) { + dev_err(&client->dev, + "Info Block CRC error calculated=0x%06X read=0x%06X\n", + data->info_crc, calculated_crc); + if (!data->pdata->ignore_crc) + goto err_free_mem; + } + + /* Save pointers in device data structure */ + data->raw_info_block = buf; + data->info = (struct mxt_info *)buf; + data->object_table = (struct mxt_object *)(buf + MXT_OBJECT_START); + + /* Parse object table information */ + error = mxt_parse_object_table(data); + if (error) { + dev_err(&client->dev, "Error %d reading object table\n", error); + goto err_free_obj_table; + } + + error = mxt_update_cfg_version(data); + if (error) + goto err_free_obj_table; + + dev_info(&client->dev, + "Family: %u Variant: %u Firmware V%u.%u.%02X Objects: %u cfg version: %d.%d.%d\n", + data->info->family_id, data->info->variant_id, + data->info->version >> 4, data->info->version & 0xf, + data->info->build, data->info->object_num, + data->cfg_version[0], data->cfg_version[1], + data->cfg_version[2]); + + + return 0; + +err_free_obj_table: + mxt_free_object_table(data); +err_free_mem: + kfree(buf); + return error; +} + +static int mxt_pinctrl_init(struct mxt_data *data) +{ + int error; + + /* Get pinctrl if target uses pinctrl */ + data->ts_pinctrl = devm_pinctrl_get((&data->client->dev)); + if (IS_ERR_OR_NULL(data->ts_pinctrl)) { + dev_dbg(&data->client->dev, + "Device does not use pinctrl\n"); + error = PTR_ERR(data->ts_pinctrl); + data->ts_pinctrl = NULL; + return error; + } + + data->gpio_state_active + = pinctrl_lookup_state(data->ts_pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(data->gpio_state_active)) { + dev_dbg(&data->client->dev, + "Can not get ts default pinstate\n"); + error = PTR_ERR(data->gpio_state_active); + data->ts_pinctrl = NULL; + return error; + } + + data->gpio_state_suspend + = pinctrl_lookup_state(data->ts_pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(data->gpio_state_suspend)) { + dev_dbg(&data->client->dev, + "Can not get ts sleep pinstate\n"); + error = PTR_ERR(data->gpio_state_suspend); + data->ts_pinctrl = NULL; + return error; + } + + return 0; +} + +static int mxt_pinctrl_select(struct mxt_data *data, bool on) +{ + struct pinctrl_state *pins_state; + int error; + + pins_state = on ? data->gpio_state_active + : data->gpio_state_suspend; + if (!IS_ERR_OR_NULL(pins_state)) { + error = pinctrl_select_state(data->ts_pinctrl, pins_state); + if (error) { + dev_err(&data->client->dev, + "can not set %s pins\n", + on ? "pmx_ts_active" : "pmx_ts_suspend"); + return error; + } + } else { + dev_err(&data->client->dev, + "not a valid '%s' pinstate\n", + on ? "pmx_ts_active" : "pmx_ts_suspend"); + } + + return 0; +} + +static int mxt_gpio_enable(struct mxt_data *data, bool enable) +{ + const struct mxt_platform_data *pdata = data->pdata; + int error; + + if (data->ts_pinctrl) { + error = mxt_pinctrl_select(data, enable); + if (error < 0) + return error; + } + + if (enable) { + if (gpio_is_valid(pdata->gpio_irq)) { + error = gpio_request(pdata->gpio_irq, + "maxtouch_gpio_irq"); + if (error) { + dev_err(&data->client->dev, + "unable to request %d gpio(%d)\n", + pdata->gpio_irq, error); + return error; + } + error = gpio_direction_input(pdata->gpio_irq); + if (error) { + dev_err(&data->client->dev, + "unable to set dir for %d gpio(%d)\n", + data->pdata->gpio_irq, error); + goto err_free_irq; + } + + } else { + dev_err(&data->client->dev, "irq gpio not provided\n"); + return -EINVAL; + } + + if (gpio_is_valid(pdata->gpio_reset)) { + error = gpio_request(pdata->gpio_reset, + "maxtouch_gpio_reset"); + if (error) { + dev_err(&data->client->dev, + "unable to request %d gpio(%d)\n", + pdata->gpio_reset, error); + goto err_free_irq; + } + error = gpio_direction_output(pdata->gpio_reset, 0); + if (error) { + dev_err(&data->client->dev, + "unable to set dir for %d gpio(%d)\n", + pdata->gpio_reset, error); + goto err_free_reset; + } + } else { + dev_err(&data->client->dev, + "reset gpio not provided\n"); + goto err_free_irq; + } + + if (gpio_is_valid(pdata->gpio_i2cmode)) { + error = gpio_request(pdata->gpio_i2cmode, + "maxtouch_gpio_i2cmode"); + if (error) { + dev_err(&data->client->dev, + "unable to request %d gpio (%d)\n", + pdata->gpio_i2cmode, error); + goto err_free_reset; + } + error = gpio_direction_output(pdata->gpio_i2cmode, 1); + if (error) { + dev_err(&data->client->dev, + "unable to set dir for %d gpio (%d)\n", + pdata->gpio_i2cmode, error); + goto err_free_i2cmode; + } + } else { + dev_info(&data->client->dev, + "i2cmode gpio is not used\n"); + } + } else { + if (gpio_is_valid(pdata->gpio_irq)) + gpio_free(pdata->gpio_irq); + + if (gpio_is_valid(pdata->gpio_reset)) { + gpio_free(pdata->gpio_reset); + } + + if (gpio_is_valid(pdata->gpio_i2cmode)) { + gpio_set_value(pdata->gpio_i2cmode, 0); + gpio_free(pdata->gpio_i2cmode); + } + } + + return 0; + +err_free_i2cmode: + if (gpio_is_valid(pdata->gpio_i2cmode)) { + gpio_set_value(pdata->gpio_i2cmode, 0); + gpio_free(pdata->gpio_i2cmode); + } +err_free_reset: + if (gpio_is_valid(pdata->gpio_reset)) { + gpio_free(pdata->gpio_reset); + } +err_free_irq: + if (gpio_is_valid(pdata->gpio_irq)) + gpio_free(pdata->gpio_irq); + return error; +} + +static int mxt_regulator_enable(struct mxt_data *data) +{ + int error; + + if (!data->use_regulator) + return 0; + + gpio_set_value(data->pdata->gpio_reset, 0); + + error = regulator_enable(data->reg_vdd); + if (error) { + dev_err(&data->client->dev, + "vdd enable failed, error=%d\n", error); + return error; + } + error = regulator_enable(data->reg_avdd); + if (error) { + dev_err(&data->client->dev, + "avdd enable failed, error=%d\n", error); + goto err_dis_vdd; + } + + if (!IS_ERR(data->reg_xvdd)) { + error = regulator_enable(data->reg_xvdd); + if (error) { + dev_err(&data->client->dev, + "xvdd enable failed, error=%d\n", error); + goto err_dis_avdd; + } + } + msleep(MXT_REGULATOR_DELAY); + + reinit_completion(&data->bl_completion); + gpio_set_value(data->pdata->gpio_reset, 1); + mxt_wait_for_completion(data, &data->bl_completion, MXT_POWERON_DELAY); + + return 0; + +err_dis_avdd: + regulator_disable(data->reg_avdd); +err_dis_vdd: + regulator_disable(data->reg_vdd); + return error; +} + +static void mxt_regulator_disable(struct mxt_data *data) +{ + regulator_disable(data->reg_vdd); + regulator_disable(data->reg_avdd); + if (!IS_ERR(data->reg_xvdd)) + regulator_disable(data->reg_xvdd); +} + +static int mxt_regulator_configure(struct mxt_data *data, bool state) +{ + struct device *dev = &data->client->dev; + struct device_node *np = dev->of_node; + struct property *prop; + int error = 0; + + /* According to maXTouch power sequencing specification, RESET line + * must be kept low until some time after regulators come up to + * voltage */ + if (!data->pdata->gpio_reset) { + dev_warn(dev, "Must have reset GPIO to use regulator support\n"); + return 0; + } + + if (!state) + goto deconfig; + + data->reg_vdd = regulator_get(dev, "vdd"); + if (IS_ERR(data->reg_vdd)) { + error = PTR_ERR(data->reg_vdd); + dev_err(dev, "Error %d getting vdd regulator\n", error); + return error; + } + + if (regulator_count_voltages(data->reg_vdd) > 0) { + error = regulator_set_voltage(data->reg_vdd, + MXT_VDD_VTG_MIN_UV, MXT_VDD_VTG_MAX_UV); + if (error) { + dev_err(&data->client->dev, + "vdd set_vtg failed err=%d\n", error); + goto fail_put_vdd; + } + } + + data->reg_avdd = regulator_get(dev, "avdd"); + if (IS_ERR(data->reg_avdd)) { + error = PTR_ERR(data->reg_avdd); + dev_err(dev, "Error %d getting avdd regulator\n", error); + goto fail_put_vdd; + } + + if (regulator_count_voltages(data->reg_avdd) > 0) { + error = regulator_set_voltage(data->reg_avdd, + MXT_AVDD_VTG_MIN_UV, MXT_AVDD_VTG_MAX_UV); + if (error) { + dev_err(&data->client->dev, + "avdd set_vtg failed err=%d\n", error); + goto fail_put_avdd; + } + } + + data->reg_xvdd = regulator_get(dev, "xvdd"); + if (IS_ERR(data->reg_xvdd)) { + error = PTR_ERR(data->reg_xvdd); + prop = of_find_property(np, "xvdd-supply", NULL); + if (prop && (error == -EPROBE_DEFER)) + return -EPROBE_DEFER; + dev_info(dev, "xvdd regulator is not used\n"); + } else { + if (regulator_count_voltages(data->reg_xvdd) > 0) { + error = regulator_set_voltage(data->reg_xvdd, + MXT_XVDD_VTG_MIN_UV, MXT_XVDD_VTG_MAX_UV); + if (error) + dev_err(&data->client->dev, + "xvdd set_vtg failed err=%d\n", error); + } + } + + data->use_regulator = true; + + dev_dbg(dev, "Initialised regulators\n"); + return 0; + +deconfig: + if (!IS_ERR(data->reg_xvdd)) + regulator_put(data->reg_xvdd); +fail_put_avdd: + regulator_put(data->reg_avdd); +fail_put_vdd: + regulator_put(data->reg_vdd); + return error; +} + + +#if defined(CONFIG_SECURE_TOUCH) +static void mxt_secure_touch_stop(struct mxt_data *data, int blocking) +{ + if (atomic_read(&data->st_enabled)) { + atomic_set(&data->st_pending_irqs, -1); + mxt_secure_touch_notify(data); + if (blocking) + wait_for_completion_interruptible(&data->st_powerdown); + } +} +#else +static void mxt_secure_touch_stop(struct mxt_data *data, int blocking) +{ +} +#endif + +static void mxt_start(struct mxt_data *data) +{ + if (!data->suspended || data->in_bootloader) + return; + + mxt_secure_touch_stop(data, 1); + + /* enable gpios */ + mxt_gpio_enable(data, true); + + /* enable regulators */ + mxt_regulator_enable(data); + + /* Discard any messages still in message buffer from before + * chip went to sleep */ + mxt_process_messages_until_invalid(data); + + mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN); + + /* Recalibrate since chip has been in deep sleep */ + mxt_t6_command(data, MXT_COMMAND_CALIBRATE, 1, false); + + mxt_acquire_irq(data); + data->enable_reporting = true; + data->suspended = false; +} + +static void mxt_reset_slots(struct mxt_data *data) +{ + struct input_dev *input_dev = data->input_dev; + unsigned int num_mt_slots; + int id; + + num_mt_slots = data->num_touchids + data->num_stylusids; + + for (id = 0; id < num_mt_slots; id++) { + input_mt_slot(input_dev, id); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, 0); + } + + mxt_input_sync(input_dev); +} + +static void mxt_stop(struct mxt_data *data) +{ + if (data->suspended || data->in_bootloader) + return; + + mxt_secure_touch_stop(data, 1); + + data->enable_reporting = false; + disable_irq(data->irq); + + /* put in deep sleep */ + mxt_set_t7_power_cfg(data, MXT_POWER_CFG_DEEPSLEEP); + + /* disable regulators */ + mxt_regulator_disable(data); + + /* disable gpios */ + mxt_gpio_enable(data, false); + + mxt_reset_slots(data); + data->suspended = true; +} + + +static int mxt_input_open(struct input_dev *dev) +{ + struct mxt_data *data = input_get_drvdata(dev); + + mxt_start(data); + + return 0; +} + +static void mxt_input_close(struct input_dev *dev) +{ + struct mxt_data *data = input_get_drvdata(dev); + + mxt_stop(data); +} + +static int mxt_create_input_dev(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + struct input_dev *input_dev; + int error; + unsigned int num_mt_slots; + int i; + + if (data->T9_reportid_min) { + error = mxt_update_t9_resolution(data); + if (error) { + dev_err(dev, "update resolution failed\n"); + return error; + } + } else if (data->T100_reportid_min) { + error = mxt_update_t100_resolution(data); + if (error) { + dev_err(dev, "update resolution failed\n"); + return error; + } + } else { + dev_warn(dev, "No touch object detected\n"); + } + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + input_dev->name = "Atmel maXTouch Touchscreen"; + input_dev->phys = data->phys; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = dev; + input_dev->open = mxt_input_open; + input_dev->close = mxt_input_close; + + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + num_mt_slots = data->num_touchids + data->num_stylusids; + error = input_mt_init_slots(input_dev, num_mt_slots, 0); + if (error) { + dev_err(dev, "Error %d initialising slots\n", error); + goto err_free_mem; + } + + input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, + 0, MXT_MAX_AREA, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, + data->pdata->disp_minx, data->pdata->disp_maxx, + 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, + data->pdata->disp_miny, data->pdata->disp_maxy, + 0, 0); + input_set_abs_params(input_dev, ABS_MT_PRESSURE, + 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_MT_ORIENTATION, + 0, 255, 0, 0); + + /* For T63 active stylus */ + if (data->T63_reportid_min) { + input_set_capability(input_dev, EV_KEY, BTN_STYLUS); + input_set_capability(input_dev, EV_KEY, BTN_STYLUS2); + input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE, + 0, MT_TOOL_MAX, 0, 0); + } + + /* For T15 key array */ + if (data->pdata->key_codes && data->T15_reportid_min) { + data->t15_keystatus = 0; + + for (i = 0; i < data->pdata->t15_num_keys; i++) + input_set_capability(input_dev, EV_KEY, + data->pdata->t15_keymap[i]); + } + + input_set_drvdata(input_dev, data); + + error = input_register_device(input_dev); + if (error) { + dev_err(dev, "Error %d registering input device\n", error); + goto err_free_mem; + } + + data->input_dev = input_dev; + + return 0; + +err_free_mem: + input_free_device(input_dev); + return error; +} + +static int mxt_configure_objects(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + + error = mxt_debug_msg_init(data); + if (error) + return error; + + error = mxt_init_t7_power_cfg(data); + if (error) + dev_dbg(&client->dev, "Failed to initialize power cfg\n"); + + return 0; +} + +#ifdef CONFIG_OF +static int mxt_get_dt_coords(struct device *dev, char *name, + struct mxt_platform_data *pdata) +{ + u32 coords[MXT_COORDS_ARR_SIZE]; + struct property *prop; + struct device_node *np = dev->of_node; + size_t coords_size; + int error; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + coords_size = prop->length / sizeof(u32); + if (coords_size != MXT_COORDS_ARR_SIZE) { + dev_err(dev, "invalid %s\n", name); + return -EINVAL; + } + + error = of_property_read_u32_array(np, name, coords, coords_size); + if (error && (error != -EINVAL)) { + dev_err(dev, "Unable to read %s\n", name); + return error; + } + + if (strcmp(name, "atmel,panel-coords") == 0) { + pdata->panel_minx = coords[0]; + pdata->panel_miny = coords[1]; + pdata->panel_maxx = coords[2]; + pdata->panel_maxy = coords[3]; + } else if (strcmp(name, "atmel,display-coords") == 0) { + pdata->disp_minx = coords[0]; + pdata->disp_miny = coords[1]; + pdata->disp_maxx = coords[2]; + pdata->disp_maxy = coords[3]; + } else { + dev_err(dev, "unsupported property %s\n", name); + return -EINVAL; + } + + return 0; +} + +static int mxt_search_fw_name(struct mxt_data *data) +{ + struct device *dev = &data->client->dev; + struct device_node *np = dev->of_node, *temp; + u32 temp_val; size_t len; + int rc; + + data->fw_name[0] = '\0'; + + if (data->in_bootloader) + return 0; + + for_each_child_of_node(np, temp) { + rc = of_property_read_u32(temp, "atmel,version", &temp_val); + if (rc) { + dev_err(dev, "Unable to read controller version\n"); + return rc; + } + + if (temp_val != data->info->version) + continue; + + rc = of_property_read_u32(temp, "atmel,build", &temp_val); + if (rc) { + dev_err(dev, "Unable to read build id\n"); + return rc; + } + + if (temp_val != data->info->build) + continue; + + rc = of_property_read_string(temp, "atmel,fw-name", + &data->pdata->fw_name); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw name\n"); + return rc; + } + + dev_dbg(dev, "fw name found(%s)\n", data->pdata->fw_name); + + if (data->pdata->fw_name) { + len = strlen(data->pdata->fw_name); + if (len > MXT_NAME_MAX_LEN - 1) { + dev_err(dev, "Invalid firmware name\n"); + return -EINVAL; + } + strlcpy(data->fw_name, data->pdata->fw_name, len + 1); + } + } + + return 0; +} + +static int mxt_parse_dt(struct device *dev, struct mxt_platform_data *pdata) +{ + int error; + u32 temp_val; + struct device_node *np = dev->of_node; + struct property *prop; + + error = mxt_get_dt_coords(dev, "atmel,panel-coords", pdata); + if (error) + return error; + + error = mxt_get_dt_coords(dev, "atmel,display-coords", pdata); + if (error) + return error; + + pdata->cfg_name = MXT_GEN_CFG; + error = of_property_read_string(np, "atmel,cfg-name", &pdata->cfg_name); + if (error && (error != -EINVAL)) { + dev_err(dev, "Unable to read cfg name\n"); + return error; + } + + /* reset, irq gpio info */ + pdata->gpio_reset = of_get_named_gpio_flags(np, "atmel,reset-gpio", + 0, &temp_val); + pdata->resetflags = temp_val; + pdata->gpio_irq = of_get_named_gpio_flags(np, "atmel,irq-gpio", + 0, &temp_val); + pdata->irqflags = temp_val; + pdata->gpio_i2cmode = of_get_named_gpio_flags(np, "atmel,i2cmode-gpio", + 0, &temp_val); + + pdata->ignore_crc = of_property_read_bool(np, "atmel,ignore-crc"); + + error = of_property_read_u32(np, "atmel,bl-addr", &temp_val); + if (error && (error != -EINVAL)) + dev_err(dev, "Unable to read bootloader address\n"); + else if (error != -EINVAL) + pdata->bl_addr = (u8) temp_val; + + /* keycodes for keyarray object */ + prop = of_find_property(np, "atmel,key-codes", NULL); + if (prop) { + pdata->key_codes = devm_kzalloc(dev, + sizeof(int) * MXT_KEYARRAY_MAX_KEYS, + GFP_KERNEL); + if (!pdata->key_codes) + return -ENOMEM; + if ((prop->length/sizeof(u32)) == MXT_KEYARRAY_MAX_KEYS) { + error = of_property_read_u32_array(np, + "atmel,key-codes", pdata->key_codes, + MXT_KEYARRAY_MAX_KEYS); + if (error) { + dev_err(dev, "Unable to read key codes\n"); + return error; + } + } else + return -EINVAL; + } + + return 0; +} +#else +static int mxt_parse_dt(struct device *dev, struct mxt_platform_data *pdata) +{ + return -ENODEV; +} + +static int mxt_search_fw_name(struct mxt_data *data) +{ + + return -ENODEV; +} +#endif + +static int mxt_initialize(struct mxt_data *data) +{ + struct i2c_client *client = data->client; + int error; + bool alt_bootloader_addr = false; + bool retry = false; + +retry_info: + error = mxt_read_info_block(data); + if (error) { +retry_bootloader: + error = mxt_probe_bootloader(data, alt_bootloader_addr); + if (error) { + if (alt_bootloader_addr) { + /* Chip is not in appmode or bootloader mode */ + return error; + } + + dev_info(&client->dev, "Trying alternate bootloader address\n"); + alt_bootloader_addr = true; + goto retry_bootloader; + } else { + if (retry) { + dev_err(&client->dev, + "Could not recover device from " + "bootloader mode\n"); + /* this is not an error state, we can reflash + * from here */ + data->in_bootloader = true; + goto recover_bootloader; + } + + /* Attempt to exit bootloader into app mode */ + mxt_send_bootloader_cmd(data, false); + msleep(MXT_FW_RESET_TIME); + retry = true; + goto retry_info; + } + } + + error = mxt_check_retrigen(data); + if (error) + return error; + + error = mxt_acquire_irq(data); + if (error) + return error; + + error = mxt_configure_objects(data); + if (error) + return error; + +recover_bootloader: + error = mxt_create_input_dev(data); + if (error) { + dev_err(&client->dev, "Failed to create input dev\n"); + return error; + } + + data->enable_reporting = true; + + error = mxt_search_fw_name(data); + if (error) + dev_dbg(&client->dev, "firmware name search fail\n"); + + return 0; +} + +/* Firmware Version is returned as Major.Minor.Build */ +static ssize_t mxt_fw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%u.%u.%02X\n", + data->info->version >> 4, data->info->version & 0xf, + data->info->build); +} + +/* Hardware Version is returned as FamilyID.VariantID */ +static ssize_t mxt_hw_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%u.%u\n", + data->info->family_id, data->info->variant_id); +} + +/* Hardware Version is returned as FamilyID.VariantID */ +static ssize_t mxt_cfg_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%u.%u.%u\n", + data->cfg_version[0], data->cfg_version[1], + data->cfg_version[2]); +} + +static ssize_t mxt_show_instance(char *buf, int count, + struct mxt_object *object, int instance, + const u8 *val) +{ + int i; + + if (mxt_obj_instances(object) > 1) + count += scnprintf(buf + count, PAGE_SIZE - count, + "Instance %u\n", instance); + + for (i = 0; i < mxt_obj_size(object); i++) + count += scnprintf(buf + count, PAGE_SIZE - count, + "\t[%2u]: %02x (%d)\n", i, val[i], val[i]); + count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); + + return count; +} + +static ssize_t mxt_object_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct mxt_object *object; + int count = 0; + int i, j; + int error; + u8 *obuf; + + /* Pre-allocate buffer large enough to hold max sized object. */ + obuf = kmalloc(256, GFP_KERNEL); + if (!obuf) + return -ENOMEM; + + error = 0; + for (i = 0; i < data->info->object_num; i++) { + object = data->object_table + i; + + if (!mxt_object_readable(object->type)) + continue; + + count += scnprintf(buf + count, PAGE_SIZE - count, + "T%u:\n", object->type); + + for (j = 0; j < mxt_obj_instances(object); j++) { + u16 size = mxt_obj_size(object); + u16 addr = object->start_address + j * size; + + error = __mxt_read_reg(data->client, addr, size, obuf); + if (error) + goto done; + + count = mxt_show_instance(buf, count, object, j, obuf); + } + } + +done: + kfree(obuf); + return error ?: count; +} + +static int mxt_check_firmware_format(struct device *dev, + const struct firmware *fw) +{ + unsigned int pos = 0; + char c; + + while (pos < fw->size) { + c = *(fw->data + pos); + + if (c < '0' || (c > '9' && c < 'A') || c > 'F') + return 0; + + pos++; + } + + /* To convert file try + * xxd -r -p mXTXXX__APP_VX-X-XX.enc > maxtouch.fw */ + dev_err(dev, "Aborting: firmware file must be in binary format\n"); + + return -1; +} + +static int mxt_load_fw(struct device *dev) +{ + struct mxt_data *data = dev_get_drvdata(dev); + const struct firmware *fw = NULL; + unsigned int frame_size; + unsigned int pos = 0; + unsigned int retry = 0; + unsigned int frame = 0; + int ret; + + ret = request_firmware(&fw, data->fw_name, dev); + if (ret) { + dev_err(dev, "Unable to open firmware %s\n", data->fw_name); + return ret; + } + + /* Check for incorrect enc file */ + ret = mxt_check_firmware_format(dev, fw); + if (ret) + goto release_firmware; + + if (data->suspended) { + if (data->use_regulator) + mxt_regulator_enable(data); + + enable_irq(data->irq); + data->suspended = false; + } + + if (!data->in_bootloader) { + /* Change to the bootloader mode */ + data->in_bootloader = true; + + ret = mxt_t6_command(data, MXT_COMMAND_RESET, + MXT_BOOT_VALUE, false); + if (ret) + goto release_firmware; + + msleep(MXT_RESET_TIME); + + /* At this stage, do not need to scan since we know + * family ID */ + ret = mxt_lookup_bootloader_address(data, 0); + if (ret) + goto release_firmware; + } else { + enable_irq(data->irq); + } + + mxt_free_object_table(data); + reinit_completion(&data->bl_completion); + + ret = mxt_check_bootloader(data, MXT_WAITING_BOOTLOAD_CMD, false); + if (ret) { + /* Bootloader may still be unlocked from previous update + * attempt */ + ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, false); + if (ret) + goto disable_irq; + } else { + dev_info(dev, "Unlocking bootloader\n"); + + /* Unlock bootloader */ + ret = mxt_send_bootloader_cmd(data, true); + if (ret) + goto disable_irq; + } + + while (pos < fw->size) { + ret = mxt_check_bootloader(data, MXT_WAITING_FRAME_DATA, true); + if (ret) + goto disable_irq; + + frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1)); + + /* Take account of CRC bytes */ + frame_size += 2; + + /* Write one frame to device */ + ret = mxt_bootloader_write(data, fw->data + pos, frame_size); + if (ret) + goto disable_irq; + + ret = mxt_check_bootloader(data, MXT_FRAME_CRC_PASS, true); + if (ret) { + retry++; + + /* Back off by 20ms per retry */ + msleep(retry * 20); + + if (retry > 20) { + dev_err(dev, "Retry count exceeded\n"); + goto disable_irq; + } + } else { + retry = 0; + pos += frame_size; + frame++; + } + + if (frame % 50 == 0) + dev_info(dev, "Sent %d frames, %d/%zd bytes\n", + frame, pos, fw->size); + } + + /* Wait for flash. */ + ret = mxt_wait_for_completion(data, &data->bl_completion, + MXT_FW_RESET_TIME); + if (ret) + goto disable_irq; + + dev_info(dev, "Sent %d frames, %u bytes\n", frame, pos); + + /* Wait for device to reset. Some bootloader versions do not assert + * the CHG line after bootloading has finished, so ignore error */ + mxt_wait_for_completion(data, &data->bl_completion, + MXT_FW_RESET_TIME); + + data->in_bootloader = false; + +disable_irq: + disable_irq(data->irq); +release_firmware: + release_firmware(fw); + return ret; +} + +static ssize_t mxt_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int error; + + if (data->fw_name[0] == '\0') { + if (data->in_bootloader) + dev_info(dev, "Manual update needed\n"); + else + dev_info(dev, "firmware is up-to-date\n"); + return count; + } + + error = mxt_load_fw(dev); + if (error) { + dev_err(dev, "The firmware update failed(%d)\n", error); + count = error; + } else { + dev_info(dev, "The firmware update succeeded\n"); + + msleep(MXT_FW_RESET_TIME); + data->suspended = false; + + error = mxt_initialize(data); + if (error) + return error; + + data->fw_w_no_cfg_update = true; + } + + return count; +} + +static int mxt_update_cfg(struct mxt_data *data, bool force) +{ + int ret; + + if (data->in_bootloader) { + dev_err(&data->client->dev, "Not in appmode\n"); + return -EINVAL; + } + + data->enable_reporting = false; + + if (data->suspended) { + if (data->use_regulator) + mxt_regulator_enable(data); + + mxt_set_t7_power_cfg(data, MXT_POWER_CFG_RUN); + + mxt_acquire_irq(data); + + data->suspended = false; + } + + /* load config */ + ret = mxt_load_cfg(data, force); + if (ret) + return ret; + + return 0; +} + +static ssize_t mxt_update_cfg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + + /* update config */ + ret = mxt_update_cfg(data, false); + if (ret) + return ret; + + return count; +} + +static ssize_t mxt_force_update_cfg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int ret; + + /* update force config */ + ret = mxt_update_cfg(data, true); + if (ret) + return ret; + + return count; +} + +static ssize_t mxt_debug_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + char c; + + c = data->debug_enabled ? '1' : '0'; + return scnprintf(buf, PAGE_SIZE, "%c\n", c); +} + +static ssize_t mxt_debug_notify_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} + +static ssize_t mxt_debug_v2_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int i; + + if (sscanf(buf, "%u", &i) == 1 && i < 2) { + if (i == 1) + mxt_debug_msg_enable(data); + else + mxt_debug_msg_disable(data); + + return count; + } else { + dev_dbg(dev, "debug_enabled write error\n"); + return -EINVAL; + } +} + +static ssize_t mxt_debug_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int i; + + if (sscanf(buf, "%u", &i) == 1 && i < 2) { + data->debug_enabled = (i == 1); + + dev_dbg(dev, "%s\n", i ? "debug enabled" : "debug disabled"); + return count; + } else { + dev_dbg(dev, "debug_enabled write error\n"); + return -EINVAL; + } +} + +static int mxt_check_mem_access_params(struct mxt_data *data, loff_t off, + size_t *count) +{ + if (off >= data->mem_size) + return -EIO; + + if (off + *count > data->mem_size) + *count = data->mem_size - off; + + if (*count > MXT_MAX_BLOCK_WRITE) + *count = MXT_MAX_BLOCK_WRITE; + + return 0; +} + +static ssize_t mxt_mem_access_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct mxt_data *data = dev_get_drvdata(dev); + int ret = 0; + + ret = mxt_check_mem_access_params(data, off, &count); + if (ret < 0) + return ret; + + if (count > 0) + ret = __mxt_read_reg(data->client, off, count, buf); + + return ret == 0 ? count : ret; +} + +static ssize_t mxt_mem_access_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct mxt_data *data = dev_get_drvdata(dev); + int ret = 0; + + ret = mxt_check_mem_access_params(data, off, &count); + if (ret < 0) + return ret; + + if (count > 0) + ret = __mxt_write_reg(data->client, off, count, buf); + + return ret == 0 ? count : 0; +} + +#if defined(CONFIG_SECURE_TOUCH) + +static int mxt_secure_touch_clk_prepare_enable( + struct mxt_data *data) +{ + int ret; + ret = clk_prepare_enable(data->iface_clk); + if (ret) { + dev_err(&data->client->dev, + "error on clk_prepare_enable(iface_clk):%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(data->core_clk); + if (ret) { + clk_disable_unprepare(data->iface_clk); + dev_err(&data->client->dev, + "error clk_prepare_enable(core_clk):%d\n", ret); + } + return ret; +} + +static void mxt_secure_touch_clk_disable_unprepare( + struct mxt_data *data) +{ + clk_disable_unprepare(data->core_clk); + clk_disable_unprepare(data->iface_clk); +} + +static ssize_t mxt_secure_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return scnprintf(buf, PAGE_SIZE, "%d", atomic_read(&data->st_enabled)); +} +/* + * Accept only "0" and "1" valid values. + * "0" will reset the st_enabled flag, then wake up the reading process. + * The bus driver is notified via pm_runtime that it is not required to stay + * awake anymore. + * It will also make sure the queue of events is emptied in the controller, + * in case a touch happened in between the secure touch being disabled and + * the local ISR being ungated. + * "1" will set the st_enabled flag and clear the st_pending_irqs flag. + * The bus driver is requested via pm_runtime to stay awake. + */ +static ssize_t mxt_secure_touch_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct device *adapter = data->client->adapter->dev.parent; + unsigned long value; + int err = 0; + + if (count > 2) + return -EINVAL; + + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + if (!data->st_initialized) + return -EIO; + + err = count; + + switch (value) { + case 0: + if (atomic_read(&data->st_enabled) == 0) + break; + + mxt_secure_touch_clk_disable_unprepare(data); + pm_runtime_put_sync(adapter); + atomic_set(&data->st_enabled, 0); + mxt_secure_touch_notify(data); + mxt_interrupt(data->client->irq, data); + complete(&data->st_powerdown); + break; + case 1: + if (atomic_read(&data->st_enabled)) { + err = -EBUSY; + break; + } + + if (pm_runtime_get_sync(adapter) < 0) { + dev_err(&data->client->dev, "pm_runtime_get failed\n"); + err = -EIO; + break; + } + + if (mxt_secure_touch_clk_prepare_enable(data) < 0) { + pm_runtime_put_sync(adapter); + err = -EIO; + break; + } + reinit_completion(&data->st_powerdown); + atomic_set(&data->st_enabled, 1); + synchronize_irq(data->client->irq); + atomic_set(&data->st_pending_irqs, 0); + break; + default: + dev_err(&data->client->dev, "unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + + return err; +} + +static ssize_t mxt_secure_touch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + int val = 0; + + if (atomic_read(&data->st_enabled) == 0) + return -EBADF; + + if (atomic_cmpxchg(&data->st_pending_irqs, -1, 0) == -1) + return -EINVAL; + + if (atomic_cmpxchg(&data->st_pending_irqs, 1, 0) == 1) + val = 1; + + return scnprintf(buf, PAGE_SIZE, "%u", val); +} + +static DEVICE_ATTR(secure_touch_enable, S_IRUGO | S_IWUSR | S_IWGRP , + mxt_secure_touch_enable_show, + mxt_secure_touch_enable_store); +static DEVICE_ATTR(secure_touch, S_IRUGO, mxt_secure_touch_show, NULL); +#endif + +static ssize_t mxt_fw_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return snprintf(buf, MXT_NAME_MAX_LEN - 1, "%s\n", data->fw_name); +} + +static ssize_t mxt_fw_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct mxt_data *data = dev_get_drvdata(dev); + + if (size > MXT_NAME_MAX_LEN - 1) + return -EINVAL; + + strlcpy(data->fw_name, buf, size); + if (data->fw_name[size-1] == '\n') + data->fw_name[size-1] = 0; + + return size; +} + +static ssize_t mxt_cfg_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mxt_data *data = dev_get_drvdata(dev); + return snprintf(buf, MXT_NAME_MAX_LEN - 1, "%s\n", data->cfg_name); +} + +static ssize_t mxt_cfg_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct mxt_data *data = dev_get_drvdata(dev); + + if (size > MXT_NAME_MAX_LEN - 1) + return -EINVAL; + + strlcpy(data->cfg_name, buf, size); + if (data->cfg_name[size-1] == '\n') + data->cfg_name[size-1] = 0; + + return size; +} + +static DEVICE_ATTR(fw_name, S_IWUSR | S_IRUSR, + mxt_fw_name_show, mxt_fw_name_store); +static DEVICE_ATTR(cfg_name, S_IWUSR | S_IRUSR, + mxt_cfg_name_show, mxt_cfg_name_store); +static DEVICE_ATTR(fw_version, S_IRUGO, mxt_fw_version_show, NULL); +static DEVICE_ATTR(hw_version, S_IRUGO, mxt_hw_version_show, NULL); +static DEVICE_ATTR(cfg_version, S_IRUGO, mxt_cfg_version_show, NULL); +static DEVICE_ATTR(object, S_IRUGO, mxt_object_show, NULL); +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, mxt_update_fw_store); +static DEVICE_ATTR(update_cfg, S_IWUSR, NULL, mxt_update_cfg_store); +static DEVICE_ATTR(force_update_cfg, S_IWUSR, NULL, mxt_force_update_cfg_store); +static DEVICE_ATTR(debug_v2_enable, S_IWUSR | S_IRUSR, NULL, mxt_debug_v2_enable_store); +static DEVICE_ATTR(debug_notify, S_IRUGO, mxt_debug_notify_show, NULL); +static DEVICE_ATTR(debug_enable, S_IWUSR | S_IRUSR, mxt_debug_enable_show, + mxt_debug_enable_store); + +static struct attribute *mxt_attrs[] = { + &dev_attr_fw_name.attr, + &dev_attr_cfg_name.attr, + &dev_attr_fw_version.attr, + &dev_attr_hw_version.attr, + &dev_attr_cfg_version.attr, + &dev_attr_object.attr, + &dev_attr_update_fw.attr, + &dev_attr_update_cfg.attr, + &dev_attr_force_update_cfg.attr, + &dev_attr_debug_enable.attr, + &dev_attr_debug_v2_enable.attr, + &dev_attr_debug_notify.attr, +#if defined(CONFIG_SECURE_TOUCH) + &dev_attr_secure_touch_enable.attr, + &dev_attr_secure_touch.attr, +#endif + NULL +}; + +static const struct attribute_group mxt_attr_group = { + .attrs = mxt_attrs, +}; + +#ifdef CONFIG_PM_SLEEP +static int mxt_suspend(struct device *dev) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct input_dev *input_dev = data->input_dev; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + mxt_stop(data); + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +static int mxt_resume(struct device *dev) +{ + struct mxt_data *data = dev_get_drvdata(dev); + struct input_dev *input_dev = data->input_dev; + + mxt_secure_touch_stop(data, 1); + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + mxt_start(data); + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +#if defined(CONFIG_FB) +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + struct mxt_data *mxt_dev_data = + container_of(self, struct mxt_data, fb_notif); + + if (evdata && evdata->data && mxt_dev_data && mxt_dev_data->client) { + if (event == FB_EARLY_EVENT_BLANK) + mxt_secure_touch_stop(mxt_dev_data, 0); + else if (event == FB_EVENT_BLANK) { + blank = evdata->data; + if (*blank == FB_BLANK_UNBLANK) + mxt_resume(&mxt_dev_data->client->dev); + else if (*blank == FB_BLANK_POWERDOWN) + mxt_suspend(&mxt_dev_data->client->dev); + } + } + + return 0; +} +#elif defined(CONFIG_HAS_EARLYSUSPEND) +static void mxt_early_suspend(struct early_suspend *h) +{ + struct mxt_data *data = container_of(h, struct mxt_data, + early_suspend); + mxt_suspend(&data->client->dev); +} + +static void mxt_late_resume(struct early_suspend *h) +{ + struct mxt_data *data = container_of(h, struct mxt_data, + early_suspend); + mxt_resume(&data->client->dev); +} +#endif + +static const struct dev_pm_ops mxt_pm_ops = { +#if (!defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND)) + .suspend = mxt_suspend, + .resume = mxt_resume, +#endif +}; +#endif + +#if defined(CONFIG_SECURE_TOUCH) +static void mxt_secure_touch_init(struct mxt_data *data) +{ + int ret = 0; + data->st_initialized = 0; + init_completion(&data->st_powerdown); + /* Get clocks */ + data->core_clk = clk_get(&data->client->dev, "core_clk"); + if (IS_ERR(data->core_clk)) { + ret = PTR_ERR(data->core_clk); + dev_err(&data->client->dev, + "%s: error on clk_get(core_clk):%d\n", __func__, ret); + return; + } + + data->iface_clk = clk_get(&data->client->dev, "iface_clk"); + if (IS_ERR(data->iface_clk)) { + ret = PTR_ERR(data->iface_clk); + dev_err(&data->client->dev, + "%s: error on clk_get(iface_clk):%d\n", __func__, ret); + goto err_iface_clk; + } + + data->st_initialized = 1; + return; + +err_iface_clk: + clk_put(data->core_clk); + data->core_clk = NULL; +} +#else +static void mxt_secure_touch_init(struct mxt_data *data) +{ +} +#endif + +static int mxt_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxt_data *data; + struct mxt_platform_data *pdata; + int error, len; + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct mxt_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + error = mxt_parse_dt(&client->dev, pdata); + if (error) + return error; + } else + pdata = client->dev.platform_data; + + if (!pdata) + return -EINVAL; + + data = devm_kzalloc(&client->dev, sizeof(struct mxt_data), GFP_KERNEL); + if (!data) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0", + client->adapter->nr, client->addr); + + data->client = client; + data->irq = client->irq; + data->pdata = pdata; + i2c_set_clientdata(client, data); + + init_completion(&data->bl_completion); + init_completion(&data->reset_completion); + init_completion(&data->crc_completion); + mutex_init(&data->debug_msg_lock); + + if (data->pdata->cfg_name) { + len = strlen(data->pdata->cfg_name); + if (len > MXT_NAME_MAX_LEN - 1) { + dev_err(&client->dev, "Invalid config name\n"); + error = -EINVAL; + goto err_destroy_mutex; + } + + strlcpy(data->cfg_name, data->pdata->cfg_name, len + 1); + } + + error = mxt_pinctrl_init(data); + if (error) + dev_info(&client->dev, "No pinctrl support\n"); + + error = mxt_gpio_enable(data, true); + if (error) { + dev_err(&client->dev, "Failed to configure gpios\n"); + goto err_destroy_mutex; + } + data->irq = data->client->irq = + gpio_to_irq(data->pdata->gpio_irq); + + error = mxt_regulator_configure(data, true); + if (error) { + dev_err(&client->dev, "Failed to probe regulators\n"); + goto err_free_gpios; + } + + error = mxt_regulator_enable(data); + if (error) { + dev_err(&client->dev, "Error %d enabling regulators\n", error); + goto err_put_regs; + } + + error = mxt_initialize(data); + if (error) + goto err_free_regs; + + error = sysfs_create_group(&client->dev.kobj, &mxt_attr_group); + if (error) { + dev_err(&client->dev, "Failure %d creating sysfs group\n", + error); + goto err_free_object; + } + + sysfs_bin_attr_init(&data->mem_access_attr); + data->mem_access_attr.attr.name = "mem_access"; + data->mem_access_attr.attr.mode = S_IRUGO | S_IWUSR; + data->mem_access_attr.read = mxt_mem_access_read; + data->mem_access_attr.write = mxt_mem_access_write; + data->mem_access_attr.size = data->mem_size; + + if (sysfs_create_bin_file(&client->dev.kobj, + &data->mem_access_attr) < 0) { + dev_err(&client->dev, "Failed to create %s\n", + data->mem_access_attr.attr.name); + goto err_remove_sysfs_group; + } + + error = request_threaded_irq(data->irq, NULL, mxt_interrupt, + data->pdata->irqflags | IRQF_ONESHOT, + client->name, data); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_remove_sysfs_group; + } + +#if defined(CONFIG_FB) + data->fb_notif.notifier_call = fb_notifier_callback; + + error = fb_register_client(&data->fb_notif); + + if (error) { + dev_err(&client->dev, "Unable to register fb_notifier: %d\n", + error); + goto err_free_irq; + } +#elif defined(CONFIG_HAS_EARLYSUSPEND) + data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + MXT_SUSPEND_LEVEL; + data->early_suspend.suspend = mxt_early_suspend; + data->early_suspend.resume = mxt_late_resume; + register_early_suspend(&data->early_suspend); +#endif + + mxt_secure_touch_init(data); + + return 0; + +err_free_irq: + free_irq(data->irq, data); +err_remove_sysfs_group: + sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); +err_free_object: + mxt_free_object_table(data); +err_put_regs: + mxt_regulator_configure(data, false); +err_free_regs: + mxt_regulator_disable(data); +err_free_gpios: + mxt_gpio_enable(data, false); +err_destroy_mutex: + mutex_destroy(&data->debug_msg_lock); + return error; +} + +static int mxt_remove(struct i2c_client *client) +{ + struct mxt_data *data = i2c_get_clientdata(client); + + if (data->mem_access_attr.attr.name) + sysfs_remove_bin_file(&client->dev.kobj, + &data->mem_access_attr); + +#if defined(CONFIG_FB) + fb_unregister_client(&data->fb_notif); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&data->early_suspend); +#endif + sysfs_remove_group(&client->dev.kobj, &mxt_attr_group); + free_irq(data->irq, data); + mxt_regulator_configure(data, false); + mxt_regulator_disable(data); + if (!IS_ERR(data->reg_xvdd)) + regulator_put(data->reg_xvdd); + regulator_put(data->reg_avdd); + regulator_put(data->reg_vdd); + mxt_free_object_table(data); + if (!dev_get_platdata(&data->client->dev)) + kfree(data->pdata); + kfree(data); + + return 0; +} + +static void mxt_shutdown(struct i2c_client *client) +{ + struct mxt_data *data = i2c_get_clientdata(client); + + disable_irq(data->irq); +} + +static const struct i2c_device_id mxt_id[] = { + { "qt602240_ts", 0 }, + { "atmel_mxt_ts", 0 }, + { "atmel_maxtouch_ts", 0 }, + { "atmel_mxt_tp", 0 }, + { "mXT224", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxt_id); +#ifdef CONFIG_OF +static struct of_device_id mxt_match_table[] = { + { .compatible = "atmel,maxtouch-ts",}, + { }, +}; +#else +#define mxt_match_table NULL +#endif + +static struct i2c_driver mxt_driver = { + .driver = { + .name = "atmel_maxtouch_ts", + .owner = THIS_MODULE, +#ifdef CONFIG_PM_SLEEP + .pm = &mxt_pm_ops, +#endif + .of_match_table = mxt_match_table, + }, + .probe = mxt_probe, + .remove = mxt_remove, + .shutdown = mxt_shutdown, + .id_table = mxt_id, +}; + +static int __init mxt_init(void) +{ + return i2c_add_driver(&mxt_driver); +} + +static void __exit mxt_exit(void) +{ + i2c_del_driver(&mxt_driver); +} + +late_initcall(mxt_init); +module_exit(mxt_exit); + +/* Module information */ +MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); +MODULE_DESCRIPTION("Atmel maXTouch Touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/ft5x06_ts.c b/drivers/input/touchscreen/ft5x06_ts.c new file mode 100644 index 000000000000..fc4bc283ccaf --- /dev/null +++ b/drivers/input/touchscreen/ft5x06_ts.c @@ -0,0 +1,2781 @@ +/* + * + * FocalTech ft5x06 TouchScreen driver. + * + * Copyright (c) 2010 Focal tech Ltd. + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/firmware.h> +#include <linux/debugfs.h> +#include <linux/input/ft5x06_ts.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> + + +#if defined(CONFIG_FB) +#include <linux/notifier.h> +#include <linux/fb.h> + +#elif defined(CONFIG_HAS_EARLYSUSPEND) +#include <linux/earlysuspend.h> +/* Early-suspend level */ +#define FT_SUSPEND_LEVEL 1 +#endif + +#if defined(CONFIG_FT_SECURE_TOUCH) +#include <linux/completion.h> +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +static irqreturn_t ft5x06_ts_interrupt(int irq, void *data); +#endif + +#define FT_DRIVER_VERSION 0x02 + +#define FT_META_REGS 3 +#define FT_ONE_TCH_LEN 6 +#define FT_TCH_LEN(x) (FT_META_REGS + FT_ONE_TCH_LEN * x) + +#define FT_PRESS 0x7F +#define FT_MAX_ID 0x0F +#define FT_TOUCH_X_H_POS 3 +#define FT_TOUCH_X_L_POS 4 +#define FT_TOUCH_Y_H_POS 5 +#define FT_TOUCH_Y_L_POS 6 +#define FT_TD_STATUS 2 +#define FT_TOUCH_EVENT_POS 3 +#define FT_TOUCH_ID_POS 5 +#define FT_TOUCH_DOWN 0 +#define FT_TOUCH_CONTACT 2 + +/* register address*/ +#define FT_REG_DEV_MODE 0x00 +#define FT_DEV_MODE_REG_CAL 0x02 +#define FT_REG_ID 0xA3 +#define FT_REG_PMODE 0xA5 +#define FT_REG_FW_VER 0xA6 +#define FT_REG_FW_VENDOR_ID 0xA8 +#define FT_REG_POINT_RATE 0x88 +#define FT_REG_THGROUP 0x80 +#define FT_REG_ECC 0xCC +#define FT_REG_RESET_FW 0x07 +#define FT_REG_FW_MIN_VER 0xB2 +#define FT_REG_FW_SUB_MIN_VER 0xB3 + +/* gesture register address*/ +#define FT_REG_GESTURE_ENABLE 0xD0 +#define FT_REG_GESTURE_OUTPUT 0xD3 + +/* gesture register bits*/ +#define FT_GESTURE_DOUBLECLICK_COORD_X 100 +#define FT_GESTURE_DOUBLECLICK_COORD_Y 100 +#define FT_GESTURE_WAKEUP_TIMEOUT 500 +#define FT_GESTURE_DEFAULT_TRACKING_ID 0x0A +#define FT_GESTURE_DOUBLECLICK_ID 0x24 +#define FT_GESTURE_POINTER_NUM_MAX 128 +#define FT_GESTURE_POINTER_SIZEOF 4 +#define FT_GESTURE_ID_FLAG_SIZE 1 +#define FT_GESTURE_POINTER_NUM_FLAG_SIZE 1 +/* 6 bytes are taken to mark which gesture is supported in firmware */ +#define FT_GESTURE_SET_FLAG_SIZE 6 +#define I2C_TRANSFER_MAX_BYTE 255 +#define FT_GESTURE_DATA_HEADER (FT_GESTURE_ID_FLAG_SIZE + \ + FT_GESTURE_POINTER_NUM_FLAG_SIZE + \ + FT_GESTURE_SET_FLAG_SIZE) + +/* power register bits*/ +#define FT_PMODE_ACTIVE 0x00 +#define FT_PMODE_MONITOR 0x01 +#define FT_PMODE_STANDBY 0x02 +#define FT_PMODE_HIBERNATE 0x03 +#define FT_FACTORYMODE_VALUE 0x40 +#define FT_WORKMODE_VALUE 0x00 +#define FT_RST_CMD_REG1 0xFC +#define FT_RST_CMD_REG2 0xBC +#define FT_READ_ID_REG 0x90 +#define FT_ERASE_APP_REG 0x61 +#define FT_ERASE_PANEL_REG 0x63 +#define FT_FW_START_REG 0xBF + +#define FT_STATUS_NUM_TP_MASK 0x0F + +#define FT_VTG_MIN_UV 2600000 +#define FT_VTG_MAX_UV 3300000 +#define FT_I2C_VTG_MIN_UV 1800000 +#define FT_I2C_VTG_MAX_UV 1800000 + +#define FT_COORDS_ARR_SIZE 4 +#define MAX_BUTTONS 4 + +#define FT_8BIT_SHIFT 8 +#define FT_4BIT_SHIFT 4 +#define FT_FW_NAME_MAX_LEN 50 + +#define FT5316_ID 0x0A +#define FT5306I_ID 0x55 +#define FT6X06_ID 0x06 +#define FT6X36_ID 0x36 + +#define FT_UPGRADE_AA 0xAA +#define FT_UPGRADE_55 0x55 + +#define FT_FW_MIN_SIZE 8 +#define FT_FW_MAX_SIZE 32768 + +/* Firmware file is not supporting minor and sub minor so use 0 */ +#define FT_FW_FILE_MAJ_VER(x) ((x)->data[(x)->size - 2]) +#define FT_FW_FILE_MIN_VER(x) 0 +#define FT_FW_FILE_SUB_MIN_VER(x) 0 +#define FT_FW_FILE_VENDOR_ID(x) ((x)->data[(x)->size - 1]) + +#define FT_FW_FILE_MAJ_VER_FT6X36(x) ((x)->data[0x10a]) +#define FT_FW_FILE_VENDOR_ID_FT6X36(x) ((x)->data[0x108]) + +/** +* Application data verification will be run before upgrade flow. +* Firmware image stores some flags with negative and positive value +* in corresponding addresses, we need pick them out do some check to +* make sure the application data is valid. +*/ +#define FT_FW_CHECK(x, ts_data) \ + (ts_data->family_id == FT6X36_ID ? \ + (((x)->data[0x104] ^ (x)->data[0x105]) == 0xFF \ + && ((x)->data[0x106] ^ (x)->data[0x107]) == 0xFF) : \ + (((x)->data[(x)->size - 8] ^ (x)->data[(x)->size - 6]) == 0xFF \ + && ((x)->data[(x)->size - 7] ^ (x)->data[(x)->size - 5]) == 0xFF \ + && ((x)->data[(x)->size - 3] ^ (x)->data[(x)->size - 4]) == 0xFF)) + +#define FT_MAX_TRIES 5 +#define FT_RETRY_DLY 20 + +#define FT_MAX_WR_BUF 10 +#define FT_MAX_RD_BUF 2 +#define FT_FW_PKT_LEN 128 +#define FT_FW_PKT_META_LEN 6 +#define FT_FW_PKT_DLY_MS 20 +#define FT_FW_LAST_PKT 0x6ffa +#define FT_EARSE_DLY_MS 100 +#define FT_55_AA_DLY_NS 5000 + +#define FT_UPGRADE_LOOP 30 +#define FT_CAL_START 0x04 +#define FT_CAL_FIN 0x00 +#define FT_CAL_STORE 0x05 +#define FT_CAL_RETRY 100 +#define FT_REG_CAL 0x00 +#define FT_CAL_MASK 0x70 + +#define FT_INFO_MAX_LEN 512 + +#define FT_BLOADER_SIZE_OFF 12 +#define FT_BLOADER_NEW_SIZE 30 +#define FT_DATA_LEN_OFF_OLD_FW 8 +#define FT_DATA_LEN_OFF_NEW_FW 14 +#define FT_FINISHING_PKT_LEN_OLD_FW 6 +#define FT_FINISHING_PKT_LEN_NEW_FW 12 +#define FT_MAGIC_BLOADER_Z7 0x7bfa +#define FT_MAGIC_BLOADER_LZ4 0x6ffa +#define FT_MAGIC_BLOADER_GZF_30 0x7ff4 +#define FT_MAGIC_BLOADER_GZF 0x7bf4 + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +static irqreturn_t ft5x06_ts_interrupt(int irq, void *data); + +enum { + FT_BLOADER_VERSION_LZ4 = 0, + FT_BLOADER_VERSION_Z7 = 1, + FT_BLOADER_VERSION_GZF = 2, +}; + +enum { + FT_FT5336_FAMILY_ID_0x11 = 0x11, + FT_FT5336_FAMILY_ID_0x12 = 0x12, + FT_FT5336_FAMILY_ID_0x13 = 0x13, + FT_FT5336_FAMILY_ID_0x14 = 0x14, +}; + +#define FT_STORE_TS_INFO(buf, id, fw_maj, fw_min, fw_sub_min) \ + snprintf(buf, FT_INFO_MAX_LEN, \ + "vendor name = Focaltech\n" \ + "model = 0x%x\n" \ + "fw_version = %d.%d.%d\n", \ + id, fw_maj, fw_min, fw_sub_min) +#define FT_TS_INFO_SYSFS_DIR_NAME "ts_info" +static char *ts_info_buff; + +#define FT_STORE_TS_DBG_INFO(buf, id, name, max_tch, group_id, \ + fw_vkey_support, fw_name, fw_maj, fw_min, fw_sub_min) \ + snprintf(buf, FT_INFO_MAX_LEN, \ + "controller\t= focaltech\n" \ + "model\t\t= 0x%x\n" \ + "name\t\t= %s\n" \ + "max_touches\t= %d\n" \ + "drv_ver\t\t= 0x%x\n" \ + "group_id\t= 0x%x\n" \ + "fw_vkey_support\t= %s\n" \ + "fw_name\t\t= %s\n" \ + "fw_ver\t\t= %d.%d.%d\n", id, name, \ + max_tch, FT_DRIVER_VERSION, group_id, \ + fw_vkey_support, fw_name, fw_maj, fw_min, \ + fw_sub_min) + +#define FT_DEBUG_DIR_NAME "ts_debug" + +struct ft5x06_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + const struct ft5x06_ts_platform_data *pdata; + struct ft5x06_gesture_platform_data *gesture_pdata; + struct regulator *vdd; + struct regulator *vcc_i2c; + struct mutex ft_clk_io_ctrl_mutex; + char fw_name[FT_FW_NAME_MAX_LEN]; + bool loading_fw; + u8 family_id; + struct dentry *dir; + u16 addr; + bool suspended; + char *ts_info; + u8 *tch_data; + u32 tch_data_len; + u8 fw_ver[3]; + u8 fw_vendor_id; + struct kobject *ts_info_kobj; +#if defined(CONFIG_FB) + struct work_struct fb_notify_work; + struct notifier_block fb_notif; +#elif defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; +#if defined(CONFIG_FT_SECURE_TOUCH) + atomic_t st_enabled; + atomic_t st_pending_irqs; + struct completion st_powerdown; + struct completion st_irq_processed; + bool st_initialized; + struct clk *core_clk; + struct clk *iface_clk; +#endif +}; + +static int ft5x06_ts_start(struct device *dev); +static int ft5x06_ts_stop(struct device *dev); + +#if defined(CONFIG_FT_SECURE_TOUCH) +static void ft5x06_secure_touch_init(struct ft5x06_ts_data *data) +{ + data->st_initialized = 0; + + init_completion(&data->st_powerdown); + init_completion(&data->st_irq_processed); + + /* Get clocks */ + data->core_clk = devm_clk_get(&data->client->dev, "core_clk"); + if (IS_ERR(data->core_clk)) { + data->core_clk = NULL; + dev_warn(&data->client->dev, + "%s: core_clk is not defined\n", __func__); + } + + data->iface_clk = devm_clk_get(&data->client->dev, "iface_clk"); + if (IS_ERR(data->iface_clk)) { + data->iface_clk = NULL; + dev_warn(&data->client->dev, + "%s: iface_clk is not defined", __func__); + } + data->st_initialized = 1; +} + +static void ft5x06_secure_touch_notify(struct ft5x06_ts_data *data) +{ + sysfs_notify(&data->input_dev->dev.kobj, NULL, "secure_touch"); +} + +static irqreturn_t ft5x06_filter_interrupt(struct ft5x06_ts_data *data) +{ + if (atomic_read(&data->st_enabled)) { + if (atomic_cmpxchg(&data->st_pending_irqs, 0, 1) == 0) { + reinit_completion(&data->st_irq_processed); + ft5x06_secure_touch_notify(data); + wait_for_completion_interruptible( + &data->st_irq_processed); + } + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +/* + * 'blocking' variable will have value 'true' when we want to prevent the driver + * from accessing the xPU/SMMU protected HW resources while the session is + * active. + */ +static void ft5x06_secure_touch_stop(struct ft5x06_ts_data *data, bool blocking) +{ + if (atomic_read(&data->st_enabled)) { + atomic_set(&data->st_pending_irqs, -1); + ft5x06_secure_touch_notify(data); + if (blocking) + wait_for_completion_interruptible( + &data->st_powerdown); + } +} + +static int ft5x06_clk_prepare_enable(struct ft5x06_ts_data *data) +{ + int ret; + + ret = clk_prepare_enable(data->iface_clk); + if (ret) { + dev_err(&data->client->dev, + "error on clk_prepare_enable(iface_clk):%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(data->core_clk); + if (ret) { + clk_disable_unprepare(data->iface_clk); + dev_err(&data->client->dev, + "error clk_prepare_enable(core_clk):%d\n", ret); + } + return ret; +} + +static void ft5x06_clk_disable_unprepare(struct ft5x06_ts_data *data) +{ + clk_disable_unprepare(data->core_clk); + clk_disable_unprepare(data->iface_clk); +} + +static int ft5x06_bus_get(struct ft5x06_ts_data *data) +{ + int retval; + + mutex_lock(&data->ft_clk_io_ctrl_mutex); + retval = pm_runtime_get_sync(data->client->adapter->dev.parent); + if (retval >= 0 && data->core_clk != NULL && data->iface_clk != NULL) { + retval = ft5x06_clk_prepare_enable(data); + if (retval) + pm_runtime_put_sync(data->client->adapter->dev.parent); + } + mutex_unlock(&data->ft_clk_io_ctrl_mutex); + return retval; +} + +static void ft5x06_bus_put(struct ft5x06_ts_data *data) +{ + mutex_lock(&data->ft_clk_io_ctrl_mutex); + if (data->core_clk != NULL && data->iface_clk != NULL) + ft5x06_clk_disable_unprepare(data); + pm_runtime_put_sync(data->client->adapter->dev.parent); + mutex_unlock(&data->ft_clk_io_ctrl_mutex); +} + +static ssize_t ft5x06_secure_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d", atomic_read(&data->st_enabled)); +} + +/* + * Accept only "0" and "1" valid values. + * "0" will reset the st_enabled flag, then wake up the reading process and + * the interrupt handler. + * The bus driver is notified via pm_runtime that it is not required to stay + * awake anymore. + * It will also make sure the queue of events is emptied in the controller, + * in case a touch happened in between the secure touch being disabled and + * the local ISR being ungated. + * "1" will set the st_enabled flag and clear the st_pending_irqs flag. + * The bus driver is requested via pm_runtime to stay awake. + */ +static ssize_t ft5x06_secure_touch_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + unsigned long value; + int err = 0; + + if (count > 2) + return -EINVAL; + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + if (!data->st_initialized) + return -EIO; + + err = count; + switch (value) { + case 0: + if (atomic_read(&data->st_enabled) == 0) + break; + ft5x06_bus_put(data); + atomic_set(&data->st_enabled, 0); + ft5x06_secure_touch_notify(data); + complete(&data->st_irq_processed); + ft5x06_ts_interrupt(data->client->irq, data); + complete(&data->st_powerdown); + break; + + case 1: + if (atomic_read(&data->st_enabled)) { + err = -EBUSY; + break; + } + synchronize_irq(data->client->irq); + if (ft5x06_bus_get(data) < 0) { + dev_err(&data->client->dev, "ft5x06_bus_get failed\n"); + err = -EIO; + break; + } + reinit_completion(&data->st_powerdown); + reinit_completion(&data->st_irq_processed); + atomic_set(&data->st_enabled, 1); + atomic_set(&data->st_pending_irqs, 0); + break; + + default: + dev_err(&data->client->dev, "unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + return err; +} + +/* + * This function returns whether there are pending interrupts, or + * other error conditions that need to be signaled to the userspace library, + * according tot he following logic: + * - st_enabled is 0 if secure touch is not enabled, returning -EBADF + * - st_pending_irqs is -1 to signal that secure touch is in being stopped, + * returning -EINVAL + * - st_pending_irqs is 1 to signal that there is a pending irq, returning + * the value "1" to the sysfs read operation + * - st_pending_irqs is 0 (only remaining case left) if the pending interrupt + * has been processed, so the interrupt handler can be allowed to continue. + */ +static ssize_t ft5x06_secure_touch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + int val = 0; + + if (atomic_read(&data->st_enabled) == 0) + return -EBADF; + if (atomic_cmpxchg(&data->st_pending_irqs, -1, 0) == -1) + return -EINVAL; + if (atomic_cmpxchg(&data->st_pending_irqs, 1, 0) == 1) + val = 1; + else + complete(&data->st_irq_processed); + return scnprintf(buf, PAGE_SIZE, "%u", val); +} +#else +static void ft5x06_secure_touch_init(struct ft5x06_ts_data *data) +{ +} +static irqreturn_t ft5x06_filter_interrupt(struct ft5x06_ts_data *data) +{ + return IRQ_NONE; +} +static void ft5x06_secure_touch_stop(struct ft5x06_ts_data *data, bool blocking) +{ +} +#endif + +static struct device_attribute attrs[] = { +#if defined(CONFIG_FT_SECURE_TOUCH) + __ATTR(secure_touch_enable, (S_IRUGO | S_IWUSR | S_IWGRP), + ft5x06_secure_touch_enable_show, + ft5x06_secure_touch_enable_store), + __ATTR(secure_touch, S_IRUGO, + ft5x06_secure_touch_show, NULL), +#endif +}; + +static inline bool ft5x06_gesture_support_enabled(void) +{ + return config_enabled(CONFIG_TOUCHSCREEN_FT5X06_GESTURE); +} + +static int ft5x06_i2c_read(struct i2c_client *client, char *writebuf, + int writelen, char *readbuf, int readlen) +{ + int ret; + + if (writelen > 0) { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret < 0) + dev_err(&client->dev, "%s: i2c read error.\n", + __func__); + } else { + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = readlen, + .buf = readbuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s:i2c read error.\n", __func__); + } + return ret; +} + +static int ft5x06_i2c_write(struct i2c_client *client, char *writebuf, + int writelen) +{ + int ret; + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = writelen, + .buf = writebuf, + }, + }; + ret = i2c_transfer(client->adapter, msgs, 1); + if (ret < 0) + dev_err(&client->dev, "%s: i2c write error.\n", __func__); + + return ret; +} + +static int ft5x0x_write_reg(struct i2c_client *client, u8 addr, const u8 val) +{ + u8 buf[2] = {0}; + + buf[0] = addr; + buf[1] = val; + + return ft5x06_i2c_write(client, buf, sizeof(buf)); +} + +static int ft5x0x_read_reg(struct i2c_client *client, u8 addr, u8 *val) +{ + return ft5x06_i2c_read(client, &addr, 1, val, 1); +} + +#ifdef CONFIG_TOUCHSCREEN_FT5X06_GESTURE +static ssize_t ft5x06_gesture_enable_to_set_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + data->gesture_pdata->gesture_enable_to_set); +} + +static ssize_t ft5x06_gesture_enable_to_set_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + unsigned long value = 0; + int ret; + + if (data->suspended) + return -EINVAL; + + ret = kstrtoul(buf, 16, &value); + if (ret < 0) { + dev_err(dev, "%s:kstrtoul failed, ret=0x%x\n", + __func__, ret); + return ret; + } + + if (value == 1) + data->gesture_pdata->gesture_enable_to_set = 1; + else + data->gesture_pdata->gesture_enable_to_set = 0; + return size; +} + +static DEVICE_ATTR(enable, 0664, + ft5x06_gesture_enable_to_set_show, + ft5x06_gesture_enable_to_set_store); + +static int ft5x06_entry_pocket(struct device *dev) +{ + return ft5x06_ts_stop(dev); +} + +static int ft5x06_leave_pocket(struct device *dev) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + int err; + + ft5x06_ts_start(dev); + ft5x0x_write_reg(data->client, FT_REG_GESTURE_ENABLE, 1); + err = enable_irq_wake(data->client->irq); + if (err) + dev_err(&data->client->dev, + "%s: set_irq_wake failed\n", __func__); + data->suspended = true; + + return err; +} + +static ssize_t gesture_in_pocket_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", + data->gesture_pdata->in_pocket); +} + +static ssize_t gesture_in_pocket_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + unsigned long value = 0; + int ret; + + ret = kstrtoul(buf, 16, &value); + if (ret < 0) { + dev_err(dev, "%s:kstrtoul failed, ret=0x%x\n", + __func__, ret); + return ret; + } + + if (value == 1 && data->gesture_pdata->in_pocket == 0) { + data->gesture_pdata->in_pocket = 1; + ft5x06_entry_pocket(dev); + } else if (value == 0 && data->gesture_pdata->in_pocket == 1) { + ft5x06_leave_pocket(dev); + data->gesture_pdata->in_pocket = 0; + } + return size; +} + +static DEVICE_ATTR(pocket, 0664, + gesture_in_pocket_mode_show, + gesture_in_pocket_mode_store); + +static int ft5x06_report_gesture_doubleclick(struct input_dev *ip_dev) +{ + int i; + + for (i = 0; i < 2; i++) { + input_mt_slot(ip_dev, FT_GESTURE_DEFAULT_TRACKING_ID); + input_mt_report_slot_state(ip_dev, MT_TOOL_FINGER, 1); + input_report_abs(ip_dev, ABS_MT_POSITION_X, + FT_GESTURE_DOUBLECLICK_COORD_X); + input_report_abs(ip_dev, ABS_MT_POSITION_Y, + FT_GESTURE_DOUBLECLICK_COORD_Y); + input_mt_report_pointer_emulation(ip_dev, false); + input_sync(ip_dev); + input_mt_slot(ip_dev, FT_GESTURE_DEFAULT_TRACKING_ID); + input_mt_report_slot_state(ip_dev, MT_TOOL_FINGER, 0); + input_mt_report_pointer_emulation(ip_dev, false); + input_sync(ip_dev); + } + return 0; +} + +static int ft5x06_report_gesture(struct i2c_client *i2c_client, + struct input_dev *ip_dev) +{ + int i, temp, gesture_data_size; + int gesture_coord_x, gesture_coord_y; + int ret = -1; + short pointnum = 0; + unsigned char buf[FT_GESTURE_POINTER_NUM_MAX * + FT_GESTURE_POINTER_SIZEOF + FT_GESTURE_DATA_HEADER]; + + buf[0] = FT_REG_GESTURE_OUTPUT; + ret = ft5x06_i2c_read(i2c_client, buf, 1, + buf, FT_GESTURE_DATA_HEADER); + if (ret < 0) { + dev_err(&i2c_client->dev, "%s read touchdata failed.\n", + __func__); + return ret; + } + + /* FW support doubleclick */ + if (buf[0] == FT_GESTURE_DOUBLECLICK_ID) { + ft5x06_report_gesture_doubleclick(ip_dev); + return 0; + } + + pointnum = (short)(buf[1]) & 0xff; + gesture_data_size = pointnum * FT_GESTURE_POINTER_SIZEOF + + FT_GESTURE_DATA_HEADER; + buf[0] = FT_REG_GESTURE_OUTPUT; + temp = gesture_data_size / I2C_TRANSFER_MAX_BYTE; + for (i = 0; i < temp; i++) + ret = ft5x06_i2c_read(i2c_client, buf, ((i == 0) ? 1 : 0), + buf + I2C_TRANSFER_MAX_BYTE * i, I2C_TRANSFER_MAX_BYTE); + ret = ft5x06_i2c_read(i2c_client, buf, ((temp == 0) ? 1 : 0), + buf + I2C_TRANSFER_MAX_BYTE * temp, + gesture_data_size - I2C_TRANSFER_MAX_BYTE * temp); + if (ret < 0) { + dev_err(&i2c_client->dev, "%s read touchdata failed.\n", + __func__); + return ret; + } + + for (i = 0; i < pointnum; i++) { + gesture_coord_x = (((s16) buf[FT_GESTURE_DATA_HEADER + + (FT_GESTURE_POINTER_SIZEOF * i)]) & 0x0F) << 8 | + (((s16) buf[FT_GESTURE_DATA_HEADER + 1 + + (FT_GESTURE_POINTER_SIZEOF * i)]) & 0xFF); + gesture_coord_y = (((s16) buf[FT_GESTURE_DATA_HEADER + 2 + + (FT_GESTURE_POINTER_SIZEOF * i)]) & 0x0F) << 8 | + (((s16) buf[FT_GESTURE_DATA_HEADER + 3 + + (FT_GESTURE_POINTER_SIZEOF * i)]) & 0xFF); + input_mt_slot(ip_dev, FT_GESTURE_DEFAULT_TRACKING_ID); + input_mt_report_slot_state(ip_dev, MT_TOOL_FINGER, 1); + input_report_abs(ip_dev, ABS_MT_POSITION_X, gesture_coord_x); + input_report_abs(ip_dev, ABS_MT_POSITION_Y, gesture_coord_y); + input_mt_report_pointer_emulation(ip_dev, false); + input_sync(ip_dev); + } + input_mt_slot(ip_dev, FT_GESTURE_DEFAULT_TRACKING_ID); + input_mt_report_slot_state(ip_dev, MT_TOOL_FINGER, 0); + input_mt_report_pointer_emulation(ip_dev, false); + input_sync(ip_dev); + + return 0; +} +#else +static DEVICE_ATTR(pocket, 0664, NULL, NULL); +static DEVICE_ATTR(enable, 0664, NULL, NULL); + +static int ft5x06_report_gesture(struct i2c_client *i2c_client, + struct input_dev *ip_dev) +{ + return 0; +} +#endif + +static void ft5x06_update_fw_vendor_id(struct ft5x06_ts_data *data) +{ + struct i2c_client *client = data->client; + u8 reg_addr; + int err; + + reg_addr = FT_REG_FW_VENDOR_ID; + err = ft5x06_i2c_read(client, ®_addr, 1, &data->fw_vendor_id, 1); + if (err < 0) + dev_err(&client->dev, "fw vendor id read failed"); +} + +static void ft5x06_update_fw_ver(struct ft5x06_ts_data *data) +{ + struct i2c_client *client = data->client; + u8 reg_addr; + int err; + + reg_addr = FT_REG_FW_VER; + err = ft5x06_i2c_read(client, ®_addr, 1, &data->fw_ver[0], 1); + if (err < 0) + dev_err(&client->dev, "fw major version read failed"); + + reg_addr = FT_REG_FW_MIN_VER; + err = ft5x06_i2c_read(client, ®_addr, 1, &data->fw_ver[1], 1); + if (err < 0) + dev_err(&client->dev, "fw minor version read failed"); + + reg_addr = FT_REG_FW_SUB_MIN_VER; + err = ft5x06_i2c_read(client, ®_addr, 1, &data->fw_ver[2], 1); + if (err < 0) + dev_err(&client->dev, "fw sub minor version read failed"); + + dev_info(&client->dev, "Firmware version = %d.%d.%d\n", + data->fw_ver[0], data->fw_ver[1], data->fw_ver[2]); +} + +static irqreturn_t ft5x06_ts_interrupt(int irq, void *dev_id) +{ + struct ft5x06_ts_data *data = dev_id; + struct input_dev *ip_dev; + int rc, i; + u32 id, x, y, status, num_touches; + u8 reg, *buf, gesture_is_active; + bool update_input = false; + + if (!data) { + pr_err("%s: Invalid data\n", __func__); + return IRQ_HANDLED; + } + + if (ft5x06_filter_interrupt(data) == IRQ_HANDLED) + return IRQ_HANDLED; + + ip_dev = data->input_dev; + buf = data->tch_data; + + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) { + ft5x0x_read_reg(data->client, FT_REG_GESTURE_ENABLE, + &gesture_is_active); + if (gesture_is_active) { + pm_wakeup_event(&(data->client->dev), + FT_GESTURE_WAKEUP_TIMEOUT); + ft5x06_report_gesture(data->client, ip_dev); + return IRQ_HANDLED; + } + } + + /* + * Read touch data start from register FT_REG_DEV_MODE. + * The touch x/y value start from FT_TOUCH_X_H/L_POS and + * FT_TOUCH_Y_H/L_POS in buf. + */ + reg = FT_REG_DEV_MODE; + rc = ft5x06_i2c_read(data->client, ®, 1, buf, data->tch_data_len); + if (rc < 0) { + dev_err(&data->client->dev, "%s: read data fail\n", __func__); + return IRQ_HANDLED; + } + + for (i = 0; i < data->pdata->num_max_touches; i++) { + /* + * Getting the finger ID of the touch event incase of + * multiple touch events + */ + id = (buf[FT_TOUCH_ID_POS + FT_ONE_TCH_LEN * i]) >> 4; + if (id >= FT_MAX_ID) + break; + + update_input = true; + + x = (buf[FT_TOUCH_X_H_POS + FT_ONE_TCH_LEN * i] & 0x0F) << 8 | + (buf[FT_TOUCH_X_L_POS + FT_ONE_TCH_LEN * i]); + y = (buf[FT_TOUCH_Y_H_POS + FT_ONE_TCH_LEN * i] & 0x0F) << 8 | + (buf[FT_TOUCH_Y_L_POS + FT_ONE_TCH_LEN * i]); + + status = buf[FT_TOUCH_EVENT_POS + FT_ONE_TCH_LEN * i] >> 6; + + num_touches = buf[FT_TD_STATUS] & FT_STATUS_NUM_TP_MASK; + + /* invalid combination */ + if (!num_touches && !status && !id) + break; + + input_mt_slot(ip_dev, id); + if (status == FT_TOUCH_DOWN || status == FT_TOUCH_CONTACT) { + input_mt_report_slot_state(ip_dev, MT_TOOL_FINGER, 1); + input_report_abs(ip_dev, ABS_MT_POSITION_X, x); + input_report_abs(ip_dev, ABS_MT_POSITION_Y, y); + } else { + input_mt_report_slot_state(ip_dev, MT_TOOL_FINGER, 0); + } + } + + if (update_input) { + input_mt_report_pointer_emulation(ip_dev, false); + input_sync(ip_dev); + } + + return IRQ_HANDLED; +} + +static int ft5x06_gpio_configure(struct ft5x06_ts_data *data, bool on) +{ + int err = 0; + + if (on) { + if (gpio_is_valid(data->pdata->irq_gpio)) { + err = gpio_request(data->pdata->irq_gpio, + "ft5x06_irq_gpio"); + if (err) { + dev_err(&data->client->dev, + "irq gpio request failed"); + goto err_irq_gpio_req; + } + + err = gpio_direction_input(data->pdata->irq_gpio); + if (err) { + dev_err(&data->client->dev, + "set_direction for irq gpio failed\n"); + goto err_irq_gpio_dir; + } + } + + if (gpio_is_valid(data->pdata->reset_gpio)) { + err = gpio_request(data->pdata->reset_gpio, + "ft5x06_reset_gpio"); + if (err) { + dev_err(&data->client->dev, + "reset gpio request failed"); + goto err_irq_gpio_dir; + } + + err = gpio_direction_output(data->pdata->reset_gpio, 0); + if (err) { + dev_err(&data->client->dev, + "set_direction for reset gpio failed\n"); + goto err_reset_gpio_dir; + } + msleep(data->pdata->hard_rst_dly); + gpio_set_value_cansleep(data->pdata->reset_gpio, 1); + } + + return 0; + } + if (gpio_is_valid(data->pdata->irq_gpio)) + gpio_free(data->pdata->irq_gpio); + if (gpio_is_valid(data->pdata->reset_gpio)) { + /* + * This is intended to save leakage current + * only. Even if the call(gpio_direction_input) + * fails, only leakage current will be more but + * functionality will not be affected. + */ + err = gpio_direction_input(data->pdata->reset_gpio); + if (err) { + dev_err(&data->client->dev, + "unable to set direction for gpio [%d]\n", + data->pdata->irq_gpio); + } + gpio_free(data->pdata->reset_gpio); + } + + return 0; + +err_reset_gpio_dir: + if (gpio_is_valid(data->pdata->reset_gpio)) + gpio_free(data->pdata->reset_gpio); +err_irq_gpio_dir: + if (gpio_is_valid(data->pdata->irq_gpio)) + gpio_free(data->pdata->irq_gpio); +err_irq_gpio_req: + return err; +} + +static int ft5x06_power_on(struct ft5x06_ts_data *data, bool on) +{ + int rc; + + if (!on) + goto power_off; + + rc = regulator_enable(data->vdd); + if (rc) { + dev_err(&data->client->dev, + "Regulator vdd enable failed rc=%d\n", rc); + return rc; + } + + rc = regulator_enable(data->vcc_i2c); + if (rc) { + dev_err(&data->client->dev, + "Regulator vcc_i2c enable failed rc=%d\n", rc); + regulator_disable(data->vdd); + } + + return rc; + +power_off: + rc = regulator_disable(data->vdd); + if (rc) { + dev_err(&data->client->dev, + "Regulator vdd disable failed rc=%d\n", rc); + return rc; + } + + rc = regulator_disable(data->vcc_i2c); + if (rc) { + dev_err(&data->client->dev, + "Regulator vcc_i2c disable failed rc=%d\n", rc); + rc = regulator_enable(data->vdd); + if (rc) { + dev_err(&data->client->dev, + "Regulator vdd enable failed rc=%d\n", rc); + } + } + + return rc; +} + +static int ft5x06_power_init(struct ft5x06_ts_data *data, bool on) +{ + int rc; + + if (!on) + goto pwr_deinit; + + data->vdd = regulator_get(&data->client->dev, "vdd"); + if (IS_ERR(data->vdd)) { + rc = PTR_ERR(data->vdd); + dev_err(&data->client->dev, + "Regulator get failed vdd rc=%d\n", rc); + return rc; + } + + if (regulator_count_voltages(data->vdd) > 0) { + rc = regulator_set_voltage(data->vdd, FT_VTG_MIN_UV, + FT_VTG_MAX_UV); + if (rc) { + dev_err(&data->client->dev, + "Regulator set_vtg failed vdd rc=%d\n", rc); + goto reg_vdd_put; + } + } + + data->vcc_i2c = regulator_get(&data->client->dev, "vcc_i2c"); + if (IS_ERR(data->vcc_i2c)) { + rc = PTR_ERR(data->vcc_i2c); + dev_err(&data->client->dev, + "Regulator get failed vcc_i2c rc=%d\n", rc); + goto reg_vdd_set_vtg; + } + + if (regulator_count_voltages(data->vcc_i2c) > 0) { + rc = regulator_set_voltage(data->vcc_i2c, FT_I2C_VTG_MIN_UV, + FT_I2C_VTG_MAX_UV); + if (rc) { + dev_err(&data->client->dev, + "Regulator set_vtg failed vcc_i2c rc=%d\n", rc); + goto reg_vcc_i2c_put; + } + } + + return 0; + +reg_vcc_i2c_put: + regulator_put(data->vcc_i2c); +reg_vdd_set_vtg: + if (regulator_count_voltages(data->vdd) > 0) + regulator_set_voltage(data->vdd, 0, FT_VTG_MAX_UV); +reg_vdd_put: + regulator_put(data->vdd); + return rc; + +pwr_deinit: + if (regulator_count_voltages(data->vdd) > 0) + regulator_set_voltage(data->vdd, 0, FT_VTG_MAX_UV); + + regulator_put(data->vdd); + + if (regulator_count_voltages(data->vcc_i2c) > 0) + regulator_set_voltage(data->vcc_i2c, 0, FT_I2C_VTG_MAX_UV); + + regulator_put(data->vcc_i2c); + return 0; +} + +static int ft5x06_ts_pinctrl_init(struct ft5x06_ts_data *ft5x06_data) +{ + int retval; + + /* Get pinctrl if target uses pinctrl */ + ft5x06_data->ts_pinctrl = devm_pinctrl_get(&(ft5x06_data->client->dev)); + if (IS_ERR_OR_NULL(ft5x06_data->ts_pinctrl)) { + retval = PTR_ERR(ft5x06_data->ts_pinctrl); + dev_dbg(&ft5x06_data->client->dev, + "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + ft5x06_data->pinctrl_state_active + = pinctrl_lookup_state(ft5x06_data->ts_pinctrl, + PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(ft5x06_data->pinctrl_state_active)) { + retval = PTR_ERR(ft5x06_data->pinctrl_state_active); + dev_err(&ft5x06_data->client->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + ft5x06_data->pinctrl_state_suspend + = pinctrl_lookup_state(ft5x06_data->ts_pinctrl, + PINCTRL_STATE_SUSPEND); + if (IS_ERR_OR_NULL(ft5x06_data->pinctrl_state_suspend)) { + retval = PTR_ERR(ft5x06_data->pinctrl_state_suspend); + dev_err(&ft5x06_data->client->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + ft5x06_data->pinctrl_state_release + = pinctrl_lookup_state(ft5x06_data->ts_pinctrl, + PINCTRL_STATE_RELEASE); + if (IS_ERR_OR_NULL(ft5x06_data->pinctrl_state_release)) { + retval = PTR_ERR(ft5x06_data->pinctrl_state_release); + dev_dbg(&ft5x06_data->client->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(ft5x06_data->ts_pinctrl); +err_pinctrl_get: + ft5x06_data->ts_pinctrl = NULL; + return retval; +} + +#ifdef CONFIG_PM +static int ft5x06_ts_start(struct device *dev) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + int err; + + if (data->pdata->power_on) { + err = data->pdata->power_on(true); + if (err) { + dev_err(dev, "power on failed"); + return err; + } + } else { + err = ft5x06_power_on(data, true); + if (err) { + dev_err(dev, "power on failed"); + return err; + } + } + + if (data->ts_pinctrl) { + err = pinctrl_select_state(data->ts_pinctrl, + data->pinctrl_state_active); + if (err < 0) + dev_err(dev, "Cannot get active pinctrl state\n"); + } + + err = ft5x06_gpio_configure(data, true); + if (err < 0) { + dev_err(&data->client->dev, + "failed to put gpios in resue state\n"); + goto err_gpio_configuration; + } + + if (gpio_is_valid(data->pdata->reset_gpio)) { + gpio_set_value_cansleep(data->pdata->reset_gpio, 0); + msleep(data->pdata->hard_rst_dly); + gpio_set_value_cansleep(data->pdata->reset_gpio, 1); + } + + msleep(data->pdata->soft_rst_dly); + + enable_irq(data->client->irq); + data->suspended = false; + + return 0; + +err_gpio_configuration: + if (data->ts_pinctrl) { + err = pinctrl_select_state(data->ts_pinctrl, + data->pinctrl_state_suspend); + if (err < 0) + dev_err(dev, "Cannot get suspend pinctrl state\n"); + } + if (data->pdata->power_on) { + err = data->pdata->power_on(false); + if (err) + dev_err(dev, "power off failed"); + } else { + err = ft5x06_power_on(data, false); + if (err) + dev_err(dev, "power off failed"); + } + return err; +} + +static int ft5x06_ts_stop(struct device *dev) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + char txbuf[2]; + int i, err; + + disable_irq(data->client->irq); + + /* release all touches */ + for (i = 0; i < data->pdata->num_max_touches; i++) { + input_mt_slot(data->input_dev, i); + input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, 0); + } + input_mt_report_pointer_emulation(data->input_dev, false); + input_sync(data->input_dev); + + if (gpio_is_valid(data->pdata->reset_gpio)) { + txbuf[0] = FT_REG_PMODE; + txbuf[1] = FT_PMODE_HIBERNATE; + ft5x06_i2c_write(data->client, txbuf, sizeof(txbuf)); + } + + if (data->pdata->power_on) { + err = data->pdata->power_on(false); + if (err) { + dev_err(dev, "power off failed"); + goto pwr_off_fail; + } + } else { + err = ft5x06_power_on(data, false); + if (err) { + dev_err(dev, "power off failed"); + goto pwr_off_fail; + } + } + + if (data->ts_pinctrl) { + err = pinctrl_select_state(data->ts_pinctrl, + data->pinctrl_state_suspend); + if (err < 0) + dev_err(dev, "Cannot get suspend pinctrl state\n"); + } + + err = ft5x06_gpio_configure(data, false); + if (err < 0) { + dev_err(&data->client->dev, + "failed to put gpios in suspend state\n"); + goto gpio_configure_fail; + } + + data->suspended = true; + + return 0; + +gpio_configure_fail: + if (data->ts_pinctrl) { + err = pinctrl_select_state(data->ts_pinctrl, + data->pinctrl_state_active); + if (err < 0) + dev_err(dev, "Cannot get active pinctrl state\n"); + } + if (data->pdata->power_on) { + err = data->pdata->power_on(true); + if (err) + dev_err(dev, "power on failed"); + } else { + err = ft5x06_power_on(data, true); + if (err) + dev_err(dev, "power on failed"); + } +pwr_off_fail: + if (gpio_is_valid(data->pdata->reset_gpio)) { + gpio_set_value_cansleep(data->pdata->reset_gpio, 0); + msleep(data->pdata->hard_rst_dly); + gpio_set_value_cansleep(data->pdata->reset_gpio, 1); + } + enable_irq(data->client->irq); + return err; +} + +static int ft5x06_ts_suspend(struct device *dev) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + int err; + + if (data->loading_fw) { + dev_info(dev, "Firmware loading in process...\n"); + return 0; + } + + if (data->suspended) { + dev_info(dev, "Already in suspend state\n"); + return 0; + } + + ft5x06_secure_touch_stop(data, true); + + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support && + device_may_wakeup(dev) && + data->gesture_pdata->gesture_enable_to_set) { + + ft5x0x_write_reg(data->client, FT_REG_GESTURE_ENABLE, 1); + err = enable_irq_wake(data->client->irq); + if (err) + dev_err(&data->client->dev, + "%s: set_irq_wake failed\n", __func__); + data->suspended = true; + return err; + } + + return ft5x06_ts_stop(dev); +} + +static int ft5x06_ts_resume(struct device *dev) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + int err; + + if (!data->suspended) { + dev_dbg(dev, "Already in awake state\n"); + return 0; + } + + ft5x06_secure_touch_stop(data, true); + + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support && + device_may_wakeup(dev) && + !(data->gesture_pdata->in_pocket) && + data->gesture_pdata->gesture_enable_to_set) { + + ft5x0x_write_reg(data->client, FT_REG_GESTURE_ENABLE, 0); + err = disable_irq_wake(data->client->irq); + if (err) + dev_err(dev, "%s: disable_irq_wake failed\n", + __func__); + data->suspended = false; + return err; + } + + err = ft5x06_ts_start(dev); + if (err < 0) + return err; + + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support && + device_may_wakeup(dev) && + data->gesture_pdata->in_pocket && + data->gesture_pdata->gesture_enable_to_set) { + + ft5x0x_write_reg(data->client, FT_REG_GESTURE_ENABLE, 0); + err = disable_irq_wake(data->client->irq); + if (err) + dev_err(dev, "%s: disable_irq_wake failed\n", + __func__); + data->suspended = false; + data->gesture_pdata->in_pocket = 0; + } + return 0; +} + +static const struct dev_pm_ops ft5x06_ts_pm_ops = { +#if (!defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND)) + .suspend = ft5x06_ts_suspend, + .resume = ft5x06_ts_resume, +#endif +}; + +#else +static int ft5x06_ts_suspend(struct device *dev) +{ + return 0; +} + +static int ft5x06_ts_resume(struct device *dev) +{ + return 0; +} + +#endif + +#if defined(CONFIG_FB) +static void fb_notify_resume_work(struct work_struct *work) +{ + struct ft5x06_ts_data *ft5x06_data = + container_of(work, struct ft5x06_ts_data, fb_notify_work); + ft5x06_ts_resume(&ft5x06_data->client->dev); +} + +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + struct ft5x06_ts_data *ft5x06_data = + container_of(self, struct ft5x06_ts_data, fb_notif); + + if (evdata && evdata->data && ft5x06_data && ft5x06_data->client) { + blank = evdata->data; + if (ft5x06_data->pdata->resume_in_workqueue) { + if (event == FB_EARLY_EVENT_BLANK && + *blank == FB_BLANK_UNBLANK) + schedule_work(&ft5x06_data->fb_notify_work); + else if (event == FB_EVENT_BLANK && + *blank == FB_BLANK_POWERDOWN) { + flush_work(&ft5x06_data->fb_notify_work); + ft5x06_ts_suspend(&ft5x06_data->client->dev); + } + } else { + if (event == FB_EVENT_BLANK) { + if (*blank == FB_BLANK_UNBLANK) + ft5x06_ts_resume( + &ft5x06_data->client->dev); + else if (*blank == FB_BLANK_POWERDOWN) + ft5x06_ts_suspend( + &ft5x06_data->client->dev); + } + } + } + + return 0; +} +#elif defined(CONFIG_HAS_EARLYSUSPEND) +static void ft5x06_ts_early_suspend(struct early_suspend *handler) +{ + struct ft5x06_ts_data *data = container_of(handler, + struct ft5x06_ts_data, + early_suspend); + + /* + * During early suspend/late resume, the driver doesn't access xPU/SMMU + * protected HW resources. So, there is no compelling need to block, + * but notifying the userspace that a power event has occurred is + * enough. Hence 'blocking' variable can be set to false. + */ + ft5x06_secure_touch_stop(data, false); + ft5x06_ts_suspend(&data->client->dev); +} + +static void ft5x06_ts_late_resume(struct early_suspend *handler) +{ + struct ft5x06_ts_data *data = container_of(handler, + struct ft5x06_ts_data, + early_suspend); + + ft5x06_secure_touch_stop(data, false); + ft5x06_ts_resume(&data->client->dev); +} +#endif + +static int ft5x06_auto_cal(struct i2c_client *client) +{ + struct ft5x06_ts_data *data = i2c_get_clientdata(client); + u8 temp = 0, i; + + /* set to factory mode */ + msleep(2 * data->pdata->soft_rst_dly); + ft5x0x_write_reg(client, FT_REG_DEV_MODE, FT_FACTORYMODE_VALUE); + msleep(data->pdata->soft_rst_dly); + + /* start calibration */ + ft5x0x_write_reg(client, FT_DEV_MODE_REG_CAL, FT_CAL_START); + msleep(2 * data->pdata->soft_rst_dly); + for (i = 0; i < FT_CAL_RETRY; i++) { + ft5x0x_read_reg(client, FT_REG_CAL, &temp); + /* return to normal mode, calibration finish */ + if (((temp & FT_CAL_MASK) >> FT_4BIT_SHIFT) == FT_CAL_FIN) + break; + } + + /*calibration OK */ + msleep(2 * data->pdata->soft_rst_dly); + ft5x0x_write_reg(client, FT_REG_DEV_MODE, FT_FACTORYMODE_VALUE); + msleep(data->pdata->soft_rst_dly); + + /* store calibration data */ + ft5x0x_write_reg(client, FT_DEV_MODE_REG_CAL, FT_CAL_STORE); + msleep(2 * data->pdata->soft_rst_dly); + + /* set to normal mode */ + ft5x0x_write_reg(client, FT_REG_DEV_MODE, FT_WORKMODE_VALUE); + msleep(2 * data->pdata->soft_rst_dly); + + return 0; +} + +static int ft5x06_fw_upgrade_start(struct i2c_client *client, + const u8 *data, u32 data_len) +{ + struct ft5x06_ts_data *ts_data = i2c_get_clientdata(client); + struct fw_upgrade_info info = ts_data->pdata->info; + u8 reset_reg; + u8 w_buf[FT_MAX_WR_BUF] = {0}, r_buf[FT_MAX_RD_BUF] = {0}; + u8 pkt_buf[FT_FW_PKT_LEN + FT_FW_PKT_META_LEN]; + int i, j, temp; + u32 pkt_num, pkt_len; + u8 is_5336_new_bootloader = false; + u8 is_5336_fwsize_30 = false; + u8 fw_ecc; + + /* determine firmware size */ + if (*(data + data_len - FT_BLOADER_SIZE_OFF) == FT_BLOADER_NEW_SIZE) + is_5336_fwsize_30 = true; + else + is_5336_fwsize_30 = false; + + for (i = 0, j = 0; i < FT_UPGRADE_LOOP; i++) { + msleep(FT_EARSE_DLY_MS); + /* reset - write 0xaa and 0x55 to reset register */ + if (ts_data->family_id == FT6X06_ID + || ts_data->family_id == FT6X36_ID) + reset_reg = FT_RST_CMD_REG2; + else + reset_reg = FT_RST_CMD_REG1; + + ft5x0x_write_reg(client, reset_reg, FT_UPGRADE_AA); + msleep(info.delay_aa); + + ft5x0x_write_reg(client, reset_reg, FT_UPGRADE_55); + if (i <= (FT_UPGRADE_LOOP / 2)) + msleep(info.delay_55 + i * 3); + else + msleep(info.delay_55 - (i - (FT_UPGRADE_LOOP / 2)) * 2); + + /* Enter upgrade mode */ + w_buf[0] = FT_UPGRADE_55; + ft5x06_i2c_write(client, w_buf, 1); + usleep_range(FT_55_AA_DLY_NS, FT_55_AA_DLY_NS + 1); + w_buf[0] = FT_UPGRADE_AA; + ft5x06_i2c_write(client, w_buf, 1); + + /* check READ_ID */ + msleep(info.delay_readid); + w_buf[0] = FT_READ_ID_REG; + w_buf[1] = 0x00; + w_buf[2] = 0x00; + w_buf[3] = 0x00; + + ft5x06_i2c_read(client, w_buf, 4, r_buf, 2); + + if (r_buf[0] != info.upgrade_id_1 + || r_buf[1] != info.upgrade_id_2) { + dev_err(&client->dev, "Upgrade ID mismatch(%d), IC=0x%x 0x%x, info=0x%x 0x%x\n", + i, r_buf[0], r_buf[1], + info.upgrade_id_1, info.upgrade_id_2); + } else + break; + } + + if (i >= FT_UPGRADE_LOOP) { + dev_err(&client->dev, "Abort upgrade\n"); + return -EIO; + } + + w_buf[0] = 0xcd; + ft5x06_i2c_read(client, w_buf, 1, r_buf, 1); + + if (r_buf[0] <= 4) + is_5336_new_bootloader = FT_BLOADER_VERSION_LZ4; + else if (r_buf[0] == 7) + is_5336_new_bootloader = FT_BLOADER_VERSION_Z7; + else if (r_buf[0] >= 0x0f && + ((ts_data->family_id == FT_FT5336_FAMILY_ID_0x11) || + (ts_data->family_id == FT_FT5336_FAMILY_ID_0x12) || + (ts_data->family_id == FT_FT5336_FAMILY_ID_0x13) || + (ts_data->family_id == FT_FT5336_FAMILY_ID_0x14))) + is_5336_new_bootloader = FT_BLOADER_VERSION_GZF; + else + is_5336_new_bootloader = FT_BLOADER_VERSION_LZ4; + + dev_dbg(&client->dev, "bootloader type=%d, r_buf=0x%x, family_id=0x%x\n", + is_5336_new_bootloader, r_buf[0], ts_data->family_id); + /* is_5336_new_bootloader = FT_BLOADER_VERSION_GZF; */ + + /* erase app and panel paramenter area */ + w_buf[0] = FT_ERASE_APP_REG; + ft5x06_i2c_write(client, w_buf, 1); + msleep(info.delay_erase_flash); + + if (is_5336_fwsize_30) { + w_buf[0] = FT_ERASE_PANEL_REG; + ft5x06_i2c_write(client, w_buf, 1); + } + msleep(FT_EARSE_DLY_MS); + + /* program firmware */ + if (is_5336_new_bootloader == FT_BLOADER_VERSION_LZ4 + || is_5336_new_bootloader == FT_BLOADER_VERSION_Z7) + data_len = data_len - FT_DATA_LEN_OFF_OLD_FW; + else + data_len = data_len - FT_DATA_LEN_OFF_NEW_FW; + + pkt_num = (data_len) / FT_FW_PKT_LEN; + pkt_len = FT_FW_PKT_LEN; + pkt_buf[0] = FT_FW_START_REG; + pkt_buf[1] = 0x00; + fw_ecc = 0; + + for (i = 0; i < pkt_num; i++) { + temp = i * FT_FW_PKT_LEN; + pkt_buf[2] = (u8) (temp >> FT_8BIT_SHIFT); + pkt_buf[3] = (u8) temp; + pkt_buf[4] = (u8) (pkt_len >> FT_8BIT_SHIFT); + pkt_buf[5] = (u8) pkt_len; + + for (j = 0; j < FT_FW_PKT_LEN; j++) { + pkt_buf[6 + j] = data[i * FT_FW_PKT_LEN + j]; + fw_ecc ^= pkt_buf[6 + j]; + } + + ft5x06_i2c_write(client, pkt_buf, + FT_FW_PKT_LEN + FT_FW_PKT_META_LEN); + msleep(FT_FW_PKT_DLY_MS); + } + + /* send remaining bytes */ + if ((data_len) % FT_FW_PKT_LEN > 0) { + temp = pkt_num * FT_FW_PKT_LEN; + pkt_buf[2] = (u8) (temp >> FT_8BIT_SHIFT); + pkt_buf[3] = (u8) temp; + temp = (data_len) % FT_FW_PKT_LEN; + pkt_buf[4] = (u8) (temp >> FT_8BIT_SHIFT); + pkt_buf[5] = (u8) temp; + + for (i = 0; i < temp; i++) { + pkt_buf[6 + i] = data[pkt_num * FT_FW_PKT_LEN + i]; + fw_ecc ^= pkt_buf[6 + i]; + } + + ft5x06_i2c_write(client, pkt_buf, temp + FT_FW_PKT_META_LEN); + msleep(FT_FW_PKT_DLY_MS); + } + + /* send the finishing packet */ + if (is_5336_new_bootloader == FT_BLOADER_VERSION_LZ4 || + is_5336_new_bootloader == FT_BLOADER_VERSION_Z7) { + for (i = 0; i < FT_FINISHING_PKT_LEN_OLD_FW; i++) { + if (is_5336_new_bootloader == FT_BLOADER_VERSION_Z7) + temp = FT_MAGIC_BLOADER_Z7 + i; + else if (is_5336_new_bootloader == + FT_BLOADER_VERSION_LZ4) + temp = FT_MAGIC_BLOADER_LZ4 + i; + pkt_buf[2] = (u8)(temp >> 8); + pkt_buf[3] = (u8)temp; + temp = 1; + pkt_buf[4] = (u8)(temp >> 8); + pkt_buf[5] = (u8)temp; + pkt_buf[6] = data[data_len + i]; + fw_ecc ^= pkt_buf[6]; + + ft5x06_i2c_write(client, + pkt_buf, temp + FT_FW_PKT_META_LEN); + msleep(FT_FW_PKT_DLY_MS); + } + } else if (is_5336_new_bootloader == FT_BLOADER_VERSION_GZF) { + for (i = 0; i < FT_FINISHING_PKT_LEN_NEW_FW; i++) { + if (is_5336_fwsize_30) + temp = FT_MAGIC_BLOADER_GZF_30 + i; + else + temp = FT_MAGIC_BLOADER_GZF + i; + pkt_buf[2] = (u8)(temp >> 8); + pkt_buf[3] = (u8)temp; + temp = 1; + pkt_buf[4] = (u8)(temp >> 8); + pkt_buf[5] = (u8)temp; + pkt_buf[6] = data[data_len + i]; + fw_ecc ^= pkt_buf[6]; + + ft5x06_i2c_write(client, + pkt_buf, temp + FT_FW_PKT_META_LEN); + msleep(FT_FW_PKT_DLY_MS); + + } + } + + /* verify checksum */ + w_buf[0] = FT_REG_ECC; + ft5x06_i2c_read(client, w_buf, 1, r_buf, 1); + if (r_buf[0] != fw_ecc) { + dev_err(&client->dev, "ECC error! dev_ecc=%02x fw_ecc=%02x\n", + r_buf[0], fw_ecc); + return -EIO; + } + + /* reset */ + w_buf[0] = FT_REG_RESET_FW; + ft5x06_i2c_write(client, w_buf, 1); + msleep(ts_data->pdata->soft_rst_dly); + + dev_info(&client->dev, "Firmware upgrade successful\n"); + + return 0; +} + +static int ft5x06_fw_upgrade(struct device *dev, bool force) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + const struct firmware *fw = NULL; + int rc; + u8 fw_file_maj, fw_file_min, fw_file_sub_min, fw_file_vendor_id; + bool fw_upgrade = false; + + if (data->suspended) { + dev_err(dev, "Device is in suspend state: Exit FW upgrade\n"); + return -EBUSY; + } + + rc = request_firmware(&fw, data->fw_name, dev); + if (rc < 0) { + dev_err(dev, "Request firmware failed - %s (%d)\n", + data->fw_name, rc); + return rc; + } + + if (fw->size < FT_FW_MIN_SIZE || fw->size > FT_FW_MAX_SIZE) { + dev_err(dev, "Invalid firmware size (%zu)\n", fw->size); + rc = -EIO; + goto rel_fw; + } + + if (data->family_id == FT6X36_ID) { + fw_file_maj = FT_FW_FILE_MAJ_VER_FT6X36(fw); + fw_file_vendor_id = FT_FW_FILE_VENDOR_ID_FT6X36(fw); + } else { + fw_file_maj = FT_FW_FILE_MAJ_VER(fw); + fw_file_vendor_id = FT_FW_FILE_VENDOR_ID(fw); + } + fw_file_min = FT_FW_FILE_MIN_VER(fw); + fw_file_sub_min = FT_FW_FILE_SUB_MIN_VER(fw); + fw_file_vendor_id = FT_FW_FILE_VENDOR_ID(fw); + + dev_info(dev, "Current firmware: %d.%d.%d", data->fw_ver[0], + data->fw_ver[1], data->fw_ver[2]); + dev_info(dev, "New firmware: %d.%d.%d", fw_file_maj, + fw_file_min, fw_file_sub_min); + + if (force) + fw_upgrade = true; + else if ((data->fw_ver[0] < fw_file_maj) && + data->fw_vendor_id == fw_file_vendor_id) + fw_upgrade = true; + + if (!fw_upgrade) { + dev_info(dev, "Exiting fw upgrade...\n"); + rc = -EFAULT; + goto rel_fw; + } + + /* start firmware upgrade */ + if (FT_FW_CHECK(fw, data)) { + rc = ft5x06_fw_upgrade_start(data->client, fw->data, fw->size); + if (rc < 0) + dev_err(dev, "update failed (%d). try later...\n", rc); + else if (data->pdata->info.auto_cal) + ft5x06_auto_cal(data->client); + } else { + dev_err(dev, "FW format error\n"); + rc = -EIO; + } + + ft5x06_update_fw_ver(data); + + FT_STORE_TS_DBG_INFO(data->ts_info, data->family_id, data->pdata->name, + data->pdata->num_max_touches, data->pdata->group_id, + data->pdata->fw_vkey_support ? "yes" : "no", + data->pdata->fw_name, data->fw_ver[0], + data->fw_ver[1], data->fw_ver[2]); + FT_STORE_TS_INFO(ts_info_buff, data->family_id, data->fw_ver[0], + data->fw_ver[1], data->fw_ver[2]); +rel_fw: + release_firmware(fw); + return rc; +} + +static ssize_t ft5x06_update_fw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + + return snprintf(buf, 2, "%d\n", data->loading_fw); +} + +static ssize_t ft5x06_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + unsigned long val; + int rc; + + if (size > 2) + return -EINVAL; + + rc = kstrtoul(buf, 10, &val); + if (rc != 0) + return rc; + + if (data->suspended) { + dev_info(dev, "In suspend state, try again later...\n"); + return size; + } + + mutex_lock(&data->input_dev->mutex); + if (!data->loading_fw && val) { + data->loading_fw = true; + ft5x06_fw_upgrade(dev, false); + data->loading_fw = false; + } + mutex_unlock(&data->input_dev->mutex); + + return size; +} + +static DEVICE_ATTR(update_fw, 0664, ft5x06_update_fw_show, + ft5x06_update_fw_store); + +static ssize_t ft5x06_force_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + unsigned long val; + int rc; + + if (size > 2) + return -EINVAL; + + rc = kstrtoul(buf, 10, &val); + if (rc != 0) + return rc; + + mutex_lock(&data->input_dev->mutex); + if (!data->loading_fw && val) { + data->loading_fw = true; + ft5x06_fw_upgrade(dev, true); + data->loading_fw = false; + } + mutex_unlock(&data->input_dev->mutex); + + return size; +} + +static DEVICE_ATTR(force_update_fw, 0664, ft5x06_update_fw_show, + ft5x06_force_update_fw_store); + +static ssize_t ft5x06_fw_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + + return snprintf(buf, FT_FW_NAME_MAX_LEN - 1, "%s\n", data->fw_name); +} + +static ssize_t ft5x06_fw_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct ft5x06_ts_data *data = dev_get_drvdata(dev); + + if (size > FT_FW_NAME_MAX_LEN - 1) + return -EINVAL; + + strlcpy(data->fw_name, buf, size); + if (data->fw_name[size-1] == '\n') + data->fw_name[size-1] = 0; + + return size; +} + +static DEVICE_ATTR(fw_name, 0664, ft5x06_fw_name_show, ft5x06_fw_name_store); + +static ssize_t ts_info_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + strlcpy(buf, ts_info_buff, FT_INFO_MAX_LEN); + return strnlen(buf, FT_INFO_MAX_LEN); +} +static struct kobj_attribute ts_info_attr = __ATTR_RO(ts_info); + +static bool ft5x06_debug_addr_is_valid(int addr) +{ + if (addr < 0 || addr > 0xFF) { + pr_err("FT reg address is invalid: 0x%x\n", addr); + return false; + } + + return true; +} + +static int ft5x06_debug_data_set(void *_data, u64 val) +{ + struct ft5x06_ts_data *data = _data; + + mutex_lock(&data->input_dev->mutex); + + if (ft5x06_debug_addr_is_valid(data->addr)) + dev_info(&data->client->dev, + "Writing into FT registers not supported\n"); + + mutex_unlock(&data->input_dev->mutex); + + return 0; +} + +static int ft5x06_debug_data_get(void *_data, u64 *val) +{ + struct ft5x06_ts_data *data = _data; + int rc; + u8 reg = 0; + + mutex_lock(&data->input_dev->mutex); + + if (ft5x06_debug_addr_is_valid(data->addr)) { + rc = ft5x0x_read_reg(data->client, data->addr, ®); + if (rc < 0) + dev_err(&data->client->dev, + "FT read register 0x%x failed (%d)\n", + data->addr, rc); + else + *val = reg; + } + + mutex_unlock(&data->input_dev->mutex); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(debug_data_fops, ft5x06_debug_data_get, + ft5x06_debug_data_set, "0x%02llX\n"); + +static int ft5x06_debug_addr_set(void *_data, u64 val) +{ + struct ft5x06_ts_data *data = _data; + + if (ft5x06_debug_addr_is_valid(val)) { + mutex_lock(&data->input_dev->mutex); + data->addr = val; + mutex_unlock(&data->input_dev->mutex); + } + + return 0; +} + +static int ft5x06_debug_addr_get(void *_data, u64 *val) +{ + struct ft5x06_ts_data *data = _data; + + mutex_lock(&data->input_dev->mutex); + + if (ft5x06_debug_addr_is_valid(data->addr)) + *val = data->addr; + + mutex_unlock(&data->input_dev->mutex); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(debug_addr_fops, ft5x06_debug_addr_get, + ft5x06_debug_addr_set, "0x%02llX\n"); + +static int ft5x06_debug_suspend_set(void *_data, u64 val) +{ + struct ft5x06_ts_data *data = _data; + + mutex_lock(&data->input_dev->mutex); + + if (val) + ft5x06_ts_suspend(&data->client->dev); + else + ft5x06_ts_resume(&data->client->dev); + + mutex_unlock(&data->input_dev->mutex); + + return 0; +} + +static int ft5x06_debug_suspend_get(void *_data, u64 *val) +{ + struct ft5x06_ts_data *data = _data; + + mutex_lock(&data->input_dev->mutex); + *val = data->suspended; + mutex_unlock(&data->input_dev->mutex); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(debug_suspend_fops, ft5x06_debug_suspend_get, + ft5x06_debug_suspend_set, "%lld\n"); + +static int ft5x06_debug_dump_info(struct seq_file *m, void *v) +{ + struct ft5x06_ts_data *data = m->private; + + seq_printf(m, "%s\n", data->ts_info); + + return 0; +} + +static int debugfs_dump_info_open(struct inode *inode, struct file *file) +{ + return single_open(file, ft5x06_debug_dump_info, inode->i_private); +} + +static const struct file_operations debug_dump_info_fops = { + .owner = THIS_MODULE, + .open = debugfs_dump_info_open, + .read = seq_read, + .release = single_release, +}; + +#ifdef CONFIG_OF +static int ft5x06_get_dt_coords(struct device *dev, char *name, + struct ft5x06_ts_platform_data *pdata) +{ + u32 coords[FT_COORDS_ARR_SIZE]; + struct property *prop; + struct device_node *np = dev->of_node; + int coords_size, rc; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + coords_size = prop->length / sizeof(u32); + if (coords_size != FT_COORDS_ARR_SIZE) { + dev_err(dev, "invalid %s\n", name); + return -EINVAL; + } + + rc = of_property_read_u32_array(np, name, coords, coords_size); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read %s\n", name); + return rc; + } + + if (!strcmp(name, "focaltech,panel-coords")) { + pdata->panel_minx = coords[0]; + pdata->panel_miny = coords[1]; + pdata->panel_maxx = coords[2]; + pdata->panel_maxy = coords[3]; + } else if (!strcmp(name, "focaltech,display-coords")) { + pdata->x_min = coords[0]; + pdata->y_min = coords[1]; + pdata->x_max = coords[2]; + pdata->y_max = coords[3]; + } else { + dev_err(dev, "unsupported property %s\n", name); + return -EINVAL; + } + + return 0; +} + +static int ft5x06_parse_dt(struct device *dev, + struct ft5x06_ts_platform_data *pdata) +{ + int rc; + struct device_node *np = dev->of_node; + struct property *prop; + u32 temp_val, num_buttons; + u32 button_map[MAX_BUTTONS]; + + pdata->name = "focaltech"; + rc = of_property_read_string(np, "focaltech,name", &pdata->name); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read name\n"); + return rc; + } + + rc = ft5x06_get_dt_coords(dev, "focaltech,panel-coords", pdata); + if (rc && (rc != -EINVAL)) + return rc; + + rc = ft5x06_get_dt_coords(dev, "focaltech,display-coords", pdata); + if (rc) + return rc; + + pdata->i2c_pull_up = of_property_read_bool(np, + "focaltech,i2c-pull-up"); + + pdata->no_force_update = of_property_read_bool(np, + "focaltech,no-force-update"); + /* reset, irq gpio info */ + pdata->reset_gpio = of_get_named_gpio_flags(np, "focaltech,reset-gpio", + 0, &pdata->reset_gpio_flags); + if (pdata->reset_gpio < 0) + return pdata->reset_gpio; + + pdata->irq_gpio = of_get_named_gpio_flags(np, "focaltech,irq-gpio", + 0, &pdata->irq_gpio_flags); + if (pdata->irq_gpio < 0) + return pdata->irq_gpio; + + pdata->fw_name = "ft_fw.bin"; + rc = of_property_read_string(np, "focaltech,fw-name", &pdata->fw_name); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw name\n"); + return rc; + } + + rc = of_property_read_u32(np, "focaltech,group-id", &temp_val); + if (!rc) + pdata->group_id = temp_val; + else + return rc; + + rc = of_property_read_u32(np, "focaltech,hard-reset-delay-ms", + &temp_val); + if (!rc) + pdata->hard_rst_dly = temp_val; + else + return rc; + + rc = of_property_read_u32(np, "focaltech,soft-reset-delay-ms", + &temp_val); + if (!rc) + pdata->soft_rst_dly = temp_val; + else + return rc; + + rc = of_property_read_u32(np, "focaltech,num-max-touches", &temp_val); + if (!rc) + pdata->num_max_touches = temp_val; + else + return rc; + + rc = of_property_read_u32(np, "focaltech,fw-delay-aa-ms", &temp_val); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw delay aa\n"); + return rc; + } else if (rc != -EINVAL) + pdata->info.delay_aa = temp_val; + + rc = of_property_read_u32(np, "focaltech,fw-delay-55-ms", &temp_val); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw delay 55\n"); + return rc; + } else if (rc != -EINVAL) + pdata->info.delay_55 = temp_val; + + rc = of_property_read_u32(np, "focaltech,fw-upgrade-id1", &temp_val); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw upgrade id1\n"); + return rc; + } else if (rc != -EINVAL) + pdata->info.upgrade_id_1 = temp_val; + + rc = of_property_read_u32(np, "focaltech,fw-upgrade-id2", &temp_val); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw upgrade id2\n"); + return rc; + } else if (rc != -EINVAL) + pdata->info.upgrade_id_2 = temp_val; + + rc = of_property_read_u32(np, "focaltech,fw-delay-readid-ms", + &temp_val); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw delay read id\n"); + return rc; + } else if (rc != -EINVAL) + pdata->info.delay_readid = temp_val; + + rc = of_property_read_u32(np, "focaltech,fw-delay-era-flsh-ms", + &temp_val); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw delay erase flash\n"); + return rc; + } else if (rc != -EINVAL) + pdata->info.delay_erase_flash = temp_val; + + pdata->info.auto_cal = of_property_read_bool(np, + "focaltech,fw-auto-cal"); + + pdata->fw_vkey_support = of_property_read_bool(np, + "focaltech,fw-vkey-support"); + + pdata->ignore_id_check = of_property_read_bool(np, + "focaltech,ignore-id-check"); + + pdata->gesture_support = of_property_read_bool(np, + "focaltech,gesture-support"); + + pdata->resume_in_workqueue = of_property_read_bool(np, + "focaltech,resume-in-workqueue"); + + rc = of_property_read_u32(np, "focaltech,family-id", &temp_val); + if (!rc) + pdata->family_id = temp_val; + else + return rc; + + prop = of_find_property(np, "focaltech,button-map", NULL); + if (prop) { + num_buttons = prop->length / sizeof(temp_val); + if (num_buttons > MAX_BUTTONS) + return -EINVAL; + + rc = of_property_read_u32_array(np, + "focaltech,button-map", button_map, + num_buttons); + if (rc) { + dev_err(dev, "Unable to read key codes\n"); + return rc; + } + } + + return 0; +} +#else +static int ft5x06_parse_dt(struct device *dev, + struct ft5x06_ts_platform_data *pdata) +{ + return -ENODEV; +} +#endif + +static int ft5x06_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ft5x06_ts_platform_data *pdata; + struct ft5x06_gesture_platform_data *gesture_pdata; + struct ft5x06_ts_data *data; + struct input_dev *input_dev; + struct dentry *temp; + u8 reg_value = 0; + u8 reg_addr; + int err, len, retval, attr_count; + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct ft5x06_ts_platform_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + err = ft5x06_parse_dt(&client->dev, pdata); + if (err) { + dev_err(&client->dev, "DT parsing failed\n"); + return err; + } + } else + pdata = client->dev.platform_data; + + if (!pdata) { + dev_err(&client->dev, "Invalid pdata\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "I2C not supported\n"); + return -ENODEV; + } + + data = devm_kzalloc(&client->dev, + sizeof(struct ft5x06_ts_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (pdata->fw_name) { + len = strlen(pdata->fw_name); + if (len > FT_FW_NAME_MAX_LEN - 1) { + dev_err(&client->dev, "Invalid firmware name\n"); + return -EINVAL; + } + + strlcpy(data->fw_name, pdata->fw_name, len + 1); + } + + data->tch_data_len = FT_TCH_LEN(pdata->num_max_touches); + data->tch_data = devm_kzalloc(&client->dev, + data->tch_data_len, GFP_KERNEL); + if (!data->tch_data) + return -ENOMEM; + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&client->dev, "failed to allocate input device\n"); + return -ENOMEM; + } + + data->input_dev = input_dev; + data->client = client; + data->pdata = pdata; + + input_dev->name = "ft5x06_ts"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + + input_set_drvdata(input_dev, data); + i2c_set_clientdata(client, data); + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(BTN_TOUCH, input_dev->keybit); + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + input_mt_init_slots(input_dev, pdata->num_max_touches, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_X, pdata->x_min, + pdata->x_max, 0, 0); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, pdata->y_min, + pdata->y_max, 0, 0); + + err = input_register_device(input_dev); + if (err) { + dev_err(&client->dev, "Input device registration failed\n"); + input_free_device(input_dev); + return err; + } + + if (pdata->power_init) { + err = pdata->power_init(true); + if (err) { + dev_err(&client->dev, "power init failed"); + goto unreg_inputdev; + } + } else { + err = ft5x06_power_init(data, true); + if (err) { + dev_err(&client->dev, "power init failed"); + goto unreg_inputdev; + } + } + + if (pdata->power_on) { + err = pdata->power_on(true); + if (err) { + dev_err(&client->dev, "power on failed"); + goto pwr_deinit; + } + } else { + err = ft5x06_power_on(data, true); + if (err) { + dev_err(&client->dev, "power on failed"); + goto pwr_deinit; + } + } + + err = ft5x06_ts_pinctrl_init(data); + if (!err && data->ts_pinctrl) { + /* + * Pinctrl handle is optional. If pinctrl handle is found + * let pins to be configured in active state. If not + * found continue further without error. + */ + err = pinctrl_select_state(data->ts_pinctrl, + data->pinctrl_state_active); + if (err < 0) { + dev_err(&client->dev, + "failed to select pin to active state"); + } + } + + err = ft5x06_gpio_configure(data, true); + if (err < 0) { + dev_err(&client->dev, + "Failed to configure the gpios\n"); + goto err_gpio_req; + } + + /* make sure CTP already finish startup process */ + msleep(data->pdata->soft_rst_dly); + + /* check the controller id */ + reg_addr = FT_REG_ID; + err = ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); + if (err < 0) { + dev_err(&client->dev, "version read failed"); + goto free_gpio; + } + + dev_info(&client->dev, "Device ID = 0x%x\n", reg_value); + + if ((pdata->family_id != reg_value) && (!pdata->ignore_id_check)) { + dev_err(&client->dev, "%s:Unsupported controller\n", __func__); + goto free_gpio; + } + + data->family_id = pdata->family_id; + + err = request_threaded_irq(client->irq, NULL, + ft5x06_ts_interrupt, + /* + * the interrupt trigger mode will be set in Device Tree with property + * "interrupts", so here we just need to set the flag IRQF_ONESHOT + */ + IRQF_ONESHOT, + client->dev.driver->name, data); + if (err) { + dev_err(&client->dev, "request irq failed\n"); + goto free_gpio; + } + + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) { + device_init_wakeup(&client->dev, 1); + gesture_pdata = devm_kzalloc(&client->dev, + sizeof(struct ft5x06_gesture_platform_data), + GFP_KERNEL); + if (!gesture_pdata) { + dev_err(&client->dev, "Failed to allocate memory\n"); + goto free_gesture_dev; + } + data->gesture_pdata = gesture_pdata; + gesture_pdata->data = data; + + gesture_pdata->gesture_class = + class_create(THIS_MODULE, "gesture"); + if (IS_ERR(gesture_pdata->gesture_class)) { + err = PTR_ERR(gesture_pdata->gesture_class); + dev_err(&client->dev, "Failed to create class.\n"); + goto free_gesture_pdata; + } + + gesture_pdata->dev = device_create(gesture_pdata->gesture_class, + NULL, 0, NULL, "gesture_ft5x06"); + if (IS_ERR(gesture_pdata->dev)) { + err = PTR_ERR(gesture_pdata->dev); + dev_err(&client->dev, "Failed to create device.\n"); + goto free_gesture_class; + } + + dev_set_drvdata(gesture_pdata->dev, data); + err = device_create_file(gesture_pdata->dev, + &dev_attr_enable); + if (err) { + dev_err(gesture_pdata->dev, + "sys file creation failed\n"); + goto free_gesture_dev; + } + err = device_create_file(gesture_pdata->dev, + &dev_attr_pocket); + if (err) { + dev_err(gesture_pdata->dev, + "sys file creation failed\n"); + goto free_enable_sys; + } + } + + err = device_create_file(&client->dev, &dev_attr_fw_name); + if (err) { + dev_err(&client->dev, "sys file creation failed\n"); + goto free_pocket_sys; + } + + err = device_create_file(&client->dev, &dev_attr_update_fw); + if (err) { + dev_err(&client->dev, "sys file creation failed\n"); + goto free_fw_name_sys; + } + + err = device_create_file(&client->dev, &dev_attr_force_update_fw); + if (err) { + dev_err(&client->dev, "sys file creation failed\n"); + goto free_update_fw_sys; + } + + data->dir = debugfs_create_dir(FT_DEBUG_DIR_NAME, NULL); + if (data->dir == NULL || IS_ERR(data->dir)) { + pr_err("debugfs_create_dir failed(%ld)\n", PTR_ERR(data->dir)); + err = PTR_ERR(data->dir); + goto free_force_update_fw_sys; + } + + temp = debugfs_create_file("addr", S_IRUSR | S_IWUSR, data->dir, data, + &debug_addr_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("debugfs_create_file failed: rc=%ld\n", PTR_ERR(temp)); + err = PTR_ERR(temp); + goto free_debug_dir; + } + + temp = debugfs_create_file("data", S_IRUSR | S_IWUSR, data->dir, data, + &debug_data_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("debugfs_create_file failed: rc=%ld\n", PTR_ERR(temp)); + err = PTR_ERR(temp); + goto free_debug_dir; + } + + temp = debugfs_create_file("suspend", S_IRUSR | S_IWUSR, data->dir, + data, &debug_suspend_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("debugfs_create_file failed: rc=%ld\n", PTR_ERR(temp)); + err = PTR_ERR(temp); + goto free_debug_dir; + } + + temp = debugfs_create_file("dump_info", S_IRUSR | S_IWUSR, data->dir, + data, &debug_dump_info_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("debugfs_create_file failed: rc=%ld\n", PTR_ERR(temp)); + err = PTR_ERR(temp); + goto free_debug_dir; + } + + data->ts_info = devm_kzalloc(&client->dev, + FT_INFO_MAX_LEN, GFP_KERNEL); + if (!data->ts_info) + goto free_debug_dir; + + /*get some register information */ + reg_addr = FT_REG_POINT_RATE; + ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); + if (err < 0) + dev_err(&client->dev, "report rate read failed"); + + dev_info(&client->dev, "report rate = %dHz\n", reg_value * 10); + + reg_addr = FT_REG_THGROUP; + err = ft5x06_i2c_read(client, ®_addr, 1, ®_value, 1); + if (err < 0) + dev_err(&client->dev, "threshold read failed"); + + dev_dbg(&client->dev, "touch threshold = %d\n", reg_value * 4); + + /*creation touch panel info kobj*/ + data->ts_info_kobj = kobject_create_and_add(FT_TS_INFO_SYSFS_DIR_NAME, + kernel_kobj); + if (!data->ts_info_kobj) { + dev_err(&client->dev, "kobject creation failed.\n"); + } else { + err = sysfs_create_file(data->ts_info_kobj, &ts_info_attr.attr); + if (err) { + kobject_put(data->ts_info_kobj); + dev_err(&client->dev, "sysfs creation failed.\n"); + } else { + ts_info_buff = devm_kzalloc(&client->dev, + FT_INFO_MAX_LEN, GFP_KERNEL); + if (!ts_info_buff) + goto free_debug_dir; + } + } + + /*Initialize secure touch */ + ft5x06_secure_touch_init(data); + ft5x06_secure_touch_stop(data, true); + mutex_init(&(data->ft_clk_io_ctrl_mutex)); + + /* Creation of secure touch sysfs files */ + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(&client->dev, + "%s: Failed to create sysfs attributes\n", + __func__); + goto free_secure_touch_sysfs; + } + } + + ft5x06_update_fw_ver(data); + ft5x06_update_fw_vendor_id(data); + + FT_STORE_TS_DBG_INFO(data->ts_info, data->family_id, data->pdata->name, + data->pdata->num_max_touches, data->pdata->group_id, + data->pdata->fw_vkey_support ? "yes" : "no", + data->pdata->fw_name, data->fw_ver[0], + data->fw_ver[1], data->fw_ver[2]); + FT_STORE_TS_INFO(ts_info_buff, data->family_id, data->fw_ver[0], + data->fw_ver[1], data->fw_ver[2]); +#if defined(CONFIG_FB) + INIT_WORK(&data->fb_notify_work, fb_notify_resume_work); + data->fb_notif.notifier_call = fb_notifier_callback; + + err = fb_register_client(&data->fb_notif); + + if (err) + dev_err(&client->dev, "Unable to register fb_notifier: %d\n", + err); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + FT_SUSPEND_LEVEL; + data->early_suspend.suspend = ft5x06_ts_early_suspend; + data->early_suspend.resume = ft5x06_ts_late_resume; + register_early_suspend(&data->early_suspend); +#endif + return 0; + +free_secure_touch_sysfs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } +free_debug_dir: + debugfs_remove_recursive(data->dir); +free_force_update_fw_sys: + device_remove_file(&client->dev, &dev_attr_force_update_fw); +free_update_fw_sys: + device_remove_file(&client->dev, &dev_attr_update_fw); +free_fw_name_sys: + device_remove_file(&client->dev, &dev_attr_fw_name); +free_pocket_sys: + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) + device_remove_file(&client->dev, &dev_attr_pocket); +free_enable_sys: + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) + device_remove_file(&client->dev, &dev_attr_enable); +free_gesture_dev: + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) + device_destroy(gesture_pdata->gesture_class, 0); +free_gesture_class: + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) + class_destroy(gesture_pdata->gesture_class); +free_gesture_pdata: + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) { + devm_kfree(&client->dev, gesture_pdata); + data->gesture_pdata = NULL; + } + +free_gpio: + if (gpio_is_valid(pdata->reset_gpio)) + gpio_free(pdata->reset_gpio); + if (gpio_is_valid(pdata->irq_gpio)) + gpio_free(pdata->irq_gpio); +err_gpio_req: + if (data->ts_pinctrl) { + if (IS_ERR_OR_NULL(data->pinctrl_state_release)) { + devm_pinctrl_put(data->ts_pinctrl); + data->ts_pinctrl = NULL; + } else { + err = pinctrl_select_state(data->ts_pinctrl, + data->pinctrl_state_release); + if (err) + pr_err("failed to select relase pinctrl state\n"); + } + } + if (pdata->power_on) + pdata->power_on(false); + else + ft5x06_power_on(data, false); +pwr_deinit: + if (pdata->power_init) + pdata->power_init(false); + else + ft5x06_power_init(data, false); +unreg_inputdev: + input_unregister_device(input_dev); + return err; +} + +static int ft5x06_ts_remove(struct i2c_client *client) +{ + struct ft5x06_ts_data *data = i2c_get_clientdata(client); + int retval, attr_count; + + if (ft5x06_gesture_support_enabled() && data->pdata->gesture_support) { + device_init_wakeup(&client->dev, 0); + device_remove_file(&client->dev, &dev_attr_pocket); + device_remove_file(&client->dev, &dev_attr_enable); + device_destroy(data->gesture_pdata->gesture_class, 0); + class_destroy(data->gesture_pdata->gesture_class); + devm_kfree(&client->dev, data->gesture_pdata); + data->gesture_pdata = NULL; + } + + debugfs_remove_recursive(data->dir); + device_remove_file(&client->dev, &dev_attr_force_update_fw); + device_remove_file(&client->dev, &dev_attr_update_fw); + device_remove_file(&client->dev, &dev_attr_fw_name); + +#if defined(CONFIG_FB) + if (fb_unregister_client(&data->fb_notif)) + dev_err(&client->dev, "Error occurred while unregistering fb_notifier.\n"); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&data->early_suspend); +#endif + free_irq(client->irq, data); + + if (gpio_is_valid(data->pdata->reset_gpio)) + gpio_free(data->pdata->reset_gpio); + + if (gpio_is_valid(data->pdata->irq_gpio)) + gpio_free(data->pdata->irq_gpio); + + if (data->ts_pinctrl) { + if (IS_ERR_OR_NULL(data->pinctrl_state_release)) { + devm_pinctrl_put(data->ts_pinctrl); + data->ts_pinctrl = NULL; + } else { + retval = pinctrl_select_state(data->ts_pinctrl, + data->pinctrl_state_release); + if (retval < 0) + pr_err("failed to select release pinctrl state\n"); + } + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + if (data->pdata->power_on) + data->pdata->power_on(false); + else + ft5x06_power_on(data, false); + + if (data->pdata->power_init) + data->pdata->power_init(false); + else + ft5x06_power_init(data, false); + + input_unregister_device(data->input_dev); + kobject_put(data->ts_info_kobj); + return 0; +} + +static const struct i2c_device_id ft5x06_ts_id[] = { + {"ft5x06_ts", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id); + +#ifdef CONFIG_OF +static const struct of_device_id ft5x06_match_table[] = { + { .compatible = "focaltech,5x06",}, + { }, +}; +#else +#define ft5x06_match_table NULL +#endif + +static struct i2c_driver ft5x06_ts_driver = { + .probe = ft5x06_ts_probe, + .remove = ft5x06_ts_remove, + .driver = { + .name = "ft5x06_ts", + .owner = THIS_MODULE, + .of_match_table = ft5x06_match_table, +#ifdef CONFIG_PM + .pm = &ft5x06_ts_pm_ops, +#endif + }, + .id_table = ft5x06_ts_id, +}; + +static int __init ft5x06_ts_init(void) +{ + return i2c_add_driver(&ft5x06_ts_driver); +} +module_init(ft5x06_ts_init); + +static void __exit ft5x06_ts_exit(void) +{ + i2c_del_driver(&ft5x06_ts_driver); +} +module_exit(ft5x06_ts_exit); + +MODULE_DESCRIPTION("FocalTech ft5x06 TouchScreen driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/gen_vkeys.c b/drivers/input/touchscreen/gen_vkeys.c new file mode 100644 index 000000000000..dde582f4f8bf --- /dev/null +++ b/drivers/input/touchscreen/gen_vkeys.c @@ -0,0 +1,230 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/input.h> +#include <linux/input/gen_vkeys.h> + +#define MAX_BUF_SIZE 256 +#define VKEY_VER_CODE "0x01" + +#define HEIGHT_SCALE_NUM 8 +#define HEIGHT_SCALE_DENOM 10 + +#define VKEY_Y_OFFSET_DEFAULT 0 + +/* numerator and denomenator for border equations */ +#define BORDER_ADJUST_NUM 3 +#define BORDER_ADJUST_DENOM 4 + +static struct kobject *vkey_obj; +static char *vkey_buf; + +static ssize_t vkey_show(struct kobject *obj, + struct kobj_attribute *attr, char *buf) +{ + strlcpy(buf, vkey_buf, MAX_BUF_SIZE); + return strnlen(buf, MAX_BUF_SIZE); +} + +static struct kobj_attribute vkey_obj_attr = { + .attr = { + .mode = S_IRUGO, + }, + .show = vkey_show, +}; + +static struct attribute *vkey_attr[] = { + &vkey_obj_attr.attr, + NULL, +}; + +static struct attribute_group vkey_grp = { + .attrs = vkey_attr, +}; + +static int vkey_parse_dt(struct device *dev, + struct vkeys_platform_data *pdata) +{ + struct device_node *np = dev->of_node; + struct property *prop; + int rc, val; + + rc = of_property_read_string(np, "label", &pdata->name); + if (rc) { + dev_err(dev, "Failed to read label\n"); + return -EINVAL; + } + + rc = of_property_read_u32(np, "qcom,disp-maxx", &pdata->disp_maxx); + if (rc) { + dev_err(dev, "Failed to read display max x\n"); + return -EINVAL; + } + + rc = of_property_read_u32(np, "qcom,disp-maxy", &pdata->disp_maxy); + if (rc) { + dev_err(dev, "Failed to read display max y\n"); + return -EINVAL; + } + + rc = of_property_read_u32(np, "qcom,panel-maxx", &pdata->panel_maxx); + if (rc) { + dev_err(dev, "Failed to read panel max x\n"); + return -EINVAL; + } + + rc = of_property_read_u32(np, "qcom,panel-maxy", &pdata->panel_maxy); + if (rc) { + dev_err(dev, "Failed to read panel max y\n"); + return -EINVAL; + } + + prop = of_find_property(np, "qcom,key-codes", NULL); + if (prop) { + pdata->num_keys = prop->length / sizeof(u32); + pdata->keycodes = devm_kzalloc(dev, + sizeof(u32) * pdata->num_keys, GFP_KERNEL); + if (!pdata->keycodes) + return -ENOMEM; + rc = of_property_read_u32_array(np, "qcom,key-codes", + pdata->keycodes, pdata->num_keys); + if (rc) { + dev_err(dev, "Failed to read key codes\n"); + return -EINVAL; + } + } + + pdata->y_offset = VKEY_Y_OFFSET_DEFAULT; + rc = of_property_read_u32(np, "qcom,y-offset", &val); + if (!rc) + pdata->y_offset = val; + else if (rc != -EINVAL) { + dev_err(dev, "Failed to read y position offset\n"); + return rc; + } + return 0; +} + +static int vkeys_probe(struct platform_device *pdev) +{ + struct vkeys_platform_data *pdata; + int width, height, center_x, center_y; + int x1 = 0, x2 = 0, i, c = 0, ret, border; + char *name; + + vkey_buf = devm_kzalloc(&pdev->dev, MAX_BUF_SIZE, GFP_KERNEL); + if (!vkey_buf) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + if (pdev->dev.of_node) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct vkeys_platform_data), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + ret = vkey_parse_dt(&pdev->dev, pdata); + if (ret) { + dev_err(&pdev->dev, "Parsing DT failed(%d)", ret); + return ret; + } + } else + pdata = pdev->dev.platform_data; + + if (!pdata || !pdata->name || !pdata->keycodes || !pdata->num_keys || + !pdata->disp_maxx || !pdata->disp_maxy || !pdata->panel_maxy) { + dev_err(&pdev->dev, "pdata is invalid\n"); + return -EINVAL; + } + + border = (pdata->panel_maxx - pdata->disp_maxx) * 2; + width = ((pdata->disp_maxx - (border * (pdata->num_keys - 1))) + / pdata->num_keys); + height = (pdata->panel_maxy - pdata->disp_maxy); + center_y = pdata->disp_maxy + (height / 2) + pdata->y_offset; + height = height * HEIGHT_SCALE_NUM / HEIGHT_SCALE_DENOM; + + x2 -= border * BORDER_ADJUST_NUM / BORDER_ADJUST_DENOM; + + for (i = 0; i < pdata->num_keys; i++) { + x1 = x2 + border; + x2 = x2 + border + width; + center_x = x1 + (x2 - x1) / 2; + c += snprintf(vkey_buf + c, MAX_BUF_SIZE - c, + "%s:%d:%d:%d:%d:%d\n", + VKEY_VER_CODE, pdata->keycodes[i], + center_x, center_y, width, height); + } + + vkey_buf[c] = '\0'; + + name = devm_kzalloc(&pdev->dev, sizeof(*name) * MAX_BUF_SIZE, + GFP_KERNEL); + if (!name) + return -ENOMEM; + + snprintf(name, MAX_BUF_SIZE, + "virtualkeys.%s", pdata->name); + vkey_obj_attr.attr.name = name; + + vkey_obj = kobject_create_and_add("board_properties", NULL); + if (!vkey_obj) { + dev_err(&pdev->dev, "unable to create kobject\n"); + return -ENOMEM; + } + + ret = sysfs_create_group(vkey_obj, &vkey_grp); + if (ret) { + dev_err(&pdev->dev, "failed to create attributes\n"); + goto destroy_kobj; + } + return 0; + +destroy_kobj: + kobject_put(vkey_obj); + + return ret; +} + +static int vkeys_remove(struct platform_device *pdev) +{ + sysfs_remove_group(vkey_obj, &vkey_grp); + kobject_put(vkey_obj); + + return 0; +} + +static struct of_device_id vkey_match_table[] = { + { .compatible = "qcom,gen-vkeys",}, + { }, +}; + +static struct platform_driver vkeys_driver = { + .probe = vkeys_probe, + .remove = vkeys_remove, + .driver = { + .owner = THIS_MODULE, + .name = "gen_vkeys", + .of_match_table = vkey_match_table, + }, +}; + +module_platform_driver(vkeys_driver); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/it7258_ts_i2c.c b/drivers/input/touchscreen/it7258_ts_i2c.c new file mode 100644 index 000000000000..1a2afd1c1117 --- /dev/null +++ b/drivers/input/touchscreen/it7258_ts_i2c.c @@ -0,0 +1,2248 @@ +/* drivers/input/touchscreen/it7258_ts_i2c.c + * + * Copyright (C) 2014 ITE Tech. Inc. + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +#include <linux/fb.h> +#include <linux/debugfs.h> +#include <linux/input/mt.h> +#include <linux/string.h> + +#define MAX_BUFFER_SIZE 144 +#define DEVICE_NAME "it7260" +#define SCREEN_X_RESOLUTION 320 +#define SCREEN_Y_RESOLUTION 320 +#define DEBUGFS_DIR_NAME "ts_debug" +#define FW_NAME "it7260_fw.bin" +#define CFG_NAME "it7260_cfg.bin" +#define VER_BUFFER_SIZE 4 +#define IT_FW_CHECK(x, y) \ + (((x)[0] < (y)->data[8]) || ((x)[1] < (y)->data[9]) || \ + ((x)[2] < (y)->data[10]) || ((x)[3] < (y)->data[11])) +#define IT_CFG_CHECK(x, y) \ + (((x)[0] < (y)->data[(y)->size - 8]) || \ + ((x)[1] < (y)->data[(y)->size - 7]) || \ + ((x)[2] < (y)->data[(y)->size - 6]) || \ + ((x)[3] < (y)->data[(y)->size - 5])) +#define IT7260_COORDS_ARR_SIZE 4 + +/* all commands writes go to this idx */ +#define BUF_COMMAND 0x20 +#define BUF_SYS_COMMAND 0x40 +/* + * "device ready?" and "wake up please" and "read touch data" reads + * go to this idx + */ +#define BUF_QUERY 0x80 +/* most command response reads go to this idx */ +#define BUF_RESPONSE 0xA0 +#define BUF_SYS_RESPONSE 0xC0 +/* reads of "point" go through here and produce 14 bytes of data */ +#define BUF_POINT_INFO 0xE0 + +/* + * commands and their subcommands. when no subcommands exist, a zero + * is send as the second byte + */ +#define CMD_IDENT_CHIP 0x00 +/* VERSION_LENGTH bytes of data in response */ +#define CMD_READ_VERSIONS 0x01 +#define SUB_CMD_READ_FIRMWARE_VERSION 0x00 +#define SUB_CMD_READ_CONFIG_VERSION 0x06 +#define VERSION_LENGTH 10 +/* subcommand is zero, next byte is power mode */ +#define CMD_PWR_CTL 0x04 +/* active mode */ +#define PWR_CTL_ACTIVE_MODE 0x00 +/* idle mode */ +#define PWR_CTL_LOW_POWER_MODE 0x01 +/* sleep mode */ +#define PWR_CTL_SLEEP_MODE 0x02 +#define WAIT_CHANGE_MODE 20 +/* command is not documented in the datasheet v1.0.0.7 */ +#define CMD_UNKNOWN_7 0x07 +#define CMD_FIRMWARE_REINIT_C 0x0C +/* needs to be followed by 4 bytes of zeroes */ +#define CMD_CALIBRATE 0x13 +#define CMD_FIRMWARE_UPGRADE 0x60 +#define SUB_CMD_ENTER_FW_UPGRADE_MODE 0x00 +#define SUB_CMD_EXIT_FW_UPGRADE_MODE 0x80 +/* address for FW read/write */ +#define CMD_SET_START_OFFSET 0x61 +/* subcommand is number of bytes to write */ +#define CMD_FW_WRITE 0x62 +/* subcommand is number of bytes to read */ +#define CMD_FW_READ 0x63 +#define CMD_FIRMWARE_REINIT_6F 0x6F + +#define FW_WRITE_CHUNK_SIZE 128 +#define FW_WRITE_RETRY_COUNT 4 +#define CHIP_FLASH_SIZE 0x8000 +#define DEVICE_READY_COUNT_MAX 500 +#define DEVICE_READY_COUNT_20 20 +#define IT_I2C_WAIT_10MS 10 +#define IT_I2C_READ_RET 2 +#define IT_I2C_WRITE_RET 1 + +/* result of reading with BUF_QUERY bits */ +#define CMD_STATUS_BITS 0x07 +#define CMD_STATUS_DONE 0x00 +#define CMD_STATUS_BUSY 0x01 +#define CMD_STATUS_ERROR 0x02 +#define CMD_STATUS_NO_CONN 0x07 +#define PT_INFO_BITS 0xF8 +#define PT_INFO_YES 0x80 + +#define PD_FLAGS_DATA_TYPE_BITS 0xF0 +/* other types (like chip-detected gestures) exist but we do not care */ +#define PD_FLAGS_DATA_TYPE_TOUCH 0x00 +#define PD_FLAGS_IDLE_TO_ACTIVE 0x10 +/* a bit for each finger data that is valid (from lsb to msb) */ +#define PD_FLAGS_HAVE_FINGERS 0x07 +#define PD_PALM_FLAG_BIT 0x01 +#define FD_PRESSURE_BITS 0x0F +#define FD_PRESSURE_NONE 0x00 +#define FD_PRESSURE_LIGHT 0x02 + +#define IT_VTG_MIN_UV 1800000 +#define IT_VTG_MAX_UV 1800000 +#define IT_ACTIVE_LOAD_UA 15000 +#define IT_I2C_VTG_MIN_UV 2600000 +#define IT_I2C_VTG_MAX_UV 3300000 +#define IT_I2C_ACTIVE_LOAD_UA 10000 +#define DELAY_VTG_REG_EN 170 + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +struct finger_data { + u8 xLo; + u8 hi; + u8 yLo; + u8 pressure; +} __packed; + +struct point_data { + u8 flags; + u8 gesture_id; + struct finger_data fd[3]; +} __packed; + +struct it7260_ts_platform_data { + u32 irq_gpio; + u32 irq_gpio_flags; + u32 reset_gpio; + u32 reset_gpio_flags; + bool wakeup; + bool palm_detect_en; + u16 palm_detect_keycode; + const char *fw_name; + const char *cfg_name; + unsigned int panel_minx; + unsigned int panel_miny; + unsigned int panel_maxx; + unsigned int panel_maxy; + unsigned int disp_minx; + unsigned int disp_miny; + unsigned int disp_maxx; + unsigned int disp_maxy; + unsigned num_of_fingers; + unsigned int reset_delay; + unsigned int avdd_lpm_cur; + bool low_reset; +}; + +struct it7260_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + const struct it7260_ts_platform_data *pdata; + struct regulator *vdd; + struct regulator *avdd; + bool in_low_power_mode; + bool suspended; + bool fw_upgrade_result; + bool cfg_upgrade_result; + bool fw_cfg_uploading; + struct work_struct work_pm_relax; + bool calibration_success; + bool had_finger_down; + char fw_name[MAX_BUFFER_SIZE]; + char cfg_name[MAX_BUFFER_SIZE]; + struct mutex fw_cfg_mutex; + u8 fw_ver[VER_BUFFER_SIZE]; + u8 cfg_ver[VER_BUFFER_SIZE]; +#ifdef CONFIG_FB + struct notifier_block fb_notif; +#endif + struct dentry *dir; + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; +}; + +/* Function declarations */ +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data); +static int it7260_ts_resume(struct device *dev); +static int it7260_ts_suspend(struct device *dev); + +static int it7260_debug_suspend_set(void *_data, u64 val) +{ + struct it7260_ts_data *ts_data = _data; + + if (val) + it7260_ts_suspend(&ts_data->client->dev); + else + it7260_ts_resume(&ts_data->client->dev); + + return 0; +} + +static int it7260_debug_suspend_get(void *_data, u64 *val) +{ + struct it7260_ts_data *ts_data = _data; + + mutex_lock(&ts_data->input_dev->mutex); + *val = ts_data->suspended; + mutex_lock(&ts_data->input_dev->mutex); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(debug_suspend_fops, it7260_debug_suspend_get, + it7260_debug_suspend_set, "%lld\n"); + +/* internal use func - does not make sure chip is ready before read */ +static int it7260_i2c_read_no_ready_check(struct it7260_ts_data *ts_data, + uint8_t buf_index, uint8_t *buffer, uint16_t buf_len) +{ + int ret; + struct i2c_msg msgs[2] = { + { + .addr = ts_data->client->addr, + .flags = I2C_M_NOSTART, + .len = 1, + .buf = &buf_index + }, + { + .addr = ts_data->client->addr, + .flags = I2C_M_RD, + .len = buf_len, + .buf = buffer + } + }; + + memset(buffer, 0xFF, buf_len); + + ret = i2c_transfer(ts_data->client->adapter, msgs, 2); + if (ret < 0) + dev_err(&ts_data->client->dev, "i2c read failed %d\n", ret); + + return ret; +} + +static int it7260_i2c_write_no_ready_check(struct it7260_ts_data *ts_data, + uint8_t buf_index, const uint8_t *buffer, uint16_t buf_len) +{ + uint8_t txbuf[257]; + int ret; + struct i2c_msg msg = { + .addr = ts_data->client->addr, + .flags = 0, + .len = buf_len + 1, + .buf = txbuf + }; + + /* just to be careful */ + if (buf_len > sizeof(txbuf) - 1) { + dev_err(&ts_data->client->dev, "buf length is out of limit\n"); + return false; + } + + txbuf[0] = buf_index; + memcpy(txbuf + 1, buffer, buf_len); + + ret = i2c_transfer(ts_data->client->adapter, &msg, 1); + if (ret < 0) + dev_err(&ts_data->client->dev, "i2c write failed %d\n", ret); + + return ret; +} + +/* + * Device is apparently always ready for I2C communication but not for + * actual register reads/writes. This function checks if it is ready + * for that too. The results of this call often were ignored. + * If forever is set to TRUE, then check the device's status until it + * becomes ready with 500 retries at max. Otherwise retry 25 times only. + * If slowly is set to TRUE, then add sleep of 50 ms in each retry, + * otherwise don't sleep. + */ +static int it7260_wait_device_ready(struct it7260_ts_data *ts_data, + bool forever, bool slowly) +{ + uint8_t query; + uint32_t count = DEVICE_READY_COUNT_20; + int ret; + + if (ts_data->fw_cfg_uploading || forever) + count = DEVICE_READY_COUNT_MAX; + + do { + ret = it7260_i2c_read_no_ready_check(ts_data, BUF_QUERY, &query, + sizeof(query)); + if (ret < 0 && ((query & CMD_STATUS_BITS) + == CMD_STATUS_NO_CONN)) + continue; + + if ((query & CMD_STATUS_BITS) == CMD_STATUS_DONE) + break; + + query = CMD_STATUS_BUSY; + if (slowly) + msleep(IT_I2C_WAIT_10MS); + } while (--count); + + return ((!(query & CMD_STATUS_BITS)) ? 0 : -ENODEV); +} + +static int it7260_i2c_read(struct it7260_ts_data *ts_data, uint8_t buf_index, + uint8_t *buffer, uint16_t buf_len) +{ + int ret; + + ret = it7260_wait_device_ready(ts_data, false, false); + if (ret < 0) + return ret; + + return it7260_i2c_read_no_ready_check(ts_data, buf_index, + buffer, buf_len); +} + +static int it7260_i2c_write(struct it7260_ts_data *ts_data, uint8_t buf_index, + const uint8_t *buffer, uint16_t buf_len) +{ + int ret; + + ret = it7260_wait_device_ready(ts_data, false, false); + if (ret < 0) + return ret; + + return it7260_i2c_write_no_ready_check(ts_data, buf_index, + buffer, buf_len); +} + +static int it7260_firmware_reinitialize(struct it7260_ts_data *ts_data, + u8 command) +{ + uint8_t cmd[] = {command}; + uint8_t rsp[2]; + int ret; + + ret = it7260_i2c_write(ts_data, BUF_COMMAND, cmd, sizeof(cmd)); + if (ret != IT_I2C_WRITE_RET) { + dev_err(&ts_data->client->dev, + "failed to write fw reinit command %d\n", ret); + return ret; + } + + ret = it7260_i2c_read(ts_data, BUF_RESPONSE, rsp, sizeof(rsp)); + if (ret != IT_I2C_READ_RET) { + dev_err(&ts_data->client->dev, + "failed to read any response from chip %d\n", ret); + return ret; + } + + /* a reply of two zero bytes signifies success */ + if (rsp[0] == 0 && rsp[1] == 0) + return 0; + else + return -EIO; +} + +static int it7260_enter_exit_fw_ugrade_mode(struct it7260_ts_data *ts_data, + bool enter) +{ + uint8_t cmd[] = {CMD_FIRMWARE_UPGRADE, 0, 'I', 'T', '7', '2', + '6', '0', 0x55, 0xAA}; + uint8_t resp[2]; + int ret; + + cmd[1] = enter ? SUB_CMD_ENTER_FW_UPGRADE_MODE : + SUB_CMD_EXIT_FW_UPGRADE_MODE; + + ret = it7260_i2c_write(ts_data, BUF_COMMAND, cmd, sizeof(cmd)); + if (ret != IT_I2C_WRITE_RET) { + dev_err(&ts_data->client->dev, + "failed to write CMD_FIRMWARE_UPGRADE %d\n", ret); + return ret; + } + + ret = it7260_i2c_read(ts_data, BUF_RESPONSE, resp, sizeof(resp)); + if (ret != IT_I2C_READ_RET) { + dev_err(&ts_data->client->dev, + "failed to read any response from chip %d\n", ret); + return ret; + } + + /* a reply of two zero bytes signifies success */ + if (resp[0] == 0 && resp[1] == 0) + return 0; + else + return -EIO; +} + +static int it7260_set_start_offset(struct it7260_ts_data *ts_data, + uint16_t offset) +{ + uint8_t cmd[] = {CMD_SET_START_OFFSET, 0, ((uint8_t)(offset)), + ((uint8_t)((offset) >> 8))}; + uint8_t resp[2]; + int ret; + + ret = it7260_i2c_write(ts_data, BUF_COMMAND, cmd, 4); + if (ret != IT_I2C_WRITE_RET) { + dev_err(&ts_data->client->dev, + "failed to write CMD_SET_START_OFFSET %d\n", ret); + return ret; + } + + + ret = it7260_i2c_read(ts_data, BUF_RESPONSE, resp, sizeof(resp)); + if (ret != IT_I2C_READ_RET) { + dev_err(&ts_data->client->dev, + "failed to read any response from chip %d\n", ret); + return ret; + } + + /* a reply of two zero bytes signifies success */ + if (resp[0] == 0 && resp[1] == 0) + return 0; + else + return -EIO; +} + + +/* write fw_length bytes from fw_data at chip offset wr_start_offset */ +static int it7260_fw_flash_write_verify(struct it7260_ts_data *ts_data, + unsigned int fw_length, const uint8_t *fw_data, + uint16_t wr_start_offset) +{ + uint32_t cur_data_off; + + for (cur_data_off = 0; cur_data_off < fw_length; + cur_data_off += FW_WRITE_CHUNK_SIZE) { + + uint8_t cmd_write[2 + FW_WRITE_CHUNK_SIZE] = {CMD_FW_WRITE}; + uint8_t buf_read[FW_WRITE_CHUNK_SIZE]; + uint8_t cmd_read[2] = {CMD_FW_READ}; + unsigned i, retries; + uint32_t cur_wr_size; + + /* figure out how much to write */ + cur_wr_size = fw_length - cur_data_off; + if (cur_wr_size > FW_WRITE_CHUNK_SIZE) + cur_wr_size = FW_WRITE_CHUNK_SIZE; + + /* prepare the write command */ + cmd_write[1] = cur_wr_size; + for (i = 0; i < cur_wr_size; i++) + cmd_write[i + 2] = fw_data[cur_data_off + i]; + + /* prepare the read command */ + cmd_read[1] = cur_wr_size; + + for (retries = 0; retries < FW_WRITE_RETRY_COUNT; + retries++) { + + /* set write offset and write the data */ + it7260_set_start_offset(ts_data, + wr_start_offset + cur_data_off); + it7260_i2c_write(ts_data, BUF_COMMAND, cmd_write, + cur_wr_size + 2); + + /* set offset and read the data back */ + it7260_set_start_offset(ts_data, + wr_start_offset + cur_data_off); + it7260_i2c_write(ts_data, BUF_COMMAND, cmd_read, + sizeof(cmd_read)); + it7260_i2c_read(ts_data, BUF_RESPONSE, buf_read, + cur_wr_size); + + /* verify. If success break out of retry loop */ + i = 0; + while (i < cur_wr_size && + buf_read[i] == cmd_write[i + 2]) + i++; + if (i == cur_wr_size) + break; + } + /* if we've failed after all the retries, tell the caller */ + if (retries == FW_WRITE_RETRY_COUNT) { + dev_err(&ts_data->client->dev, + "write of data offset %u failed on try %u at byte %u/%u\n", + cur_data_off, retries, i, cur_wr_size); + return -EIO; + } + } + + return 0; +} + +/* + * this code to get versions from the chip via i2c transactions, and save + * them in driver data structure. + */ +static void it7260_get_chip_versions(struct it7260_ts_data *ts_data) +{ + static const u8 cmd_read_fw_ver[] = {CMD_READ_VERSIONS, + SUB_CMD_READ_FIRMWARE_VERSION}; + static const u8 cmd_read_cfg_ver[] = {CMD_READ_VERSIONS, + SUB_CMD_READ_CONFIG_VERSION}; + u8 ver_fw[VERSION_LENGTH], ver_cfg[VERSION_LENGTH]; + int ret; + + ret = it7260_i2c_write(ts_data, BUF_COMMAND, cmd_read_fw_ver, + sizeof(cmd_read_fw_ver)); + if (ret == IT_I2C_WRITE_RET) { + /* + * Sometimes, the controller may not respond immediately after + * writing the command, so wait for device to get ready. + */ + ret = it7260_wait_device_ready(ts_data, true, false); + if (ret < 0) + dev_err(&ts_data->client->dev, + "failed to read chip status %d\n", ret); + + ret = it7260_i2c_read_no_ready_check(ts_data, BUF_RESPONSE, + ver_fw, VERSION_LENGTH); + if (ret == IT_I2C_READ_RET) + memcpy(ts_data->fw_ver, ver_fw + (5 * sizeof(u8)), + VER_BUFFER_SIZE * sizeof(u8)); + else + dev_err(&ts_data->client->dev, + "failed to read fw-ver from chip %d\n", ret); + } else { + dev_err(&ts_data->client->dev, + "failed to write fw-read command %d\n", ret); + } + + ret = it7260_i2c_write(ts_data, BUF_COMMAND, cmd_read_cfg_ver, + sizeof(cmd_read_cfg_ver)); + if (ret == IT_I2C_WRITE_RET) { + /* + * Sometimes, the controller may not respond immediately after + * writing the command, so wait for device to get ready. + */ + ret = it7260_wait_device_ready(ts_data, true, false); + if (ret < 0) + dev_err(&ts_data->client->dev, + "failed to read chip status %d\n", ret); + + ret = it7260_i2c_read_no_ready_check(ts_data, BUF_RESPONSE, + ver_cfg, VERSION_LENGTH); + if (ret == IT_I2C_READ_RET) + memcpy(ts_data->cfg_ver, ver_cfg + (1 * sizeof(u8)), + VER_BUFFER_SIZE * sizeof(u8)); + else + dev_err(&ts_data->client->dev, + "failed to read cfg-ver from chip %d\n", ret); + } else { + dev_err(&ts_data->client->dev, + "failed to write cfg-read command %d\n", ret); + } + + dev_info(&ts_data->client->dev, "Current fw{%X.%X.%X.%X} cfg{%X.%X.%X.%X}\n", + ts_data->fw_ver[0], ts_data->fw_ver[1], ts_data->fw_ver[2], + ts_data->fw_ver[3], ts_data->cfg_ver[0], ts_data->cfg_ver[1], + ts_data->cfg_ver[2], ts_data->cfg_ver[3]); +} + +static int it7260_cfg_upload(struct it7260_ts_data *ts_data, bool force) +{ + const struct firmware *cfg = NULL; + int ret; + bool cfg_upgrade = false; + struct device *dev = &ts_data->client->dev; + + ret = request_firmware(&cfg, ts_data->cfg_name, dev); + if (ret) { + dev_err(dev, "failed to get config data %s for it7260 %d\n", + ts_data->cfg_name, ret); + return ret; + } + + /* + * This compares the cfg version number from chip and the cfg + * data file. IT flashes only when version of cfg data file is + * greater than that of chip or if it is set for force cfg upgrade. + */ + if (force) + cfg_upgrade = true; + else if (IT_CFG_CHECK(ts_data->cfg_ver, cfg)) + cfg_upgrade = true; + + if (!cfg_upgrade) { + dev_err(dev, "CFG upgrade not required ...\n"); + dev_info(dev, + "Chip CFG : %X.%X.%X.%X Binary CFG : %X.%X.%X.%X\n", + ts_data->cfg_ver[0], ts_data->cfg_ver[1], + ts_data->cfg_ver[2], ts_data->cfg_ver[3], + cfg->data[cfg->size - 8], cfg->data[cfg->size - 7], + cfg->data[cfg->size - 6], cfg->data[cfg->size - 5]); + ret = -EFAULT; + goto out; + } else { + dev_info(dev, "Config upgrading...\n"); + + disable_irq(ts_data->client->irq); + /* enter cfg upload mode */ + ret = it7260_enter_exit_fw_ugrade_mode(ts_data, true); + if (ret < 0) { + dev_err(dev, "Can't enter cfg upgrade mode %d\n", ret); + enable_irq(ts_data->client->irq); + goto out; + } + /* flash config data if requested */ + ret = it7260_fw_flash_write_verify(ts_data, cfg->size, + cfg->data, CHIP_FLASH_SIZE - cfg->size); + if (ret < 0) { + dev_err(dev, + "failed to upgrade touch cfg data %d\n", ret); + ret = it7260_enter_exit_fw_ugrade_mode(ts_data, false); + if (ret < 0) + dev_err(dev, + "Can't exit cfg upgrade mode%d\n", ret); + + ret = it7260_firmware_reinitialize(ts_data, + CMD_FIRMWARE_REINIT_6F); + if (ret < 0) + dev_err(dev, "Can't reinit cfg %d\n", ret); + + ret = -EIO; + enable_irq(ts_data->client->irq); + goto out; + } else { + memcpy(ts_data->cfg_ver, cfg->data + + (cfg->size - 8 * sizeof(u8)), + VER_BUFFER_SIZE * sizeof(u8)); + dev_info(dev, "CFG upgrade is success. New cfg ver: %X.%X.%X.%X\n", + ts_data->cfg_ver[0], ts_data->cfg_ver[1], + ts_data->cfg_ver[2], ts_data->cfg_ver[3]); + + } + enable_irq(ts_data->client->irq); + } + +out: + release_firmware(cfg); + + return ret; +} + +static int it7260_fw_upload(struct it7260_ts_data *ts_data, bool force) +{ + const struct firmware *fw = NULL; + int ret; + bool fw_upgrade = false; + struct device *dev = &ts_data->client->dev; + + ret = request_firmware(&fw, ts_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to get firmware %s for it7260 %d\n", + ts_data->fw_name, ret); + return ret; + } + + /* + * This compares the fw version number from chip and the fw data + * file. It flashes only when version of fw data file is greater + * than that of chip or it it is set for force fw upgrade. + */ + if (force) + fw_upgrade = true; + else if (IT_FW_CHECK(ts_data->fw_ver, fw)) + fw_upgrade = true; + + if (!fw_upgrade) { + dev_err(dev, "FW upgrade not required ...\n"); + dev_info(dev, "Chip FW : %X.%X.%X.%X Binary FW : %X.%X.%X.%X\n", + ts_data->fw_ver[0], ts_data->fw_ver[1], + ts_data->fw_ver[2], ts_data->fw_ver[3], + fw->data[8], fw->data[9], fw->data[10], fw->data[11]); + ret = -EFAULT; + goto out; + } else { + dev_info(dev, "Firmware upgrading...\n"); + + disable_irq(ts_data->client->irq); + /* enter fw upload mode */ + ret = it7260_enter_exit_fw_ugrade_mode(ts_data, true); + if (ret < 0) { + dev_err(dev, "Can't enter fw upgrade mode %d\n", ret); + enable_irq(ts_data->client->irq); + goto out; + } + /* flash the firmware if requested */ + ret = it7260_fw_flash_write_verify(ts_data, fw->size, + fw->data, 0); + if (ret < 0) { + dev_err(dev, + "failed to upgrade touch firmware %d\n", ret); + ret = it7260_enter_exit_fw_ugrade_mode(ts_data, false); + if (ret < 0) + dev_err(dev, + "Can't exit fw upgrade mode %d\n", ret); + + ret = it7260_firmware_reinitialize(ts_data, + CMD_FIRMWARE_REINIT_6F); + if (ret < 0) + dev_err(dev, "Can't reinit firmware %d\n", ret); + + ret = -EIO; + enable_irq(ts_data->client->irq); + goto out; + } else { + memcpy(ts_data->fw_ver, fw->data + (8 * sizeof(u8)), + VER_BUFFER_SIZE * sizeof(u8)); + dev_info(dev, "FW upgrade is success. New fw ver: %X.%X.%X.%X\n", + ts_data->fw_ver[0], ts_data->fw_ver[1], + ts_data->fw_ver[2], ts_data->fw_ver[3]); + } + enable_irq(ts_data->client->irq); + } + +out: + release_firmware(fw); + + return ret; +} + +static int it7260_ts_chip_low_power_mode(struct it7260_ts_data *ts_data, + const u8 sleep_type) +{ + const u8 cmd_sleep[] = {CMD_PWR_CTL, 0x00, sleep_type}; + u8 dummy; + int ret; + + if (sleep_type) { + ret = it7260_i2c_write_no_ready_check(ts_data, BUF_COMMAND, + cmd_sleep, sizeof(cmd_sleep)); + if (ret != IT_I2C_WRITE_RET) + dev_err(&ts_data->client->dev, + "Can't go to sleep or low power mode(%d) %d\n", + sleep_type, ret); + else + ret = 0; + } else { + ret = it7260_i2c_read_no_ready_check(ts_data, BUF_QUERY, &dummy, + sizeof(dummy)); + if (ret != IT_I2C_READ_RET) + dev_err(&ts_data->client->dev, + "Can't go to active mode %d\n", ret); + else + ret = 0; + } + + msleep(WAIT_CHANGE_MODE); + return ret; +} + +static ssize_t sysfs_fw_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + int mode = 0, ret; + + if (ts_data->suspended) { + dev_err(dev, "Device is suspended, can't flash fw!!!\n"); + return -EBUSY; + } + + ret = kstrtoint(buf, 10, &mode); + if (ret) { + dev_err(dev, "failed to read input for sysfs\n"); + return -EINVAL; + } + + mutex_lock(&ts_data->fw_cfg_mutex); + if (mode == 1) { + ts_data->fw_cfg_uploading = true; + ret = it7260_fw_upload(ts_data, false); + if (ret) { + dev_err(dev, "Failed to flash fw: %d", ret); + ts_data->fw_upgrade_result = false; + } else { + ts_data->fw_upgrade_result = true; + } + ts_data->fw_cfg_uploading = false; + } + mutex_unlock(&ts_data->fw_cfg_mutex); + + return count; +} + +static ssize_t sysfs_cfg_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + int mode = 0, ret; + + if (ts_data->suspended) { + dev_err(dev, "Device is suspended, can't flash cfg!!!\n"); + return -EBUSY; + } + + ret = kstrtoint(buf, 10, &mode); + if (ret) { + dev_err(dev, "failed to read input for sysfs\n"); + return -EINVAL; + } + + mutex_lock(&ts_data->fw_cfg_mutex); + if (mode == 1) { + ts_data->fw_cfg_uploading = true; + ret = it7260_cfg_upload(ts_data, false); + if (ret) { + dev_err(dev, "Failed to flash cfg: %d", ret); + ts_data->cfg_upgrade_result = false; + } else { + ts_data->cfg_upgrade_result = true; + } + ts_data->fw_cfg_uploading = false; + } + mutex_unlock(&ts_data->fw_cfg_mutex); + + return count; +} + +static ssize_t sysfs_fw_upgrade_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + return scnprintf(buf, MAX_BUFFER_SIZE, "%d\n", + ts_data->fw_upgrade_result); +} + +static ssize_t sysfs_cfg_upgrade_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + return scnprintf(buf, MAX_BUFFER_SIZE, "%d\n", + ts_data->cfg_upgrade_result); +} + +static ssize_t sysfs_force_fw_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + int mode = 0, ret; + + if (ts_data->suspended) { + dev_err(dev, "Device is suspended, can't flash fw!!!\n"); + return -EBUSY; + } + + ret = kstrtoint(buf, 10, &mode); + if (ret) { + dev_err(dev, "failed to read input for sysfs\n"); + return -EINVAL; + } + + mutex_lock(&ts_data->fw_cfg_mutex); + if (mode == 1) { + ts_data->fw_cfg_uploading = true; + ret = it7260_fw_upload(ts_data, true); + if (ret) { + dev_err(dev, "Failed to force flash fw: %d", ret); + ts_data->fw_upgrade_result = false; + } else { + ts_data->fw_upgrade_result = true; + } + ts_data->fw_cfg_uploading = false; + } + mutex_unlock(&ts_data->fw_cfg_mutex); + + return count; +} + +static ssize_t sysfs_force_cfg_upgrade_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + int mode = 0, ret; + + if (ts_data->suspended) { + dev_err(dev, "Device is suspended, can't flash cfg!!!\n"); + return -EBUSY; + } + + ret = kstrtoint(buf, 10, &mode); + if (ret) { + dev_err(dev, "failed to read input for sysfs\n"); + return -EINVAL; + } + + mutex_lock(&ts_data->fw_cfg_mutex); + if (mode == 1) { + ts_data->fw_cfg_uploading = true; + ret = it7260_cfg_upload(ts_data, true); + if (ret) { + dev_err(dev, "Failed to force flash cfg: %d", ret); + ts_data->cfg_upgrade_result = false; + } else { + ts_data->cfg_upgrade_result = true; + } + ts_data->fw_cfg_uploading = false; + } + mutex_unlock(&ts_data->fw_cfg_mutex); + + return count; +} + +static ssize_t sysfs_force_fw_upgrade_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + return snprintf(buf, MAX_BUFFER_SIZE, "%d", ts_data->fw_upgrade_result); +} + +static ssize_t sysfs_force_cfg_upgrade_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + return snprintf(buf, MAX_BUFFER_SIZE, "%d", + ts_data->cfg_upgrade_result); +} + +static ssize_t sysfs_calibration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + return scnprintf(buf, MAX_BUFFER_SIZE, "%d\n", + ts_data->calibration_success); +} + +static int it7260_ts_send_calibration_cmd(struct it7260_ts_data *ts_data, + bool auto_tune_on) +{ + uint8_t cmd_calibrate[] = {CMD_CALIBRATE, 0, + auto_tune_on ? 1 : 0, 0, 0}; + + return it7260_i2c_write(ts_data, BUF_COMMAND, cmd_calibrate, + sizeof(cmd_calibrate)); +} + +static ssize_t sysfs_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + uint8_t resp; + int ret; + + ret = it7260_ts_send_calibration_cmd(ts_data, false); + if (ret < 0) { + dev_err(dev, "failed to send calibration command\n"); + } else { + ret = it7260_i2c_read(ts_data, BUF_RESPONSE, &resp, + sizeof(resp)); + if (ret == IT_I2C_READ_RET) + ts_data->calibration_success = true; + + /* + * previous logic that was here never called + * it7260_firmware_reinitialize() due to checking a + * guaranteed-not-null value against null. We now + * call it. Hopefully this is OK + */ + if (!resp) + dev_dbg(dev, "it7260_firmware_reinitialize-> %s\n", + it7260_firmware_reinitialize(ts_data, + CMD_FIRMWARE_REINIT_6F) ? "success" : "fail"); + } + + return count; +} + +static ssize_t sysfs_point_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + uint8_t pt_data[sizeof(struct point_data)]; + int readSuccess; + ssize_t ret; + + readSuccess = it7260_i2c_read_no_ready_check(ts_data, BUF_POINT_INFO, + pt_data, sizeof(pt_data)); + + if (readSuccess == IT_I2C_READ_RET) { + ret = scnprintf(buf, MAX_BUFFER_SIZE, + "point_show read ret[%d]--point[%x][%x][%x][%x][%x][%x][%x][%x][%x][%x][%x][%x][%x][%x]\n", + readSuccess, pt_data[0], pt_data[1], + pt_data[2], pt_data[3], pt_data[4], + pt_data[5], pt_data[6], pt_data[7], + pt_data[8], pt_data[9], pt_data[10], + pt_data[11], pt_data[12], pt_data[13]); + } else { + ret = scnprintf(buf, MAX_BUFFER_SIZE, + "failed to read point data\n"); + } + dev_dbg(dev, "%s", buf); + + return ret; +} + +static ssize_t sysfs_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + return scnprintf(buf, MAX_BUFFER_SIZE, + "fw{%X.%X.%X.%X} cfg{%X.%X.%X.%X}\n", + ts_data->fw_ver[0], ts_data->fw_ver[1], + ts_data->fw_ver[2], ts_data->fw_ver[3], + ts_data->cfg_ver[0], ts_data->cfg_ver[1], + ts_data->cfg_ver[2], ts_data->cfg_ver[3]); +} + +static ssize_t sysfs_sleep_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + /* + * The usefulness of this was questionable at best - we were at least + * leaking a byte of kernel data (by claiming to return a byte but not + * writing to buf. To fix this now we actually return the sleep status + */ + *buf = ts_data->suspended ? '1' : '0'; + + return 1; +} + +static ssize_t sysfs_sleep_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + int go_to_sleep, ret; + + ret = kstrtoint(buf, 10, &go_to_sleep); + + /* (ts_data->suspended == true && goToSleepVal > 0) means + * device is already suspended and you want it to be in sleep, + * (ts_data->suspended == false && goToSleepVal == 0) means + * device is already active and you also want it to be active. + */ + if ((ts_data->suspended && go_to_sleep > 0) || + (!ts_data->suspended && go_to_sleep == 0)) + dev_err(dev, "duplicate request to %s chip\n", + go_to_sleep ? "sleep" : "wake"); + else if (go_to_sleep) { + disable_irq(ts_data->client->irq); + it7260_ts_chip_low_power_mode(ts_data, PWR_CTL_SLEEP_MODE); + dev_dbg(dev, "touch is going to sleep...\n"); + } else { + it7260_ts_chip_low_power_mode(ts_data, PWR_CTL_ACTIVE_MODE); + enable_irq(ts_data->client->irq); + dev_dbg(dev, "touch is going to wake!\n"); + } + ts_data->suspended = go_to_sleep; + + return count; +} + +static ssize_t sysfs_cfg_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + char *strptr; + + if (count >= MAX_BUFFER_SIZE) { + dev_err(dev, "Input over %d chars long\n", MAX_BUFFER_SIZE); + return -EINVAL; + } + + strptr = strnstr(buf, ".bin", count); + if (!strptr) { + dev_err(dev, "Input is invalid cfg file\n"); + return -EINVAL; + } + + strlcpy(ts_data->cfg_name, buf, count); + + return count; +} + +static ssize_t sysfs_cfg_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + if (strnlen(ts_data->cfg_name, MAX_BUFFER_SIZE) > 0) + return scnprintf(buf, MAX_BUFFER_SIZE, "%s\n", + ts_data->cfg_name); + else + return scnprintf(buf, MAX_BUFFER_SIZE, + "No config file name given\n"); +} + +static ssize_t sysfs_fw_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + char *strptr; + + if (count >= MAX_BUFFER_SIZE) { + dev_err(dev, "Input over %d chars long\n", MAX_BUFFER_SIZE); + return -EINVAL; + } + + strptr = strnstr(buf, ".bin", count); + if (!strptr) { + dev_err(dev, "Input is invalid fw file\n"); + return -EINVAL; + } + + strlcpy(ts_data->fw_name, buf, count); + return count; +} + +static ssize_t sysfs_fw_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + + if (strnlen(ts_data->fw_name, MAX_BUFFER_SIZE) > 0) + return scnprintf(buf, MAX_BUFFER_SIZE, "%s\n", + ts_data->fw_name); + else + return scnprintf(buf, MAX_BUFFER_SIZE, + "No firmware file name given\n"); +} + +static DEVICE_ATTR(version, S_IRUGO | S_IWUSR, + sysfs_version_show, NULL); +static DEVICE_ATTR(sleep, S_IRUGO | S_IWUSR, + sysfs_sleep_show, sysfs_sleep_store); +static DEVICE_ATTR(calibration, S_IRUGO | S_IWUSR, + sysfs_calibration_show, sysfs_calibration_store); +static DEVICE_ATTR(fw_update, S_IRUGO | S_IWUSR, + sysfs_fw_upgrade_show, sysfs_fw_upgrade_store); +static DEVICE_ATTR(cfg_update, S_IRUGO | S_IWUSR, + sysfs_cfg_upgrade_show, sysfs_cfg_upgrade_store); +static DEVICE_ATTR(point, S_IRUGO | S_IWUSR, + sysfs_point_show, NULL); +static DEVICE_ATTR(fw_name, S_IRUGO | S_IWUSR, + sysfs_fw_name_show, sysfs_fw_name_store); +static DEVICE_ATTR(cfg_name, S_IRUGO | S_IWUSR, + sysfs_cfg_name_show, sysfs_cfg_name_store); +static DEVICE_ATTR(force_fw_update, S_IRUGO | S_IWUSR, + sysfs_force_fw_upgrade_show, + sysfs_force_fw_upgrade_store); +static DEVICE_ATTR(force_cfg_update, S_IRUGO | S_IWUSR, + sysfs_force_cfg_upgrade_show, + sysfs_force_cfg_upgrade_store); + +static struct attribute *it7260_attributes[] = { + &dev_attr_version.attr, + &dev_attr_sleep.attr, + &dev_attr_calibration.attr, + &dev_attr_fw_update.attr, + &dev_attr_cfg_update.attr, + &dev_attr_point.attr, + &dev_attr_fw_name.attr, + &dev_attr_cfg_name.attr, + &dev_attr_force_fw_update.attr, + &dev_attr_force_cfg_update.attr, + NULL +}; + +static const struct attribute_group it7260_attr_group = { + .attrs = it7260_attributes, +}; + +static void it7260_ts_release_all(struct it7260_ts_data *ts_data) +{ + int finger; + + for (finger = 0; finger < ts_data->pdata->num_of_fingers; finger++) { + input_mt_slot(ts_data->input_dev, finger); + input_mt_report_slot_state(ts_data->input_dev, + MT_TOOL_FINGER, 0); + } + + input_report_key(ts_data->input_dev, BTN_TOUCH, 0); + input_sync(ts_data->input_dev); +} + +static irqreturn_t it7260_ts_threaded_handler(int irq, void *devid) +{ + struct point_data pt_data; + struct it7260_ts_data *ts_data = devid; + struct input_dev *input_dev = ts_data->input_dev; + u8 dev_status, finger, touch_count = 0, finger_status; + u8 pressure = FD_PRESSURE_NONE; + u16 x, y; + bool palm_detected; + int ret; + + /* verify there is point data to read & it is readable and valid */ + ret = it7260_i2c_read_no_ready_check(ts_data, BUF_QUERY, &dev_status, + sizeof(dev_status)); + if (ret == IT_I2C_READ_RET) + if (!((dev_status & PT_INFO_BITS) & PT_INFO_YES)) + return IRQ_HANDLED; + ret = it7260_i2c_read_no_ready_check(ts_data, BUF_POINT_INFO, + (void *)&pt_data, sizeof(pt_data)); + if (ret != IT_I2C_READ_RET) { + dev_err(&ts_data->client->dev, + "failed to read point data buffer\n"); + return IRQ_HANDLED; + } + + /* Check if controller moves from idle to active state */ + if ((pt_data.flags & PD_FLAGS_DATA_TYPE_BITS) != + PD_FLAGS_DATA_TYPE_TOUCH) { + /* + * This code adds the touch-to-wake functionality to the ITE + * tech driver. When user puts a finger on touch controller in + * idle state, the controller moves to active state and driver + * sends the KEY_WAKEUP event to wake the device. The + * pm_stay_awake() call tells the pm core to stay awake until + * the CPU cores are up already. The schedule_work() call + * schedule a work that tells the pm core to relax once the CPU + * cores are up. + */ + if ((pt_data.flags & PD_FLAGS_DATA_TYPE_BITS) == + PD_FLAGS_IDLE_TO_ACTIVE && + pt_data.gesture_id == 0) { + pm_stay_awake(&ts_data->client->dev); + input_report_key(input_dev, KEY_WAKEUP, 1); + input_sync(input_dev); + input_report_key(input_dev, KEY_WAKEUP, 0); + input_sync(input_dev); + schedule_work(&ts_data->work_pm_relax); + } else { + dev_dbg(&ts_data->client->dev, + "Ignore the touch data\n"); + } + return IRQ_HANDLED; + } + + /* + * Check if touch data also includes any palm gesture or not. + * If palm gesture is detected, then send the keycode parsed + * from the DT. + */ + palm_detected = pt_data.gesture_id & PD_PALM_FLAG_BIT; + if (palm_detected && ts_data->pdata->palm_detect_en) { + input_report_key(input_dev, + ts_data->pdata->palm_detect_keycode, 1); + input_sync(input_dev); + input_report_key(input_dev, + ts_data->pdata->palm_detect_keycode, 0); + input_sync(input_dev); + } + + for (finger = 0; finger < ts_data->pdata->num_of_fingers; finger++) { + finger_status = pt_data.flags & (0x01 << finger); + + input_mt_slot(input_dev, finger); + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, + finger_status != 0); + + x = pt_data.fd[finger].xLo + + (((u16)(pt_data.fd[finger].hi & 0x0F)) << 8); + y = pt_data.fd[finger].yLo + + (((u16)(pt_data.fd[finger].hi & 0xF0)) << 4); + + pressure = pt_data.fd[finger].pressure & FD_PRESSURE_BITS; + + if (finger_status) { + if (pressure >= FD_PRESSURE_LIGHT) { + input_report_key(input_dev, BTN_TOUCH, 1); + input_report_abs(input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(input_dev, + ABS_MT_POSITION_Y, y); + touch_count++; + } + } + } + + input_report_key(input_dev, BTN_TOUCH, touch_count > 0); + input_sync(input_dev); + + return IRQ_HANDLED; +} + +static void it7260_ts_work_func(struct work_struct *work) +{ + struct it7260_ts_data *ts_data = container_of(work, + struct it7260_ts_data, work_pm_relax); + + pm_relax(&ts_data->client->dev); +} + +static int it7260_ts_chip_identify(struct it7260_ts_data *ts_data) +{ + static const uint8_t cmd_ident[] = {CMD_IDENT_CHIP}; + static const uint8_t expected_id[] = {0x0A, 'I', 'T', 'E', '7', + '2', '6', '0'}; + uint8_t chip_id[10] = {0,}; + int ret; + + /* + * Sometimes, the controller may not respond immediately after + * writing the command, so wait for device to get ready. + * FALSE means to retry 20 times at max to read the chip status. + * TRUE means to add delay in each retry. + */ + ret = it7260_wait_device_ready(ts_data, false, true); + if (ret < 0) { + dev_err(&ts_data->client->dev, + "failed to read chip status %d\n", ret); + return ret; + } + + ret = it7260_i2c_write_no_ready_check(ts_data, BUF_COMMAND, cmd_ident, + sizeof(cmd_ident)); + if (ret != IT_I2C_WRITE_RET) { + dev_err(&ts_data->client->dev, + "failed to write CMD_IDENT_CHIP %d\n", ret); + return ret; + } + + /* + * Sometimes, the controller may not respond immediately after + * writing the command, so wait for device to get ready. + * TRUE means to retry 500 times at max to read the chip status. + * FALSE means to avoid unnecessary delays in each retry. + */ + ret = it7260_wait_device_ready(ts_data, true, false); + if (ret < 0) { + dev_err(&ts_data->client->dev, + "failed to read chip status %d\n", ret); + return ret; + } + + + ret = it7260_i2c_read_no_ready_check(ts_data, BUF_RESPONSE, chip_id, + sizeof(chip_id)); + if (ret != IT_I2C_READ_RET) { + dev_err(&ts_data->client->dev, + "failed to read chip-id %d\n", ret); + return ret; + } + dev_info(&ts_data->client->dev, + "it7260_ts_chip_identify read id: %02X %c%c%c%c%c%c%c %c%c\n", + chip_id[0], chip_id[1], chip_id[2], chip_id[3], chip_id[4], + chip_id[5], chip_id[6], chip_id[7], chip_id[8], chip_id[9]); + + if (memcmp(chip_id, expected_id, sizeof(expected_id))) + return -EINVAL; + + if (chip_id[8] == '5' && chip_id[9] == '6') + dev_info(&ts_data->client->dev, "rev BX3 found\n"); + else if (chip_id[8] == '6' && chip_id[9] == '6') + dev_info(&ts_data->client->dev, "rev BX4 found\n"); + else + dev_info(&ts_data->client->dev, "unknown revision (0x%02X 0x%02X) found\n", + chip_id[8], chip_id[9]); + + return 0; +} + +static int reg_set_optimum_mode_check(struct regulator *reg, int load_uA) +{ + return (regulator_count_voltages(reg) > 0) ? + regulator_set_load(reg, load_uA) : 0; +} + +static int it7260_regulator_configure(struct it7260_ts_data *ts_data, bool on) +{ + int retval; + + if (on == false) + goto hw_shutdown; + + ts_data->vdd = devm_regulator_get(&ts_data->client->dev, "vdd"); + if (IS_ERR(ts_data->vdd)) { + dev_err(&ts_data->client->dev, + "%s: Failed to get vdd regulator\n", __func__); + return PTR_ERR(ts_data->vdd); + } + + if (regulator_count_voltages(ts_data->vdd) > 0) { + retval = regulator_set_voltage(ts_data->vdd, + IT_VTG_MIN_UV, IT_VTG_MAX_UV); + if (retval) { + dev_err(&ts_data->client->dev, + "regulator set_vtg failed retval =%d\n", + retval); + goto err_set_vtg_vdd; + } + } + + ts_data->avdd = devm_regulator_get(&ts_data->client->dev, "avdd"); + if (IS_ERR(ts_data->avdd)) { + dev_err(&ts_data->client->dev, + "%s: Failed to get i2c regulator\n", __func__); + retval = PTR_ERR(ts_data->avdd); + goto err_get_vtg_i2c; + } + + if (regulator_count_voltages(ts_data->avdd) > 0) { + retval = regulator_set_voltage(ts_data->avdd, + IT_I2C_VTG_MIN_UV, IT_I2C_VTG_MAX_UV); + if (retval) { + dev_err(&ts_data->client->dev, + "reg set i2c vtg failed retval =%d\n", + retval); + goto err_set_vtg_i2c; + } + } + + return 0; + +err_set_vtg_i2c: +err_get_vtg_i2c: + if (regulator_count_voltages(ts_data->vdd) > 0) + regulator_set_voltage(ts_data->vdd, 0, IT_VTG_MAX_UV); +err_set_vtg_vdd: + return retval; + +hw_shutdown: + if (regulator_count_voltages(ts_data->vdd) > 0) + regulator_set_voltage(ts_data->vdd, 0, IT_VTG_MAX_UV); + if (regulator_count_voltages(ts_data->avdd) > 0) + regulator_set_voltage(ts_data->avdd, 0, IT_I2C_VTG_MAX_UV); + return 0; +}; + +static int it7260_power_on(struct it7260_ts_data *ts_data, bool on) +{ + int retval; + + if (on == false) + goto power_off; + + retval = reg_set_optimum_mode_check(ts_data->vdd, + IT_ACTIVE_LOAD_UA); + if (retval < 0) { + dev_err(&ts_data->client->dev, + "Regulator vdd set_opt failed rc=%d\n", + retval); + return retval; + } + + retval = regulator_enable(ts_data->vdd); + if (retval) { + dev_err(&ts_data->client->dev, + "Regulator vdd enable failed rc=%d\n", + retval); + goto error_reg_en_vdd; + } + + retval = reg_set_optimum_mode_check(ts_data->avdd, + IT_I2C_ACTIVE_LOAD_UA); + if (retval < 0) { + dev_err(&ts_data->client->dev, + "Regulator avdd set_opt failed rc=%d\n", + retval); + goto error_reg_opt_i2c; + } + + retval = regulator_enable(ts_data->avdd); + if (retval) { + dev_err(&ts_data->client->dev, + "Regulator avdd enable failed rc=%d\n", + retval); + goto error_reg_en_avdd; + } + + return 0; + +error_reg_en_avdd: + reg_set_optimum_mode_check(ts_data->avdd, 0); +error_reg_opt_i2c: + regulator_disable(ts_data->vdd); +error_reg_en_vdd: + reg_set_optimum_mode_check(ts_data->vdd, 0); + return retval; + +power_off: + reg_set_optimum_mode_check(ts_data->vdd, 0); + regulator_disable(ts_data->vdd); + reg_set_optimum_mode_check(ts_data->avdd, 0); + regulator_disable(ts_data->avdd); + + return 0; +} + +static int it7260_gpio_configure(struct it7260_ts_data *ts_data, bool on) +{ + int retval = 0; + + if (on) { + if (gpio_is_valid(ts_data->pdata->irq_gpio)) { + /* configure touchscreen irq gpio */ + retval = gpio_request(ts_data->pdata->irq_gpio, + "ite_irq_gpio"); + if (retval) { + dev_err(&ts_data->client->dev, + "unable to request irq gpio [%d]\n", + retval); + goto err_irq_gpio_req; + } + + retval = gpio_direction_input(ts_data->pdata->irq_gpio); + if (retval) { + dev_err(&ts_data->client->dev, + "unable to set direction for irq gpio [%d]\n", + retval); + goto err_irq_gpio_dir; + } + } else { + dev_err(&ts_data->client->dev, + "irq gpio not provided\n"); + goto err_irq_gpio_req; + } + + if (gpio_is_valid(ts_data->pdata->reset_gpio)) { + /* configure touchscreen reset out gpio */ + retval = gpio_request(ts_data->pdata->reset_gpio, + "ite_reset_gpio"); + if (retval) { + dev_err(&ts_data->client->dev, + "unable to request reset gpio [%d]\n", + retval); + goto err_reset_gpio_req; + } + + retval = gpio_direction_output( + ts_data->pdata->reset_gpio, 1); + if (retval) { + dev_err(&ts_data->client->dev, + "unable to set direction for reset gpio [%d]\n", + retval); + goto err_reset_gpio_dir; + } + + if (ts_data->pdata->low_reset) + gpio_set_value(ts_data->pdata->reset_gpio, 0); + else + gpio_set_value(ts_data->pdata->reset_gpio, 1); + + msleep(ts_data->pdata->reset_delay); + } else { + dev_err(&ts_data->client->dev, + "reset gpio not provided\n"); + goto err_reset_gpio_req; + } + } else { + if (gpio_is_valid(ts_data->pdata->irq_gpio)) + gpio_free(ts_data->pdata->irq_gpio); + if (gpio_is_valid(ts_data->pdata->reset_gpio)) { + /* + * This is intended to save leakage current + * only. Even if the call(gpio_direction_input) + * fails, only leakage current will be more but + * functionality will not be affected. + */ + retval = gpio_direction_input( + ts_data->pdata->reset_gpio); + if (retval) { + dev_err(&ts_data->client->dev, + "unable to set direction for gpio reset [%d]\n", + retval); + } + gpio_free(ts_data->pdata->reset_gpio); + } + } + + return 0; + +err_reset_gpio_dir: + if (gpio_is_valid(ts_data->pdata->reset_gpio)) + gpio_free(ts_data->pdata->reset_gpio); +err_reset_gpio_req: +err_irq_gpio_dir: + if (gpio_is_valid(ts_data->pdata->irq_gpio)) + gpio_free(ts_data->pdata->irq_gpio); +err_irq_gpio_req: + return retval; +} + +#if CONFIG_OF +static int it7260_get_dt_coords(struct device *dev, char *name, + struct it7260_ts_platform_data *pdata) +{ + u32 coords[IT7260_COORDS_ARR_SIZE]; + struct property *prop; + struct device_node *np = dev->of_node; + int coords_size, rc; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + coords_size = prop->length / sizeof(u32); + if (coords_size != IT7260_COORDS_ARR_SIZE) { + dev_err(dev, "invalid %s\n", name); + return -EINVAL; + } + + rc = of_property_read_u32_array(np, name, coords, coords_size); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read %s\n", name); + return rc; + } + + if (strcmp(name, "ite,panel-coords") == 0) { + pdata->panel_minx = coords[0]; + pdata->panel_miny = coords[1]; + pdata->panel_maxx = coords[2]; + pdata->panel_maxy = coords[3]; + + if (pdata->panel_maxx == 0 || pdata->panel_minx > 0) + rc = -EINVAL; + else if (pdata->panel_maxy == 0 || pdata->panel_miny > 0) + rc = -EINVAL; + + if (rc) { + dev_err(dev, "Invalid panel resolution %d\n", rc); + return rc; + } + } else if (strcmp(name, "ite,display-coords") == 0) { + pdata->disp_minx = coords[0]; + pdata->disp_miny = coords[1]; + pdata->disp_maxx = coords[2]; + pdata->disp_maxy = coords[3]; + } else { + dev_err(dev, "unsupported property %s\n", name); + return -EINVAL; + } + + return 0; +} + +static int it7260_parse_dt(struct device *dev, + struct it7260_ts_platform_data *pdata) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + struct device_node *np = dev->of_node; + u32 temp_val; + int rc; + + /* reset, irq gpio info */ + pdata->reset_gpio = of_get_named_gpio_flags(np, + "ite,reset-gpio", 0, &pdata->reset_gpio_flags); + pdata->irq_gpio = of_get_named_gpio_flags(np, + "ite,irq-gpio", 0, &pdata->irq_gpio_flags); + + rc = of_property_read_u32(np, "ite,num-fingers", &temp_val); + if (!rc) + pdata->num_of_fingers = temp_val; + else if (rc != -EINVAL) { + dev_err(dev, "Unable to read reset delay\n"); + return rc; + } + + pdata->wakeup = of_property_read_bool(np, "ite,wakeup"); + pdata->palm_detect_en = of_property_read_bool(np, "ite,palm-detect-en"); + if (pdata->palm_detect_en) { + rc = of_property_read_u32(np, "ite,palm-detect-keycode", + &temp_val); + if (!rc) { + pdata->palm_detect_keycode = temp_val; + } else { + dev_err(dev, "Unable to read palm-detect-keycode\n"); + return rc; + } + } + + rc = of_property_read_string(np, "ite,fw-name", &pdata->fw_name); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw image name %d\n", rc); + return rc; + } + + rc = of_property_read_string(np, "ite,cfg-name", &pdata->cfg_name); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read cfg image name %d\n", rc); + return rc; + } + + snprintf(ts_data->fw_name, MAX_BUFFER_SIZE, "%s", + (pdata->fw_name != NULL) ? pdata->fw_name : FW_NAME); + snprintf(ts_data->cfg_name, MAX_BUFFER_SIZE, "%s", + (pdata->cfg_name != NULL) ? pdata->cfg_name : CFG_NAME); + + rc = of_property_read_u32(np, "ite,reset-delay", &temp_val); + if (!rc) + pdata->reset_delay = temp_val; + else if (rc != -EINVAL) { + dev_err(dev, "Unable to read reset delay\n"); + return rc; + } + + rc = of_property_read_u32(np, "ite,avdd-lpm-cur", &temp_val); + if (!rc) { + pdata->avdd_lpm_cur = temp_val; + } else if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read avdd lpm current value %d\n", rc); + return rc; + } + + pdata->low_reset = of_property_read_bool(np, "ite,low-reset"); + + rc = it7260_get_dt_coords(dev, "ite,display-coords", pdata); + if (rc && (rc != -EINVAL)) + return rc; + + rc = it7260_get_dt_coords(dev, "ite,panel-coords", pdata); + if (rc && (rc != -EINVAL)) + return rc; + + return 0; +} +#else +static inline int it7260_ts_parse_dt(struct device *dev, + struct it7260_ts_platform_data *pdata) +{ + return 0; +} +#endif + +static int it7260_ts_pinctrl_init(struct it7260_ts_data *ts_data) +{ + int retval; + + /* Get pinctrl if target uses pinctrl */ + ts_data->ts_pinctrl = devm_pinctrl_get(&(ts_data->client->dev)); + if (IS_ERR_OR_NULL(ts_data->ts_pinctrl)) { + retval = PTR_ERR(ts_data->ts_pinctrl); + dev_dbg(&ts_data->client->dev, + "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + ts_data->pinctrl_state_active + = pinctrl_lookup_state(ts_data->ts_pinctrl, + PINCTRL_STATE_ACTIVE); + if (IS_ERR_OR_NULL(ts_data->pinctrl_state_active)) { + retval = PTR_ERR(ts_data->pinctrl_state_active); + dev_err(&ts_data->client->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + ts_data->pinctrl_state_suspend + = pinctrl_lookup_state(ts_data->ts_pinctrl, + PINCTRL_STATE_SUSPEND); + if (IS_ERR_OR_NULL(ts_data->pinctrl_state_suspend)) { + retval = PTR_ERR(ts_data->pinctrl_state_suspend); + dev_err(&ts_data->client->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + ts_data->pinctrl_state_release + = pinctrl_lookup_state(ts_data->ts_pinctrl, + PINCTRL_STATE_RELEASE); + if (IS_ERR_OR_NULL(ts_data->pinctrl_state_release)) { + retval = PTR_ERR(ts_data->pinctrl_state_release); + dev_dbg(&ts_data->client->dev, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(ts_data->ts_pinctrl); +err_pinctrl_get: + ts_data->ts_pinctrl = NULL; + return retval; +} + +static int it7260_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + static const uint8_t cmd_start[] = {CMD_UNKNOWN_7}; + struct it7260_ts_data *ts_data; + struct it7260_ts_platform_data *pdata; + uint8_t rsp[2]; + int ret = -1, err; + struct dentry *temp; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "need I2C_FUNC_I2C\n"); + return -ENODEV; + } + + ts_data = devm_kzalloc(&client->dev, sizeof(*ts_data), GFP_KERNEL); + if (!ts_data) + return -ENOMEM; + + ts_data->client = client; + i2c_set_clientdata(client, ts_data); + + if (client->dev.of_node) { + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = it7260_parse_dt(&client->dev, pdata); + if (ret) + return ret; + } else { + pdata = client->dev.platform_data; + } + + if (!pdata) { + dev_err(&client->dev, "No platform data found\n"); + return -ENOMEM; + } + + ts_data->pdata = pdata; + + ret = it7260_regulator_configure(ts_data, true); + if (ret < 0) { + dev_err(&client->dev, "Failed to configure regulators\n"); + goto err_reg_configure; + } + + ret = it7260_power_on(ts_data, true); + if (ret < 0) { + dev_err(&client->dev, "Failed to power on\n"); + goto err_power_device; + } + + /* + * After enabling regulators, controller needs a delay to come to + * an active state. + */ + msleep(DELAY_VTG_REG_EN); + + ret = it7260_ts_pinctrl_init(ts_data); + if (!ret && ts_data->ts_pinctrl) { + /* + * Pinctrl handle is optional. If pinctrl handle is found + * let pins to be configured in active state. If not + * found continue further without error. + */ + ret = pinctrl_select_state(ts_data->ts_pinctrl, + ts_data->pinctrl_state_active); + if (ret < 0) { + dev_err(&ts_data->client->dev, + "failed to select pin to active state %d", + ret); + } + } else { + ret = it7260_gpio_configure(ts_data, true); + if (ret < 0) { + dev_err(&client->dev, "Failed to configure gpios\n"); + goto err_gpio_config; + } + } + + ret = it7260_ts_chip_identify(ts_data); + if (ret) { + dev_err(&client->dev, "Failed to identify chip %d!!!", ret); + goto err_identification_fail; + } + + it7260_get_chip_versions(ts_data); + + ts_data->input_dev = input_allocate_device(); + if (!ts_data->input_dev) { + dev_err(&client->dev, "failed to allocate input device\n"); + ret = -ENOMEM; + goto err_input_alloc; + } + + /* Initialize mutex for fw and cfg upgrade */ + mutex_init(&ts_data->fw_cfg_mutex); + + ts_data->input_dev->name = DEVICE_NAME; + ts_data->input_dev->phys = "I2C"; + ts_data->input_dev->id.bustype = BUS_I2C; + ts_data->input_dev->id.vendor = 0x0001; + ts_data->input_dev->id.product = 0x7260; + set_bit(EV_SYN, ts_data->input_dev->evbit); + set_bit(EV_KEY, ts_data->input_dev->evbit); + set_bit(EV_ABS, ts_data->input_dev->evbit); + set_bit(INPUT_PROP_DIRECT, ts_data->input_dev->propbit); + set_bit(BTN_TOUCH, ts_data->input_dev->keybit); + input_set_abs_params(ts_data->input_dev, ABS_MT_POSITION_X, + ts_data->pdata->disp_minx, ts_data->pdata->disp_maxx, 0, 0); + input_set_abs_params(ts_data->input_dev, ABS_MT_POSITION_Y, + ts_data->pdata->disp_miny, ts_data->pdata->disp_maxy, 0, 0); + input_mt_init_slots(ts_data->input_dev, + ts_data->pdata->num_of_fingers, 0); + + input_set_drvdata(ts_data->input_dev, ts_data); + + if (pdata->wakeup) { + set_bit(KEY_WAKEUP, ts_data->input_dev->keybit); + INIT_WORK(&ts_data->work_pm_relax, it7260_ts_work_func); + device_init_wakeup(&client->dev, pdata->wakeup); + } + + if (pdata->palm_detect_en) + set_bit(ts_data->pdata->palm_detect_keycode, + ts_data->input_dev->keybit); + + if (input_register_device(ts_data->input_dev)) { + dev_err(&client->dev, "failed to register input device\n"); + goto err_input_register; + } + + if (request_threaded_irq(client->irq, NULL, it7260_ts_threaded_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, client->name, ts_data)) { + dev_err(&client->dev, "request_irq failed\n"); + goto err_irq_reg; + } + + if (sysfs_create_group(&(client->dev.kobj), &it7260_attr_group)) { + dev_err(&client->dev, "failed to register sysfs #2\n"); + goto err_sysfs_grp_create; + } + +#if defined(CONFIG_FB) + ts_data->fb_notif.notifier_call = fb_notifier_callback; + + ret = fb_register_client(&ts_data->fb_notif); + if (ret) + dev_err(&client->dev, "Unable to register fb_notifier %d\n", + ret); +#endif + + it7260_i2c_write_no_ready_check(ts_data, BUF_COMMAND, cmd_start, + sizeof(cmd_start)); + msleep(pdata->reset_delay); + it7260_i2c_read_no_ready_check(ts_data, BUF_RESPONSE, rsp, sizeof(rsp)); + msleep(pdata->reset_delay); + + ts_data->dir = debugfs_create_dir(DEBUGFS_DIR_NAME, NULL); + if (ts_data->dir == NULL || IS_ERR(ts_data->dir)) { + dev_err(&client->dev, + "%s: Failed to create debugfs directory, ret = %ld\n", + __func__, PTR_ERR(ts_data->dir)); + ret = PTR_ERR(ts_data->dir); + goto err_create_debugfs_dir; + } + + temp = debugfs_create_file("suspend", S_IRUSR | S_IWUSR, ts_data->dir, + ts_data, &debug_suspend_fops); + if (temp == NULL || IS_ERR(temp)) { + dev_err(&client->dev, + "%s: Failed to create suspend debugfs file, ret = %ld\n", + __func__, PTR_ERR(temp)); + ret = PTR_ERR(temp); + goto err_create_debugfs_file; + } + + return 0; + +err_create_debugfs_file: + debugfs_remove_recursive(ts_data->dir); +err_create_debugfs_dir: +#if defined(CONFIG_FB) + if (fb_unregister_client(&ts_data->fb_notif)) + dev_err(&client->dev, "Error occurred while unregistering fb_notifier.\n"); +#endif + sysfs_remove_group(&(client->dev.kobj), &it7260_attr_group); + +err_sysfs_grp_create: + free_irq(client->irq, ts_data); + +err_irq_reg: + input_unregister_device(ts_data->input_dev); + +err_input_register: + if (pdata->wakeup) { + cancel_work_sync(&ts_data->work_pm_relax); + device_init_wakeup(&client->dev, false); + } + if (ts_data->input_dev) + input_free_device(ts_data->input_dev); + ts_data->input_dev = NULL; + +err_input_alloc: +err_identification_fail: + if (ts_data->ts_pinctrl) { + if (IS_ERR_OR_NULL(ts_data->pinctrl_state_release)) { + devm_pinctrl_put(ts_data->ts_pinctrl); + ts_data->ts_pinctrl = NULL; + } else { + err = pinctrl_select_state(ts_data->ts_pinctrl, + ts_data->pinctrl_state_release); + if (err) + dev_err(&ts_data->client->dev, + "failed to select relase pinctrl state %d\n", + err); + } + } else { + if (gpio_is_valid(pdata->reset_gpio)) + gpio_free(pdata->reset_gpio); + if (gpio_is_valid(pdata->irq_gpio)) + gpio_free(pdata->irq_gpio); + } + +err_gpio_config: + it7260_power_on(ts_data, false); + +err_power_device: + it7260_regulator_configure(ts_data, false); + +err_reg_configure: + return ret; +} + +static int it7260_ts_remove(struct i2c_client *client) +{ + struct it7260_ts_data *ts_data = i2c_get_clientdata(client); + int ret; + + debugfs_remove_recursive(ts_data->dir); +#if defined(CONFIG_FB) + if (fb_unregister_client(&ts_data->fb_notif)) + dev_err(&client->dev, "Error occurred while unregistering fb_notifier.\n"); +#endif + sysfs_remove_group(&(client->dev.kobj), &it7260_attr_group); + free_irq(client->irq, ts_data); + input_unregister_device(ts_data->input_dev); + if (ts_data->input_dev) + input_free_device(ts_data->input_dev); + ts_data->input_dev = NULL; + if (ts_data->pdata->wakeup) { + cancel_work_sync(&ts_data->work_pm_relax); + device_init_wakeup(&client->dev, false); + } + if (ts_data->ts_pinctrl) { + if (IS_ERR_OR_NULL(ts_data->pinctrl_state_release)) { + devm_pinctrl_put(ts_data->ts_pinctrl); + ts_data->ts_pinctrl = NULL; + } else { + ret = pinctrl_select_state(ts_data->ts_pinctrl, + ts_data->pinctrl_state_release); + if (ret) + dev_err(&ts_data->client->dev, + "failed to select relase pinctrl state %d\n", + ret); + } + } else { + if (gpio_is_valid(ts_data->pdata->reset_gpio)) + gpio_free(ts_data->pdata->reset_gpio); + if (gpio_is_valid(ts_data->pdata->irq_gpio)) + gpio_free(ts_data->pdata->irq_gpio); + } + it7260_power_on(ts_data, false); + it7260_regulator_configure(ts_data, false); + + return 0; +} + +#if defined(CONFIG_FB) +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct it7260_ts_data *ts_data = container_of(self, + struct it7260_ts_data, fb_notif); + struct fb_event *evdata = data; + int *blank; + + if (evdata && evdata->data && ts_data && ts_data->client) { + if (event == FB_EVENT_BLANK) { + blank = evdata->data; + if (*blank == FB_BLANK_UNBLANK) + it7260_ts_resume(&(ts_data->client->dev)); + else if (*blank == FB_BLANK_POWERDOWN || + *blank == FB_BLANK_VSYNC_SUSPEND) + it7260_ts_suspend(&(ts_data->client->dev)); + } + } + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int it7260_ts_resume(struct device *dev) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + int retval; + + if (device_may_wakeup(dev)) { + if (ts_data->in_low_power_mode) { + /* Set active current for the avdd regulator */ + if (ts_data->pdata->avdd_lpm_cur) { + retval = reg_set_optimum_mode_check( + ts_data->avdd, + IT_I2C_ACTIVE_LOAD_UA); + if (retval < 0) + dev_err(dev, "Regulator avdd set_opt failed at resume rc=%d\n", + retval); + } + + ts_data->in_low_power_mode = false; + disable_irq_wake(ts_data->client->irq); + } + return 0; + } + + if (ts_data->ts_pinctrl) { + retval = pinctrl_select_state(ts_data->ts_pinctrl, + ts_data->pinctrl_state_active); + if (retval < 0) { + dev_err(dev, "Cannot get default pinctrl state %d\n", + retval); + goto err_pinctrl_select_suspend; + } + } + + enable_irq(ts_data->client->irq); + ts_data->suspended = false; + return 0; + +err_pinctrl_select_suspend: + return retval; +} + +static int it7260_ts_suspend(struct device *dev) +{ + struct it7260_ts_data *ts_data = dev_get_drvdata(dev); + int retval; + + if (ts_data->fw_cfg_uploading) { + dev_dbg(dev, "Fw/cfg uploading. Can't go to suspend.\n"); + return -EBUSY; + } + + if (device_may_wakeup(dev)) { + if (!ts_data->in_low_power_mode) { + /* put the device in low power idle mode */ + retval = it7260_ts_chip_low_power_mode(ts_data, + PWR_CTL_LOW_POWER_MODE); + if (retval) + dev_err(dev, "Can't go to low power mode %d\n", + retval); + + /* Set lpm current for avdd regulator */ + if (ts_data->pdata->avdd_lpm_cur) { + retval = reg_set_optimum_mode_check( + ts_data->avdd, + ts_data->pdata->avdd_lpm_cur); + if (retval < 0) + dev_err(dev, "Regulator avdd set_opt failed at suspend rc=%d\n", + retval); + } + + ts_data->in_low_power_mode = true; + enable_irq_wake(ts_data->client->irq); + } + return 0; + } + + disable_irq(ts_data->client->irq); + + it7260_ts_release_all(ts_data); + + if (ts_data->ts_pinctrl) { + retval = pinctrl_select_state(ts_data->ts_pinctrl, + ts_data->pinctrl_state_suspend); + if (retval < 0) { + dev_err(dev, "Cannot get idle pinctrl state %d\n", + retval); + goto err_pinctrl_select_suspend; + } + } + + ts_data->suspended = true; + + return 0; + +err_pinctrl_select_suspend: + return retval; +} + +static const struct dev_pm_ops it7260_ts_dev_pm_ops = { + .suspend = it7260_ts_suspend, + .resume = it7260_ts_resume, +}; +#else +static int it7260_ts_resume(struct device *dev) +{ + return 0; +} + +static int it7260_ts_suspend(struct device *dev) +{ + return 0; +} +#endif + +static const struct i2c_device_id it7260_ts_id[] = { + { DEVICE_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, it7260_ts_id); + +static const struct of_device_id it7260_match_table[] = { + { .compatible = "ite,it7260_ts",}, + {}, +}; + +static struct i2c_driver it7260_ts_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DEVICE_NAME, + .of_match_table = it7260_match_table, +#ifdef CONFIG_PM + .pm = &it7260_ts_dev_pm_ops, +#endif + }, + .probe = it7260_ts_probe, + .remove = it7260_ts_remove, + .id_table = it7260_ts_id, +}; + +module_i2c_driver(it7260_ts_driver); + +MODULE_DESCRIPTION("it7260 Touchscreen Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/maxim_sti.c b/drivers/input/touchscreen/maxim_sti.c new file mode 100644 index 000000000000..d10f26357611 --- /dev/null +++ b/drivers/input/touchscreen/maxim_sti.c @@ -0,0 +1,2842 @@ +/* drivers/input/touchscreen/maxim_sti.c + * + * Maxim SmartTouch Imager Touchscreen Driver + * + * Copyright (c)2013 Maxim Integrated Products, Inc. + * Copyright (C) 2013, NVIDIA Corporation. All Rights Reserved. + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kmod.h> +#include <linux/kthread.h> +#include <linux/spi/spi.h> +#include <linux/firmware.h> +#include <linux/crc16.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/regulator/consumer.h> +#include <net/genetlink.h> +#include <net/sock.h> +#include <uapi/linux/maxim_sti.h> +#include <linux/input/mt.h> +#include <asm/byteorder.h> /* MUST include this header to get byte order */ +#ifdef CONFIG_OF +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#endif + +/****************************************************************************\ +* Custom features * +\****************************************************************************/ + +#define VTG_DVDD_MIN_UV 1800000 +#define VTG_DVDD_MAX_UV 1800000 +#define VTG_AVDD_MIN_UV 2850000 +#define VTG_AVDD_MAX_UV 2850000 +#define INPUT_DEVICES 1 +#define CFG_FILE_NAME_MAX 64 + +#define BUF_SIZE 4100 +#define DEF_NL_MC_GROUPS 5 +#define DEF_CHIP_ACC_METHOD 1 +#define TOUCH_FUSION "touch_fusion" + +#if defined(CONFIG_FB) +#include <linux/fb.h> +#endif + +/****************************************************************************\ +* Device context structure, globals, and macros * +\****************************************************************************/ +#define MAXIM_STI_NAME "maxim_sti" + +struct maxim_sti_pdata { + char *touch_fusion; + char *config_file; + char *nl_family; + char *fw_name; + u32 nl_mc_groups; + u32 chip_access_method; + u32 default_reset_state; + u32 tx_buf_size; + u32 rx_buf_size; + int gpio_reset; + int gpio_irq; + int (*init)(struct maxim_sti_pdata *pdata, bool init); + void (*reset)(struct maxim_sti_pdata *pdata, int value); + int (*irq)(struct maxim_sti_pdata *pdata); + bool mt_type_b_enabled; +}; + +struct dev_data; + +struct chip_access_method { + int (*read)(struct dev_data *dd, u16 address, u8 *buf, u16 len); + int (*write)(struct dev_data *dd, u16 address, u8 *buf, u16 len); +}; + +struct dev_data { + u8 *tx_buf; + u8 *rx_buf; + u8 send_fail_count; + u32 nl_seq; + u8 nl_mc_group_count; + bool nl_enabled; + bool start_fusion; + bool suspended; + bool suspend_in_progress; + bool resume_in_progress; + bool expect_resume_ack; + bool eraser_active; + bool legacy_acceleration; + bool input_no_deconfig; + bool irq_registered; + u16 irq_param[MAX_IRQ_PARAMS]; + pid_t fusion_process; + char input_phys[128]; + struct input_dev *input_dev[INPUT_DEVICES]; + struct completion suspend_resume; + struct chip_access_method chip; + struct spi_device *spi; + struct genl_family nl_family; + struct genl_ops *nl_ops; + struct genl_multicast_group *nl_mc_groups; + struct sk_buff *outgoing_skb; + struct sk_buff_head incoming_skb_queue; + struct task_struct *thread; + struct sched_param thread_sched; + struct list_head dev_list; + struct regulator *reg_avdd; + struct regulator *reg_dvdd; + bool supply; + void (*service_irq)(struct dev_data *dd); + struct notifier_block fb_notifier; + struct kobject *parent; + char config_file[CFG_FILE_NAME_MAX]; + u8 sysfs_created; + u16 chip_id; + u16 glove_enabled; + u16 charger_mode_en; + u16 lcd_fps; + u16 tf_ver; + u16 drv_ver; + u16 fw_ver; + u16 fw_protocol; + u32 tf_status; + bool idle; + bool gesture_reset; + bool touch_status[INPUT_DEVICES] + [MAX_INPUT_EVENTS]; + u16 gesture_en; + unsigned long int sysfs_update_type; + struct completion sysfs_ack_glove; + struct completion sysfs_ack_charger; + struct completion sysfs_ack_lcd_fps; + struct mutex sysfs_update_mutex; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspended; + struct pinctrl *ts_pinctrl; +}; + +static unsigned short panel_id; +static struct list_head dev_list; +static spinlock_t dev_lock; + +static irqreturn_t irq_handler(int irq, void *context); +static void service_irq(struct dev_data *dd); +static void service_irq_legacy_acceleration(struct dev_data *dd); +static int send_sysfs_info(struct dev_data *dd); + +#define ERROR(a, b...) dev_err(&dd->spi->dev, "maxim: %s (ERROR:%s:%d): " \ + a "\n", dd->nl_family.name, __func__, \ + __LINE__, ##b) +#define INFO(a, b...) dev_info(&dd->spi->dev, "maxim: %s: " a "\n", \ + dd->nl_family.name, ##b) +#define DBG(a, b...) dev_dbg(&dd->spi->dev, "maxim: %s: " a "\n", \ + dd->nl_family.name, ##b) + +#define DRV_MSG_DBG(...) +#define DRV_PT_DBG(...) + +/****************************************************************************\ +* Chip access methods * +\****************************************************************************/ + +static int +spi_read_123(struct dev_data *dd, u16 address, u8 *buf, u16 len, bool add_len) +{ + struct spi_message message; + struct spi_transfer transfer; + u16 *tx_buf = (u16 *)dd->tx_buf; + u16 *rx_buf = (u16 *)dd->rx_buf; + u16 words = len / sizeof(u16), header_len = 1; + u16 *ptr2 = rx_buf + 1; + int ret; + + if (tx_buf == NULL || rx_buf == NULL) + return -ENOMEM; + + tx_buf[0] = (address << 1) | 0x0001; + + if (add_len) { + tx_buf[1] = words; + ptr2++; + header_len++; + } + + spi_message_init(&message); + memset(&transfer, 0, sizeof(transfer)); + + transfer.len = len + header_len * sizeof(u16); + transfer.tx_buf = tx_buf; + transfer.rx_buf = rx_buf; + spi_message_add_tail(&transfer, &message); + + do { + ret = spi_sync(dd->spi, &message); + } while (ret == -EAGAIN); + + memcpy(buf, ptr2, len); + + return ret; +} + +static int +spi_write_123(struct dev_data *dd, u16 address, u8 *buf, u16 len, + bool add_len) +{ + struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; + u16 *tx_buf = (u16 *)dd->tx_buf; + u16 words = len / sizeof(u16), header_len = 1; + int ret; + + if (tx_buf == NULL) + return -ENOMEM; + + tx_buf[0] = address << 1; + if (add_len) { + tx_buf[1] = words; + header_len++; + } + memcpy(tx_buf + header_len, buf, len); + + do { + ret = spi_write(dd->spi, tx_buf, + len + header_len * sizeof(u16)); + } while (ret == -EAGAIN); + + memset(dd->tx_buf, 0xFF, pdata->tx_buf_size); + return ret; +} + +/* ======================================================================== */ + +static int +spi_read_1(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + return spi_read_123(dd, address, buf, len, true); +} + +static int +spi_write_1(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + return spi_write_123(dd, address, buf, len, true); +} + +/* ======================================================================== */ + +static inline int +stop_legacy_acceleration(struct dev_data *dd) +{ + u16 value = 0xDEAD, status, i; + int ret; + + ret = spi_write_123(dd, 0x0003, (u8 *)&value, + sizeof(value), false); + if (ret < 0) + return -1; + usleep_range(100, 120); + + for (i = 0; i < 200; i++) { + ret = spi_read_123(dd, 0x0003, (u8 *)&status, sizeof(status), + false); + if (ret < 0) + return -1; + if (status == 0xABCD) + return 0; + } + + return -2; +} + +static inline int +start_legacy_acceleration(struct dev_data *dd) +{ + u16 value = 0xBEEF; + int ret; + + ret = spi_write_123(dd, 0x0003, (u8 *)&value, sizeof(value), false); + usleep_range(100, 120); + + return ret; +} + +static inline int +spi_rw_2_poll_status(struct dev_data *dd) +{ + u16 status, i; + int ret; + + for (i = 0; i < 200; i++) { + ret = spi_read_123(dd, 0x0000, (u8 *)&status, sizeof(status), + false); + if (ret < 0) + return -1; + if (status == 0xABCD) + return 0; + } + + return -2; +} + +static inline int +spi_read_2_page(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + u16 request[] = {0xFEDC, (address << 1) | 0x0001, len / sizeof(u16)}; + int ret; + + /* write read request header */ + ret = spi_write_123(dd, 0x0000, (u8 *)request, sizeof(request), + false); + if (ret < 0) + return -1; + + /* poll status */ + ret = spi_rw_2_poll_status(dd); + if (ret < 0) + return ret; + + /* read data */ + ret = spi_read_123(dd, 0x0004, (u8 *)buf, len, false); + return ret; +} + +static inline int +spi_write_2_page(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + u16 page[254]; + int ret; + + page[0] = 0xFEDC; + page[1] = address << 1; + page[2] = len / sizeof(u16); + page[3] = 0x0000; + memcpy(page + 4, buf, len); + + /* write data with write request header */ + ret = spi_write_123(dd, 0x0000, (u8 *)page, len + 4 * sizeof(u16), + false); + if (ret < 0) + return -1; + + /* poll status */ + return spi_rw_2_poll_status(dd); +} + +static inline int +spi_rw_2(struct dev_data *dd, u16 address, u8 *buf, u16 len, + int (*func)(struct dev_data *dd, u16 address, u8 *buf, u16 len)) +{ + u16 rx_len, rx_limit = 250 * sizeof(u16), offset = 0; + int ret; + + while (len > 0) { + rx_len = (len > rx_limit) ? rx_limit : len; + if (dd->legacy_acceleration) + stop_legacy_acceleration(dd); + ret = func(dd, address + (offset / sizeof(u16)), buf + offset, + rx_len); + if (dd->legacy_acceleration) + start_legacy_acceleration(dd); + if (ret < 0) + return ret; + offset += rx_len; + len -= rx_len; + } + + return 0; +} + +static int +spi_read_2(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + return spi_rw_2(dd, address, buf, len, spi_read_2_page); +} + +static int +spi_write_2(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + return spi_rw_2(dd, address, buf, len, spi_write_2_page); +} + +/* ======================================================================== */ + +static int +spi_read_3(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + return spi_read_123(dd, address, buf, len, false); +} + +static int +spi_write_3(struct dev_data *dd, u16 address, u8 *buf, u16 len) +{ + return spi_write_123(dd, address, buf, len, false); +} + +/* ======================================================================== */ + +static struct chip_access_method chip_access_methods[] = { + { + .read = spi_read_1, + .write = spi_write_1, + }, + { + .read = spi_read_2, + .write = spi_write_2, + }, + { + .read = spi_read_3, + .write = spi_write_3, + }, +}; + +static int +set_chip_access_method(struct dev_data *dd, u8 method) +{ + if (method == 0 || method > ARRAY_SIZE(chip_access_methods)) + return -1; + + memcpy(&dd->chip, &chip_access_methods[method - 1], sizeof(dd->chip)); + return 0; +} + +/* ======================================================================== */ + +static inline int +stop_legacy_acceleration_canned(struct dev_data *dd) +{ + u16 value = dd->irq_param[18]; + + return dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, + sizeof(value)); +} + +static inline int +start_legacy_acceleration_canned(struct dev_data *dd) +{ + u16 value = dd->irq_param[17]; + + return dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, + sizeof(value)); +} + +/* ======================================================================== */ + +#define FLASH_BLOCK_SIZE 64 /* flash write buffer in words */ +#define FIRMWARE_SIZE 0xC000 /* fixed 48Kbytes */ + +static int bootloader_wait_ready(struct dev_data *dd) +{ + u16 status, i; + + for (i = 0; i < 15; i++) { + if (spi_read_3(dd, 0x00FF, (u8 *)&status, + sizeof(status)) != 0) + return -1; + if (status == 0xABCC) + return 0; + if (i >= 3) + usleep_range(500, 700); + } + ERROR("unexpected status %04X", status); + return -1; +} + +static int bootloader_complete(struct dev_data *dd) +{ + u16 value = 0x5432; + + return spi_write_3(dd, 0x00FF, (u8 *)&value, sizeof(value)); +} + +static int bootloader_read_data(struct dev_data *dd, u16 *value) +{ + u16 buffer[2]; + + if (spi_read_3(dd, 0x00FE, (u8 *)buffer, sizeof(buffer)) != 0) + return -1; + if (buffer[1] != 0xABCC) + return -1; + + *value = buffer[0]; + return bootloader_complete(dd); +} + +static int bootloader_write_data(struct dev_data *dd, u16 value) +{ + u16 buffer[2] = {value, 0x5432}; + + if (bootloader_wait_ready(dd) != 0) + return -1; + return spi_write_3(dd, 0x00FE, (u8 *)buffer, sizeof(buffer)); +} + +static int bootloader_wait_command(struct dev_data *dd) +{ + u16 value, i; + + for (i = 0; i < 15; i++) { + if (bootloader_read_data(dd, &value) == 0 && value == 0x003E) + return 0; + if (i >= 3) + usleep_range(500, 700); + } + return -1; +} + +static int bootloader_enter(struct dev_data *dd) +{ + int i; + u16 enter[3] = {0x0047, 0x00C7, 0x0007}; + + for (i = 0; i < 3; i++) { + if (spi_write_3(dd, 0x7F00, (u8 *)&enter[i], + sizeof(enter[i])) != 0) + return -1; + } + + if (bootloader_wait_command(dd) != 0) + return -1; + return 0; +} + +static int bootloader_exit(struct dev_data *dd) +{ + u16 value = 0x0000; + + if (bootloader_write_data(dd, 0x0001) != 0) + return -1; + return spi_write_3(dd, 0x7F00, (u8 *)&value, sizeof(value)); +} + +static int bootloader_get_crc(struct dev_data *dd, u16 *crc16, u16 len) +{ + u16 command[] = {0x0030, 0x0002, 0x0000, 0x0000, len & 0xFF, + len >> 8}, value[2], i; + + for (i = 0; i < ARRAY_SIZE(command); i++) + if (bootloader_write_data(dd, command[i]) != 0) + return -1; + msleep(200); /* wait 200ms for it to get done */ + + for (i = 0; i < 2; i++) + if (bootloader_read_data(dd, &value[i]) != 0) + return -1; + + if (bootloader_wait_command(dd) != 0) + return -1; + *crc16 = (value[1] << 8) | value[0]; + return 0; +} + +static int bootloader_set_byte_mode(struct dev_data *dd) +{ + u16 command[2] = {0x000A, 0x0000}, i; + + for (i = 0; i < ARRAY_SIZE(command); i++) + if (bootloader_write_data(dd, command[i]) != 0) + return -1; + if (bootloader_wait_command(dd) != 0) + return -1; + return 0; +} + +static int bootloader_erase_flash(struct dev_data *dd) +{ + if (bootloader_write_data(dd, 0x0002) != 0) + return -1; + msleep(60); /* wait 60ms */ + if (bootloader_wait_command(dd) != 0) + return -1; + return 0; +} + +static int bootloader_write_flash(struct dev_data *dd, u16 *image, u16 len) +{ + u16 command[] = {0x00F0, 0x0000, len >> 8, 0x0000, 0x0000}; + u16 i, buffer[FLASH_BLOCK_SIZE]; + + for (i = 0; i < ARRAY_SIZE(command); i++) + if (bootloader_write_data(dd, command[i]) != 0) + return -1; + + for (i = 0; i < ((len / sizeof(u16)) / FLASH_BLOCK_SIZE); i++) { + if (bootloader_wait_ready(dd) != 0) + return -1; + memcpy(buffer, (void *)(image + i * FLASH_BLOCK_SIZE), + sizeof(buffer)); + if (spi_write_3(dd, ((i % 2) == 0) ? 0x0000 : 0x0040, + (u8 *)buffer, sizeof(buffer)) != 0) + return -1; + if (bootloader_complete(dd) != 0) + return -1; + } + + usleep_range(10000, 11000); + if (bootloader_wait_command(dd) != 0) + return -1; + return 0; +} + +static int device_fw_load(struct dev_data *dd, const struct firmware *fw) +{ + u16 fw_crc16, chip_crc16; + + fw_crc16 = crc16(0, fw->data, fw->size); + INFO("firmware size (%d) CRC16(0x%04X)", (int)fw->size, fw_crc16); + if (bootloader_enter(dd) != 0) { + ERROR("failed to enter bootloader"); + return -1; + } + if (bootloader_get_crc(dd, &chip_crc16, fw->size) != 0) { + ERROR("failed to get CRC16 from the chip"); + return -1; + } + INFO("chip CRC16(0x%04X)", chip_crc16); + if (fw_crc16 != chip_crc16) { + INFO("will reprogram chip"); + if (bootloader_erase_flash(dd) != 0) { + ERROR("failed to erase chip flash"); + return -1; + } + INFO("flash erase OK"); + if (bootloader_set_byte_mode(dd) != 0) { + ERROR("failed to set byte mode"); + return -1; + } + INFO("byte mode OK"); + if (bootloader_write_flash(dd, (u16 *)fw->data, + fw->size) != 0) { + ERROR("failed to write flash"); + return -1; + } + INFO("flash write OK"); + if (bootloader_get_crc(dd, &chip_crc16, fw->size) != 0) { + ERROR("failed to get CRC16 from the chip"); + return -1; + } + if (fw_crc16 != chip_crc16) { + ERROR("failed to verify programming! (0x%04X)", + chip_crc16); + return -1; + } + INFO("chip programmed successfully, new chip CRC16(0x%04X)", + chip_crc16); + } + if (bootloader_exit(dd) != 0) { + ERROR("failed to exit bootloader"); + return -1; + } + return 0; +} + +static int fw_request_load(struct dev_data *dd) +{ + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, "maxim_fp35.bin", &dd->spi->dev); + if (ret || fw == NULL) { + ERROR("firmware request failed (%d,%p)", ret, fw); + return -1; + } + if (fw->size != FIRMWARE_SIZE) { + release_firmware(fw); + ERROR("incoming firmware is of wrong size (%04X)", + (unsigned int)fw->size); + return -1; + } + ret = device_fw_load(dd, fw); + if (ret != 0 && bootloader_exit(dd) != 0) + ERROR("failed to exit bootloader"); + release_firmware(fw); + return ret; +} + +/* ======================================================================== */ + +static void stop_idle_scan(struct dev_data *dd) +{ + u16 value; + + value = dd->irq_param[13]; + (void)dd->chip.write(dd, dd->irq_param[19], + (u8 *)&value, sizeof(value)); +} + +static void stop_scan_canned(struct dev_data *dd) +{ + u16 value; + u16 i, clear[2] = { 0 }; + + if (dd->legacy_acceleration) + (void)stop_legacy_acceleration_canned(dd); + value = dd->irq_param[13]; + (void)dd->chip.write(dd, dd->irq_param[12], (u8 *)&value, + sizeof(value)); + usleep_range(dd->irq_param[15], dd->irq_param[15] + 1000); + (void)dd->chip.read(dd, dd->irq_param[0], (u8 *)&clear[0], + sizeof(clear[0])); + (void)dd->chip.write(dd, dd->irq_param[0], (u8 *)&clear, + sizeof(clear)); + + for (i = 28; i < 32; i++) { + value = dd->irq_param[i]; + (void)dd->chip.write(dd, dd->irq_param[27], + (u8 *)&value, sizeof(value)); + } + value = dd->irq_param[33]; + (void)dd->chip.write(dd, dd->irq_param[32], + (u8 *)&value, sizeof(value)); + usleep_range(500, 1000); + for (i = 28; i < 32; i++) { + value = dd->irq_param[i]; + (void)dd->chip.write(dd, dd->irq_param[27], + (u8 *)&value, sizeof(value)); + } + value = dd->irq_param[13]; + (void)dd->chip.write(dd, dd->irq_param[32], + (u8 *)&value, sizeof(value)); +} + +static void start_scan_canned(struct dev_data *dd) +{ + u16 value; + + if (dd->legacy_acceleration) { + (void)start_legacy_acceleration_canned(dd); + } else { + value = dd->irq_param[14]; + (void)dd->chip.write(dd, dd->irq_param[12], (u8 *)&value, + sizeof(value)); + } +} + +static void enable_gesture(struct dev_data *dd) +{ + u16 value; + + if (!dd->idle) + stop_scan_canned(dd); + + value = dd->irq_param[13]; + (void)dd->chip.write(dd, dd->irq_param[34], + (u8 *)&value, sizeof(value)); + value = dd->irq_param[36]; + (void)dd->chip.write(dd, dd->irq_param[35], + (u8 *)&value, sizeof(value)); + + value = dd->irq_param[18]; + (void)dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, + sizeof(value)); + + value = dd->irq_param[20]; + (void)dd->chip.write(dd, dd->irq_param[19], (u8 *)&value, + sizeof(value)); + if (!dd->idle) { + value = dd->irq_param[26]; + (void)dd->chip.write(dd, dd->irq_param[25], (u8 *)&value, + sizeof(value)); + } +} + +static void gesture_wake_detect(struct dev_data *dd) +{ + u16 status, value; + int ret = 0; + + ret = dd->chip.read(dd, dd->irq_param[0], + (u8 *)&status, sizeof(status)); + INFO("%s: %d = %04X", __func__, dd->irq_param[0], status); + if (status & dd->irq_param[10]) + dd->gesture_reset = true; + if (status & dd->irq_param[18] && dd->input_dev[INPUT_DEVICES - 1]) { + ret = dd->chip.read(dd, dd->irq_param[22], + (u8 *)&value, sizeof(value)); + INFO("gesture code = 0x%04X", value); + if (value == dd->irq_param[24]) { + INFO("gesture detected"); + input_report_key(dd->input_dev[INPUT_DEVICES - 1], KEY_POWER, 1); + input_sync(dd->input_dev[INPUT_DEVICES - 1]); + input_report_key(dd->input_dev[INPUT_DEVICES - 1], KEY_POWER, 0); + input_sync(dd->input_dev[INPUT_DEVICES - 1]); + } + value = dd->irq_param[18]; + ret = dd->chip.write(dd, dd->irq_param[0], + (u8 *)&value, sizeof(value)); + } else { + ret = dd->chip.write(dd, dd->irq_param[0], + (u8 *)&status, sizeof(status)); + } +} + +static void finish_gesture(struct dev_data *dd) +{ + struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; + u16 value; + u8 fail_count = 0; + int ret; + + value = dd->irq_param[21]; + (void)dd->chip.write(dd, dd->irq_param[19], (u8 *)&value, + sizeof(value)); + do { + msleep(20); + ret = dd->chip.read(dd, dd->irq_param[25], (u8 *)&value, + sizeof(value)); + if (ret < 0 || fail_count >= 20) { + ERROR("failed to read control register (%d)", ret); + pdata->reset(pdata, 0); + msleep(20); + pdata->reset(pdata, 1); + return; + } + INFO("ctrl = 0x%04X", value); + fail_count++; + } while (value); +} + +static int regulator_init(struct dev_data *dd) +{ + int ret; + + dd->reg_avdd = devm_regulator_get(&dd->spi->dev, "avdd"); + if (IS_ERR(dd->reg_avdd)) { + ret = PTR_ERR(dd->reg_avdd); + dd->reg_avdd = NULL; + dev_err(&dd->spi->dev, "can't find avdd regulator: %d\n", + ret); + return ret; + } + + ret = regulator_set_voltage(dd->reg_avdd, + VTG_AVDD_MIN_UV, VTG_AVDD_MAX_UV); + if (ret) { + dev_err(&dd->spi->dev, + "can't set avdd regulator voltage: %d\n", ret); + goto err_free_avdd; + } + + dd->reg_dvdd = devm_regulator_get(&dd->spi->dev, "dvdd"); + if (IS_ERR(dd->reg_dvdd)) { + ret = PTR_ERR(dd->reg_avdd); + dd->reg_dvdd = NULL; + dev_err(&dd->spi->dev, "can't find avdd regulator: %d\n", + ret); + goto err_free_avdd; + } + + ret = regulator_set_voltage(dd->reg_dvdd, + VTG_DVDD_MIN_UV, VTG_DVDD_MAX_UV); + if (ret) { + dev_err(&dd->spi->dev, + "can't set dvdd regulator voltage: %d\n", ret); + goto err_free_dvdd; + } + return 0; + +err_free_dvdd: + devm_regulator_put(dd->reg_dvdd); + dd->reg_dvdd = NULL; +err_free_avdd: + devm_regulator_put(dd->reg_avdd); + dd->reg_avdd = NULL; + return ret; +} + +static void regulator_uninit(struct dev_data *dd) +{ + if (dd->reg_dvdd) + devm_regulator_put(dd->reg_dvdd); + if (dd->reg_avdd) + devm_regulator_put(dd->reg_avdd); +} + +static int regulator_control(struct dev_data *dd, bool on) +{ + int ret; + + if (!dd->reg_avdd || !dd->reg_dvdd) + return 0; + + if (on && !dd->supply) { + DBG("regulator ON."); + ret = regulator_enable(dd->reg_dvdd); + if (ret < 0) { + ERROR("failed to enable dvdd regulator: %d", ret); + return ret; + } + usleep_range(1000, 1020); + + ret = regulator_enable(dd->reg_avdd); + if (ret < 0) { + ERROR("failed to enable avdd regulator: %d", ret); + regulator_disable(dd->reg_dvdd); + return ret; + } + + dd->supply = true; + } else if (!on && dd->supply) { + DBG("regulator OFF."); + + ret = regulator_disable(dd->reg_avdd); + if (ret < 0) + ERROR("Failed to disable regulator avdd: %d", ret); + + ret = regulator_disable(dd->reg_dvdd); + if (ret < 0) + ERROR("Failed to disable regulator dvdd: %d", ret); + + dd->supply = false; + } + + return 0; +} + +#define MAXIM_STI_GPIO_ERROR(ret, gpio, op) \ + if (ret < 0) { \ + pr_err("%s: GPIO %d %s failed (%d)\n", __func__, gpio, op, \ + ret); \ + return ret; \ + } + + +int maxim_sti_gpio_init(struct maxim_sti_pdata *pdata, bool init) +{ + int ret; + + if (init) { + ret = gpio_request(pdata->gpio_irq, "maxim_sti_irq"); + MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_irq, "request"); + ret = gpio_direction_input(pdata->gpio_irq); + MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_irq, "direction"); + ret = gpio_request(pdata->gpio_reset, "maxim_sti_reset"); + MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_reset, "request"); + ret = gpio_direction_output(pdata->gpio_reset, + pdata->default_reset_state); + MAXIM_STI_GPIO_ERROR(ret, pdata->gpio_reset, "direction"); + } else { + gpio_free(pdata->gpio_irq); + gpio_free(pdata->gpio_reset); + } + + return 0; +} + +void maxim_sti_gpio_reset(struct maxim_sti_pdata *pdata, int value) +{ + gpio_set_value(pdata->gpio_reset, !!value); +} + +int maxim_sti_gpio_irq(struct maxim_sti_pdata *pdata) +{ + return gpio_get_value(pdata->gpio_irq); +} + +/****************************************************************************\ +* Device Tree Support +\****************************************************************************/ + +#ifdef CONFIG_OF +static int maxim_parse_dt(struct device *dev, struct maxim_sti_pdata *pdata) +{ + struct device_node *np = dev->of_node; + u32 flags; + const char *str; + int ret; + + pdata->gpio_reset = of_get_named_gpio_flags(np, + "maxim_sti,reset-gpio", 0, &flags); + pdata->default_reset_state = (u8)flags; + + pdata->gpio_irq = of_get_named_gpio_flags(np, + "maxim_sti,irq-gpio", 0, &flags); + + pdata->mt_type_b_enabled = + of_property_read_bool(np, "maxim_sti,mt_type_b_enabled"); + + ret = of_property_read_string(np, "maxim_sti,touch_fusion", &str); + if (ret) { + dev_err(dev, "%s: unable to read touch_fusion location (%d)\n", + __func__, ret); + goto fail; + } + pdata->touch_fusion = (char *)str; + + ret = of_property_read_string(np, "maxim_sti,config_file", &str); + if (ret) { + dev_err(dev, "%s: unable to read config_file location (%d)\n", + __func__, ret); + goto fail; + } + pdata->config_file = (char *)str; + + ret = of_property_read_string(np, "maxim_sti,fw_name", &str); + if (ret) { + dev_err(dev, "%s: unable to read fw_name (%d)\n", + __func__, ret); + } + pdata->fw_name = (char *)str; + + return 0; + +fail: + return ret; +} +#endif + +/****************************************************************************\ +* Suspend/resume processing * +\****************************************************************************/ + +#ifdef CONFIG_PM_SLEEP +static int suspend(struct device *dev) +{ + struct dev_data *dd = spi_get_drvdata(to_spi_device(dev)); + + DBG("%s: suspending", __func__); + + if (dd->suspended) { + DBG("%s: already suspended", __func__); + return 0; + } + + if (dd->suspend_in_progress) { + dev_err(dev, "suspend already in progress!"); + return -EINVAL; + } + + dd->suspend_in_progress = true; + wake_up_process(dd->thread); + wait_for_completion(&dd->suspend_resume); + + DBG("%s: suspended", __func__); + + return 0; +} + +static int resume(struct device *dev) +{ + struct dev_data *dd = spi_get_drvdata(to_spi_device(dev)); + + DBG("%s: resuming", __func__); + + if (!dd->suspended && !dd->suspend_in_progress) { + DBG("%s: not suspended", __func__); + return 0; + } + + dd->resume_in_progress = true; + wake_up_process(dd->thread); + wait_for_completion(&dd->suspend_resume); + DBG("%s: resumed", __func__); + return 0; +} + +static const struct dev_pm_ops pm_ops = { + .suspend = suspend, + .resume = resume, +}; + +#if defined(CONFIG_FB) +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + struct dev_data *dd = container_of(self, + struct dev_data, fb_notifier); + + DBG("%s: event = %lu", __func__, event); + if (evdata && evdata->data && event == FB_EVENT_BLANK) { + blank = evdata->data; + if (*blank == FB_BLANK_UNBLANK) + resume(&dd->spi->dev); + else if (*blank == FB_BLANK_POWERDOWN) + suspend(&dd->spi->dev); + } + return 0; +} +#endif +#endif + +/****************************************************************************\ +* Netlink processing * +\****************************************************************************/ + +static inline int +nl_msg_new(struct dev_data *dd, u8 dst) +{ + dd->outgoing_skb = alloc_skb(NL_BUF_SIZE, GFP_KERNEL); + if (dd->outgoing_skb == NULL) + return -ENOMEM; + nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, dd->nl_seq++, + dst); + if (dd->nl_seq == 0) + dd->nl_seq++; + return 0; +} + +static int +nl_callback_noop(struct sk_buff *skb, struct genl_info *info) +{ + return 0; +} + +static void +release_slot_events(struct dev_data *dd, bool *active_touch, int inp) +{ + int i; + + for (i = 0; i < MAX_INPUT_EVENTS; i++) { + if (dd->touch_status[inp][i] == true && + active_touch[i] == false) { + input_mt_slot(dd->input_dev[inp], i); + input_mt_report_slot_state(dd->input_dev[inp], + MT_TOOL_FINGER, 0); + } + dd->touch_status[inp][i] = active_touch[i]; + } +} + +static inline bool +nl_process_driver_msg(struct dev_data *dd, u16 msg_id, void *msg) +{ + struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; + struct dr_echo_request *echo_msg; + struct fu_echo_response *echo_response; + struct dr_chip_read *read_msg; + struct fu_chip_read_result *read_result; + struct dr_chip_write *write_msg; + struct dr_chip_access_method *chip_access_method_msg; + struct dr_delay *delay_msg; + struct fu_irqline_status *irqline_status; + struct dr_config_irq *config_irq_msg; + struct dr_config_input *config_input_msg; + struct dr_config_watchdog *config_watchdog_msg; + struct dr_input *input_msg; + struct dr_legacy_acceleration *legacy_acceleration_msg; + struct dr_handshake *handshake_msg; + struct fu_handshake_response *handshake_response; + struct dr_config_fw *config_fw_msg; + struct dr_sysfs_ack *sysfs_ack_msg; + struct dr_idle *idle_msg; + struct dr_tf_status *tf_status_msg; + u8 i, inp; + int ret, id; + u16 read_value[2] = { 0 }; + bool active_touch[INPUT_DEVICES][MAX_INPUT_EVENTS] = { {false} }; + + if (dd->expect_resume_ack && msg_id != DR_DECONFIG && + msg_id != DR_RESUME_ACK) + return false; + + switch (msg_id) { + case DR_ADD_MC_GROUP: + INFO("warning: deprecated dynamic mc group add request!"); + return false; + case DR_ECHO_REQUEST: + DRV_MSG_DBG("msg: %s", "DR_ECHO_REQUEST"); + echo_msg = msg; + echo_response = nl_alloc_attr(dd->outgoing_skb->data, + FU_ECHO_RESPONSE, + sizeof(*echo_response)); + if (echo_response == NULL) + goto alloc_attr_failure; + echo_response->cookie = echo_msg->cookie; + return true; + case DR_CHIP_READ: + DRV_MSG_DBG("msg: %s", "DR_CHIP_READ"); + read_msg = msg; + read_result = nl_alloc_attr(dd->outgoing_skb->data, + FU_CHIP_READ_RESULT, + sizeof(*read_result) + read_msg->length); + if (read_result == NULL) + goto alloc_attr_failure; + read_result->address = read_msg->address; + read_result->length = read_msg->length; + ret = dd->chip.read(dd, read_msg->address, read_result->data, + read_msg->length); + if (ret < 0) + ERROR("failed to read from chip (%d, %d, %d)", + read_msg->address, read_msg->length, ret); + return true; + case DR_CHIP_WRITE: + DRV_MSG_DBG("msg: %s", "DR_CHIP_WRITE"); + write_msg = msg; + if (write_msg->address == dd->irq_param[12] && + write_msg->data[0] == dd->irq_param[13]) { + ret = dd->chip.write(dd, write_msg->address, + write_msg->data, + write_msg->length); + if (ret < 0) + ERROR("failed to write chip (%d)", ret); + msleep(15); + ret = dd->chip.read(dd, dd->irq_param[0], + (u8 *)read_value, + sizeof(read_value)); + if (ret < 0) + ERROR("failed to read from chip (%d)", ret); + ret = dd->chip.write(dd, dd->irq_param[0], + (u8 *)read_value, + sizeof(read_value)); + return false; + } + if (write_msg->address == dd->irq_param[0]) { + if (((u16 *)write_msg->data)[0] == dd->irq_param[11]) { + ret = dd->chip.read(dd, dd->irq_param[0], + (u8 *)read_value, + sizeof(read_value)); + if (ret < 0) + ERROR("failed to read from chip (%d)", ret); + ret = dd->chip.write(dd, dd->irq_param[0], + (u8 *)read_value, + sizeof(read_value)); + return false; + } + read_value[0] = ((u16 *)write_msg->data)[0]; + ret = dd->chip.write(dd, write_msg->address, + (u8 *)read_value, + sizeof(read_value)); + return false; + } + ret = dd->chip.write(dd, write_msg->address, write_msg->data, + write_msg->length); + if (ret < 0) + ERROR("failed to write chip (%d, %d, %d)", + write_msg->address, write_msg->length, ret); + return false; + case DR_CHIP_RESET: + DRV_MSG_DBG("msg: %s", "DR_CHIP_RESET"); + pdata->reset(pdata, ((struct dr_chip_reset *)msg)->state); + return false; + case DR_GET_IRQLINE: + DRV_MSG_DBG("msg: %s", "DR_GET_IRQLINE"); + irqline_status = nl_alloc_attr(dd->outgoing_skb->data, + FU_IRQLINE_STATUS, + sizeof(*irqline_status)); + if (irqline_status == NULL) + goto alloc_attr_failure; + irqline_status->status = pdata->irq(pdata); + return true; + case DR_DELAY: + DRV_MSG_DBG("msg: %s", "DR_DELAY"); + delay_msg = msg; + if (delay_msg->period > 1000) + msleep(delay_msg->period / 1000); + usleep_range(delay_msg->period % 1000, + (delay_msg->period % 1000) + 10); + return false; + case DR_CHIP_ACCESS_METHOD: + DRV_MSG_DBG("msg: %s", "DR_CHIP_ACCESS_METHOD"); + chip_access_method_msg = msg; + ret = set_chip_access_method(dd, + chip_access_method_msg->method); + if (ret < 0) + ERROR("failed to set chip access method (%d) (%d)", + ret, chip_access_method_msg->method); + return false; + case DR_CONFIG_IRQ: + DRV_MSG_DBG("msg: %s", "DR_CONFIG_IRQ"); + config_irq_msg = msg; + if (config_irq_msg->irq_params > MAX_IRQ_PARAMS) { + ERROR("too many IRQ parameters"); + return false; + } + memcpy(dd->irq_param, config_irq_msg->irq_param, + config_irq_msg->irq_params * sizeof(dd->irq_param[0])); + if (dd->irq_registered) + return false; + dd->service_irq = service_irq; + ret = request_irq(dd->spi->irq, irq_handler, + (config_irq_msg->irq_edge == DR_IRQ_RISING_EDGE) ? + IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING, + pdata->nl_family, dd); + if (ret < 0) { + ERROR("failed to request IRQ (%d)", ret); + } else { + dd->irq_registered = true; + wake_up_process(dd->thread); + } + return false; + case DR_CONFIG_INPUT: + DRV_MSG_DBG("msg: %s", "DR_CONFIG_INPUT"); + config_input_msg = msg; + for (i = 0; i < INPUT_DEVICES; i++) + if (dd->input_dev[i] != NULL) + return false; + for (i = 0; i < INPUT_DEVICES; i++) { + dd->input_dev[i] = input_allocate_device(); + if (dd->input_dev[i] == NULL) { + ERROR("failed to allocate input device"); + continue; + } + snprintf(dd->input_phys, sizeof(dd->input_phys), + "%s/input%d", dev_name(&dd->spi->dev), i); + dd->input_dev[i]->name = pdata->nl_family; + dd->input_dev[i]->phys = dd->input_phys; + dd->input_dev[i]->id.bustype = BUS_SPI; + __set_bit(EV_SYN, dd->input_dev[i]->evbit); + __set_bit(EV_ABS, dd->input_dev[i]->evbit); + __set_bit(INPUT_PROP_DIRECT, dd->input_dev[i]->propbit); + if (i == (INPUT_DEVICES - 1)) { + __set_bit(EV_KEY, dd->input_dev[i]->evbit); + __set_bit(BTN_TOOL_RUBBER, + dd->input_dev[i]->keybit); + __set_bit(KEY_POWER, + dd->input_dev[i]->keybit); + } + if (pdata->mt_type_b_enabled && + input_mt_init_slots(dd->input_dev[i], + MAX_INPUT_EVENTS, 0)) { + ERROR("Error in initialising slots\n"); + input_free_device(dd->input_dev[i]); + dd->input_dev[i] = NULL; + continue; + } else { + input_set_abs_params(dd->input_dev[i], + ABS_MT_TRACKING_ID, 0, + MAX_INPUT_EVENTS, 0, 0); + } + + input_set_abs_params(dd->input_dev[i], + ABS_MT_POSITION_X, 0, + config_input_msg->x_range, 0, 0); + input_set_abs_params(dd->input_dev[i], + ABS_MT_POSITION_Y, 0, + config_input_msg->y_range, 0, 0); + input_set_abs_params(dd->input_dev[i], + ABS_MT_PRESSURE, 0, 0xFF, 0, 0); + + if (i == (INPUT_DEVICES - 1)) + input_set_abs_params(dd->input_dev[i], + ABS_MT_TOOL_TYPE, 0, + MT_TOOL_MAX, 0, 0); + else + input_set_abs_params(dd->input_dev[i], + ABS_MT_TOOL_TYPE, 0, + MT_TOOL_FINGER, 0, 0); + + ret = input_register_device(dd->input_dev[i]); + if (ret < 0) { + input_free_device(dd->input_dev[i]); + dd->input_dev[i] = NULL; + ERROR("failed to register input device"); + } + } +#if defined(CONFIG_FB) + dd->fb_notifier.notifier_call = fb_notifier_callback; + fb_register_client(&dd->fb_notifier); +#endif + /* create symlink */ + if (dd->parent == NULL && dd->input_dev[0] != NULL) { + dd->parent = dd->input_dev[0]->dev.kobj.parent; + ret = sysfs_create_link(dd->parent, &dd->spi->dev.kobj, + MAXIM_STI_NAME); + if (ret) { + ERROR("sysfs_create_link error\n"); + dd->parent = NULL; + } + } + return false; + case DR_CONFIG_WATCHDOG: + DRV_MSG_DBG("msg: %s", "DR_CONFIG_WATCHDOG"); + config_watchdog_msg = msg; + dd->fusion_process = (pid_t)config_watchdog_msg->pid; + return false; + case DR_DECONFIG: + DRV_MSG_DBG("msg: %s", "DR_DECONFIG"); + if (dd->irq_registered) { + free_irq(dd->spi->irq, dd); + dd->irq_registered = false; + } + stop_scan_canned(dd); + + if (!dd->input_no_deconfig) { + if (dd->parent != NULL) { + sysfs_remove_link( + dd->input_dev[0]->dev.kobj.parent, + MAXIM_STI_NAME); + dd->parent = NULL; + } + } + + if (!dd->input_no_deconfig) { + for (i = 0; i < INPUT_DEVICES; i++) { + if (dd->input_dev[i] == NULL) + continue; + input_unregister_device(dd->input_dev[i]); + dd->input_dev[i] = NULL; + } +#if defined(CONFIG_FB) + fb_unregister_client(&dd->fb_notifier); +#endif + } + dd->expect_resume_ack = false; + dd->eraser_active = false; + dd->legacy_acceleration = false; + dd->service_irq = service_irq; + dd->fusion_process = (pid_t)0; + dd->sysfs_update_type = DR_SYSFS_UPDATE_NONE; + return false; + case DR_INPUT: + DRV_MSG_DBG("msg: %s", "DR_INPUT"); + input_msg = msg; + if (input_msg->events == 0) { + if (dd->eraser_active) { + input_report_key( + dd->input_dev[INPUT_DEVICES - 1], + BTN_TOOL_RUBBER, 0); + dd->eraser_active = false; + } + for (i = 0; i < INPUT_DEVICES; i++) { + if (pdata->mt_type_b_enabled) + release_slot_events(dd, + active_touch[i], i); + else + input_mt_sync(dd->input_dev[i]); + + input_sync(dd->input_dev[i]); + } + } else { + for (i = 0; i < input_msg->events; i++) { + switch (input_msg->event[i].tool_type) { + case DR_INPUT_FINGER: + inp = 0; + id = input_msg->event[i].id; + if (pdata->mt_type_b_enabled) { + input_mt_slot( + dd->input_dev[inp], id); + input_mt_report_slot_state( + dd->input_dev[inp], + MT_TOOL_FINGER, 1); + active_touch[inp][id] = true; + } else { + input_report_abs( + dd->input_dev[inp], + ABS_MT_TOOL_TYPE, + MT_TOOL_FINGER); + } + break; + case DR_INPUT_STYLUS: + inp = INPUT_DEVICES - 1; + if (pdata->mt_type_b_enabled) { + input_mt_slot( + dd->input_dev[inp], id); + input_mt_report_slot_state( + dd->input_dev[inp], + MT_TOOL_PEN, 1); + active_touch[inp][id] = true; + } else { + input_report_abs( + dd->input_dev[inp], + ABS_MT_TOOL_TYPE, + MT_TOOL_PEN); + } + break; + case DR_INPUT_ERASER: + inp = INPUT_DEVICES - 1; + input_report_key(dd->input_dev[inp], + BTN_TOOL_RUBBER, 1); + dd->eraser_active = true; + break; + default: + inp = 0; + ERROR("invalid input tool type (%d)", + input_msg->event[i].tool_type); + break; + } + if (!pdata->mt_type_b_enabled) + input_report_abs(dd->input_dev[inp], + ABS_MT_TRACKING_ID, id); + input_report_abs(dd->input_dev[inp], + ABS_MT_POSITION_X, + input_msg->event[i].x); + input_report_abs(dd->input_dev[inp], + ABS_MT_POSITION_Y, + input_msg->event[i].y); + input_report_abs(dd->input_dev[inp], + ABS_MT_PRESSURE, + input_msg->event[i].z); + if (!pdata->mt_type_b_enabled) + input_mt_sync(dd->input_dev[inp]); + } + + if (pdata->mt_type_b_enabled) { + for (i = 0; i < INPUT_DEVICES; i++) + release_slot_events(dd, + active_touch[i], i); + } + + for (i = 0; i < INPUT_DEVICES; i++) + input_sync(dd->input_dev[i]); + } + return false; + case DR_RESUME_ACK: + DRV_MSG_DBG("msg: %s", "DR_RESUME_ACK"); + dd->expect_resume_ack = false; + if (dd->irq_registered + && !dd->gesture_en + ) + enable_irq(dd->spi->irq); + return false; + case DR_LEGACY_FWDL: + DRV_MSG_DBG("msg: %s", "DR_LEGACY_FWDL"); + ret = fw_request_load(dd); + if (ret < 0) + ERROR("firmware download failed (%d)", ret); + else + INFO("firmware download OK"); + return false; + case DR_LEGACY_ACCELERATION: + DRV_MSG_DBG("msg: %s", "DR_LEGACY_ACCELERATION"); + legacy_acceleration_msg = msg; + if (legacy_acceleration_msg->enable) { + dd->service_irq = service_irq_legacy_acceleration; + start_legacy_acceleration(dd); + dd->legacy_acceleration = true; + } else { + stop_legacy_acceleration(dd); + dd->legacy_acceleration = false; + dd->service_irq = service_irq; + } + return false; + case DR_HANDSHAKE: + DRV_MSG_DBG("msg: %s", "DR_HANDSHAKE"); + handshake_msg = msg; + dd->tf_ver = handshake_msg->tf_ver; + dd->chip_id = handshake_msg->chip_id; + dd->drv_ver = DRIVER_VERSION_NUM; + handshake_response = nl_alloc_attr(dd->outgoing_skb->data, + FU_HANDSHAKE_RESPONSE, + sizeof(*handshake_response)); + if (handshake_response == NULL) + goto alloc_attr_failure; + handshake_response->driver_ver = dd->drv_ver; + handshake_response->panel_id = panel_id; + handshake_response->driver_protocol = DRIVER_PROTOCOL; + return true; + case DR_CONFIG_FW: + DRV_MSG_DBG("msg: %s", "DR_CONFIG_FW"); + config_fw_msg = msg; + dd->fw_ver = config_fw_msg->fw_ver; + dd->fw_protocol = config_fw_msg->fw_protocol; + return false; + case DR_SYSFS_ACK: + sysfs_ack_msg = msg; + if (sysfs_ack_msg->type == DR_SYSFS_ACK_GLOVE) + complete(&dd->sysfs_ack_glove); + if (sysfs_ack_msg->type == DR_SYSFS_ACK_CHARGER) + complete(&dd->sysfs_ack_charger); + if (sysfs_ack_msg->type == DR_SYSFS_ACK_LCD_FPS) + complete(&dd->sysfs_ack_lcd_fps); + return false; + case DR_IDLE: + DRV_MSG_DBG("msg: %s", "DR_IDLE"); + idle_msg = msg; + dd->idle = !!(idle_msg->idle); + return false; + case DR_TF_STATUS: + tf_status_msg = msg; + dd->tf_status = tf_status_msg->tf_status; + return false; + default: + ERROR("unexpected message %d", msg_id); + return false; + } + +alloc_attr_failure: + ERROR("failed to allocate response for msg_id %d", msg_id); + return false; +} + +static int nl_process_msg(struct dev_data *dd, struct sk_buff *skb) +{ + struct nlattr *attr; + bool send_reply = false; + int ret = 0, ret2; + + /* process incoming message */ + attr = NL_ATTR_FIRST(skb->data); + for (; attr < NL_ATTR_LAST(skb->data); attr = NL_ATTR_NEXT(attr)) { + if (nl_process_driver_msg(dd, attr->nla_type, + NL_ATTR_VAL(attr, void))) + send_reply = true; + } + + /* send back reply if requested */ + if (send_reply) { + (void)skb_put(dd->outgoing_skb, + NL_SIZE(dd->outgoing_skb->data)); + if (NL_SEQ(skb->data) == 0) + ret = genlmsg_unicast(&init_net, + dd->outgoing_skb, + NETLINK_CB(skb).portid); + else + ret = genlmsg_multicast(&dd->nl_family, + dd->outgoing_skb, 0, + MC_FUSION, GFP_KERNEL); + if (ret < 0) + ERROR("could not reply to fusion (%d)", ret); + + /* allocate new outgoing skb */ + ret2 = nl_msg_new(dd, MC_FUSION); + if (ret2 < 0) + ERROR("could not allocate outgoing skb (%d)", ret2); + } + + /* free incoming message */ + kfree_skb(skb); + return ret; +} + +static int +nl_callback_driver(struct sk_buff *skb, struct genl_info *info) +{ + struct dev_data *dd; + struct sk_buff *skb2; + unsigned long flags; + + /* locate device structure */ + spin_lock_irqsave(&dev_lock, flags); + list_for_each_entry(dd, &dev_list, dev_list) + if (dd->nl_family.id == NL_TYPE(skb->data)) + break; + spin_unlock_irqrestore(&dev_lock, flags); + if (&dd->dev_list == &dev_list) + return -ENODEV; + if (!dd->nl_enabled) + return -EAGAIN; + + /* queue incoming skb and wake up processing thread */ + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2 == NULL) { + ERROR("failed to clone incoming skb"); + return -ENOMEM; + } else { + skb_queue_tail(&dd->incoming_skb_queue, skb2); + wake_up_process(dd->thread); + return 0; + } +} + +static int +nl_callback_fusion(struct sk_buff *skb, struct genl_info *info) +{ + struct dev_data *dd; + unsigned long flags; + + /* locate device structure */ + spin_lock_irqsave(&dev_lock, flags); + list_for_each_entry(dd, &dev_list, dev_list) + if (dd->nl_family.id == NL_TYPE(skb->data)) + break; + spin_unlock_irqrestore(&dev_lock, flags); + if (&dd->dev_list == &dev_list) + return -ENODEV; + if (!dd->nl_enabled) + return -EAGAIN; + + (void)genlmsg_multicast(&dd->nl_family, + skb_clone(skb, GFP_ATOMIC), 0, + MC_FUSION, GFP_ATOMIC); + return 0; +} + +/****************************************************************************\ +* Interrupt processing * +\****************************************************************************/ + +static unsigned long interrupt_count; +static irqreturn_t irq_handler(int irq, void *context) +{ + struct dev_data *dd = context; + + DBG("%s: interrupt #%lu", __func__, interrupt_count++); + + wake_up_process(dd->thread); + return IRQ_HANDLED; +} + +static void service_irq_legacy_acceleration(struct dev_data *dd) +{ + struct fu_async_data *async_data; + u16 len, rx_len = 0, offset = 0; + u16 buf[255], rx_limit = 250 * sizeof(u16); + int ret = 0, counter = 0; + + async_data = nl_alloc_attr(dd->outgoing_skb->data, FU_ASYNC_DATA, + sizeof(*async_data) + dd->irq_param[4] + + 2 * sizeof(u16)); + if (async_data == NULL) { + ERROR("can't add data to async IRQ buffer"); + return; + } + async_data->length = dd->irq_param[4] + 2 * sizeof(u16); + len = async_data->length; + async_data->address = 0; + + while (len > 0) { + rx_len = (len > rx_limit) ? rx_limit : len; + ret = spi_read_123(dd, 0x0000, (u8 *)&buf, + rx_len + 4 * sizeof(u16), false); + if (ret < 0) + break; + + if (buf[3] == 0xBABE) { + dd->legacy_acceleration = false; + dd->service_irq = service_irq; + nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, + dd->nl_seq - 1, MC_FUSION); + return; + } + + if (rx_limit == rx_len) + usleep_range(200, 300); + + if (buf[0] == 0x6060) { + ERROR("data not ready"); + start_legacy_acceleration_canned(dd); + ret = -EBUSY; + break; + } else if (buf[0] == 0x8070) { + if (buf[1] == dd->irq_param[1] || + buf[1] == dd->irq_param[2]) + async_data->address = buf[1]; + + if (async_data->address + + offset / sizeof(u16) != buf[1]) { + ERROR("sequence number incorrect %04X", buf[1]); + start_legacy_acceleration_canned(dd); + ret = -EBUSY; + break; + } + } + counter++; + memcpy(async_data->data + offset, buf + 4, rx_len); + offset += rx_len; + len -= rx_len; + } + async_data->status = *(buf + rx_len / sizeof(u16) + 2); + + if (ret < 0) { + ERROR("can't read IRQ buffer (%d)", ret); + nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, + dd->nl_seq - 1, MC_FUSION); + } else { + (void)skb_put(dd->outgoing_skb, + NL_SIZE(dd->outgoing_skb->data)); + ret = genlmsg_multicast(&dd->nl_family, dd->outgoing_skb, 0, + MC_FUSION, GFP_KERNEL); + if (ret < 0) { + ERROR("can't send IRQ buffer %d", ret); + msleep(300); + if (++dd->send_fail_count >= 10 && + dd->fusion_process != (pid_t)0) { + (void)kill_pid( + find_get_pid(dd->fusion_process), + SIGKILL, 1); + wake_up_process(dd->thread); + } + } else { + dd->send_fail_count = 0; + } + ret = nl_msg_new(dd, MC_FUSION); + if (ret < 0) + ERROR("could not allocate outgoing skb (%d)", ret); + } +} + +static void service_irq(struct dev_data *dd) +{ + struct fu_async_data *async_data; + u16 status, test, xbuf, value; + u16 address[2] = { 0 }; + u16 clear[2] = { 0 }; + bool read_buf[2] = {true, false}; + int ret, ret2; + + DBG("%s", __func__); + ret = dd->chip.read(dd, dd->irq_param[0], (u8 *)&status, + sizeof(status)); + if (ret < 0) { + ERROR("can't read IRQ status (%d)", ret); + return; + } + DBG("%s: status = 0x%04X", __func__, status); + + if (dd->gesture_reset) { + read_buf[0] = false; + clear[0] = dd->irq_param[10]; + status = dd->irq_param[10]; + dd->gesture_reset = false; + } else + if (status & dd->irq_param[10]) { + read_buf[0] = false; + clear[0] = dd->irq_param[10]; + } else if (status & dd->irq_param[18]) { + (void)dd->chip.read(dd, dd->irq_param[22], (u8 *)&value, + sizeof(value)); + if (value != dd->irq_param[23]) { + ERROR("unrecognized value [%#x] => %#x", + dd->irq_param[22], value); + clear[0] = dd->irq_param[18]; + ret2 = dd->chip.write(dd, dd->irq_param[0], + (u8 *)clear, sizeof(clear)); + return; + } + + test = status & (dd->irq_param[6] | dd->irq_param[7]); + + if (test == 0) + return; + else if (test == (dd->irq_param[6] | dd->irq_param[7])) + xbuf = ((status & dd->irq_param[5]) == 0) ? 0 : 1; + else if (test == dd->irq_param[6]) + xbuf = 0; + else if (test == dd->irq_param[7]) + xbuf = 1; + else { + ERROR("unexpected IRQ handler case 0x%04X ", status); + return; + } + + read_buf[1] = true; + address[1] = xbuf ? dd->irq_param[2] : dd->irq_param[1]; + address[0] = dd->irq_param[3]; + clear[0] = xbuf ? dd->irq_param[7] : dd->irq_param[6]; + clear[0] |= dd->irq_param[8]; + clear[0] |= dd->irq_param[18]; + + value = dd->irq_param[17]; + (void)dd->chip.write(dd, dd->irq_param[16], (u8 *)&value, + sizeof(value)); + } else if (status & dd->irq_param[9]) { + test = status & (dd->irq_param[6] | dd->irq_param[7]); + + if (test == (dd->irq_param[6] | dd->irq_param[7])) + xbuf = ((status & dd->irq_param[5]) != 0) ? 0 : 1; + else if (test == dd->irq_param[6]) + xbuf = 0; + else if (test == dd->irq_param[7]) + xbuf = 1; + else { + ERROR("unexpected IRQ handler case"); + return; + } + read_buf[1] = true; + address[1] = xbuf ? dd->irq_param[2] : dd->irq_param[1]; + + address[0] = dd->irq_param[3]; + clear[0] = dd->irq_param[6] | dd->irq_param[7] | + dd->irq_param[8] | dd->irq_param[9]; + value = dd->irq_param[13]; + (void)dd->chip.write(dd, dd->irq_param[12], (u8 *)&value, + sizeof(value)); + } else { + test = status & (dd->irq_param[6] | dd->irq_param[7]); + + if (test == 0) + return; + else if (test == (dd->irq_param[6] | dd->irq_param[7])) + xbuf = ((status & dd->irq_param[5]) == 0) ? 0 : 1; + else if (test == dd->irq_param[6]) + xbuf = 0; + else if (test == dd->irq_param[7]) + xbuf = 1; + else { + ERROR("unexpected IRQ handler case 0x%04X ",status); + return; + } + + address[0] = xbuf ? dd->irq_param[2] : dd->irq_param[1]; + clear[0] = xbuf ? dd->irq_param[7] : dd->irq_param[6]; + clear[0] |= dd->irq_param[8]; + } + + async_data = nl_alloc_attr(dd->outgoing_skb->data, FU_ASYNC_DATA, + sizeof(*async_data) + dd->irq_param[4]); + if (async_data == NULL) { + ERROR("can't add data to async IRQ buffer 1"); + return; + } + + async_data->status = status; + if (read_buf[0]) { + async_data->address = address[0]; + async_data->length = dd->irq_param[4]; + ret = dd->chip.read(dd, address[0], async_data->data, + dd->irq_param[4]); + } + + if (read_buf[1] && ret == 0) { + async_data = nl_alloc_attr(dd->outgoing_skb->data, + FU_ASYNC_DATA, + sizeof(*async_data) + + dd->irq_param[4]); + if (async_data == NULL) { + ERROR("can't add data to async IRQ buffer 2"); + nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, + dd->nl_seq - 1, MC_FUSION); + return; + } + async_data->address = address[1]; + async_data->length = dd->irq_param[4]; + async_data->status = status; + ret = dd->chip.read(dd, address[1], async_data->data, + dd->irq_param[4]); + } + + ret2 = dd->chip.write(dd, dd->irq_param[0], (u8 *)clear, + sizeof(clear)); + if (ret2 < 0) + ERROR("can't clear IRQ status (%d)", ret2); + + if (ret < 0) { + ERROR("can't read IRQ buffer (%d)", ret); + nl_msg_init(dd->outgoing_skb->data, dd->nl_family.id, + dd->nl_seq - 1, MC_FUSION); + } else { + DBG("%s: sending buffer", __func__); + (void)skb_put(dd->outgoing_skb, + NL_SIZE(dd->outgoing_skb->data)); + ret = genlmsg_multicast(&dd->nl_family, dd->outgoing_skb, 0, + MC_FUSION, GFP_KERNEL); + if (ret < 0) { + ERROR("can't send IRQ buffer %d", ret); + msleep(300); + if (read_buf[0] == false || + (++dd->send_fail_count >= 10 && + dd->fusion_process != (pid_t)0)) { + (void)kill_pid( + find_get_pid(dd->fusion_process), + SIGKILL, 1); + wake_up_process(dd->thread); + } + } else { + dd->send_fail_count = 0; + } + ret = nl_msg_new(dd, MC_FUSION); + if (ret < 0) + ERROR("could not allocate outgoing skb (%d)", ret); + } +} + +/****************************************************************************\ +* Processing thread * +\****************************************************************************/ + +static int processing_thread(void *arg) +{ + struct dev_data *dd = arg; + struct maxim_sti_pdata *pdata = dd->spi->dev.platform_data; + struct sk_buff *skb; + char *argv[] = { pdata->touch_fusion, "daemon", + pdata->nl_family, + dd->config_file, NULL }; + int ret = 0, ret2 = 0; + bool fusion_dead; + + DRV_PT_DBG("%s started", __FUNCTION__); + sched_setscheduler(current, SCHED_FIFO, &dd->thread_sched); + + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + DRV_PT_DBG("Begin"); + + /* ensure that we have outgoing skb */ + if (dd->outgoing_skb == NULL) + if (nl_msg_new(dd, MC_FUSION) < 0) { + schedule(); + continue; + } + + /* priority 1: start up fusion process */ + if (dd->fusion_process != (pid_t)0 && get_pid_task( + find_get_pid(dd->fusion_process), + PIDTYPE_PID) == NULL) { + DRV_PT_DBG("Re-Starting TF"); + stop_scan_canned(dd); + dd->start_fusion = true; + dd->fusion_process = (pid_t)0; + dd->input_no_deconfig = true; + } + if (dd->start_fusion) { + DRV_PT_DBG("Spawning TF"); + do { + ret = call_usermodehelper(argv[0], argv, NULL, + UMH_WAIT_EXEC); + if (ret != 0) + msleep(100); + } while (ret != 0 && !kthread_should_stop()); + + if (ret == 0) { + /* power-up and reset-high */ + ret = regulator_control(dd, true); + if (ret < 0) + ERROR("failed to enable regulators"); + + usleep_range(300, 400); + pdata->reset(pdata, 1); + + dd->start_fusion = false; + dd->tf_status |= TF_STATUS_BUSY; + } + } + if (kthread_should_stop()) + break; + + /* priority 2: process pending Netlink messages */ + while ((skb = skb_dequeue(&dd->incoming_skb_queue)) != NULL) { + if (kthread_should_stop()) + break; + if (nl_process_msg(dd, skb) < 0) + skb_queue_purge(&dd->incoming_skb_queue); + } + if (kthread_should_stop()) + break; + + /* priority 3: suspend/resume */ + if (dd->suspend_in_progress && !(dd->tf_status & TF_STATUS_BUSY)) { + if (dd->irq_registered) { + disable_irq(dd->spi->irq); + if (dd->gesture_en) { + enable_gesture(dd); + enable_irq(dd->spi->irq); + } else + if (!dd->idle) + stop_scan_canned(dd); + else + stop_idle_scan(dd); + } + + dd->suspended = true; + complete(&dd->suspend_resume); + dd->expect_resume_ack = true; + dd->gesture_reset = false; + while (!dd->resume_in_progress) { + /* the line below is a MUST */ + set_current_state(TASK_INTERRUPTIBLE); + if (kthread_should_stop()) + break; + if (dd->gesture_en && dd->irq_registered && + pdata->irq(pdata) == 0) + gesture_wake_detect(dd); + schedule(); + } + if (dd->irq_registered) { + if (dd->gesture_en) + finish_gesture(dd); + else + if (!dd->idle) + start_scan_canned(dd); + } + dd->suspended = false; + dd->resume_in_progress = false; + dd->suspend_in_progress = false; + complete(&dd->suspend_resume); + + fusion_dead = false; + dd->send_fail_count = 0; + do { + if (dd->fusion_process != (pid_t)0 && + get_pid_task(find_get_pid( + dd->fusion_process), + PIDTYPE_PID) == NULL) { + fusion_dead = true; + break; + } + ret = nl_add_attr(dd->outgoing_skb->data, + FU_RESUME, NULL, 0); + if (ret < 0) { + ERROR("can't add data to resume " \ + "buffer"); + nl_msg_init(dd->outgoing_skb->data, + dd->nl_family.id, + dd->nl_seq - 1, MC_FUSION); + msleep(100); + continue; + } + (void)skb_put(dd->outgoing_skb, + NL_SIZE(dd->outgoing_skb->data)); + ret = genlmsg_multicast(&dd->nl_family, + dd->outgoing_skb, 0, MC_FUSION, + GFP_KERNEL); + if (ret < 0) { + ERROR("can't send resume message %d", + ret); + msleep(100); + if (++dd->send_fail_count >= 10) { + fusion_dead = true; + ret = 0; + } + } + ret2 = nl_msg_new(dd, MC_FUSION); + if (ret2 < 0) + ERROR("could not allocate outgoing " \ + "skb (%d)", ret2); + } while (ret != 0 && !kthread_should_stop()); + + if (!ret && dd->send_fail_count>= 10 && + dd->fusion_process != (pid_t)0) + (void)kill_pid(find_get_pid(dd->fusion_process), + SIGKILL, 1); + + dd->send_fail_count = 0; + if (fusion_dead) + continue; + } + if (kthread_should_stop()) + break; + + /* priority 4: update /sys/device information */ + if (dd->sysfs_update_type != DR_SYSFS_UPDATE_NONE) + if (!dd->expect_resume_ack) { + if (send_sysfs_info(dd) < 0) + ERROR( + "Can not send sysfs update to touch fusion" + ); + dd->sysfs_update_type = DR_SYSFS_UPDATE_NONE; + } + + /* priority 5: service interrupt */ + if (dd->irq_registered && !dd->expect_resume_ack && + ((pdata->irq(pdata) == 0) + || dd->gesture_reset + )) + dd->service_irq(dd); + if (dd->irq_registered && !dd->expect_resume_ack && + pdata->irq(pdata) == 0) + continue; + + DRV_PT_DBG("End"); + /* nothing more to do; sleep */ + schedule(); + } + + return 0; +} + +/****************************************************************************\ +* SYSFS interface * +\****************************************************************************/ + +static ssize_t chip_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->chip_id); +} + +static ssize_t fw_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->fw_ver); +} + +static ssize_t driver_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->drv_ver); +} + +static ssize_t tf_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "0x%04X\n", dd->tf_ver); +} + +static ssize_t panel_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%04X\n", panel_id); +} + +static ssize_t glove_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "%u\n", dd->glove_enabled); +} + +static ssize_t glove_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + u32 temp; + unsigned long time_left; + + mutex_lock(&dd->sysfs_update_mutex); + + if (sscanf(buf, "%u", &temp) != 1) { + ERROR("Invalid (%s)", buf); + return -EINVAL; + } + dd->glove_enabled = !!temp; + set_bit(DR_SYSFS_UPDATE_BIT_GLOVE, &(dd->sysfs_update_type)); + wake_up_process(dd->thread); + time_left = wait_for_completion_timeout(&dd->sysfs_ack_glove, 3*HZ); + + mutex_unlock(&dd->sysfs_update_mutex); + + if (time_left > 0) + return strnlen(buf, PAGE_SIZE); + else + return -EAGAIN; +} + +static ssize_t gesture_wakeup_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "%u\n", dd->gesture_en); +} + +static ssize_t gesture_wakeup_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + u32 temp; + + if (sscanf(buf, "%u", &temp) != 1) { + ERROR("Invalid (%s)", buf); + return -EINVAL; + } + dd->gesture_en = temp; + + return strnlen(buf, PAGE_SIZE); +} + +static ssize_t charger_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "%u\n", dd->charger_mode_en); +} + +static ssize_t charger_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + u32 temp; + unsigned long time_left; + + mutex_lock(&dd->sysfs_update_mutex); + + if (sscanf(buf, "%u", &temp) != 1) { + ERROR("Invalid (%s)", buf); + return -EINVAL; + } + if (temp > DR_WIRELESS_CHARGER) { + ERROR("Charger Mode Value Out of Range"); + return -EINVAL; + } + dd->charger_mode_en = temp; + set_bit(DR_SYSFS_UPDATE_BIT_CHARGER, &(dd->sysfs_update_type)); + wake_up_process(dd->thread); + time_left = wait_for_completion_timeout(&dd->sysfs_ack_charger, 3*HZ); + + mutex_unlock(&dd->sysfs_update_mutex); + + if (time_left > 0) + return strnlen(buf, PAGE_SIZE); + else + return -EAGAIN; +} + +static ssize_t info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "chip id: 0x%04X driver ver: 0x%04X " + "fw ver: 0x%04X panel id: 0x%04X " + "tf ver: 0x%04X\n", + dd->chip_id, dd->drv_ver, dd->fw_ver, + panel_id, dd->tf_ver); +} + +static ssize_t screen_status_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + u32 temp; + + if (sscanf(buf, "%u", &temp) != 1) { + ERROR("Invalid (%s)", buf); + return -EINVAL; + } + if (temp) + resume(dev); + else + suspend(dev); + + return strnlen(buf, PAGE_SIZE); +} + +static ssize_t tf_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "0x%08X", dd->tf_status); +} + +static ssize_t default_loaded_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "%d", + (dd->tf_status & TF_STATUS_DEFAULT_LOADED)); +} + +static ssize_t lcd_fps_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + return snprintf(buf, PAGE_SIZE, "%u\n", dd->lcd_fps); +} + +static ssize_t lcd_fps_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + struct dev_data *dd = spi_get_drvdata(spi); + u32 temp; + unsigned long time_left; + + mutex_lock(&dd->sysfs_update_mutex); + + if (sscanf(buf, "%u", &temp) != 1) { + ERROR("Invalid (%s)", buf); + return -EINVAL; + } + + dd->lcd_fps = temp; + set_bit(DR_SYSFS_UPDATE_BIT_LCD_FPS, &(dd->sysfs_update_type)); + wake_up_process(dd->thread); + time_left = wait_for_completion_timeout(&dd->sysfs_ack_lcd_fps, 3*HZ); + + mutex_unlock(&dd->sysfs_update_mutex); + + if (time_left > 0) + return strnlen(buf, PAGE_SIZE); + else + return -EAGAIN; +} + +static struct device_attribute dev_attrs[] = { + __ATTR(fw_ver, S_IRUGO, fw_ver_show, NULL), + __ATTR(chip_id, S_IRUGO, chip_id_show, NULL), + __ATTR(tf_ver, S_IRUGO, tf_ver_show, NULL), + __ATTR(driver_ver, S_IRUGO, driver_ver_show, NULL), + __ATTR(panel_id, S_IRUGO, panel_id_show, NULL), + __ATTR(glove_en, S_IRUGO | S_IWUSR, glove_show, glove_store), + __ATTR(gesture_wakeup, S_IRUGO | S_IWUSR, gesture_wakeup_show, gesture_wakeup_store), + __ATTR(charger_mode, S_IRUGO | S_IWUSR, charger_mode_show, charger_mode_store), + __ATTR(info, S_IRUGO, info_show, NULL), + __ATTR(screen_status, S_IWUSR, NULL, screen_status_store), + __ATTR(tf_status, S_IRUGO, tf_status_show, NULL), + __ATTR(default_loaded, S_IRUGO, default_loaded_show, NULL), + __ATTR(lcd_fps, S_IRUGO | S_IWUSR, lcd_fps_show, lcd_fps_store), +}; + +static int create_sysfs_entries(struct dev_data *dd) +{ + int i, ret = 0; + + for (i = 0; i < ARRAY_SIZE(dev_attrs); i++) { + ret = device_create_file(&dd->spi->dev, &dev_attrs[i]); + if (ret) { + for (; i >= 0; --i) { + device_remove_file(&dd->spi->dev, + &dev_attrs[i]); + dd->sysfs_created--; + } + break; + } + dd->sysfs_created++; + } + return ret; +} + +static void remove_sysfs_entries(struct dev_data *dd) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dev_attrs); i++) + if (dd->sysfs_created && dd->sysfs_created--) + device_remove_file(&dd->spi->dev, &dev_attrs[i]); +} + +static int send_sysfs_info(struct dev_data *dd) +{ + struct fu_sysfs_info *sysfs_info; + int ret, ret2; + + sysfs_info = nl_alloc_attr(dd->outgoing_skb->data, + FU_SYSFS_INFO, + sizeof(*sysfs_info)); + if (sysfs_info == NULL) { + ERROR("Can't allocate sysfs_info"); + return -EAGAIN; + } + sysfs_info->type = dd->sysfs_update_type; + + sysfs_info->glove_value = dd->glove_enabled; + sysfs_info->charger_value = dd->charger_mode_en; + sysfs_info->lcd_fps_value = dd->lcd_fps; + + (void)skb_put(dd->outgoing_skb, + NL_SIZE(dd->outgoing_skb->data)); + ret = genlmsg_multicast(&dd->nl_family, + dd->outgoing_skb, 0, + MC_FUSION, GFP_KERNEL); + if (ret < 0) + ERROR("could not reply to fusion (%d)", ret); + + /* allocate new outgoing skb */ + ret2 = nl_msg_new(dd, MC_FUSION); + if (ret2 < 0) + ERROR("could not allocate outgoing skb (%d)", ret2); + + return 0; +} + +/****************************************************************************\ +* Driver initialization * +\****************************************************************************/ + +static int probe(struct spi_device *spi) +{ + struct maxim_sti_pdata *pdata = spi->dev.platform_data; + struct dev_data *dd; + unsigned long flags; + int ret, i; + void *ptr; + +#ifdef CONFIG_OF + if (pdata == NULL && spi->dev.of_node) { + pdata = devm_kzalloc(&spi->dev, + sizeof(struct maxim_sti_pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&spi->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + ret = maxim_parse_dt(&spi->dev, pdata); + if (ret) + goto of_parse_failure; + spi->dev.platform_data = pdata; + } +#endif + + pdata->init = maxim_sti_gpio_init; + pdata->reset = maxim_sti_gpio_reset; + pdata->irq = maxim_sti_gpio_irq; + + pdata->nl_mc_groups = DEF_NL_MC_GROUPS; + pdata->chip_access_method = DEF_CHIP_ACC_METHOD; + pdata->tx_buf_size = BUF_SIZE; + pdata->rx_buf_size = BUF_SIZE; + pdata->nl_family = TOUCH_FUSION; + + dev_dbg(&spi->dev, "maxim_sti,reset-gpio: %d", pdata->gpio_reset); + dev_dbg(&spi->dev, "maxim_sti,irq-gpio: %d", pdata->gpio_irq); + dev_dbg(&spi->dev, "maxim_sti,nl_mc_groups: %d", pdata->nl_mc_groups); + dev_dbg(&spi->dev, "maxim_sti,chip_access_method: %d", pdata->chip_access_method); + dev_dbg(&spi->dev, "maxim_sti,default_reset_state: %d", pdata->default_reset_state); + dev_dbg(&spi->dev, "maxim_sti,tx_buf_size: %d", pdata->tx_buf_size); + dev_dbg(&spi->dev, "maxim_sti,rx_buf_size: %d", pdata->rx_buf_size); + dev_dbg(&spi->dev, "maxim_sti,touch_fusion: %s", pdata->touch_fusion); + dev_dbg(&spi->dev, "maxim_sti,config_file: %s", pdata->config_file); + dev_dbg(&spi->dev, "maxim_sti,nl_family: %s", pdata->nl_family); + dev_dbg(&spi->dev, "maxim_sti,fw_name: %s", pdata->fw_name); + + /* validate platform data */ + if (pdata == NULL || pdata->init == NULL || pdata->reset == NULL || + pdata->irq == NULL || pdata->touch_fusion == NULL || + pdata->config_file == NULL || pdata->nl_family == NULL || + GENL_CHK(pdata->nl_family) || + pdata->nl_mc_groups < MC_GROUPS || + pdata->chip_access_method == 0 || + pdata->chip_access_method > ARRAY_SIZE(chip_access_methods) || + pdata->default_reset_state > 1 || + pdata->tx_buf_size == 0 || + pdata->rx_buf_size == 0) { + dev_err(&spi->dev, "invalid platform data!"); + ret = -EINVAL; + goto of_parse_failure; + } + + /* device context: allocate structure */ + dd = kzalloc(sizeof(*dd) + + sizeof(*dd->nl_ops) * pdata->nl_mc_groups + + sizeof(*dd->nl_mc_groups) * pdata->nl_mc_groups, + GFP_KERNEL); + if (dd == NULL) { + dev_err(&spi->dev, "cannot allocate memory!"); + ret = -ENOMEM; + goto of_parse_failure; + } + + /* device context: set up dynamic allocation pointers */ + ptr = (void *)dd + sizeof(*dd); + dd->nl_ops = ptr; + ptr += sizeof(*dd->nl_ops) * pdata->nl_mc_groups; + dd->nl_mc_groups = ptr; + + /* device context: initialize structure members */ + spi_set_drvdata(spi, dd); + spi->bits_per_word = 16; + spi_setup(spi); + dd->spi = spi; + dd->nl_seq = 1; + init_completion(&dd->suspend_resume); + + /* allocate DMA memory */ + dd->tx_buf = kzalloc(pdata->tx_buf_size + pdata->rx_buf_size, + GFP_KERNEL | GFP_DMA); + if (dd->tx_buf == NULL) { + dev_err(&spi->dev, "cannot allocate DMA accesible memory"); + ret = -ENOMEM; + goto buf_alloc_failure; + } + dd->rx_buf = dd->tx_buf + pdata->tx_buf_size; + memset(dd->tx_buf, 0xFF, pdata->tx_buf_size); + (void)set_chip_access_method(dd, pdata->chip_access_method); + + /* initialize regulators */ + regulator_init(dd); + + /* initialize platform */ + + /* Get pinctrl if target uses pinctrl */ + dd->ts_pinctrl = devm_pinctrl_get(&(spi->dev)); + if (IS_ERR_OR_NULL(dd->ts_pinctrl)) { + ret = PTR_ERR(dd->ts_pinctrl); + pr_debug("Target does not use pinctrl %d\n", ret); + } + + dd->pinctrl_state_active + = pinctrl_lookup_state(dd->ts_pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(dd->pinctrl_state_active)) { + ret = PTR_ERR(dd->pinctrl_state_active); + pr_debug("Can not lookup active pinstate %d\n", ret); + } + + dd->pinctrl_state_suspended + = pinctrl_lookup_state(dd->ts_pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(dd->pinctrl_state_suspended)) { + ret = PTR_ERR(dd->pinctrl_state_suspended); + pr_debug("Can not lookup active pinstate %d\n", ret); + } + ret = pinctrl_select_state(dd->ts_pinctrl, + dd->pinctrl_state_active); + if (ret < 0) + pr_debug("pinctrl set active fail\n"); + + + ret = pdata->init(pdata, true); + if (ret < 0) + goto pinit_failure; + + /* Netlink: initialize incoming skb queue */ + skb_queue_head_init(&dd->incoming_skb_queue); + + /* Netlink: register GENL family */ + dd->nl_family.id = GENL_ID_GENERATE; + dd->nl_family.version = NL_FAMILY_VERSION; + GENL_COPY(dd->nl_family.name, pdata->nl_family); + + /* Netlink: register family ops */ + for (i = 0; i < MC_GROUPS; i++) { + dd->nl_ops[i].cmd = i; + dd->nl_ops[i].doit = nl_callback_noop; + } + dd->nl_ops[MC_DRIVER].doit = nl_callback_driver; + dd->nl_ops[MC_FUSION].doit = nl_callback_fusion; + dd->nl_ops[MC_EVENT_BROADCAST].doit = nl_callback_noop; + + /* Netlink: register family multicast groups */ + GENL_COPY(dd->nl_mc_groups[MC_DRIVER].name, MC_DRIVER_NAME); + GENL_COPY(dd->nl_mc_groups[MC_FUSION].name, MC_FUSION_NAME); + GENL_COPY(dd->nl_mc_groups[MC_EVENT_BROADCAST].name, + MC_EVENT_BROADCAST_NAME); + ret = _genl_register_family_with_ops_grps(&dd->nl_family, + dd->nl_ops, + MC_GROUPS, + dd->nl_mc_groups, + MC_GROUPS); + if (ret < 0) { + dev_err(&spi->dev, "error registering nl_mc_group"); + goto nl_family_failure; + } + dd->nl_mc_group_count = MC_GROUPS; + + /* Netlink: pre-allocate outgoing skb */ + ret = nl_msg_new(dd, MC_FUSION); + if (ret < 0) { + dev_err(&spi->dev, "error alloc msg"); + goto nl_failure; + } + + /* start processing thread */ + dd->thread_sched.sched_priority = MAX_USER_RT_PRIO / 2; + dd->thread = kthread_run(processing_thread, dd, pdata->nl_family); + if (IS_ERR(dd->thread)) { + dev_err(&spi->dev, "error creating kthread!"); + ret = PTR_ERR(dd->thread); + goto kthread_failure; + } + + /* Netlink: ready to start processing incoming messages */ + dd->nl_enabled = true; + + /* No sysfs update initially */ + dd->sysfs_update_type = DR_SYSFS_UPDATE_NONE; + init_completion(&dd->sysfs_ack_glove); + init_completion(&dd->sysfs_ack_charger); + init_completion(&dd->sysfs_ack_lcd_fps); + mutex_init(&dd->sysfs_update_mutex); + + /* add us to the devices list */ + spin_lock_irqsave(&dev_lock, flags); + list_add_tail(&dd->dev_list, &dev_list); + spin_unlock_irqrestore(&dev_lock, flags); + + ret = create_sysfs_entries(dd); + if (ret) { + dev_err(&spi->dev, "failed to create sysfs file"); + goto sysfs_failure; + } + snprintf(dd->config_file, CFG_FILE_NAME_MAX, pdata->config_file, panel_id); + dev_dbg(&spi->dev, "configuration file: %s", dd->config_file); + + /* start up Touch Fusion */ + dd->start_fusion = true; + wake_up_process(dd->thread); + dev_info(&spi->dev, "%s: driver loaded; version %s; release date %s", + dd->nl_family.name, DRIVER_VERSION, DRIVER_RELEASE); + + return 0; + +sysfs_failure: + dd->nl_enabled = false; + spin_lock_irqsave(&dev_lock, flags); + list_del(&dd->dev_list); + spin_unlock_irqrestore(&dev_lock, flags); + (void)kthread_stop(dd->thread); +kthread_failure: + if (dd->outgoing_skb) + kfree_skb(dd->outgoing_skb); +nl_failure: + genl_unregister_family(&dd->nl_family); +nl_family_failure: + pdata->init(pdata, false); +pinit_failure: + regulator_uninit(dd); + kfree(dd->tx_buf); +buf_alloc_failure: + spi_set_drvdata(spi, NULL); + kfree(dd); +of_parse_failure: + spi->dev.platform_data = NULL; + devm_kfree(&spi->dev, pdata); + return ret; +} + +static int remove(struct spi_device *spi) +{ + struct maxim_sti_pdata *pdata = spi->dev.platform_data; + struct dev_data *dd = spi_get_drvdata(spi); + unsigned long flags; + u8 i; + + dev_dbg(&spi->dev, "remove"); + + /* BEWARE: tear-down sequence below is carefully staged: */ + /* 1) first the feeder of Netlink messages to the processing thread */ + /* is turned off */ + /* 2) then the thread itself is shut down */ + /* 3) then Netlink family is torn down since no one would be using */ + /* it at this point */ + /* 4) above step (3) insures that all Netlink senders are */ + /* definitely gone and it is safe to free up outgoing skb buffer */ + /* and incoming skb queue */ + dev_dbg(&spi->dev, "stopping thread.."); + dd->nl_enabled = false; + (void)kthread_stop(dd->thread); + dev_dbg(&spi->dev, "kthread stopped."); + genl_unregister_family(&dd->nl_family); + if (dd->outgoing_skb) + kfree_skb(dd->outgoing_skb); + skb_queue_purge(&dd->incoming_skb_queue); + + if (dd->fusion_process != (pid_t)0) + (void)kill_pid(find_get_pid(dd->fusion_process), SIGKILL, 1); + + dev_dbg(&spi->dev, "removing sysfs entries.."); + if (dd->parent != NULL) + sysfs_remove_link(dd->input_dev[0]->dev.kobj.parent, + MAXIM_STI_NAME); + remove_sysfs_entries(dd); + + dev_dbg(&spi->dev, "unregister input devices.."); + for (i = 0; i < INPUT_DEVICES; i++) + if (dd->input_dev[i]) + input_unregister_device(dd->input_dev[i]); +#if defined(CONFIG_FB) + fb_unregister_client(&dd->fb_notifier); +#endif + + if (dd->irq_registered) { + dev_dbg(&spi->dev, "disabling interrupt"); + disable_irq(dd->spi->irq); + dev_dbg(&spi->dev, "stopping scan"); + stop_scan_canned(dd); + free_irq(dd->spi->irq, dd); + } + + spin_lock_irqsave(&dev_lock, flags); + list_del(&dd->dev_list); + spin_unlock_irqrestore(&dev_lock, flags); + + pdata->reset(pdata, 0); + usleep_range(100, 120); + regulator_control(dd, false); + pdata->init(pdata, false); + regulator_uninit(dd); + + dev_dbg(&spi->dev, "detaching from spi.."); + spi_set_drvdata(spi, NULL); + kzfree(dd); + spi->dev.platform_data = NULL; + devm_kfree(&spi->dev, pdata); + + dev_info(&spi->dev, "driver unloaded"); + return 0; +} + +static void shutdown(struct spi_device *spi) +{ + struct maxim_sti_pdata *pdata = spi->dev.platform_data; + struct dev_data *dd = spi_get_drvdata(spi); + + if (dd->parent != NULL) + sysfs_remove_link(dd->input_dev[0]->dev.kobj.parent, + MAXIM_STI_NAME); + remove_sysfs_entries(dd); + + pdata->reset(pdata, 0); + usleep_range(100, 120); + regulator_control(dd, false); +} + +/****************************************************************************\ +* Module initialization * +\****************************************************************************/ + +static const struct spi_device_id id[] = { + { MAXIM_STI_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(spi, id); + +#ifdef CONFIG_OF +static struct of_device_id maxim_match_table[] = { + { .compatible = "maxim,maxim_sti",}, + { }, +}; +#endif + +static struct spi_driver driver = { + .probe = probe, + .remove = remove, + .shutdown = shutdown, + .id_table = id, + .driver = { + .name = MAXIM_STI_NAME, +#ifdef CONFIG_OF + .of_match_table = maxim_match_table, +#endif + .owner = THIS_MODULE, +#ifdef CONFIG_PM_SLEEP + .pm = &pm_ops, +#endif + }, +}; + +static int maxim_sti_init(void) +{ + INIT_LIST_HEAD(&dev_list); + spin_lock_init(&dev_lock); + return spi_register_driver(&driver); +} + +static void maxim_sti_exit(void) +{ + spi_unregister_driver(&driver); +} + +module_param(panel_id, ushort, S_IRUGO); +MODULE_PARM_DESC(panel_id, "Touch Panel ID Configuration"); + +module_init(maxim_sti_init); +module_exit(maxim_sti_exit); + +MODULE_AUTHOR("Maxim Integrated Products, Inc."); +MODULE_DESCRIPTION("Maxim SmartTouch Imager Touchscreen Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/input/touchscreen/st/Kconfig b/drivers/input/touchscreen/st/Kconfig new file mode 100644 index 000000000000..817faea01742 --- /dev/null +++ b/drivers/input/touchscreen/st/Kconfig @@ -0,0 +1,9 @@ +# +# STMicroelectronics touchscreen driver configuration +# + +config TOUCHSCREEN_ST_I2C + tristate "STMicroelectronics i2c touchscreen" + depends on TOUCHSCREEN_ST + help + This enables support for ST touch panel over I2C based touchscreens. diff --git a/drivers/input/touchscreen/st/Makefile b/drivers/input/touchscreen/st/Makefile new file mode 100644 index 000000000000..0aa7b4a364da --- /dev/null +++ b/drivers/input/touchscreen/st/Makefile @@ -0,0 +1,5 @@ +# +## Makefile for the STMicroelectronics touchscreen driver. +# + +obj-$(CONFIG_TOUCHSCREEN_ST_I2C) += fts.o fts_gui.o fts_driver_test.o fts_lib/ diff --git a/drivers/input/touchscreen/st/fts.c b/drivers/input/touchscreen/st/fts.c new file mode 100644 index 000000000000..06f7f569f417 --- /dev/null +++ b/drivers/input/touchscreen/st/fts.c @@ -0,0 +1,2385 @@ +/* + * fts.c + * + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * marco.cali@st.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/completion.h> + +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> + +#include <linux/notifier.h> +#include <linux/fb.h> + +#ifdef KERNEL_ABOVE_2_6_38 +#include <linux/input/mt.h> +#endif + +#include "fts.h" +#include "fts_lib/ftsCompensation.h" +#include "fts_lib/ftsIO.h" +#include "fts_lib/ftsError.h" +#include "fts_lib/ftsFlash.h" +#include "fts_lib/ftsFrame.h" +#include "fts_lib/ftsGesture.h" +#include "fts_lib/ftsTest.h" +#include "fts_lib/ftsTime.h" +#include "fts_lib/ftsTool.h" + +#define LINK_KOBJ_NAME "tp" + +/* + * Uncomment to use polling mode instead of interrupt mode. + * + */ +/* #define FTS_USE_POLLING_MODE */ + +/* + * Event installer helpers + */ +#define event_id(_e) EVENTID_##_e +#define handler_name(_h) fts_##_h##_event_handler + +#define install_handler(_i, _evt, _hnd) \ +do { \ + _i->event_dispatch_table[event_id(_evt)] = handler_name(_hnd); \ +} while (0) + +/* + * Asyncronouns command helper + */ +#define WAIT_WITH_TIMEOUT(_info, _timeout, _command) \ +do { \ + if (wait_for_completion_timeout(&_info->cmd_done, _timeout) == 0) { \ + dev_warn(_info->dev, "Waiting for %s command: timeout\n", \ + #_command); \ + } \ +} while (0) + +#ifdef KERNEL_ABOVE_2_6_38 +#define TYPE_B_PROTOCOL +#endif + +#if defined(SCRIPTLESS) || defined(DRIVER_TEST) +static struct class *fts_cmd_class; +#endif + +extern chipInfo ftsInfo; + +unsigned char tune_version_same; + +char tag[8] = "[ FTS ]\0"; + +static u32 *typeOfComand; +static int numberParameters; +static int feature_feasibility = ERROR_OP_NOT_ALLOW; +#ifdef PHONE_GESTURE +static u8 mask[GESTURE_MASK_SIZE+2]; +#endif +static void fts_interrupt_enable(struct fts_ts_info *info); +static int fts_init_hw(struct fts_ts_info *info); +static int fts_mode_handler(struct fts_ts_info *info, int force); +static int fts_command(struct fts_ts_info *info, unsigned char cmd); +static void fts_unblank(struct fts_ts_info *info); +static int fts_chip_initialization(struct fts_ts_info *info); + +void touch_callback(unsigned int status) +{ + /* Empty */ +} + +unsigned int le_to_uint(const unsigned char *ptr) +{ + return (unsigned int) ptr[0] + (unsigned int) ptr[1] * 0x100; +} + +unsigned int be_to_uint(const unsigned char *ptr) +{ + return (unsigned int) ptr[1] + (unsigned int) ptr[0] * 0x100; +} + +/* force update firmware*/ +static ssize_t fts_fw_control_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count){ + int ret, mode; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /* reading out firmware upgrade mode */ + sscanf(buf, "%d", &mode); +#ifdef FTM3_CHIP + ret = flashProcedure(PATH_FILE_FW, mode, !mode); +#else + ret = flashProcedure(PATH_FILE_FW, mode, 1); +#endif + info->fwupdate_stat = ret; + + if (ret < OK) + logError(1, "%s %s :Unable to upgrade firmware\n", tag, __func__); + return count; +} + +static ssize_t fts_sysfs_config_id_show(struct device *dev, struct device_attribute *attr, + char *buf) { + int error; + + error = snprintf(buf, TSP_BUF_SIZE, "%x.%x\n", ftsInfo.u16_fwVer, ftsInfo.u16_cfgId); + return error; +} + +static ssize_t fts_sysfs_fwupdate_show(struct device *dev, struct device_attribute *attr, + char *buf) { + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + /* fwupdate_stat: ERROR code Returned by flashProcedure. */ + return snprintf(buf, TSP_BUF_SIZE, "%08X\n", info->fwupdate_stat); +} + +static ssize_t fts_fw_test_show(struct device *dev, struct device_attribute *attr, + char *buf) { + + Firmware fw; + int ret; + + fw.data = NULL; + ret = readFwFile(PATH_FILE_FW, &fw, 0); + + if (ret < OK) { + logError(1, "%s: Error during reading FW file! ERROR %08X\n", tag, ret); + } else + logError(1, "%s: fw_version = %04X, config_version = %04X, size = %d bytes\n", + tag, fw.fw_ver, fw.config_id, fw.data_size); + + kfree(fw.data); + return 0; +} + +/* TODO: edit this function according to the features policy to allow during the screen on/off */ +int check_feature_feasibility(struct fts_ts_info *info, unsigned int feature) +{ + int res = ERROR_OP_NOT_ALLOW; + + if (info->resume_bit == 0) { + switch (feature) { +#ifdef PHONE_GESTURE + case FEAT_GESTURE: + res = OK; + break; +#endif + default: + logError(1, "%s %s: Feature not allowed in this operating mode! ERROR %08X\n", tag, __func__, res); + break; + + } + } else{ + switch (feature) { +#ifdef PHONE_GESTURE + case FEAT_GESTURE: +#endif + case FEAT_GLOVE: + /* glove mode can only activate during sense on */ + res = OK; + break; + + default: + logError(1, "%s %s: Feature not allowed in this operating mode! ERROR %08X\n", tag, __func__, res); + break; + + } + } + + return res; + +} + +static ssize_t fts_feature_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + char *p = (char *)buf; + unsigned int temp; + int res = OK; + + if ((count + 1) / 3 != 2) { + logError(1, "%s fts_feature_enable: Number of parameter wrong! %d > %d\n", + tag, (count + 1) / 3, 2); + } else{ + sscanf(p, "%02X ", &temp); + p += 3; + res = check_feature_feasibility(info, temp); + if (res >= OK) { + switch (temp) { +#ifdef PHONE_GESTURE + case FEAT_GESTURE: + sscanf(p, "%02X ", &info->gesture_enabled); + logError(1, "%s fts_feature_enable: Gesture Enabled = %d\n", tag, + info->gesture_enabled); + break; +#endif + case FEAT_GLOVE: + sscanf(p, "%02X ", &info->glove_enabled); + logError(1, "%s fts_feature_enable: Glove Enabled = %d\n", + tag, info->glove_enabled); + + break; + + default: + logError(1, "%s fts_feature_enable: Feature %02X not valid! ERROR %08X\n", tag, temp, ERROR_OP_NOT_ALLOW); + + } + feature_feasibility = res; + } + + } + return count; +} + +static ssize_t fts_feature_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0, res; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + if (feature_feasibility >= OK) + res = fts_mode_handler(info, 1); + else{ + res = feature_feasibility; + logError(1, "%s %s: Call before echo xx xx > feature_enable with a correct feature! ERROR %08X\n", tag, __func__, res); + } + + all_strbuff = (u8 *) kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else{ + logError(1, "%s fts_feature_enable_show: Unable to allocate all_strbuff! ERROR %08X\n", tag, ERROR_ALLOC); + } + + feature_feasibility = ERROR_OP_NOT_ALLOW; + return count; +} + +#ifdef PHONE_GESTURE +static ssize_t fts_gesture_mask_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + char buff[CMD_STR_LEN] = {0}; + int size = 6 * 2; + u8 *all_strbuff = NULL; + int count = 0, res; + + if (mask[0] == 0) { + res = ERROR_OP_NOT_ALLOW; + logError(1, "%s %s: Call before echo enable/disable xx xx .... > gesture_mask with a correct number of parameters! ERROR %08X\n", tag, __func__, res); + + } else{ + res = fts_disableInterrupt(); + if (res >= OK) { + if (mask[1] == FEAT_ENABLE) + res = enableGesture(&mask[2], mask[0]); + else{ + if (mask[1] == FEAT_DISABLE) + res = disableGesture(&mask[2], mask[0]); + else + res = ERROR_OP_NOT_ALLOW; + } + if (res < OK) { + logError(1, "%s fts_gesture_mask_store: ERROR %08X\n", tag, res); + } + } + res |= fts_enableInterrupt(); + } + + all_strbuff = (u8 *) kmalloc(size, GFP_KERNEL); + if (all_strbuff != NULL) { + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + kfree(all_strbuff); + } else{ + logError(1, "%s fts_gesture_mask_show: Unable to allocate all_strbuff! ERROR %08X\n", tag, ERROR_ALLOC); + } + + mask[0] = 0; + return count; +} + +static ssize_t fts_gesture_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + char *p = (char *)buf; + int n; + unsigned int temp; + + if ((count + 1) / 3 > GESTURE_MASK_SIZE+1) { + logError(1, "%s fts_gesture_mask_store: Number of bytes of parameter wrong! %d > (enable/disable + %d )\n", tag, (count + 1) / 3, GESTURE_MASK_SIZE); + mask[0] = 0; + } else { + mask[0] = ((count + 1) / 3) - 1; + for (n = 1; n <= (count + 1) / 3; n++) { + sscanf(p, "%02X ", &temp); + p += 3; + mask[n] = (u8)temp; + logError(1, "%s mask[%d] = %02X\n", tag, n, mask[n]); + + } + + } + + return count; +} +#endif + +/************************ PRODUCTION TEST **********************************/ +static ssize_t stm_fts_cmd_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { + int n; + char *p = (char *) buf; + + typeOfComand = (u32 *) kmalloc(8 * sizeof (u32), GFP_KERNEL); + if (typeOfComand == NULL) { + logError(1, "%s impossible to allocate typeOfComand!\n", tag); + return count; + } + memset(typeOfComand, 0, 8 * sizeof (u32)); + + logError(1, "%s\n", tag); + for (n = 0; n < (count + 1) / 3; n++) { + sscanf(p, "%02X ", &typeOfComand[n]); + p += 3; + logError(1, "%s typeOfComand[%d] = %02X\n", tag, n, typeOfComand[n]); + + } + + numberParameters = n; + logError(1, "%s Number of Parameters = %d\n", tag, numberParameters); + return count; +} + +static ssize_t stm_fts_cmd_show(struct device *dev, struct device_attribute *attr, + char *buf) { + char buff[CMD_STR_LEN] = {0}; + int res, j, doClean = 0, count; + + int size = 6 * 2; + u8 *all_strbuff = NULL; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + MutualSenseData compData; + SelfSenseData comData; + MutualSenseFrame frameMS; + SelfSenseFrame frameSS; + + /* struct used for defining which test + *perform during the production test + */ + TestToDo todoDefault; + + todoDefault.MutualRaw = 1; + todoDefault.MutualRawGap = 1; + todoDefault.MutualCx1 = 0; + todoDefault.MutualCx2 = 1; + todoDefault.MutualCx2Adj = 1; + todoDefault.MutualCxTotal = 0; + todoDefault.MutualCxTotalAdj = 0; + + todoDefault.MutualKeyRaw = 0; + todoDefault.MutualKeyCx1 = 0; + todoDefault.MutualKeyCx2 = 0; + todoDefault.MutualKeyCxTotal = 0; + + todoDefault.SelfForceRaw = 1; + todoDefault.SelfForceRawGap = 0; + todoDefault.SelfForceIx1 = 0; + todoDefault.SelfForceIx2 = 0; + todoDefault.SelfForceIx2Adj = 0; + todoDefault.SelfForceIxTotal = 1; + todoDefault.SelfForceIxTotalAdj = 0; + todoDefault.SelfForceCx1 = 0; + todoDefault.SelfForceCx2 = 0; + todoDefault.SelfForceCx2Adj = 0; + todoDefault.SelfForceCxTotal = 0; + todoDefault.SelfForceCxTotalAdj = 0; + + todoDefault.SelfSenseRaw = 1; + todoDefault.SelfSenseRawGap = 0; + todoDefault.SelfSenseIx1 = 0; + todoDefault.SelfSenseIx2 = 0; + todoDefault.SelfSenseIx2Adj = 0; + todoDefault.SelfSenseIxTotal = 1; + todoDefault.SelfSenseIxTotalAdj = 0; + todoDefault.SelfSenseCx1 = 0; + todoDefault.SelfSenseCx2 = 0; + todoDefault.SelfSenseCx2Adj = 0; + todoDefault.SelfSenseCxTotal = 0; + todoDefault.SelfSenseCxTotalAdj = 0; + + if (numberParameters >= 1 && typeOfComand != NULL) { + res = fts_disableInterrupt(); + if (res < 0) { + logError(0, "%s fts_disableInterrupt: ERROR %08X\n", tag, res); + res = (res | ERROR_DISABLE_INTER); + goto END; + } + + res = fb_unregister_client(&info->notifier); + if (res < 0) { + logError(1, "%s ERROR: unregister notifier failed!\n", tag); + goto END; + } + + switch (typeOfComand[0]) { + /*ITO TEST*/ + case 0x01: + res = production_test_ito(); + break; + /*PRODUCTION TEST*/ + case 0x00: + if (ftsInfo.u32_mpPassFlag != INIT_MP) { + logError(0, "%s MP Flag not set!\n", tag, res); + res = production_test_main(LIMITS_FILE, 1, 1, &todoDefault, INIT_MP); + } else{ + logError(0, "%s MP Flag set!\n", tag, res); + res = production_test_main(LIMITS_FILE, 1, 0, &todoDefault, INIT_MP); + } + break; + /*read mutual raw*/ + case 0x13: + logError(0, "%s Get 1 MS Frame\n", tag); + /* res = getMSFrame(ADDR_RAW_TOUCH, &frame, 0); */ + res = getMSFrame2(MS_TOUCH_ACTIVE, &frameMS); + if (res < 0) { + logError(0, "%s Error while taking the MS frame... ERROR %02X\n", tag, res); + + } else { + logError(0, "%s The frame size is %d words\n", tag, res); + size = (res * sizeof (short) + 8)*2; + /* set res to OK because if getMSFrame is + * successful res = number of words read + */ + res = OK; + } + break; + /*read self raw*/ + case 0x15: + logError(0, "%s Get 1 SS Frame\n", tag); + res = getSSFrame2(SS_TOUCH, &frameSS); + + if (res < OK) { + logError(0, "%s Error while taking the SS frame... ERROR %02X\n", tag, res); + + } else { + logError(0, "%s The frame size is %d words\n", tag, res); + size = (res * sizeof (short) + 8)*2+1; + /* set res to OK because if getMSFrame is + * successful res = number of words read + */ + res = OK; + } + + break; + + case 0x14: /*read mutual comp data */ + logError(0, "%s Get MS Compensation Data\n", tag); + res = readMutualSenseCompensationData(MS_TOUCH_ACTIVE, &compData); + + if (res < 0) { + logError(0, "%s Error reading MS compensation data ERROR %02X\n", tag, res); + } else { + logError(0, "%s MS Compensation Data Reading Finished!\n", tag); + size = ((compData.node_data_size + 9) * sizeof (u8))*2; + } + break; + + case 0x16: /* read self comp data */ + logError(0, "%s Get SS Compensation Data...\n", tag); + res = readSelfSenseCompensationData(SS_TOUCH, &comData); + if (res < 0) { + logError(0, "%s Error reading SS compensation data ERROR %02X\n", tag, res); + } else { + logError(0, "%s SS Compensation Data Reading Finished!\n", tag); + size = ((comData.header.force_node + comData.header.sense_node)*2 + 12) * sizeof (u8)*2; + } + break; + + case 0x03: /* MS Raw DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ms_raw(LIMITS_FILE, 1, &todoDefault); + break; + + case 0x04: /* MS CX DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ms_cx(LIMITS_FILE, 1, &todoDefault); + break; + + case 0x05: /* SS RAW DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ss_raw(LIMITS_FILE, 1, &todoDefault); + break; + + case 0x06: /* SS IX CX DATA TEST */ + res = fts_system_reset(); + if (res >= OK) + res = production_test_ss_ix_cx(LIMITS_FILE, 1, &todoDefault); + break; + + case 0xF0: + case 0xF1: /* TOUCH ENABLE/DISABLE */ + doClean = (int) (typeOfComand[0]&0x01); + res = cleanUp(doClean); + + break; + + default: + logError(1, "%s COMMAND NOT VALID!! Insert a proper value ...\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + doClean = fts_enableInterrupt(); + if (doClean < 0) { + logError(0, "%s fts_enableInterrupt: ERROR %08X\n", tag, (doClean|ERROR_ENABLE_INTER)); + } + } else { + logError(1, "%s NO COMMAND SPECIFIED!!! do: 'echo [cmd_code] [args] > stm_fts_cmd' before looking for result!\n", tag); + res = ERROR_OP_NOT_ALLOW; + typeOfComand = NULL; + + } + + if (fb_register_client(&info->notifier) < 0) { + logError(1, "%s ERROR: register notifier failed!\n", tag); + } + +END: /* here start the reporting phase, assembling the data to send in the file node */ + all_strbuff = (u8 *) kmalloc(size, GFP_KERNEL); + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof(buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + if (res >= OK) { + /*all the other cases are already fine printing only the res.*/ + switch (typeOfComand[0]) { + case 0x13: + snprintf(buff, sizeof (buff), "%02X", (u8) frameMS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", (u8) frameMS.header.sense_node); + strlcat(all_strbuff, buff, size); + + for (j = 0; j < frameMS.node_data_size; j++) { + snprintf(buff, sizeof(buff), "%04X", frameMS.node_data[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(frameMS.node_data); + break; + + case 0x15: + snprintf(buff, sizeof(buff), "%02X", (u8) frameSS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", (u8) frameSS.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Copying self raw data Force */ + for (j = 0; j < frameSS.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%04X", frameSS.force_data[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying self raw data Sense */ + for (j = 0; j < frameSS.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%04X", frameSS.sense_data[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + case 0x14: + snprintf(buff, sizeof(buff), "%02X", (u8) compData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", (u8) compData.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Cpying CX1 value */ + snprintf(buff, sizeof(buff), "%02X", compData.cx1); + strlcat(all_strbuff, buff, size); + + /* Copying CX2 values */ + for (j = 0; j < compData.node_data_size; j++) { + snprintf(buff, sizeof(buff), "%02X", *(compData.node_data + j)); + strlcat(all_strbuff, buff, size); + } + + kfree(compData.node_data); + break; + + case 0x16: + snprintf(buff, sizeof(buff), "%02X", comData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.header.sense_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.f_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.s_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.f_cx1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof(buff), "%02X", comData.s_cx1); + strlcat(all_strbuff, buff, size); + + /* Copying IX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%02X", comData.ix2_fm[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying IX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%02X", comData.ix2_sn[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying CX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof(buff), "%02X", comData.cx2_fm[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying CX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof(buff), "%02X", comData.cx2_sn[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(comData.ix2_fm); + kfree(comData.ix2_sn); + kfree(comData.cx2_fm); + kfree(comData.cx2_sn); + break; + + default: + break; + + } + } + + snprintf(buff, sizeof(buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + numberParameters = 0; /* need to reset the number of parameters + * in order to wait the next command, comment + *if you want to repeat the last command sent + *just doing a cat + */ + /* logError(0,"%s numberParameters = %d\n", tag, numberParameters); */ + kfree(all_strbuff); + + kfree(typeOfComand); + return count; + +} + +static DEVICE_ATTR(fwupdate, (S_IRUGO | S_IWUSR | S_IWGRP), fts_sysfs_fwupdate_show, fts_fw_control_store); +static DEVICE_ATTR(appid, (S_IRUGO), fts_sysfs_config_id_show, NULL); +static DEVICE_ATTR(fw_file_test, (S_IRUGO), fts_fw_test_show, NULL); +static DEVICE_ATTR(stm_fts_cmd, (S_IRUGO | S_IWUSR | S_IWGRP), stm_fts_cmd_show, stm_fts_cmd_store); +static DEVICE_ATTR(feature_enable, (S_IRUGO | S_IWUSR | S_IWGRP), fts_feature_enable_show, fts_feature_enable_store); +#ifdef PHONE_GESTURE +static DEVICE_ATTR(gesture_mask, (S_IRUGO | S_IWUSR | S_IWGRP), fts_gesture_mask_show, fts_gesture_mask_store); +#endif +/* /sys/devices/soc.0/f9928000.i2c/i2c-6/6-0049 */ +static struct attribute *fts_attr_group[] = { + &dev_attr_fwupdate.attr, + &dev_attr_appid.attr, + &dev_attr_fw_file_test.attr, + /* &dev_attr_touch_debug.attr, */ + &dev_attr_stm_fts_cmd.attr, + &dev_attr_feature_enable.attr, +#ifdef PHONE_GESTURE + &dev_attr_gesture_mask.attr, +#endif + NULL, +}; + +static int fts_command(struct fts_ts_info *info, unsigned char cmd) +{ + unsigned char regAdd; + int ret; + + regAdd = cmd; + + ret = fts_writeCmd(®Add, sizeof (regAdd)); /* 0 = ok */ + + logError(0, "%s Issued command 0x%02x, return value %08X\n", cmd, ret); + + return ret; +} + +void fts_input_report_key(struct fts_ts_info *info, int key_code) +{ + mutex_lock(&info->input_report_mutex); + input_report_key(info->input_dev, key_code, 1); + input_sync(info->input_dev); + input_report_key(info->input_dev, key_code, 0); + input_sync(info->input_dev); + mutex_unlock(&info->input_report_mutex); +} + +/* + * New Interrupt handle implementation + */ + +static inline unsigned char *fts_next_event(unsigned char *evt) +{ + /* Nothing to do with this event, moving to the next one */ + evt += FIFO_EVENT_SIZE; + + /* the previous one was the last event ? */ + return (evt[-1] & 0x1F) ? evt : NULL; +} + +/* EventId : 0x00 */ +static unsigned char *fts_nop_event_handler(struct fts_ts_info *info, + unsigned char *event) { + /* logError(1, "%s %s Doing nothing for event = %02X %02X %02X %02X %02X %02X %02X %02X\n", + * tag, __func__, event[0], event[1], event[2], event[3], event[4], event[5], event[6], event[7]); + */ + return fts_next_event(event); +} + +/* EventId : 0x03 */ +static unsigned char *fts_enter_pointer_event_handler(struct fts_ts_info *info, + unsigned char *event) { + unsigned char touchId, touchcount; + int x, y, z; + + if (!info->resume_bit) + goto no_report; + + touchId = event[1] & 0x0F; + touchcount = (event[1] & 0xF0) >> 4; + + __set_bit(touchId, &info->touch_id); + + x = (event[2] << 4) | (event[4] & 0xF0) >> 4; + y = (event[3] << 4) | (event[4] & 0x0F); + z = (event[5] & 0x3F); + + if (x == X_AXIS_MAX) + x--; + + if (y == Y_AXIS_MAX) + y--; + + input_mt_slot(info->input_dev, touchId); + input_mt_report_slot_state(info->input_dev, MT_TOOL_FINGER, 1); + logError(0, "%s %s : TouchID = %d,Touchcount = %d\n", tag, __func__, touchId, touchcount); + if (touchcount == 1) { + input_report_key(info->input_dev, BTN_TOUCH, 1); + input_report_key(info->input_dev, BTN_TOOL_FINGER, 1); + } + /* input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, touchId); */ + input_report_abs(info->input_dev, ABS_MT_POSITION_X, x); + input_report_abs(info->input_dev, ABS_MT_POSITION_Y, y); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MAJOR, z); + input_report_abs(info->input_dev, ABS_MT_TOUCH_MINOR, z); + input_report_abs(info->input_dev, ABS_MT_PRESSURE, z); + logError(0, "%s %s : Event 0x%02x - ID[%d], (x, y, z) = (%3d, %3d, %3d)\n", tag, __func__, *event, touchId, x, y, z); + +no_report: + return fts_next_event(event); +} + +/* EventId : 0x04 */ +static unsigned char *fts_leave_pointer_event_handler(struct fts_ts_info *info, + unsigned char *event) { + unsigned char touchId, touchcount; + + touchId = event[1] & 0x0F; + touchcount = (event[1] & 0xF0) >> 4; + + __clear_bit(touchId, &info->touch_id); + + input_mt_slot(info->input_dev, touchId); + input_mt_report_slot_state(info->input_dev, MT_TOOL_FINGER, 0); + logError(0, "%s %s : TouchID = %d, Touchcount = %d\n", tag, __func__, touchId, touchcount); + if (touchcount == 0) { + input_report_key(info->input_dev, BTN_TOUCH, 0); + input_report_key(info->input_dev, BTN_TOOL_FINGER, 0); + } + + input_report_abs(info->input_dev, ABS_MT_TRACKING_ID, -1); + logError(0, "%s %s : Event 0x%02x - release ID[%d]\n", tag, __func__, event[0], touchId); + + return fts_next_event(event); +} + +/* EventId : 0x05 */ +#define fts_motion_pointer_event_handler fts_enter_pointer_event_handler + +#ifdef PHONE_KEY +/* EventId : 0x0E */ +static unsigned char *fts_key_status_event_handler(struct fts_ts_info *info, unsigned char *event) +{ + int value; + logError(0, "%s %s Received event %02X %02X %02X %02X %02X %02X %02X %02X\n", tag, __func__, event[0], event[1], event[2], event[3], event[4], event[5], event[6], event[7]); + /* TODO: the customer should handle the events coming from the keys according his needs (this is an example that report only the single pressure of one key at time) */ + if (event[2] != 0) { /* event[2] contain the bitmask of the keys that are actually pressed */ + switch (event[2]) { + case KEY1: + value = KEY_HOMEPAGE; + logError(0, "%s %s: Button HOME !\n", tag, __func__); + break; + + case KEY2: + value = KEY_BACK; + logError(0, "%s %s: Button Back !\n", tag, __func__); + break; + + case KEY3: + value = KEY_MENU; + logError(0, "%s %s: Button Menu !\n", tag, __func__); + break; + + default: + logError(0, "%s %s: No valid Button ID or more than one key pressed!\n", tag, __func__); + goto done; + } + + fts_input_report_key(info, value); + } else{ + logError(0, "%s %s: All buttons released!\n", tag, __func__); + } +done: + return fts_next_event(event); +} +#endif + +/* EventId : 0x0F */ +static unsigned char *fts_error_event_handler(struct fts_ts_info *info, + unsigned char *event) { + int error = 0, i = 0; + logError(0, "%s %s Received event 0x%02x 0x%02x\n", tag, __func__, event[0], event[1]); + + switch (event[1]) { + case EVENT_TYPE_ESD_ERROR: + { + for (i = 0; i < TOUCH_ID_MAX; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, (i < FINGER_MAX) ? MT_TOOL_FINGER : MT_TOOL_PEN, 0); + } + input_sync(info->input_dev); + + fts_chip_powercycle(info); + + error = fts_system_reset(); + error |= fts_mode_handler(info, 0); + error |= fts_enableInterrupt(); + if (error < OK) { + logError(1, "%s %s Cannot restore the device ERROR %08X\n", tag, __func__, error); + } + } + break; + case EVENT_TYPE_WATCHDOG_ERROR: /* watch dog timer */ + { + if (event[2] == 0) { + /* before reset clear all slot */ + for (i = 0; i < TOUCH_ID_MAX; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, + (i < FINGER_MAX) ? MT_TOOL_FINGER : MT_TOOL_PEN, 0); + } + input_sync(info->input_dev); + error = fts_system_reset(); + error |= fts_mode_handler(info, 0); + error |= fts_enableInterrupt(); + if (error < OK) { + logError(1, "%s %s Cannot reset the device ERROR %08X\n", tag, __func__, error); + } + } + } + break; + + } + return fts_next_event(event); +} + +/* EventId : 0x10 */ +static unsigned char *fts_controller_ready_event_handler( + struct fts_ts_info *info, unsigned char *event) { + int error; + logError(0, "%s %s Received event 0x%02x\n", tag, __func__, event[0]); + info->touch_id = 0; + input_sync(info->input_dev); + setSystemResettedUp(1); + setSystemResettedDown(1); + error = fts_mode_handler(info, 0); + if (error < OK) { + logError(1, "%s %s Cannot restore the device status ERROR %08X\n", tag, __func__, error); + } + return fts_next_event(event); +} + +/* EventId : 0x16 */ +static unsigned char *fts_status_event_handler( + struct fts_ts_info *info, unsigned char *event) { + /* logError(1, "%s Received event 0x%02x\n", tag, event[0]); */ + + switch (event[1]) { + case EVENT_TYPE_MS_TUNING_CMPL: + case EVENT_TYPE_SS_TUNING_CMPL: + case FTS_FORCE_CAL_SELF_MUTUAL: + case FTS_FLASH_WRITE_CONFIG: + case FTS_FLASH_WRITE_COMP_MEMORY: + case FTS_FORCE_CAL_SELF: + case FTS_WATER_MODE_ON: + case FTS_WATER_MODE_OFF: + default: + logError(0, + "%s %s Received unhandled status event = %02X %02X %02X %02X %02X %02X %02X %02X\n", + tag, __func__, event[0], event[1], event[2], + event[3], event[4], event[5], event[6], event[7]); + break; + } + + return fts_next_event(event); +} + +#ifdef PHONE_GESTURE +static unsigned char *fts_gesture_event_handler(struct fts_ts_info *info, unsigned char *event) +{ + unsigned char touchId; + int value; + + logError(0, "%s gesture event data: %02X %02X %02X %02X %02X %02X %02X %02X\n", tag, event[0], event[1], event[2], event[3], event[4], event[5], event[6], event[7]); + + if (event[1] == 0x03) { + + logError(1, "%s %s: Gesture ID %02X enable_status = %02X\n", tag, __func__, event[2], event[3]); + + } + if (event[1] == EVENT_TYPE_ENB && event[2] == 0x00) { + switch (event[3]) { + case GESTURE_ENABLE: + logError(1, "%s %s: Gesture Enabled! res = %02X\n", tag, __func__, event[4]); + break; + + case GESTURE_DISABLE: + logError(1, "%s %s: Gesture Disabled! res = %02X\n", tag, __func__, event[4]); + break; + + default: + logError(1, "%s %s: Event not Valid!\n", tag, __func__); + + } + + } + + /* always use touchId zero */ + touchId = 0; + __set_bit(touchId, &info->touch_id); + + if (event[0] == EVENTID_GESTURE && (event[1] == EVENT_TYPE_GESTURE_DTC1 || event[1] == EVENT_TYPE_GESTURE_DTC2)) { + + switch (event[2]) { + case GES_ID_DBLTAP: + value = KEY_WAKEUP; + logError(0, "%s %s: double tap !\n", tag, __func__); + break; + + case GES_ID_AT: + value = KEY_WWW; + logError(0, "%s %s: @ !\n", tag, __func__); + break; + + case GES_ID_C: + value = KEY_C; + logError(0, "%s %s: C !\n", tag, __func__); + break; + + case GES_ID_E: + value = KEY_E; + logError(0, "%s %s: e !\n", tag, __func__); + break; + + case GES_ID_F: + value = KEY_F; + logError(0, "%s %s: F !\n", tag, __func__); + break; + + case GES_ID_L: + value = KEY_L; + logError(0, "%s %s: L !\n", tag, __func__); + break; + + case GES_ID_M: + value = KEY_M; + logError(0, "%s %s: M !\n", tag, __func__); + break; + + case GES_ID_O: + value = KEY_O; + logError(0, "%s %s: O !\n", tag, __func__); + break; + + case GES_ID_S: + value = KEY_S; + logError(0, "%s %s: S !\n", tag, __func__); + break; + + case GES_ID_V: + value = KEY_V; + logError(0, "%s %s: V !\n", tag, __func__); + break; + + case GES_ID_W: + value = KEY_W; + logError(0, "%s %s: W !\n", tag, __func__); + break; + + case GES_ID_Z: + value = KEY_Z; + logError(0, "%s %s: Z !\n", tag, __func__); + break; + + case GES_ID_HFLIP_L2R: + value = KEY_RIGHT; + logError(0, "%s %s: -> !\n", tag, __func__); + break; + + case GES_ID_HFLIP_R2L: + value = KEY_LEFT; + logError(0, "%s %s: <- !\n", tag, __func__); + break; + + case GES_ID_VFLIP_D2T: + value = KEY_UP; + logError(0, "%s %s: UP !\n", tag, __func__); + break; + + case GES_ID_VFLIP_T2D: + value = KEY_DOWN; + logError(0, "%s %s: DOWN !\n", tag, __func__); + break; + + case GES_ID_CUST1: + value = KEY_F1; + logError(0, "%s %s: F1 !\n", tag, __func__); + break; + + case GES_ID_CUST2: + value = KEY_F1; + logError(0, "%s %s: F2 !\n", tag, __func__); + break; + + case GES_ID_CUST3: + value = KEY_F3; + logError(0, "%s %s: F3 !\n", tag, __func__); + break; + + case GES_ID_CUST4: + value = KEY_F1; + logError(0, "%s %s: F4 !\n", tag, __func__); + break; + + case GES_ID_CUST5: + value = KEY_F1; + logError(0, "%s %s: F5 !\n", tag, __func__); + break; + + case GES_ID_LEFTBRACE: + value = KEY_LEFTBRACE; + logError(0, "%s %s: < !\n", tag, __func__); + break; + + case GES_ID_RIGHTBRACE: + value = KEY_RIGHTBRACE; + logError(0, "%s %s: > !\n", tag, __func__); + break; + default: + logError(0, "%s %s: No valid GestureID!\n", tag, __func__); + goto gesture_done; + + } + + fts_input_report_key(info, value); + + gesture_done: + /* Done with gesture event, clear bit. */ + __clear_bit(touchId, &info->touch_id); + } + + return fts_next_event(event); +} +#endif + +/* EventId : 0x05 */ +#define fts_motion_pointer_event_handler fts_enter_pointer_event_handler + +/* + * This handler is called each time there is at least + * one new event in the FIFO + */ +static void fts_event_handler(struct work_struct *work) +{ + struct fts_ts_info *info; + int error, error1; + int left_events; + unsigned char regAdd; + unsigned char data[FIFO_EVENT_SIZE * (FIFO_DEPTH)] = {0}; + unsigned char *event = NULL; + unsigned char eventId; + event_dispatch_handler_t event_handler; + + info = container_of(work, struct fts_ts_info, work); + /* + * to avoid reading all FIFO, we read the first event and + * then check how many events left in the FIFO + */ + + + regAdd = FIFO_CMD_READONE; + error = fts_readCmd(®Add, + sizeof (regAdd), data, FIFO_EVENT_SIZE); + + if (!error) { + + left_events = data[7] & 0x1F; + if ((left_events > 0) && (left_events < FIFO_DEPTH)) { + /* + * Read remaining events. + */ + regAdd = FIFO_CMD_READALL; + + error1 = fts_readCmd(®Add, sizeof (regAdd), + &data[FIFO_EVENT_SIZE], + left_events * FIFO_EVENT_SIZE); + /* + * Got an error reading remaining events, + * process at least * the first one that was + * reading fine. + */ + if (error1) + data[7] &= 0xE0; + } + + /* At least one event is available */ + event = data; + do { + eventId = *event; + event_handler = info->event_dispatch_table[eventId]; + + if (eventId < EVENTID_LAST) { + event = event_handler(info, (event)); + } else { + event = fts_next_event(event); + } + input_sync(info->input_dev); + } while (event); + } + + /* + * re-enable interrupts + */ + fts_interrupt_enable(info); +} + +static int cx_crc_check(void) +{ + unsigned char regAdd1[3] = {FTS_CMD_HW_REG_R, ADDR_CRC_BYTE0, ADDR_CRC_BYTE1}; + unsigned char val = 0; + unsigned char crc_status; + unsigned int error; + + error = fts_readCmd(regAdd1, sizeof (regAdd1), &val, 1); + if (error < OK) { + logError(1, "%s %s Cannot read crc status ERROR %08X\n", tag, __func__, error); + return error; + } + + crc_status = val & CRC_MASK; + if (crc_status != OK) { /* CRC error if crc_status!= 0 */ + logError(1, "%s %s CRC ERROR = %X\n", tag, __func__, crc_status); + } + + return crc_status; /* return OK if no CRC error, or a number >OK if crc error */ +} + +static void fts_fw_update_auto(struct work_struct *work) +{ + int retval = 0; + int retval1 = 0; + int ret; + struct fts_ts_info *info; + struct delayed_work *fwu_work = container_of(work, struct delayed_work, work); + int crc_status = 0; + int error = 0; + info = container_of(fwu_work, struct fts_ts_info, fwu_work); + + logError(1, "%s Fw Auto Update is starting...\n", tag); + + /* check CRC status */ + ret = cx_crc_check(); + if (ret > OK && ftsInfo.u16_fwVer == 0x0000) { + logError(1, "%s %s: CRC Error or NO FW!\n", tag, __func__); + crc_status = 1; + } else { + crc_status = 0; + logError(1, "%s %s: NO CRC Error or Impossible to read CRC register!\n", tag, __func__); + } +#ifdef FTM3_CHIP + retval = flashProcedure(PATH_FILE_FW, crc_status, !crc_status); +#else + retval = flashProcedure(PATH_FILE_FW, crc_status, 1); +#endif + if ((retval & 0xFF000000) == ERROR_FLASH_PROCEDURE) { + logError(1, "%s %s: firmware update failed and retry! ERROR %08X\n", tag, __func__, retval); + fts_chip_powercycle(info); /* power reset */ +#ifdef FTM3_CHIP + retval1 = flashProcedure(PATH_FILE_FW, crc_status, !crc_status); +#else + retval1 = flashProcedure(PATH_FILE_FW, crc_status, 1); +#endif + if ((retval1 & 0xFF000000) == ERROR_FLASH_PROCEDURE) { + logError(1, "%s %s: firmware update failed again! ERROR %08X\n", tag, __func__, retval1); + logError(1, "%s Fw Auto Update Failed!\n", tag); + /* return; */ + } + } + + if ((ftsInfo.u32_mpPassFlag != INIT_MP) && (ftsInfo.u32_mpPassFlag != INIT_FIELD)) + ret = ERROR_GET_INIT_STATUS; + else + ret = OK; + + if (ret == ERROR_GET_INIT_STATUS) { /* initialization status not correct or after FW complete update, do initialization. */ + error = fts_chip_initialization(info); + if (error < OK) { + logError(1, "%s %s Cannot initialize the chip ERROR %08X\n", tag, __func__, error); + } + } + error = fts_init_hw(info); + if (error < OK) { + logError(1, "%s Cannot initialize the hardware device ERROR %08X\n", tag, error); + } + + logError(1, "%s Fw Auto Update Finished!\n", tag); +} + +static int fts_chip_initialization(struct fts_ts_info *info) +{ + int ret2 = 0; + int retry; + int initretrycnt = 0; + + /* initialization error, retry initialization */ + for (retry = 0; retry <= INIT_FLAG_CNT; retry++) { + ret2 = production_test_initialization(); + if (ret2 == OK) { + ret2 = save_mp_flag(INIT_FIELD); + if (ret2 == OK) + break; + } + initretrycnt++; + logError(1, "%s initialization cycle count = %04d - ERROR %08X\n", tag, initretrycnt, ret2); + fts_chip_powercycle(info); + } + if (ret2 < OK) { /* initialization error */ + logError(1, "%s fts initialization failed 3 times\n", tag); + } + + return ret2; +} + +#ifdef FTS_USE_POLLING_MODE + +static enum hrtimer_restart fts_timer_func(struct hrtimer *timer) +{ + struct fts_ts_info *info = + container_of(timer, struct fts_ts_info, timer); + + queue_work(info->event_wq, &info->work); + return HRTIMER_NORESTART; +} +#else + +static irqreturn_t fts_interrupt_handler(int irq, void *handle) +{ + struct fts_ts_info *info = handle; + disable_irq_nosync(info->client->irq); + queue_work(info->event_wq, &info->work); + return IRQ_HANDLED; +} +#endif + +static int fts_interrupt_install(struct fts_ts_info *info) +{ + int i, error = 0; + + info->event_dispatch_table = kzalloc( + sizeof (event_dispatch_handler_t) * EVENTID_LAST, GFP_KERNEL); + + if (!info->event_dispatch_table) { + logError(1, "%s OOM allocating event dispatch table\n", tag); + return -ENOMEM; + } + + for (i = 0; i < EVENTID_LAST; i++) + info->event_dispatch_table[i] = fts_nop_event_handler; + + install_handler(info, ENTER_POINTER, enter_pointer); + install_handler(info, LEAVE_POINTER, leave_pointer); + install_handler(info, MOTION_POINTER, motion_pointer); + install_handler(info, ERROR_EVENT, error); + install_handler(info, CONTROL_READY, controller_ready); + install_handler(info, STATUS_UPDATE, status); +#ifdef PHONE_GESTURE + install_handler(info, GESTURE, gesture); +#endif +#ifdef PHONE_KEY + install_handler(info, KEY_STATUS, key_status); +#endif + + /* disable interrupts in any case */ + error = fts_disableInterrupt(); + +#ifdef FTS_USE_POLLING_MODE + logError(1, "%s Polling Mode\n"); + hrtimer_init(&info->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + info->timer.function = fts_timer_func; + hrtimer_start(&info->timer, ktime_set(1, 0), HRTIMER_MODE_REL); +#else + logError(1, "%s Interrupt Mode\n", tag); + if (request_irq(info->client->irq, fts_interrupt_handler, + IRQF_TRIGGER_LOW, info->client->name, + info)) { + logError(1, "%s Request irq failed\n", tag); + kfree(info->event_dispatch_table); + error = -EBUSY; + } /*else { + error = fts_enableInterrupt(); + }*/ +#endif + + return error; +} + +static void fts_interrupt_uninstall(struct fts_ts_info *info) +{ + + fts_disableInterrupt(); + + kfree(info->event_dispatch_table); +#ifdef FTS_USE_POLLING_MODE + hrtimer_cancel(&info->timer); +#else + free_irq(info->client->irq, info); +#endif +} + +static void fts_interrupt_enable(struct fts_ts_info *info) +{ +#ifdef FTS_USE_POLLING_MODE + hrtimer_start(&info->timer, + ktime_set(0, 10000000), HRTIMER_MODE_REL); +#else + enable_irq(info->client->irq); +#endif +} + +/* +static void fts_interrupt_disable(struct fts_ts_info *info) +{ +#ifdef FTS_USE_POLLING_MODE + hrtimer_cancel(&info->timer); +#else + disable_irq(info->client->irq); +#endif +} +*/ + +static int fts_init(struct fts_ts_info *info) +{ + int error; + + error = fts_system_reset(); + if (error < OK && error != (ERROR_TIMEOUT | ERROR_SYSTEM_RESET_FAIL)) { + logError(1, "%s Cannot reset the device! ERROR %08X\n", tag, error); + return error; + } + if (error == (ERROR_TIMEOUT | ERROR_SYSTEM_RESET_FAIL)) { + logError(1, "%s Setting default Chip INFO!\n", tag); + defaultChipInfo(0); + } else { + error = readChipInfo(0); /* system reset OK */ + if (error < OK) { + logError(1, "%s Cannot read Chip Info! ERROR %08X\n", tag, error); + } + } + + error = fts_interrupt_install(info); + + if (error != OK) { + logError(1, "%s Init (1) error (ERROR = %08X)\n", error); + return error; + } + + fts_unblank(info); + + return error; +} + +int fts_chip_powercycle(struct fts_ts_info *info) +{ + int error, i; + + logError(1, "%s %s: Power Cycle Starting...\n", tag, __func__); + if (info->pwr_reg) { + error = regulator_disable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable DVDD regulator\n", tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_disable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable AVDD regulator\n", tag, __func__); + } + } + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) + gpio_set_value(info->bdata->reset_gpio, 0); + + msleep(300); + if (info->pwr_reg) { + error = regulator_enable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable AVDD regulator\n", tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_enable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable DVDD regulator\n", tag, __func__); + } + } + msleep(300); /* time needed by the regulators for reaching the regime values */ + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) { + msleep(10); /* time to wait before bring up the reset gpio after the power up of the regulators */ + gpio_set_value(info->bdata->reset_gpio, 1); + /* msleep(300); */ + } + + /* before reset clear all slot */ + for (i = 0; i < TOUCH_ID_MAX; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, + (i < FINGER_MAX) ? MT_TOOL_FINGER : MT_TOOL_PEN, 0); + } + input_sync(info->input_dev); + + logError(1, "%s %s: Power Cycle Finished! ERROR CODE = %08x\n", tag, __func__, error); + setSystemResettedUp(1); + setSystemResettedDown(1); + return error; +} + +int fts_chip_powercycle2(struct fts_ts_info *info, unsigned long sleep) +{ + int error, i; + + logError(1, "%s %s: Power Cycle Starting...\n", tag, __func__); + if (info->pwr_reg) { + error = regulator_disable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable DVDD regulator\n", tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_disable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to disable AVDD regulator\n", tag, __func__); + } + } + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) + gpio_set_value(info->bdata->reset_gpio, 0); + + msleep(sleep); + if (info->pwr_reg) { + error = regulator_enable(info->bus_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable AVDD regulator\n", tag, __func__); + } + } + + if (info->bus_reg) { + error = regulator_enable(info->pwr_reg); + if (error < 0) { + logError(1, "%s %s: Failed to enable DVDD regulator\n", tag, __func__); + } + } + msleep(500); /*time needed by the regulators for reaching the regime values */ + + if (info->bdata->reset_gpio != GPIO_NOT_DEFINED) { + msleep(10); /*time to wait before bring up the reset gpio after the power up of the regulators */ + gpio_set_value(info->bdata->reset_gpio, 1); + /* msleep(300); */ + } + + /* before reset clear all slot */ + for (i = 0; i < TOUCH_ID_MAX; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, + (i < FINGER_MAX) ? MT_TOOL_FINGER : MT_TOOL_PEN, 0); + } + input_sync(info->input_dev); + + logError(1, "%s %s: Power Cycle Finished! ERROR CODE = %08x\n", tag, __func__, error); + setSystemResettedUp(1); + setSystemResettedDown(1); + return error; +} + +static int fts_init_hw(struct fts_ts_info *info) +{ + int error = 0; + + error = cleanUp(1); + if (error < OK) + logError(1, "%s Init (2) error (ERROR = %08X)\n", tag, error); + + info->mode = MODE_NORMAL; + + return error; +} + + /* + * TODO: change this function according with the needs of + *customer in temrs of feature to enable/disable + */ +static int fts_mode_handler(struct fts_ts_info *info, int force) +{ + int res = OK; + int ret = OK; + + logError(0, "%s %s: Mode Handler starting...\n", tag, __func__); + switch (info->resume_bit) { + case 0:/* screen down */ + logError(0, "%s %s: Screen OFF...\n", tag, __func__); +#ifdef PHONE_GESTURE + if (info->gesture_enabled == 1) { + logError(0, "%s %s: enter in gesture mode !\n", tag, __func__); + res = enterGestureMode(isSystemResettedDown()); + if (res >= OK) { + + info->mode = MODE_GESTURE; + /* return OK; */ + } else { + logError(1, "%s %s: enterGestureMode failed! ERROR %08X recovery in senseOff...\n", tag, __func__, res); + } + } +#endif + if (info->mode != MODE_GESTURE || info->gesture_enabled == 0) { + logError(0, "%s %s: Sense OFF!\n", tag, __func__); + res |= fts_command(info, FTS_CMD_MS_MT_SENSE_OFF); /* we need to use fts_command for speed reason (no need to check echo in this case and interrupt can be enabled) */ +#ifdef PHONE_KEY + logError(0, "%s %s: Key OFF!\n", tag, __func__); + res |= fts_command(info, FTS_CMD_MS_KEY_OFF); +#endif + + info->mode = MODE_SENSEOFF; + + } + setSystemResettedDown(0); + break; + + case 1: /* screen up */ + logError(0, "%s %s: Screen ON...\n", tag, __func__); + logError(0, "%s %s: Sense ON!\n", tag, __func__); + res |= fts_command(info, FTS_CMD_MS_MT_SENSE_ON); +#ifdef PHONE_KEY + logError(0, "%s %s: Key ON!\n", tag, __func__); + res |= fts_command(info, FTS_CMD_MS_KEY_ON); +#endif + info->mode = MODE_NORMAL; + + if (info->glove_enabled == FEAT_ENABLE || force == 1) { + if (isSystemResettedUp() || force == 1) { + logError(0, "%s %s: Glove Mode setting...\n", tag, __func__); + ret = featureEnableDisable(info->glove_enabled, FEAT_GLOVE); + if (ret < OK) { + logError(1, "%s %s: error during setting GLOVE_MODE! ERROR %08X\n", tag, __func__, ret); + } + res |= ret; + } + if (ret >= OK && info->glove_enabled == FEAT_ENABLE) { + info->mode = MODE_GLOVE; + logError(1, "%s %s: GLOVE_MODE Enabled!\n", tag, __func__); + } else{ + logError(1, "%s %s: GLOVE_MODE Disabled!\n", tag, __func__); + } + } + + setSystemResettedUp(0); + break; + + default: + logError(1, "%s %s: invalid resume_bit value = %d! ERROR %08X\n", tag, __func__, info->resume_bit, ERROR_OP_NOT_ALLOW); + res = ERROR_OP_NOT_ALLOW; + } + + logError(0, "%s %s: Mode Handler finished! res = %08X\n", tag, __func__, res); + return res; + +} + +static int fts_fb_state_chg_callback(struct notifier_block *nb, unsigned long val, void *data) +{ + struct fts_ts_info *info = container_of(nb, struct fts_ts_info, notifier); + struct fb_event *evdata = data; + int i; + unsigned int blank; + + if (val != FB_EVENT_BLANK) + return 0; + + logError(0, "%s %s: fts notifier begin!\n", tag, __func__); + + if (evdata && evdata->data && val == FB_EVENT_BLANK && info) { + + blank = *(int *) (evdata->data); + + switch (blank) { + case FB_BLANK_POWERDOWN: + if (info->sensor_sleep) + break; + + logError(0, "%s %s: FB_BLANK_POWERDOWN\n", tag, __func__); + + /* Release all slots */ + for (i = 0; i < TOUCH_ID_MAX; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, + (i < FINGER_MAX) ? MT_TOOL_FINGER : MT_TOOL_PEN, 0); + } + input_sync(info->input_dev); + + info->resume_bit = 0; + + fts_mode_handler(info, 0); + + info->sensor_sleep = true; + + fts_disableInterrupt(); + + break; + + case FB_BLANK_UNBLANK: + if (!info->sensor_sleep) + break; + + logError(0, "%s %s: FB_BLANK_UNBLANK\n", tag, __func__); + + for (i = 0; i < TOUCH_ID_MAX; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, + (i < FINGER_MAX) ? MT_TOOL_FINGER : MT_TOOL_PEN, 0); + } + input_sync(info->input_dev); + + info->resume_bit = 1; + + fts_mode_handler(info, 0); + + info->sensor_sleep = false; + + fts_enableInterrupt(); + break; + default: + break; + + } + } + return NOTIFY_OK; + +} + +static void fts_unblank(struct fts_ts_info *info) +{ + int i; + + for (i = 0; i < TOUCH_ID_MAX; i++) { + input_mt_slot(info->input_dev, i); + input_mt_report_slot_state(info->input_dev, + (i < FINGER_MAX) ? MT_TOOL_FINGER : MT_TOOL_PEN, 0); + } + input_sync(info->input_dev); + + info->resume_bit = 1; + + fts_mode_handler(info, 0); + + info->sensor_sleep = false; + + fts_enableInterrupt(); +} + +static struct notifier_block fts_noti_block = { + .notifier_call = fts_fb_state_chg_callback, +}; + +static int fts_get_reg(struct fts_ts_info *rmi4_data, + bool get) { + int retval; + const struct fts_i2c_platform_data *bdata = + rmi4_data->bdata; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if ((bdata->pwr_reg_name != NULL) && (*bdata->pwr_reg_name != 0)) { + rmi4_data->pwr_reg = regulator_get(rmi4_data->dev, + bdata->pwr_reg_name); + if (IS_ERR(rmi4_data->pwr_reg)) { + logError(1, "%s %s: Failed to get power regulator\n", tag, + __func__); + retval = PTR_ERR(rmi4_data->pwr_reg); + goto regulator_put; + } + } + + if ((bdata->bus_reg_name != NULL) && (*bdata->bus_reg_name != 0)) { + rmi4_data->bus_reg = regulator_get(rmi4_data->dev, + bdata->bus_reg_name); + if (IS_ERR(rmi4_data->bus_reg)) { + logError(1, "%s %s: Failed to get bus pullup regulator\n", tag, + __func__); + retval = PTR_ERR(rmi4_data->bus_reg); + goto regulator_put; + } + } + + return 0; + +regulator_put: + if (rmi4_data->pwr_reg) { + regulator_put(rmi4_data->pwr_reg); + rmi4_data->pwr_reg = NULL; + } + + if (rmi4_data->bus_reg) { + regulator_put(rmi4_data->bus_reg); + rmi4_data->bus_reg = NULL; + } + + return retval; +} + +static int fts_enable_reg(struct fts_ts_info *rmi4_data, + bool enable) { + int retval; + + if (!enable) { + retval = 0; + goto disable_pwr_reg; + } + + if (rmi4_data->bus_reg) { + retval = regulator_enable(rmi4_data->bus_reg); + if (retval < 0) { + logError(1, "%s %s: Failed to enable bus regulator\n", tag, + __func__); + goto exit; + } + } + + if (rmi4_data->pwr_reg) { + retval = regulator_enable(rmi4_data->pwr_reg); + if (retval < 0) { + logError(1, "%s %s: Failed to enable power regulator\n", tag, + __func__); + goto disable_bus_reg; + } + } + + return OK; + +disable_pwr_reg: + if (rmi4_data->pwr_reg) + regulator_disable(rmi4_data->pwr_reg); + +disable_bus_reg: + if (rmi4_data->bus_reg) + regulator_disable(rmi4_data->bus_reg); + +exit: + return retval; +} + +static int fts_gpio_setup(int gpio, bool config, int dir, int state) +{ + int retval = 0; + unsigned char buf[16]; + + if (config) { + snprintf(buf, 16, "fts_gpio_%u\n", gpio); + + retval = gpio_request(gpio, buf); + if (retval) { + logError(1, "%s %s: Failed to get gpio %d (code: %d)", tag, + __func__, gpio, retval); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval) { + logError(1, "%s %s: Failed to set gpio %d direction", tag, + __func__, gpio); + return retval; + } + } else { + gpio_free(gpio); + } + + return retval; +} + +static int fts_set_gpio(struct fts_ts_info *rmi4_data) +{ + int retval; + const struct fts_i2c_platform_data *bdata = + rmi4_data->bdata; + + retval = fts_gpio_setup(bdata->irq_gpio, true, 0, 0); + if (retval < 0) { + logError(1, "%s %s: Failed to configure irq GPIO\n", tag, __func__); + goto err_gpio_irq; + } + + if (bdata->reset_gpio >= 0) { + retval = fts_gpio_setup(bdata->reset_gpio, true, 1, 0); + if (retval < 0) { + logError(1, "%s %s: Failed to configure reset GPIO\n", tag, __func__); + goto err_gpio_reset; + } + } + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, 0); + msleep(10); + gpio_set_value(bdata->reset_gpio, 1); + } + + setResetGpio(bdata->reset_gpio); + return OK; + +err_gpio_reset: + fts_gpio_setup(bdata->irq_gpio, false, 0, 0); + setResetGpio(GPIO_NOT_DEFINED); +err_gpio_irq: + return retval; +} + +static int parse_dt(struct device *dev, struct fts_i2c_platform_data *bdata) +{ + int retval; + const char *name; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "st,irq-gpio", 0, NULL); + + logError(0, "%s irq_gpio = %d\n", tag, bdata->irq_gpio); + + retval = of_property_read_string(np, "st,regulator_dvdd", &name); + if (retval == -EINVAL) + bdata->pwr_reg_name = NULL; + else if (retval < 0) + return retval; + bdata->pwr_reg_name = name; + logError(0, "%s pwr_reg_name = %s\n", tag, name); + + retval = of_property_read_string(np, "st,regulator_avdd", &name); + if (retval == -EINVAL) + bdata->bus_reg_name = NULL; + else if (retval < 0) + return retval; + bdata->bus_reg_name = name; + logError(0, "%s bus_reg_name = %s\n", tag, name); + + if (of_property_read_bool(np, "st,reset-gpio")) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "st,reset-gpio", 0, NULL); + logError(0, "%s reset_gpio =%d\n", tag, bdata->reset_gpio); + } else { + bdata->reset_gpio = GPIO_NOT_DEFINED; + } + + return OK; +} + +static int fts_probe(struct i2c_client *client, + const struct i2c_device_id *idp) { + struct fts_ts_info *info = NULL; + char fts_ts_phys[64]; + int error = 0; + struct device_node *dp = client->dev.of_node; + int retval; + + logError(1, "%s %s: driver probe begin!\n", tag, __func__); + + logError(1, "%s SET I2C Functionality and Dev INFO:\n", tag); + openChannel(client); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + logError(1, "%s Unsupported I2C functionality\n", tag); + error = -EIO; + goto ProbeErrorExit_0; + } + + info = kzalloc(sizeof (struct fts_ts_info), GFP_KERNEL); + if (!info) { + logError(1, "%s Out of memory... Impossible to allocate struct info!\n", tag); + error = -ENOMEM; + goto ProbeErrorExit_0; + } + + info->client = client; + + i2c_set_clientdata(client, info); + logError(1, "%s i2c address: %x\n", tag, client->addr); + info->dev = &info->client->dev; + if (dp) { + info->bdata = devm_kzalloc(&client->dev, sizeof (struct fts_i2c_platform_data), GFP_KERNEL); + if (!info->bdata) { + logError(1, "%s ERROR:info.bdata kzalloc failed\n", tag); + goto ProbeErrorExit_1; + } + parse_dt(&client->dev, info->bdata); + } + + logError(1, "%s SET Regulators:\n", tag); + retval = fts_get_reg(info, true); + if (retval < 0) { + logError(1, "%s ERROR: %s: Failed to get regulators\n", tag, __func__); + goto ProbeErrorExit_1; + } + + retval = fts_enable_reg(info, true); + if (retval < 0) { + logError(1, "%s %s: ERROR Failed to enable regulators\n", tag, __func__); + goto ProbeErrorExit_2; + } + + logError(1, "%s SET GPIOS:\n", tag); + retval = fts_set_gpio(info); + if (retval < 0) { + logError(1, "%s %s: ERROR Failed to set up GPIO's\n", tag, __func__); + goto ProbeErrorExit_2; + } + info->client->irq = gpio_to_irq(info->bdata->irq_gpio); + + logError(1, "%s SET Auto Fw Update:\n", tag); + info->fwu_workqueue = create_singlethread_workqueue("fts-fwu-queue"); + if (!info->fwu_workqueue) { + logError(1, "%s ERROR: Cannot create fwu work thread\n", tag); + goto ProbeErrorExit_3; + } + INIT_DELAYED_WORK(&info->fwu_work, fts_fw_update_auto); + + logError(1, "%s SET Event Handler:\n", tag); + info->event_wq = create_singlethread_workqueue("fts-event-queue"); + if (!info->event_wq) { + logError(1, "%s ERROR: Cannot create work thread\n", tag); + error = -ENOMEM; + goto ProbeErrorExit_4; + } + + INIT_WORK(&info->work, fts_event_handler); + + logError(1, "%s SET Input Device Property:\n", tag); + info->dev = &info->client->dev; + info->input_dev = input_allocate_device(); + if (!info->input_dev) { + logError(1, "%s ERROR: No such input device defined!\n", tag); + error = -ENODEV; + goto ProbeErrorExit_5; + } + info->input_dev->dev.parent = &client->dev; + info->input_dev->name = FTS_TS_DRV_NAME; + snprintf(fts_ts_phys, sizeof (fts_ts_phys), "%s/input0", + info->input_dev->name); + info->input_dev->phys = fts_ts_phys; + info->input_dev->id.bustype = BUS_I2C; + info->input_dev->id.vendor = 0x0001; + info->input_dev->id.product = 0x0002; + info->input_dev->id.version = 0x0100; + + __set_bit(EV_SYN, info->input_dev->evbit); + __set_bit(EV_KEY, info->input_dev->evbit); + __set_bit(EV_ABS, info->input_dev->evbit); + __set_bit(BTN_TOUCH, info->input_dev->keybit); + __set_bit(BTN_TOOL_FINGER, info->input_dev->keybit); + + input_mt_init_slots(info->input_dev, TOUCH_ID_MAX, INPUT_MT_DIRECT); + + /* input_mt_init_slots(info->input_dev, TOUCH_ID_MAX); */ + + /* input_set_abs_params(info->input_dev, ABS_MT_TRACKING_ID, 0, FINGER_MAX, 0, 0); */ + input_set_abs_params(info->input_dev, ABS_MT_POSITION_X, + X_AXIS_MIN, X_AXIS_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_POSITION_Y, + Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MAJOR, + AREA_MIN, AREA_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_TOUCH_MINOR, + AREA_MIN, AREA_MAX, 0, 0); + input_set_abs_params(info->input_dev, ABS_MT_PRESSURE, + PRESSURE_MIN, PRESSURE_MAX, 0, 0); + +#ifdef PHONE_GESTURE + input_set_capability(info->input_dev, EV_KEY, KEY_WAKEUP); + + input_set_capability(info->input_dev, EV_KEY, KEY_M); + input_set_capability(info->input_dev, EV_KEY, KEY_O); + input_set_capability(info->input_dev, EV_KEY, KEY_E); + input_set_capability(info->input_dev, EV_KEY, KEY_W); + input_set_capability(info->input_dev, EV_KEY, KEY_C); + input_set_capability(info->input_dev, EV_KEY, KEY_L); + input_set_capability(info->input_dev, EV_KEY, KEY_F); + input_set_capability(info->input_dev, EV_KEY, KEY_V); + input_set_capability(info->input_dev, EV_KEY, KEY_S); + input_set_capability(info->input_dev, EV_KEY, KEY_Z); + input_set_capability(info->input_dev, EV_KEY, KEY_WWW); + + input_set_capability(info->input_dev, EV_KEY, KEY_LEFT); + input_set_capability(info->input_dev, EV_KEY, KEY_RIGHT); + input_set_capability(info->input_dev, EV_KEY, KEY_UP); + input_set_capability(info->input_dev, EV_KEY, KEY_DOWN); + + input_set_capability(info->input_dev, EV_KEY, KEY_F1); + input_set_capability(info->input_dev, EV_KEY, KEY_F2); + input_set_capability(info->input_dev, EV_KEY, KEY_F3); + input_set_capability(info->input_dev, EV_KEY, KEY_F4); + input_set_capability(info->input_dev, EV_KEY, KEY_F5); + + input_set_capability(info->input_dev, EV_KEY, KEY_LEFTBRACE); + input_set_capability(info->input_dev, EV_KEY, KEY_RIGHTBRACE); +#endif + +#ifdef PHONE_KEY + /* KEY associated to the touch screen buttons */ + input_set_capability(info->input_dev, EV_KEY, KEY_HOMEPAGE); + input_set_capability(info->input_dev, EV_KEY, KEY_BACK); + input_set_capability(info->input_dev, EV_KEY, KEY_MENU); +#endif + + mutex_init(&(info->input_report_mutex)); + + /* register the multi-touch input device */ + error = input_register_device(info->input_dev); + if (error) { + logError(1, "%s ERROR: No such input device\n", tag); + error = -ENODEV; + goto ProbeErrorExit_5_1; + } + + /* track slots */ + info->touch_id = 0; + + /* init hardware device */ + logError(1, "%s Device Initialization:\n", tag); + error = fts_init(info); + if (error < OK) { + logError(1, "%s Cannot initialize the device ERROR %08X\n", tag, error); + error = -ENODEV; + goto ProbeErrorExit_6; + } + + info->gesture_enabled = 0; + info->glove_enabled = 0; + info->resume_bit = 1; + info->notifier = fts_noti_block; + error = fb_register_client(&info->notifier); + if (error) { + logError(1, "%s ERROR: register notifier failed!\n", tag); + goto ProbeErrorExit_6; + } + + logError(1, "%s SET Device File Nodes:\n", tag); + /* sysfs stuff */ + info->attrs.attrs = fts_attr_group; + error = sysfs_create_group(&client->dev.kobj, &info->attrs); + if (error) { + logError(1, "%s ERROR: Cannot create sysfs structure!\n", tag); + error = -ENODEV; + goto ProbeErrorExit_7; + } + +#ifdef SCRIPTLESS + /*I2C cmd*/ + if (fts_cmd_class == NULL) + fts_cmd_class = class_create(THIS_MODULE, FTS_TS_DRV_NAME); + info->i2c_cmd_dev = device_create(fts_cmd_class, + NULL, DCHIP_ID_0, info, "fts_i2c"); + if (IS_ERR(info->i2c_cmd_dev)) { + logError(1, "%s ERROR: Failed to create device for the sysfs!\n", tag); + goto ProbeErrorExit_8; + } + + dev_set_drvdata(info->i2c_cmd_dev, info); + + error = sysfs_create_group(&info->i2c_cmd_dev->kobj, + &i2c_cmd_attr_group); + if (error) { + logError(1, "%s ERROR: Failed to create sysfs group!\n", tag); + goto ProbeErrorExit_9; + } + +#endif + +#ifdef DRIVER_TEST + if (fts_cmd_class == NULL) + fts_cmd_class = class_create(THIS_MODULE, FTS_TS_DRV_NAME); + info->test_cmd_dev = device_create(fts_cmd_class, + NULL, DCHIP_ID_0, info, "fts_driver_test"); + if (IS_ERR(info->test_cmd_dev)) { + logError(1, "%s ERROR: Failed to create device for the sysfs!\n", tag); + goto ProbeErrorExit_10; + } + + dev_set_drvdata(info->test_cmd_dev, info); + + error = sysfs_create_group(&info->test_cmd_dev->kobj, + &test_cmd_attr_group); + if (error) { + logError(1, "%s ERROR: Failed to create sysfs group!\n", tag); + goto ProbeErrorExit_11; + } + +#endif + /*if wanna auto-update FW when probe, + * please don't comment the following code + */ + + /* queue_delayed_work(info->fwu_workqueue, &info->fwu_work, + * msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + */ + logError(1, "%s Probe Finished!\n", tag); + return OK; + + /* error exit path */ +#ifdef DRIVER_TEST +ProbeErrorExit_11: +#ifndef SCRIPTLESS + device_destroy(fts_cmd_class, DCHIP_ID_0); +#endif + +ProbeErrorExit_10: +#ifndef SCRIPTLESS + sysfs_remove_group(&client->dev.kobj, &info->attrs); +#endif +#endif + +#ifdef SCRIPTLESS +ProbeErrorExit_9: + device_destroy(fts_cmd_class, DCHIP_ID_0); + +ProbeErrorExit_8: + sysfs_remove_group(&client->dev.kobj, &info->attrs); +#endif + +ProbeErrorExit_7: + fb_unregister_client(&info->notifier); + +ProbeErrorExit_6: + input_unregister_device(info->input_dev); + +ProbeErrorExit_5_1: + /* intput_free_device(info->input_dev ); */ + + ProbeErrorExit_5: + destroy_workqueue(info->event_wq); + +ProbeErrorExit_4: + destroy_workqueue(info->fwu_workqueue); + +ProbeErrorExit_3: + fts_enable_reg(info, false); + +ProbeErrorExit_2: + fts_get_reg(info, false); + +ProbeErrorExit_1: + kfree(info); + +ProbeErrorExit_0: + logError(1, "%s Probe Failed!\n", tag); + + return error; +} + +static int fts_remove(struct i2c_client *client) +{ + struct fts_ts_info *info = i2c_get_clientdata(client); + +#ifdef DRIVER_TEST + sysfs_remove_group(&info->test_cmd_dev->kobj, + &test_cmd_attr_group); +#endif + +#ifdef SCRIPTLESS + /*I2C cmd*/ + sysfs_remove_group(&info->i2c_cmd_dev->kobj, + &i2c_cmd_attr_group); + +#endif + +#if defined(SCRIPTLESS) || defined(DRIVER_TEST) + device_destroy(fts_cmd_class, DCHIP_ID_0); +#endif + + /* sysfs stuff */ + sysfs_remove_group(&client->dev.kobj, &info->attrs); + + /* remove interrupt and event handlers */ + fts_interrupt_uninstall(info); + + fb_unregister_client(&info->notifier); + + /* unregister the device */ + input_unregister_device(info->input_dev); + + /* intput_free_device(info->input_dev ); */ + + /* Empty the FIFO buffer */ + fts_command(info, FIFO_CMD_FLUSH); + /* flushFIFO(); */ + + /* Remove the work thread */ + destroy_workqueue(info->event_wq); + destroy_workqueue(info->fwu_workqueue); + + fts_enable_reg(info, false); + fts_get_reg(info, false); + + /* free all */ + kfree(info); + + return OK; +} + +static struct of_device_id fts_of_match_table[] = { + { + .compatible = "st,fts", + }, + {}, +}; +static const struct i2c_device_id fts_device_id[] = { + {FTS_TS_DRV_NAME, 0}, + {} +}; + +static struct i2c_driver fts_i2c_driver = { + .driver = { + .name = FTS_TS_DRV_NAME, + .of_match_table = fts_of_match_table, + }, + .probe = fts_probe, + .remove = fts_remove, + .id_table = fts_device_id, +}; + +static int __init fts_driver_init(void) +{ + return i2c_add_driver(&fts_i2c_driver); +} + +static void __exit fts_driver_exit(void) +{ + i2c_del_driver(&fts_i2c_driver); +} + +MODULE_DESCRIPTION("STMicroelectronics MultiTouch IC Driver"); +MODULE_AUTHOR("STMicroelectronics, Inc."); +MODULE_LICENSE("GPL"); + +late_initcall(fts_driver_init); +module_exit(fts_driver_exit); diff --git a/drivers/input/touchscreen/st/fts.h b/drivers/input/touchscreen/st/fts.h new file mode 100644 index 000000000000..7d72d226349f --- /dev/null +++ b/drivers/input/touchscreen/st/fts.h @@ -0,0 +1,249 @@ +/* + * fts.c + * + * FTS Capacitive touch screen controller (FingerTipS) + * + * Copyright (C) 2016, STMicroelectronics Limited. + * Authors: AMG(Analog Mems Group) + * + * marco.cali@st.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _LINUX_FTS_I2C_H_ +#define _LINUX_FTS_I2C_H_ + +#include "fts_lib/ftsSoftware.h" +#include "fts_lib/ftsHardware.h" + +#define FTS_POWER_ON 1 +#define FTS_POWER_OFF 0 + +/****************** CONFIGURATION SECTION ******************/ +/* #define PHONE_KEY */ + +/* #define PHONE_GESTURE */ + +#define SCRIPTLESS +#ifdef SCRIPTLESS +#define SCRIPTLESS_DEBUG +/* uncomment this macro definition to print debug +* message for script less support +*/ +#endif + +#define DRIVER_TEST + +#define FW_H_FILE +#ifdef FW_H_FILE +#define FW_SIZE_NAME myArray_size +#define FW_ARRAY_NAME myArray +#endif + +#define LIMITS_H_FILE +#ifdef LIMITS_H_FILE +#define LIMITS_SIZE_NAME myArray2_size +#define LIMITS_ARRAY_NAME myArray2 +#endif + +#define FTS_TS_DRV_NAME "fts" +#define FTS_TS_DRV_VERSION "4.1.0" + +#define X_AXIS_MAX 1440 +#define X_AXIS_MIN 0 +#define Y_AXIS_MAX 2560 +#define Y_AXIS_MIN 0 + +#define PRESSURE_MIN 0 +#define PRESSURE_MAX 127 + +#define FINGER_MAX 10 +#define STYLUS_MAX 1 +#define TOUCH_ID_MAX (FINGER_MAX + STYLUS_MAX) + +#define AREA_MIN PRESSURE_MIN +#define AREA_MAX PRESSURE_MAX + +/*********************************************************/ + +/* Flash programming */ + +#define INIT_FLAG_CNT 3 + +/* KEYS */ +#define KEY1 0x02 +#define KEY2 0x01 +#define KEY3 0x04 + +/* + * Configuration mode + */ +#define MODE_NORMAL 0 +#define MODE_GESTURE 1 +#define MODE_GLOVE 2 +#define MODE_SENSEOFF 3 + +/* + * Status Event Field: + * id of command that triggered the event + */ + +#define FTS_FLASH_WRITE_CONFIG 0x03 +#define FTS_FLASH_WRITE_COMP_MEMORY 0x04 +#define FTS_FORCE_CAL_SELF_MUTUAL 0x05 +#define FTS_FORCE_CAL_SELF 0x06 +#define FTS_WATER_MODE_ON 0x07 +#define FTS_WATER_MODE_OFF 0x08 + +#define EXP_FN_WORK_DELAY_MS 1000 + +#define CMD_STR_LEN 32 + +#ifdef SCRIPTLESS +/* + * I2C Command Read/Write Function + */ + +#define CMD_RESULT_STR_LEN 2048 +#endif + +#define TSP_BUF_SIZE 4096 + +struct fts_i2c_platform_data { + int (*power)(bool on); + int irq_gpio; + int reset_gpio; + const char *pwr_reg_name; + const char *bus_reg_name; + +}; + +/* + * Forward declaration + */ +struct fts_ts_info; +extern char tag[8]; + +/* + * Dispatch event handler + */ +typedef unsigned char * (*event_dispatch_handler_t) +(struct fts_ts_info *info, unsigned char *data); + +/* + * struct fts_ts_info - FTS capacitive touch screen device information + * @dev: Pointer to the structure device + * @client: I2C client structure + * @input_dev Input device structure + * @work Work thread + * @event_wq Event queue for work thread + * @cmd_done Asyncronous command notification + * @event_dispatch_table Event dispatch table handlers + * @fw_version Firmware version + * @attrs SysFS attributes + * @mode Device operating mode + * @touch_id Bitmask for touch id (mapped to input slots) + * @buttons Bitmask for buttons status + * @timer Timer when operating in polling mode + * @early_suspend Structure for early suspend functions + * @power Power on/off routine + */ + +struct fts_ts_info { + struct device *dev; + struct i2c_client *client; + struct input_dev *input_dev; + + struct work_struct work; + struct workqueue_struct *event_wq; + + struct delayed_work fwu_work; + struct workqueue_struct *fwu_workqueue; + struct completion cmd_done; + + event_dispatch_handler_t *event_dispatch_table; + + unsigned int fw_version; + unsigned int config_id; + + struct attribute_group attrs; + + unsigned int mode; + unsigned long touch_id; + unsigned int buttons; + +#ifdef FTS_USE_POLLING_MODE + struct hrtimer timer; +#endif + +#ifdef SCRIPTLESS + /*I2C cmd*/ + struct device *i2c_cmd_dev; + char cmd_read_result[CMD_RESULT_STR_LEN]; + char cmd_wr_result[CMD_RESULT_STR_LEN]; + char cmd_write_result[20]; +#endif + +#ifdef DRIVER_TEST + struct device *test_cmd_dev; +#endif + + int (*power)(bool on); + + struct fts_i2c_platform_data *bdata; + struct regulator *pwr_reg; + struct regulator *bus_reg; + + bool fw_force; + int debug_enable; + + int resume_bit; + int fwupdate_stat; + int touch_debug; + + struct notifier_block notifier; + bool sensor_sleep; + bool stay_awake; + + /* input lock */ + struct mutex input_report_mutex; + + /* switches */ + int gesture_enabled; + int glove_enabled; + +}; + +typedef enum { + ERR_ITO_NO_ERR, /* < 0 No ITO Error */ + ERR_ITO_PANEL_OPEN_FORCE, /* < 1 Panel Open Force */ + ERR_ITO_PANEL_OPEN_SENSE, /* < 2 Panel Open Sense */ + ERR_ITO_F2G, /* < 3 Force short to ground */ + ERR_ITO_S2G, /* < 4 Sense short to ground */ + ERR_ITO_F2VDD, /* < 5 Force short to VDD */ + ERR_ITO_S2VDD, /* < 6 Sense short to VDD */ + ERR_ITO_P2P_FORCE, /* < 7 Pin to Pin short (Force) */ + ERR_ITO_P2P_SENSE, /* < 8 Pin to Pin short (Sense) */ +} errItoSubTypes_t; + +int fts_chip_powercycle(struct fts_ts_info *info); +int fts_chip_powercycle2(struct fts_ts_info *info, unsigned long sleep); +int fts_get_fw_version(struct fts_ts_info *info); +extern unsigned int le_to_uint(const unsigned char *ptr); +extern unsigned int be_to_uint(const unsigned char *ptr); +extern int input_register_notifier_client(struct notifier_block *nb); +extern int input_unregister_notifier_client(struct notifier_block *nb); + +#ifdef SCRIPTLESS +extern struct attribute_group i2c_cmd_attr_group; +#endif + +#ifdef DRIVER_TEST +extern struct attribute_group test_cmd_attr_group; +#endif + +#endif diff --git a/drivers/input/touchscreen/st/fts_driver_test.c b/drivers/input/touchscreen/st/fts_driver_test.c new file mode 100644 index 000000000000..5c55d8e4ac88 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_driver_test.c @@ -0,0 +1,871 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* API used by Driver Test Apk * +* * +************************************************************************** +************************************************************************** + +*/ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/completion.h> + +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include "fts.h" +#include "fts_lib/ftsCompensation.h" +#include "fts_lib/ftsIO.h" +#include "fts_lib/ftsError.h" +#include "fts_lib/ftsFrame.h" +#include "fts_lib/ftsFlash.h" +#include "fts_lib/ftsTest.h" +#include "fts_lib/ftsTime.h" +#include "fts_lib/ftsTool.h" + +#ifdef DRIVER_TEST + +#define MAX_PARAMS 10 + +/* DEFINE COMMANDS TO TEST */ +#define CMD_READ 0x00 +#define CMD_WRITE 0x01 +#define CMD_READU16 0x02 +#define CMD_READB2 0x03 +#define CMD_READB2U16 0x04 +#define CMD_POLLFOREVENT 0x05 +#define CMD_SYSTEMRESET 0x06 +#define CMD_CLEANUP 0x07 +#define CMD_GETFORCELEN 0x08 +#define CMD_GETSENSELEN 0x09 +#define CMD_GETMSFRAME 0x0A +/* #define CMD_GETMSKEYFRAME 0x0B */ +#define CMD_GETSSFRAME 0x0C +#define CMD_REQCOMPDATA 0x0D +#define CMD_READCOMPDATAHEAD 0x0E +#define CMD_READMSCOMPDATA 0x0F +#define CMD_READSSCOMPDATA 0x10 +#define CMD_READGNCOMPDATA 0x11 +#define CMD_GETFWVER 0x12 +#define CMD_FLASHSTATUS 0x13 +#define CMD_FLASHUNLOCK 0x14 +#define CMD_READFWFILE 0x15 +#define CMD_FLASHPROCEDURE 0x16 +#define CMD_ITOTEST 0x17 +#define CMD_INITTEST 0x18 +#define CMD_MSRAWTEST 0x19 +#define CMD_MSINITDATATEST 0x1A +#define CMD_SSRAWTEST 0x1B +#define CMD_SSINITDATATEST 0x1C +#define CMD_MAINTEST 0x1D +#define CMD_POWERCYCLE 0x1E +#define CMD_FWWRITE 0x1F +#define CMD_READCHIPINFO 0x20 +#define CMD_REQFRAME 0x21 + +u32 *functionToTest; +int numberParam; + +static ssize_t stm_driver_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) { + int n; + char *p = (char *) buf; + + functionToTest = (u32 *) kmalloc(MAX_PARAMS * sizeof (u32), GFP_KERNEL); + if (functionToTest == NULL) { + logError(1, "%s impossible to allocate functionToTest!\n", tag); + return count; + } + memset(functionToTest, 0, MAX_PARAMS * sizeof (u32)); + + for (n = 0; n < (count + 1) / 3 && n < MAX_PARAMS; n++) { + sscanf(p, "%02X ", &functionToTest[n]); + p += 3; + logError(1, "%s functionToTest[%d] = %02X\n", tag, n, functionToTest[n]); + + } + + numberParam = n; + logError(1, "%s Number of Parameters = %d\n", tag, numberParam); + return count; +} + +static ssize_t stm_driver_test_show(struct device *dev, struct device_attribute *attr, + char *buf) { + char buff[CMD_STR_LEN] = {0}; + int res = -1, j, count; + /* int res2; */ + int size = 6 * 2; + int temp, i, byteToRead; + u8 *readData = NULL; + u8 *all_strbuff = NULL; + u8 *cmd; + + MutualSenseFrame frameMS; + SelfSenseFrame frameSS; + + DataHeader dataHead; + MutualSenseData compData; + SelfSenseData comData; + GeneralData gnData; + + u16 address; + u16 fw_version; + u16 config_id; + + Firmware fw; + + /* struct used for defining which test perform during the MP test */ + + TestToDo todoDefault; + + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + + fw.data = NULL; + + todoDefault.MutualRaw = 1; + todoDefault.MutualRawGap = 1; + todoDefault.MutualCx1 = 0; + todoDefault.MutualCx2 = 1; + todoDefault.MutualCx2Adj = 1; + todoDefault.MutualCxTotal = 0; + todoDefault.MutualCxTotalAdj = 0; + + todoDefault.MutualKeyRaw = 0; + todoDefault.MutualKeyCx1 = 0; + todoDefault.MutualKeyCx2 = 0; + todoDefault.MutualKeyCxTotal = 0; + + todoDefault.SelfForceRaw = 1; + todoDefault.SelfForceRawGap = 0; + todoDefault.SelfForceIx1 = 0; + todoDefault.SelfForceIx2 = 0; + todoDefault.SelfForceIx2Adj = 0; + todoDefault.SelfForceIxTotal = 1; + todoDefault.SelfForceIxTotalAdj = 0; + todoDefault.SelfForceCx1 = 0; + todoDefault.SelfForceCx2 = 0; + todoDefault.SelfForceCx2Adj = 0; + todoDefault.SelfForceCxTotal = 0; + todoDefault.SelfForceCxTotalAdj = 0; + + todoDefault.SelfSenseRaw = 1; + todoDefault.SelfSenseRawGap = 0; + todoDefault.SelfSenseIx1 = 0; + todoDefault.SelfSenseIx2 = 0; + todoDefault.SelfSenseIx2Adj = 0; + todoDefault.SelfSenseIxTotal = 1; + todoDefault.SelfSenseIxTotalAdj = 0; + todoDefault.SelfSenseCx1 = 0; + todoDefault.SelfSenseCx2 = 0; + todoDefault.SelfSenseCx2Adj = 0; + todoDefault.SelfSenseCxTotal = 0; + todoDefault.SelfSenseCxTotalAdj = 0; + + if (numberParam >= 1 && functionToTest != NULL) { + res = fts_disableInterrupt(); + if (res < 0) { + logError(0, "%s stm_driver_test_show: ERROR %08X\n", tag, res); + res = (res | ERROR_DISABLE_INTER); + goto END; + } + switch (functionToTest[0]) { + case CMD_READ: + if (numberParam >= 4) { + temp = (int) functionToTest[1]; + if (numberParam == 4 + (temp - 1) && temp != 0) { + cmd = (u8 *) kmalloc(temp * sizeof (u8), GFP_KERNEL); + for (i = 0; i < temp; i++) { + cmd[i] = functionToTest[i + 2]; + } + byteToRead = functionToTest[i + 2]; + readData = (u8 *) kmalloc(byteToRead * sizeof (u8), GFP_KERNEL); + res = fts_readCmd(cmd, temp, readData, byteToRead); + size += (byteToRead * sizeof (u8))*2; + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_WRITE: + if (numberParam >= 3) { /* need to pass: cmdLength cmd[0] cmd[1] … cmd[cmdLength-1] */ + temp = (int) functionToTest[1]; + if (numberParam == 3 + (temp - 1) && temp != 0) { + cmd = (u8 *) kmalloc(temp * sizeof (u8), GFP_KERNEL); + for (i = 0; i < temp; i++) { + cmd[i] = functionToTest[i + 2]; + } + res = fts_writeCmd(cmd, temp); + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FWWRITE: + if (numberParam >= 3) { /* need to pass: cmdLength cmd[0] cmd[1] … cmd[cmdLength-1] */ + temp = (int) functionToTest[1]; + if (numberParam == 3 + (temp - 1) && temp != 0) { + cmd = (u8 *) kmalloc(temp * sizeof (u8), GFP_KERNEL); + for (i = 0; i < temp; i++) { + cmd[i] = functionToTest[i + 2]; + } + res = fts_writeFwCmd(cmd, temp); + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READU16: + if (numberParam == 6) { /* need to pass: cmd addr[0] addr[1] byteToRead hasDummyByte */ + byteToRead = functionToTest[4]; + readData = (u8 *) kmalloc(byteToRead * sizeof (u8), GFP_KERNEL); + res = readCmdU16((u8) functionToTest[1], (u16) ((((u8) functionToTest[2] & 0x00FF) << 8) + ((u8) functionToTest[3] & 0x00FF)), readData, byteToRead, functionToTest[5]); + size += (byteToRead * sizeof (u8))*2; + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READB2: + if (numberParam == 4) { /* need to pass: addr[0] addr[1] byteToRead */ + byteToRead = functionToTest[3]; + readData = (u8 *) kmalloc(byteToRead * sizeof (u8), GFP_KERNEL); + res = readB2((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), readData, byteToRead); + size += (byteToRead * sizeof (u8))*2; + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READB2U16: + if (numberParam == 4) { /* need to pass: addr[0] addr[1] byteToRead */ + byteToRead = functionToTest[3]; + readData = (u8 *) kmalloc(byteToRead * sizeof (u8), GFP_KERNEL); + res = readB2U16((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), readData, byteToRead); + size += (byteToRead * sizeof (u8))*2; + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_POLLFOREVENT: + if (numberParam >= 5) { /* need to pass: eventLength event[0] event[1] event[eventLength-1] timeTowait */ + temp = (int) functionToTest[1]; + if (numberParam == 5 + (temp - 1) && temp != 0) { + readData = (u8 *) kmalloc(FIFO_EVENT_SIZE * sizeof (u8), GFP_KERNEL); + res = pollForEvent((int *) &functionToTest[2], temp, readData, ((functionToTest[temp + 2] & 0x00FF) << 8)+(functionToTest[temp + 3] & 0x00FF)); + if (res >= OK) + res = OK; /* pollForEvent return the number of error found */ + size += (FIFO_EVENT_SIZE * sizeof (u8))*2; + byteToRead = FIFO_EVENT_SIZE; + } else { + logError(1, "%s Wrong parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SYSTEMRESET: + res = fts_system_reset(); + + break; + + case CMD_READCHIPINFO: + if (numberParam == 2) { /* need to pass: doRequest */ + res = readChipInfo(functionToTest[1]); + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + + break; + + case CMD_CLEANUP: /* TOUCH ENABLE/DISABLE */ + if (numberParam == 2) { /* need to pass: enableTouch */ + res = cleanUp(functionToTest[1]); + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + + break; + + case CMD_GETFORCELEN: /* read number Tx channels */ + temp = getForceLen(); + if (temp < OK) + res = temp; + else { + size += (1 * sizeof (u8))*2; + res = OK; + } + break; + + case CMD_GETSENSELEN: /* read number Rx channels */ + temp = getSenseLen(); + if (temp < OK) + res = temp; + else { + size += (1 * sizeof (u8))*2; + res = OK; + } + break; + + case CMD_REQFRAME: /* request a frame */ + if (numberParam == 3) { + logError(0, "%s Requesting Frame\n", tag); + res = requestFrame((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF))); + + if (res < OK) { + logError(0, "%s Error requesting frame ERROR %02X\n", tag, res); + } else { + logError(0, "%s Requesting Frame Finished!\n", tag); + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_GETMSFRAME: + if (numberParam == 3) { + logError(0, "%s Get 1 MS Frame\n", tag); + flushFIFO(); /* delete the events related to some touch (allow to call this function while touching the sreen without having a flooding of the FIFO) */ + res = getMSFrame2((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), &frameMS); + if (res < 0) { + logError(0, "%s Error while taking the MS frame... ERROR %02X\n", tag, res); + + } else { + logError(0, "%s The frame size is %d words\n", tag, res); + size = (res * sizeof (short) + 8)*2; + /* set res to OK because if getMSFrame is + successful res = number of words read + */ + res = OK; + print_frame_short("MS frame =", array1dTo2d_short(frameMS.node_data, frameMS.node_data_size, frameMS.header.sense_node), frameMS.header.force_node, frameMS.header.sense_node); + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + /*read self raw*/ + case CMD_GETSSFRAME: + if (numberParam == 3) { + logError(0, "%s Get 1 SS Frame\n", tag); + flushFIFO(); /* delete the events related to some touch (allow to call this function while touching the sreen without having a flooding of the FIFO) */ + res = getSSFrame2((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), &frameSS); + + if (res < OK) { + logError(0, "%s Error while taking the SS frame... ERROR %02X\n", tag, res); + + } else { + logError(0, "%s The frame size is %d words\n", tag, res); + size = (res * sizeof (short) + 8)*2+1; + /* set res to OK because if getMSFrame is + successful res = number of words read + */ + res = OK; + print_frame_short("SS force frame =", array1dTo2d_short(frameSS.force_data, frameSS.header.force_node, 1), frameSS.header.force_node, 1); + print_frame_short("SS sense frame =", array1dTo2d_short(frameSS.sense_data, frameSS.header.sense_node, frameSS.header.sense_node), 1, frameSS.header.sense_node); + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_REQCOMPDATA: /* request comp data */ + if (numberParam == 3) { + logError(0, "%s Requesting Compensation Data\n", tag); + res = requestCompensationData((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF))); + + if (res < OK) { + logError(0, "%s Error requesting compensation data ERROR %02X\n", tag, res); + } else { + logError(0, "%s Requesting Compensation Data Finished!\n", tag); + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READCOMPDATAHEAD: /* read comp data header */ + if (numberParam == 3) { + logError(0, "%s Requesting Compensation Data\n", tag); + res = requestCompensationData((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF))); + if (res < OK) { + logError(0, "%s Error requesting compensation data ERROR %02X\n", tag, res); + } else { + logError(0, "%s Requesting Compensation Data Finished!\n", tag); + res = readCompensationDataHeader((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), &dataHead, &address); + if (res < OK) { + logError(0, "%s Read Compensation Data Header ERROR %02X\n", tag, res); + } else { + logError(0, "%s Read Compensation Data Header OK!\n", tag); + size += (2 * sizeof (u8))*2; + } + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READMSCOMPDATA: /* read mutual comp data */ + if (numberParam == 3) { + logError(0, "%s Get MS Compensation Data\n", tag); + res = readMutualSenseCompensationData((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), &compData); + + if (res < OK) { + logError(0, "%s Error reading MS compensation data ERROR %02X\n", tag, res); + } else { + logError(0, "%s MS Compensation Data Reading Finished!\n", tag); + size = ((compData.node_data_size + 9) * sizeof (u8))*2; + print_frame_u8("MS Data (Cx2) =", array1dTo2d_u8(compData.node_data, compData.node_data_size, compData.header.sense_node), compData.header.force_node, compData.header.sense_node); + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READSSCOMPDATA: + if (numberParam == 3) { /* read self comp data */ + logError(0, "%s Get SS Compensation Data...\n", tag); + res = readSelfSenseCompensationData((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), &comData); + if (res < OK) { + logError(0, "%s Error reading SS compensation data ERROR %02X\n", tag, res); + } else { + logError(0, "%s SS Compensation Data Reading Finished!\n", tag); + size = ((comData.header.force_node + comData.header.sense_node)*2 + 12) * sizeof (u8)*2; + print_frame_u8("SS Data Ix2_fm = ", array1dTo2d_u8(comData.ix2_fm, comData.header.force_node, comData.header.force_node), 1, comData.header.force_node); + print_frame_u8("SS Data Cx2_fm = ", array1dTo2d_u8(comData.cx2_fm, comData.header.force_node, comData.header.force_node), 1, comData.header.force_node); + print_frame_u8("SS Data Ix2_sn = ", array1dTo2d_u8(comData.ix2_sn, comData.header.sense_node, comData.header.sense_node), 1, comData.header.sense_node); + print_frame_u8("SS Data Cx2_sn = ", array1dTo2d_u8(comData.cx2_sn, comData.header.sense_node, comData.header.sense_node), 1, comData.header.sense_node); + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_READGNCOMPDATA: + if (numberParam == 3) { /* read self comp data */ + logError(0, "%s Get General Compensation Data...\n", tag); + res = readGeneralCompensationData((u16) ((((u8) functionToTest[1] & 0x00FF) << 8) + ((u8) functionToTest[2] & 0x00FF)), &gnData); + if (res < OK) { + logError(0, "%s Error reading General compensation data ERROR %02X\n", tag, res); + } else { + logError(0, "%s General Compensation Data Reading Finished!\n", tag); + size = (14) * sizeof (u8)*2; + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_GETFWVER: + res = getFirmwareVersion(&fw_version, &config_id); + if (res < OK) { + logError(1, "%s Error reading firmware version and config id ERROR %02X\n", tag, res); + } else { + logError(0, "%s getFirmwareVersion Finished!\n", tag); + size += (4) * sizeof (u8)*2; + } + break; +#ifdef FTM3_CHIP + case CMD_FLASHSTATUS: + res = flash_status(); /* return 0 = flash ready, 1 = flash busy, <0 error */ + if (res < OK) { + logError(1, "%s Error reading flash status ERROR %02X\n", tag, res); + } else { + logError(0, "%s Flash Status: %d\n", tag, res); + size += (1 * sizeof (u8))*2; + temp = res; /* need to store the value for further display */ + res = OK; /* set res =ok for returning code */ + } + break; +#endif + + case CMD_FLASHUNLOCK: + res = flash_unlock(); + if (res < OK) { + logError(1, "%s Impossible Unlock Flash ERROR %02X\n", tag, res); + } else { + logError(0, "%s Flash Unlock OK!\n", tag); + } + break; + + case CMD_READFWFILE: + if (numberParam == 2) { /* read fw file */ + logError(0, "%s Reading FW File...\n", tag); + res = readFwFile(PATH_FILE_FW, &fw, functionToTest[1]); + if (res < OK) { + logError(0, "%s Error reading FW File ERROR %02X\n", tag, res); + } else { + logError(0, "%s Read FW File Finished!\n", tag); + } + kfree(fw.data); + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_FLASHPROCEDURE: + if (numberParam == 3) { /* flashing procedure */ + logError(0, "%s Starting Flashing Procedure...\n", tag); + res = flashProcedure(PATH_FILE_FW, functionToTest[1], functionToTest[2]); + if (res < OK) { + logError(0, "%s Error during flash procedure ERROR %02X\n", tag, res); + } else { + logError(0, "%s Flash Procedure Finished!\n", tag); + } + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + /*ITO TEST*/ + case CMD_ITOTEST: + res = production_test_ito(); + break; + + /*Initialization*/ + case CMD_INITTEST: + if (numberParam == 2) { /* need to specify if if save value on Flash */ + if (functionToTest[1] == 0x01) + res = production_test_initialization(); + else + res = production_test_splited_initialization(false); + } else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_MSRAWTEST: /* MS Raw DATA TEST */ + if (numberParam == 2) /* need to specify if stopOnFail */ + res = production_test_ms_raw(LIMITS_FILE, functionToTest[1], &todoDefault); + else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_MSINITDATATEST: /* MS CX DATA TEST */ + if (numberParam == 2) /* need to specify if stopOnFail */ + res = production_test_ms_cx(LIMITS_FILE, functionToTest[1], &todoDefault); + else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SSRAWTEST: /* SS RAW DATA TEST */ + if (numberParam == 2) /* need to specify if stopOnFail */ + res = production_test_ss_raw(LIMITS_FILE, functionToTest[1], &todoDefault); + else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_SSINITDATATEST: /* SS IX CX DATA TEST */ + if (numberParam == 2) /* need to specify if stopOnFail */ + res = production_test_ss_ix_cx(LIMITS_FILE, functionToTest[1], &todoDefault); + else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + /*PRODUCTION TEST*/ + case CMD_MAINTEST: + if (numberParam == 3) /* need to specify if stopOnFail and saveInit */ + res = production_test_main(LIMITS_FILE, functionToTest[1], functionToTest[2], &todoDefault, INIT_FIELD); + else { + logError(1, "%s Wrong number of parameters!\n", tag); + res = ERROR_OP_NOT_ALLOW; + } + break; + + case CMD_POWERCYCLE: + res = fts_chip_powercycle(info); + break; + + default: + logError(1, "%s COMMAND ID NOT VALID!! Inset a value between 00 and 1E..\n", tag); + res = ERROR_OP_NOT_ALLOW; + break; + } + + /*res2 = fts_enableInterrupt(); enabling the interrupt was disabled on purpose in this node because it can be used for testing procedure and between one step and another the interrupt wan to be kept disabled + if (res2 < 0) { + logError(0, "%s stm_driver_test_show: ERROR %08X\n", tag, (res2 | ERROR_ENABLE_INTER)); + }*/ + } else { + logError(1, "%s NO COMMAND SPECIFIED!!! do: 'echo [cmd_code] [args] > stm_fts_cmd' before looking for result!\n", tag); + res = ERROR_OP_NOT_ALLOW; + functionToTest = NULL; + } + +END: /* here start the reporting phase, assembling the data to send in the file node */ + all_strbuff = (u8 *) kmalloc(size, GFP_KERNEL); + memset(all_strbuff, 0, size); + + snprintf(buff, sizeof (buff), "%02X", 0xAA); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%08X", res); + strlcat(all_strbuff, buff, size); + + if (res >= OK) { + /*all the other cases are already fine printing only the res.*/ + switch (functionToTest[0]) { + case CMD_READ: + case CMD_READU16: + case CMD_READB2: + case CMD_READB2U16: + case CMD_POLLFOREVENT: + for (j = 0; j < byteToRead; j++) { + snprintf(buff, sizeof (buff), "%02X", readData[j]); + strlcat(all_strbuff, buff, size); + } + break; + + case CMD_GETFORCELEN: + case CMD_GETSENSELEN: + case CMD_FLASHSTATUS: + snprintf(buff, sizeof (buff), "%02X", (u8) temp); + strlcat(all_strbuff, buff, size); + break; + + case CMD_GETMSFRAME: + snprintf(buff, sizeof (buff), "%02X", (u8) frameMS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", (u8) frameMS.header.sense_node); + strlcat(all_strbuff, buff, size); + + for (j = 0; j < frameMS.node_data_size; j++) { + snprintf(buff, sizeof (buff), "%04X", frameMS.node_data[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(frameMS.node_data); + break; + + case CMD_GETSSFRAME: + snprintf(buff, sizeof (buff), "%02X", (u8) frameSS.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", (u8) frameSS.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Copying self raw data Force */ + for (j = 0; j < frameSS.header.force_node; j++) { + snprintf(buff, sizeof (buff), "%04X", frameSS.force_data[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying self raw data Sense */ + for (j = 0; j < frameSS.header.sense_node; j++) { + snprintf(buff, sizeof (buff), "%04X", frameSS.sense_data[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(frameSS.force_data); + kfree(frameSS.sense_data); + break; + + case CMD_READMSCOMPDATA: + snprintf(buff, sizeof (buff), "%02X", (u8) compData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", (u8) compData.header.sense_node); + strlcat(all_strbuff, buff, size); + + /* Cpying CX1 value */ + snprintf(buff, sizeof (buff), "%02X", compData.cx1); + strlcat(all_strbuff, buff, size); + + /* Copying CX2 values */ + for (j = 0; j < compData.node_data_size; j++) { + snprintf(buff, sizeof (buff), "%02X", *(compData.node_data + j)); + strlcat(all_strbuff, buff, size); + } + + kfree(compData.node_data); + break; + + case CMD_READSSCOMPDATA: + snprintf(buff, sizeof (buff), "%02X", comData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", comData.header.sense_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", comData.f_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", comData.s_ix1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", comData.f_cx1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", comData.s_cx1); + strlcat(all_strbuff, buff, size); + + /* Copying IX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof (buff), "%02X", comData.ix2_fm[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying IX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof (buff), "%02X", comData.ix2_sn[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying CX2 Force */ + for (j = 0; j < comData.header.force_node; j++) { + snprintf(buff, sizeof (buff), "%02X", comData.cx2_fm[j]); + strlcat(all_strbuff, buff, size); + } + + /* Copying CX2 Sense */ + for (j = 0; j < comData.header.sense_node; j++) { + snprintf(buff, sizeof (buff), "%02X", comData.cx2_sn[j]); + strlcat(all_strbuff, buff, size); + } + + kfree(comData.ix2_fm); + kfree(comData.ix2_sn); + kfree(comData.cx2_fm); + kfree(comData.cx2_sn); + break; + + case CMD_READGNCOMPDATA: + snprintf(buff, sizeof (buff), "%02X", gnData.header.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", gnData.header.sense_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", gnData.ftsd_lp_timer_cal0); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", gnData.ftsd_lp_timer_cal1); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", gnData.ftsd_lp_timer_cal2); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", gnData.ftsd_lp_timer_cal3); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", gnData.ftsa_lp_timer_cal0); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", gnData.ftsa_lp_timer_cal1); + strlcat(all_strbuff, buff, size); + break; + + case CMD_GETFWVER: + snprintf(buff, sizeof (buff), "%04X", fw_version); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%04X", config_id); + strlcat(all_strbuff, buff, size); + break; + + case CMD_READCOMPDATAHEAD: + snprintf(buff, sizeof (buff), "%02X", dataHead.force_node); + strlcat(all_strbuff, buff, size); + + snprintf(buff, sizeof (buff), "%02X", dataHead.sense_node); + strlcat(all_strbuff, buff, size); + break; + + default: + break; + + } + } + + snprintf(buff, sizeof (buff), "%02X", 0xBB); + strlcat(all_strbuff, buff, size); + + count = snprintf(buf, TSP_BUF_SIZE, "%s\n", all_strbuff); + numberParam = 0; /* need to reset the number of parameters in order to wait the next comand, comment if you want to repeat the last comand sent just doing a cat */ + /* logError(0,"%s numberParameters = %d\n",tag, numberParam); */ + kfree(all_strbuff); + kfree(functionToTest); + return count; + +} + +/*static DEVICE_ATTR(stm_driver_test, (S_IRWXU|S_IRWXG), stm_driver_test_show, stm_driver_test_store);*/ +static DEVICE_ATTR(stm_driver_test, (S_IRUGO | S_IWUSR | S_IWGRP), stm_driver_test_show, stm_driver_test_store); + +static struct attribute *test_cmd_attributes[] = { + &dev_attr_stm_driver_test.attr, + NULL, +}; + +struct attribute_group test_cmd_attr_group = { + .attrs = test_cmd_attributes, +}; + +#endif diff --git a/drivers/input/touchscreen/st/fts_fw.h b/drivers/input/touchscreen/st/fts_fw.h new file mode 100644 index 000000000000..d928a06951ea --- /dev/null +++ b/drivers/input/touchscreen/st/fts_fw.h @@ -0,0 +1,10 @@ +#ifndef FTS_FW_H +#define FTS_FW_H +/* This is an auto generated header file +* --->Remember to change the name of the two variables!<--- */ +const uint32_t myArray_size; + +const uint8_t myArray[] = { +}; + +#endif diff --git a/drivers/input/touchscreen/st/fts_gui.c b/drivers/input/touchscreen/st/fts_gui.c new file mode 100644 index 000000000000..800237c6bff7 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_gui.c @@ -0,0 +1,359 @@ +#include <linux/device.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/completion.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include "fts.h" +#include "fts_lib/ftsCompensation.h" +#include "fts_lib/ftsIO.h" +#include "fts_lib/ftsError.h" +#include "fts_lib/ftsFrame.h" +#include "fts_lib/ftsTest.h" +#include "fts_lib/ftsTime.h" +#include "fts_lib/ftsTool.h" + +#ifdef SCRIPTLESS + +unsigned int data[CMD_RESULT_STR_LEN] = {0}; +unsigned char pAddress_i2c[CMD_RESULT_STR_LEN] = {0}; +int byte_count_read; +char Out_buff[TSP_BUF_SIZE]; + +/*I2C CMd functions: functions to interface with GUI without script */ + +ssize_t fts_i2c_wr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + int i; + char buff[16]; + memset(Out_buff, 0x00, ARRAY_SIZE(Out_buff)); + if (byte_count_read == 0) { + snprintf(Out_buff, sizeof(Out_buff), "{FAILED}"); + return snprintf(buf, TSP_BUF_SIZE, "{%s}\n", Out_buff); + } +#ifdef SCRIPTLESS_DEBUG + printk("%s:DATA READ {", __func__); + for (i = 0; i < byte_count_read; i++) { + printk(" %02X", (unsigned int)info->cmd_wr_result[i]); + if (i < (byte_count_read-1)) { + printk(" "); + } + } + printk("}\n"); +#endif + snprintf(buff, sizeof(buff), "{"); + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + for (i = 0; i < (byte_count_read+2); i++) { + if (i == 0) { + char temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + snprintf(buff, sizeof(buff), "%02X", temp_byte_count_read); + } else if (i == 1) { + char temp_byte_count_read = (byte_count_read) & 0xFF; + snprintf(buff, sizeof(buff), "%02X", temp_byte_count_read); + + } else { + snprintf(buff, sizeof(buff), "%02X", info->cmd_wr_result[i-2]); + } + /* snprintf(buff, sizeof(buff), "%02X", info->cmd_wr_result[i]); */ + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + if (i < (byte_count_read+1)) { + snprintf(buff, sizeof(buff), " "); + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + } + } + snprintf(buff, sizeof(buff), "}"); + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + return snprintf(buf, TSP_BUF_SIZE, "%s\n", Out_buff); +} + +ssize_t fts_i2c_wr_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + unsigned char pAddress[8] = {0}; + unsigned int byte_count = 0 ; + int i ; + + unsigned int data[8] = {0}; + memset(data, 0x00, ARRAY_SIZE(data)); + memset(info->cmd_wr_result, 0x00, ARRAY_SIZE(info->cmd_wr_result)); + sscanf(buf, "%x %x %x %x %x %x %x %x ", (data+7), (data), (data+1), + (data+2), (data+3), (data+4), (data+5), (data+6)); + + byte_count = data[7]; + + /*if (sizeof(buf) != byte_count ) + { + printk("%s : Byte count is wrong\n",__func__); + return count; + }*/ +#ifdef SCRIPTLESS_DEBUG + printk("\n"); + printk("%s: Input Data 1:", __func__); + + for (i = 0 ; i < 7; i++) { + printk(" %02X", data[i]); + pAddress[i] = (unsigned char)data[i]; + } + printk("\n"); +#else + for (i = 0 ; i < 7; i++) { + pAddress[i] = (unsigned char)data[i]; + } +#endif + byte_count_read = data[byte_count-1]; + ret = fts_writeCmd(pAddress, 3); + msleep(20); + ret = fts_readCmd(&pAddress[3], (byte_count-4), info->cmd_wr_result, + byte_count_read); +#ifdef SCRIPTLESS_DEBUG + printk("%s:DATA READ\n{", __func__); + for (i = 0; i < (2+byte_count_read); i++) { + if (i == 0) { + char temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + printk("%02X", (unsigned int)temp_byte_count_read); + } else if (i == 1) { + char temp_byte_count_read = (byte_count_read) & 0xFF; + printk("%02X", (unsigned int)temp_byte_count_read); + + } else { + printk("%02X", (unsigned int)info->cmd_read_result[i-2]); + } + if (i < (byte_count_read+1)) { + printk(" "); + } + + } + printk("}\n"); +#endif + if (ret) + dev_err(dev, "Unable to read register\n"); + return count; +} + +ssize_t fts_i2c_read_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + int i ; + char buff[16]; + + memset(Out_buff, 0x00, ARRAY_SIZE(Out_buff)); + if (byte_count_read == 0) { + snprintf(Out_buff, sizeof(Out_buff), "{FAILED}"); + return snprintf(buf, TSP_BUF_SIZE, "{%s}\n", Out_buff); + } +#ifdef SCRIPTLESS_DEBUG + printk("%s:DATA READ {", __func__); + for (i = 0; i < byte_count_read; i++) { + printk("%02X", (unsigned int)info->cmd_read_result[i]); + if (i < (byte_count_read-1)) { + printk(" "); + } + } + printk("}\n"); +#endif + snprintf(buff, sizeof(buff), "{"); + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + for (i = 0; i < (byte_count_read+2); i++) { + if (i == 0) { + char temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + snprintf(buff, sizeof(buff), "%02X", temp_byte_count_read); + } else if (i == 1) { + char temp_byte_count_read = (byte_count_read) & 0xFF; + snprintf(buff, sizeof(buff), "%02X", temp_byte_count_read); + + } else { + snprintf(buff, sizeof(buff), "%02X", info->cmd_read_result[i-2]); + } + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + if (i < (byte_count_read+1)) { + snprintf(buff, sizeof(buff), " "); + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + } + } + snprintf(buff, sizeof(buff), "}"); + strlcat(Out_buff, buff, ARRAY_SIZE(Out_buff)); + + return snprintf(buf, TSP_BUF_SIZE, "%s\n", Out_buff); +} + +ssize_t fts_i2c_read_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + unsigned char pAddress[8] = {0}; + unsigned int byte_count = 0; + int i ; + unsigned int data[8] = {0}; + + byte_count_read = 0; + memset(data, 0x00, ARRAY_SIZE(data)); + memset(info->cmd_read_result, 0x00, ARRAY_SIZE(info->cmd_read_result)); + sscanf(buf, "%x %x %x %x %x %x %x %x ", (data+7), (data), (data+1), (data+2), (data+3), (data+4), (data+5), (data+6)); + byte_count = data[7]; + + if (byte_count > 7) { +#ifdef SCRIPTLESS_DEBUG + printk("%s : Byte count is more than 7\n", __func__); +#endif + return count; + } + /*if (sizeof(buf) != byte_count ) + { + printk("%s : Byte count is wrong\n",__func__); + return count; + }*/ +#ifdef SCRIPTLESS_DEBUG + printk("\n"); + printk("%s: Input Data 1:", __func__); + for (i = 0 ; i < byte_count; i++) { + printk(" %02X", data[i]); + pAddress[i] = (unsigned char)data[i]; + } + printk("\n"); +#else + for (i = 0 ; i < byte_count; i++) { + pAddress[i] = (unsigned char)data[i]; + } +#endif + byte_count_read = data[byte_count-1]; + ret = fts_readCmd(pAddress, (byte_count-1), info->cmd_read_result, byte_count_read); +#ifdef SCRIPTLESS_DEBUG + printk("%s:DATA READ\n{", __func__); + for (i = 0; i < (byte_count_read+2); i++) { + if (i == 0) { + char temp_byte_count_read = (byte_count_read >> 8) & 0xFF; + printk("%02X", (unsigned int)temp_byte_count_read); + } else if (i == 1) { + char temp_byte_count_read = (byte_count_read) & 0xFF; + printk("%02X", (unsigned int)temp_byte_count_read); + + } else { + printk("%02X", (unsigned int)info->cmd_read_result[i-2]); + } + if (i < (byte_count_read+1)) { + printk(" "); + } + } + printk("}\n"); +#endif + if (ret) + dev_err(dev, "Unable to read register\n"); + return count; +} + +ssize_t fts_i2c_write_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + return snprintf(buf, TSP_BUF_SIZE, "%s", info->cmd_write_result); + +} + +ssize_t fts_i2c_write_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct i2c_client *client = to_i2c_client(dev); + struct fts_ts_info *info = i2c_get_clientdata(client); + unsigned int byte_count = 0; + int i ; + memset(data, 0x00, ARRAY_SIZE(data)); + memset(pAddress_i2c, 0x00, ARRAY_SIZE(pAddress_i2c)); + memset(info->cmd_write_result, 0x00, ARRAY_SIZE(info->cmd_write_result)); + sscanf(buf, "%x %x", data, (data + 1)); + byte_count = data[0] << 8 | data[1]; + + if (byte_count <= ARRAY_SIZE(pAddress_i2c)) { + for (i = 0; i < (byte_count); i++) { + sscanf(&buf[3*(i+2)], "%x ", (data+i)); + } } else { +#ifdef SCRIPTLESS_DEBUG + printk("%s : message size is more than allowed limit of 512 bytes\n", __func__); +#endif + snprintf(info->cmd_write_result, sizeof(info->cmd_write_result), "{Write NOT OK}\n"); + } +#ifdef SCRIPTLESS_DEBUG + printk("\n"); + printk("%s: Byte_count= %02d | Count = %02d | size of buf:%02d\n", __func__, byte_count, (int)count, (int)sizeof(buf)); + printk("%s: Input Data 1:", __func__); + for (i = 0 ; i < byte_count; i++) { + printk("%02X", data[i]); + pAddress_i2c[i] = (unsigned char)data[i]; + } + printk("\n"); +#else + for (i = 0; i < byte_count; i++) { + pAddress_i2c[i] = (unsigned char)data[i]; + } +#endif + if ((pAddress_i2c[0] == 0xb3) && (pAddress_i2c[3] == 0xb1)) { + ret = fts_writeCmd(pAddress_i2c, 3); + msleep(20); + ret = fts_writeCmd(&pAddress_i2c[3], byte_count-3); + } else { + ret = fts_writeCmd(pAddress_i2c, byte_count); + } + +#ifdef SCRIPTLESS_DEBUG + printk("%s:DATA :", __func__); + for (i = 0; i < byte_count; i++) { + printk(" %02X", (unsigned int)pAddress_i2c[i]); + } + printk(" byte_count: %02X\n", byte_count); +#endif + if (ret < 0) { + dev_err(dev, "{Write NOT OK}\n"); + snprintf(info->cmd_write_result, sizeof(info->cmd_write_result), "{Write NOT OK}\n"); + } else { + snprintf(info->cmd_write_result, sizeof(info->cmd_write_result), "{Write OK}\n"); +#ifdef SCRIPTLESS_DEBUG + printk("%s : {Write OK}\n", __func__); +#endif + } + return count; +} + +static DEVICE_ATTR(iread, (S_IWUSR|S_IWGRP), NULL, fts_i2c_read_store); +static DEVICE_ATTR(iread_result, (S_IRUSR|S_IRGRP), fts_i2c_read_show, NULL); +static DEVICE_ATTR(iwr, (S_IWUSR|S_IWGRP), NULL, fts_i2c_wr_store); +static DEVICE_ATTR(iwr_result, (S_IRUSR|S_IRGRP), fts_i2c_wr_show, NULL); +static DEVICE_ATTR(iwrite, (S_IWUSR|S_IWGRP), NULL, fts_i2c_write_store); +static DEVICE_ATTR(iwrite_result, (S_IRUSR|S_IRGRP), fts_i2c_write_show, NULL); + +static struct attribute *i2c_cmd_attributes[] = { + &dev_attr_iread.attr, + &dev_attr_iread_result.attr, + &dev_attr_iwr.attr, + &dev_attr_iwr_result.attr, + &dev_attr_iwrite.attr, + &dev_attr_iwrite_result.attr, + NULL, +}; + +struct attribute_group i2c_cmd_attr_group = { + .attrs = i2c_cmd_attributes, +}; + +#endif diff --git a/drivers/input/touchscreen/st/fts_lib/Makefile b/drivers/input/touchscreen/st/fts_lib/Makefile new file mode 100644 index 000000000000..24eca48fc3cd --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the FTS touchscreen driver. +# + +obj-$(CONFIG_TOUCHSCREEN_ST_I2C) += ftsCompensation.o \ + ftsCrossCompile.o ftsError.o ftsFrame.o ftsIO.o ftsTest.o \ + ftsTime.o ftsTool.o ftsFlash.o ftsGesture.o diff --git a/drivers/input/touchscreen/st/fts_lib/ftsCompensation.c b/drivers/input/touchscreen/st/fts_lib/ftsCompensation.c new file mode 100644 index 000000000000..2ca0067f34b0 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsCompensation.c @@ -0,0 +1,591 @@ +/* +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS functions for getting Initialization Data * +* * +************************************************************************** +************************************************************************** +*/ + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTool.h" + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ + +static char tag[8] = "[ FTS ]\0"; + +chipInfo ftsInfo; + +int requestCompensationData(u16 type) +{ + int retry = 0; + int ret; + u16 answer; + + int event_to_search[3]; + u8 readEvent[FIFO_EVENT_SIZE]; + + u8 cmd[3] = { FTS_CMD_REQU_COMP_DATA, 0x00, 0x00 }; + /* B8 is the command for asking compensation data */ + u16ToU8(type, &cmd[1]); + + event_to_search[0] = (int)EVENTID_COMP_DATA_READ; + event_to_search[1] = cmd[1]; + event_to_search[2] = cmd[2]; + + while (retry < COMP_DATA_READ_RETRY) { + logError(0, "%s %s", tag, printHex("Command = ", cmd, 3)); + ret = fts_writeFwCmd(cmd, 3); + /* send the request to the chip to load in memory the Compensation Data */ + if (ret < OK) { + logError(1, "%s requestCompensationData: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + ret = pollForEvent(event_to_search, 3, readEvent, TIMEOUT_REQU_COMP_DATA); + if (ret < OK) { + logError(0, "%s Event did not Found at %d attemp! \n", tag, retry+1); + retry += 1; + } else { + retry = 0; + break; + } + } + + if (retry == COMP_DATA_READ_RETRY) { + logError(1, "%s requestCompensationData: ERROR %02X\n", tag, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + + u8ToU16_le(&readEvent[1], &answer); + + if (answer == type) + return OK; + logError(1, "%s The event found has a different type of Compensation data ERROR %02X \n", tag, ERROR_DIFF_COMP_TYPE); + return ERROR_DIFF_COMP_TYPE; + +} + +int readCompensationDataHeader(u16 type, DataHeader *header, u16 *address) +{ + + u16 offset = ADDR_FRAMEBUFFER_DATA; + u16 answer; + u8 data[COMP_DATA_HEADER]; + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, offset, data, COMP_DATA_HEADER, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readCompensationDataHeader: ERROR %02X \n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + logError(0, "%s Read Data Header done! \n", tag); + + if (data[0] != HEADER_SIGNATURE) { + logError(1, "%s readCompensationDataHeader: ERROR %02X The Header Signature was wrong! %02X != %02X \n", tag, ERROR_WRONG_COMP_SIGN, data[0], HEADER_SIGNATURE); + return ERROR_WRONG_COMP_SIGN; + } + + u8ToU16_le(&data[1], &answer); + + if (answer != type) { + logError(1, "%s readCompensationDataHeader: ERROR %02X\n", tag, ERROR_DIFF_COMP_TYPE); + return ERROR_DIFF_COMP_TYPE; + } + + logError(0, "%s Type of Compensation data OK! \n", tag); + + header->type = type; + header->force_node = (int)data[4]; + header->sense_node = (int)data[5]; + + *address = offset + COMP_DATA_HEADER; + + return OK; + +} + +int readMutualSenseGlobalData(u16 *address, MutualSenseData *global) +{ + + u8 data[COMP_DATA_GLOBAL]; + + logError(0, "%s Address for Global data= %02X \n", tag, *address); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, *address, data, COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readMutualSenseGlobalData: ERROR %02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + logError(0, "%s Global data Read !\n", tag); + + global->tuning_ver = data[0]; + global->cx1 = data[1]; + + logError(0, "%s tuning_ver = %d CX1 = %d \n", tag, global->tuning_ver, global->cx1); + + *address += COMP_DATA_GLOBAL; + return OK; + +} + +int readMutualSenseNodeData(u16 address, MutualSenseData *node) +{ + + int size = node->header.force_node*node->header.sense_node; + + logError(0, "%s Address for Node data = %02X \n", tag, address); + + node->node_data = (u8 *)kmalloc(size*(sizeof(u8)), GFP_KERNEL); + + if (node->node_data == NULL) { + logError(1, "%s readMutualSenseNodeData: ERROR %02X", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + logError(0, "%s Node Data to read %d bytes \n", tag, size); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, node->node_data, size, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readMutualSenseNodeData: ERROR %02X \n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + node->node_data_size = size; + + logError(0, "%s Read node data ok! \n", tag); + + return size; + +} + +int readMutualSenseCompensationData(u16 type, MutualSenseData *data) +{ + + int ret; + u16 address; + + if (!(type == MS_TOUCH_ACTIVE || type == MS_TOUCH_LOW_POWER || + type == MS_TOUCH_ULTRA_LOW_POWER || type == MS_KEY)) { + logError(1, "%s readMutualSenseCompensationData: Choose a MS type of compensation data ERROR %02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestCompensationData(type); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensationData: ERROR %02X\n", tag, ERROR_REQU_COMP_DATA); + return (ret|ERROR_REQU_COMP_DATA); + } + + ret = readCompensationDataHeader(type, &(data->header), &address); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_HEADER); + return (ret|ERROR_COMP_DATA_HEADER); + } + + ret = readMutualSenseGlobalData(&address, data); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensationData: ERROR %02X \n", tag, ERROR_COMP_DATA_GLOBAL); + return (ret|ERROR_COMP_DATA_GLOBAL); + } + + ret = readMutualSenseNodeData(address, data); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_NODE); + return (ret|ERROR_COMP_DATA_NODE); + } + + return OK; + +} + +int readSelfSenseGlobalData(u16 *address, SelfSenseData *global) +{ + + u8 data[COMP_DATA_GLOBAL]; + + logError(0, "%s Address for Global data= %02X \n", tag, *address); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, *address, data, COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readSelfSenseGlobalData: ERROR %02X \n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + + logError(0, "%s Global data Read !\n", tag); + + global->tuning_ver = data[0]; + global->f_ix1 = data[1]; + global->s_ix1 = data[2]; + global->f_cx1 = data[3]; + global->s_cx1 = data[4]; + global->f_max_n = data[5]; + global->s_max_n = data[6]; + + logError(0, "%s tuning_ver = %d f_ix1 = %d s_ix1 = %d f_cx1 = %d s_cx1 = %d \n", tag, global->tuning_ver, global->f_ix1, global->s_ix1, global->f_cx1, global->s_cx1); + logError(0, "%s max_n = %d s_max_n = %d \n", tag, global->f_max_n, global->s_max_n); + + *address += COMP_DATA_GLOBAL; + + return OK; + +} + +int readSelfSenseNodeData(u16 address, SelfSenseData *node) +{ + + int size = node->header.force_node*2+node->header.sense_node*2; + u8 data[size]; + + node->ix2_fm = (u8 *)kmalloc(node->header.force_node*(sizeof(u8)), GFP_KERNEL); + node->cx2_fm = (u8 *)kmalloc(node->header.force_node*(sizeof(u8)), GFP_KERNEL); + node->ix2_sn = (u8 *)kmalloc(node->header.sense_node*(sizeof(u8)), GFP_KERNEL); + node->cx2_sn = (u8 *)kmalloc(node->header.sense_node*(sizeof(u8)), GFP_KERNEL); + + if (node->ix2_fm == NULL || node->cx2_fm == NULL || node->ix2_sn == NULL + || node->cx2_sn == NULL) { + logError(1, "%s readSelfSenseNodeData: ERROR %02X", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + logError(0, "%s Address for Node data = %02X \n", tag, address); + + logError(0, "%s Node Data to read %d bytes \n", tag, size); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, data, size, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readSelfSenseNodeData: ERROR %02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + + logError(0, "%s Read node data ok! \n", tag); + + memcpy(node->ix2_fm, data, node->header.force_node); + memcpy(node->ix2_sn, &data[node->header.force_node], node->header.sense_node); + memcpy(node->cx2_fm, &data[node->header.force_node + node->header.sense_node], node->header.force_node); + memcpy(node->cx2_sn, &data[node->header.force_node*2 + node->header.sense_node], node->header.sense_node); + + return OK; + +} + +int readSelfSenseCompensationData(u16 type, SelfSenseData *data) +{ + + int ret; + u16 address; + + if (!(type == SS_TOUCH || type == SS_KEY || type == SS_HOVER || type == SS_PROXIMITY)) { + logError(1, "%s readSelfSenseCompensationData: Choose a SS type of compensation data ERROR %02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestCompensationData(type); + if (ret < 0) { + logError(1, "%s readSelfSenseCompensationData: ERROR %02X\n", tag, ERROR_REQU_COMP_DATA); + return (ret|ERROR_REQU_COMP_DATA); + } + + ret = readCompensationDataHeader(type, &(data->header), &address); + if (ret < 0) { + logError(1, "%s readSelfSenseCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_HEADER); + return (ret|ERROR_COMP_DATA_HEADER); + } + + ret = readSelfSenseGlobalData(&address, data); + if (ret < 0) { + logError(1, "%s readSelfSenseCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_GLOBAL); + return (ret|ERROR_COMP_DATA_GLOBAL); + } + + ret = readSelfSenseNodeData(address, data); + if (ret < 0) { + logError(1, "%s readSelfSenseCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_NODE); + return (ret|ERROR_COMP_DATA_NODE); + } + + return OK; + +} + +int readGeneralGlobalData(u16 address, GeneralData *global) +{ + u8 data[COMP_DATA_GLOBAL]; + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, data, COMP_DATA_GLOBAL, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readGeneralGlobalData: ERROR %02X \n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + + global->ftsd_lp_timer_cal0 = data[0]; + global->ftsd_lp_timer_cal1 = data[1]; + global->ftsd_lp_timer_cal2 = data[2]; + global->ftsd_lp_timer_cal3 = data[3]; + global->ftsa_lp_timer_cal0 = data[4]; + global->ftsa_lp_timer_cal1 = data[5]; + + return OK; + +} + +int readGeneralCompensationData(u16 type, GeneralData *data) +{ + + int ret; + u16 address; + + if (!(type == GENERAL_TUNING)) { + logError(1, "%s readGeneralCompensationData: Choose a GENERAL type of compensation data ERROR %02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestCompensationData(type); + if (ret < 0) { + logError(1, "%s readGeneralCompensationData: ERROR %02X \n", tag, ERROR_REQU_COMP_DATA); + return ERROR_REQU_COMP_DATA; + } + + ret = readCompensationDataHeader(type, &(data->header), &address); + if (ret < 0) { + logError(1, "%s readGeneralCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_HEADER); + return ERROR_COMP_DATA_HEADER; + } + + ret = readGeneralGlobalData(address, data); + if (ret < 0) { + logError(1, "%s readGeneralCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_GLOBAL); + return ERROR_COMP_DATA_GLOBAL; + } + + return OK; + +} + +int defaultChipInfo(int i2cError) +{ + int i; + logError(0, "%s Setting default Chip Info... \n", tag); + ftsInfo.u32_echoEn = 0x00000000; + ftsInfo.u8_msScrConfigTuneVer = 0; + ftsInfo.u8_ssTchConfigTuneVer = 0; + ftsInfo.u8_msScrCxmemTuneVer = 0; + ftsInfo.u8_ssTchCxmemTuneVer = 0; + if (i2cError == 1) { + ftsInfo.u16_fwVer = 0xFFFF; + ftsInfo.u16_cfgId = 0xFFFF; + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) { + ftsInfo.u8_extReleaseInfo[i] = 0xFF; + } + } else { + ftsInfo.u16_fwVer = 0x0000; + ftsInfo.u16_cfgId = 0x0000; + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) { + ftsInfo.u8_extReleaseInfo[i] = 0x00; + } + } + ftsInfo.u32_mpPassFlag = INIT_FIELD; + logError(0, "%s default Chip Info DONE! \n", tag); + return OK; + +} + +int readChipInfo(int doRequest) +{ + int ret, i; + u16 answer; + u8 data[CHIP_INFO_SIZE+3]; + /* +3 because need to read all the field of the struct plus the signature and 2 address bytes */ + int index = 0; + + logError(0, "%s Starting Read Chip Info... \n", tag); + if (doRequest == 1) { + ret = requestCompensationData(CHIP_INFO); + if (ret < 0) { + logError(1, "%s readChipInfo: ERROR %02X\n", tag, ERROR_REQU_COMP_DATA); + ret = (ret | ERROR_REQU_COMP_DATA); + goto FAIL; + } + } + + logError(0, "%s Byte to read = %d bytes \n", tag, CHIP_INFO_SIZE+3); + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, ADDR_FRAMEBUFFER_DATA, data, CHIP_INFO_SIZE+3, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readChipInfo: ERROR %02X\n", tag, ERROR_I2C_R); + ret = ERROR_I2C_R; + goto FAIL; + } + + logError(0, "%s Read data ok! \n", tag); + + logError(0, "%s Starting parsing of data... \n", tag); + + if (data[0] != HEADER_SIGNATURE) { + logError(1, "%s readChipInfo: ERROR %02X The Header Signature was wrong! %02X != %02X \n", tag, ERROR_WRONG_COMP_SIGN, data[0], HEADER_SIGNATURE); + ret = ERROR_WRONG_COMP_SIGN; + goto FAIL; + } + + u8ToU16_le(&data[1], &answer); + + if (answer != CHIP_INFO) { + logError(1, "%s readChipInfo: ERROR %02X\n", tag, ERROR_DIFF_COMP_TYPE); + ret = ERROR_DIFF_COMP_TYPE; + goto FAIL; + } + + index += 3; + ftsInfo.u8_loadCnt = data[index++]; + ftsInfo.u8_infoVer = data[index++]; + u8ToU16(&data[index], &ftsInfo.u16_ftsdId); + index += 2; + ftsInfo.u8_ftsdVer = data[index++]; + ftsInfo.u8_ftsaId = data[index++]; + ftsInfo.u8_ftsaVer = data[index++]; + ftsInfo.u8_tchRptVer = data[index++]; + + logError(1, "%s External Release = ", tag); + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) { + ftsInfo.u8_extReleaseInfo[i] = data[index++]; + logError(1, "%02X ", ftsInfo.u8_extReleaseInfo[i]); + } + logError(1, "\n"); + + for (i = 0; i < sizeof(ftsInfo.u8_custInfo); i++) { + ftsInfo.u8_custInfo[i] = data[index++]; + } + + u8ToU16(&data[index], &ftsInfo.u16_fwVer); + index += 2; + logError(1, "%s FW VERSION = %04X \n", tag, ftsInfo.u16_fwVer); + + u8ToU16(&data[index], &ftsInfo.u16_cfgId); + index += 2; + logError(1, "%s CONFIG ID = %04X \n", tag, ftsInfo.u16_cfgId); + + ftsInfo.u32_projId = ((data[index + 3] & 0x000000FF) << 24) + ((data[index + 2] & 0x000000FF) << 16) + ((data[index + 1] & 0x000000FF) << 8) + (data[index] & 0x000000FF); + index += 4; + + u8ToU16(&data[index], &ftsInfo.u16_scrXRes); + index += 2; + + u8ToU16(&data[index], &ftsInfo.u16_scrYRes); + index += 2; + + ftsInfo.u8_scrForceLen = data[index++]; + logError(1, "%s Force Len = %d \n", tag, ftsInfo.u8_scrForceLen); + + ftsInfo.u8_scrSenseLen = data[index++]; + logError(1, "%s Sense Len = %d \n", tag, ftsInfo.u8_scrSenseLen); + + for (i = 0; i < 8; i++) { + ftsInfo.u64_scrForceEn[i] = data[index++]; + } + + for (i = 0; i < 8; i++) { + ftsInfo.u64_scrSenseEn[i] = data[index++]; + } + + ftsInfo.u8_msKeyLen = data[index++]; + logError(1, "%s MS Key Len = %d \n", tag, ftsInfo.u8_msKeyLen); + + for (i = 0; i < 8; i++) { + ftsInfo.u64_msKeyForceEn[i] = data[index++]; + } + + for (i = 0; i < 8; i++) { + ftsInfo.u64_msKeySenseEn[i] = data[index++]; + } + + ftsInfo.u8_ssKeyLen = data[index++]; + logError(1, "%s SS Key Len = %d \n", tag, ftsInfo.u8_ssKeyLen); + + for (i = 0; i < 8; i++) { + ftsInfo.u64_ssKeyForceEn[i] = data[index++]; + } + + for (i = 0; i < 8; i++) { + ftsInfo.u64_ssKeySenseEn[i] = data[index++]; + } + + ftsInfo.u8_frcTchXLen = data[index++]; + + ftsInfo.u8_frcTchYLen = data[index++]; + + for (i = 0; i < 8; i++) { + ftsInfo.u64_frcTchForceEn[i] = data[index++]; + } + + for (i = 0; i < 8; i++) { + ftsInfo.u64_frcTchSenseEn[i] = data[index++]; + } + + ftsInfo.u8_msScrConfigTuneVer = data[index++]; + logError(1, "%s CFG MS TUNING VERSION = %02X \n", tag, ftsInfo.u8_msScrConfigTuneVer); + ftsInfo.u8_msScrLpConfigTuneVer = data[index++]; + ftsInfo.u8_msScrHwulpConfigTuneVer = data[index++]; + ftsInfo.u8_msKeyConfigTuneVer = data[index++]; + ftsInfo.u8_ssTchConfigTuneVer = data[index++]; + logError(1, "%s CFG SS TUNING VERSION = %02X \n", tag, ftsInfo.u8_ssTchConfigTuneVer); + ftsInfo.u8_ssKeyConfigTuneVer = data[index++]; + ftsInfo.u8_ssHvrConfigTuneVer = data[index++]; + ftsInfo.u8_frcTchConfigTuneVer = data[index++]; + ftsInfo.u8_msScrCxmemTuneVer = data[index++]; + logError(1, "%s CX MS TUNING VERSION = %02X \n", tag, ftsInfo.u8_msScrCxmemTuneVer); + ftsInfo.u8_msScrLpCxmemTuneVer = data[index++]; + ftsInfo.u8_msScrHwulpCxmemTuneVer = data[index++]; + ftsInfo.u8_msKeyCxmemTuneVer = data[index++]; + ftsInfo.u8_ssTchCxmemTuneVer = data[index++]; + logError(1, "%s CX SS TUNING VERSION = %02X \n", tag, ftsInfo.u8_ssTchCxmemTuneVer); + ftsInfo.u8_ssKeyCxmemTuneVer = data[index++]; + ftsInfo.u8_ssHvrCxmemTuneVer = data[index++]; + ftsInfo.u8_frcTchCxmemTuneVer = data[index++]; + ftsInfo.u32_mpPassFlag = ((data[index + 3] & 0x000000FF) << 24) + ((data[index + 2] & 0x000000FF) << 16) + ((data[index + 1] & 0x000000FF) << 8) + (data[index] & 0x000000FF); + index += 4; + logError(1, "%s MP SIGNATURE = %08X \n", tag, ftsInfo.u32_mpPassFlag); + ftsInfo.u32_featEn = ((data[index + 3] & 0x000000FF) << 24) + ((data[index + 2] & 0x000000FF) << 16) + ((data[index + 1] & 0x000000FF) << 8) + (data[index] & 0x000000FF); + index += 4; + ftsInfo.u32_echoEn = ((data[index + 3] & 0x000000FF) << 24) + ((data[index + 2] & 0x000000FF) << 16) + ((data[index + 1] & 0x000000FF) << 8) + (data[index] & 0x000000FF); + index += 4; + logError(1, "%s FEATURES = %08X \n", tag, ftsInfo.u32_echoEn); + + logError(1, "%s Parsed %d bytes! \n", tag, index); + + if (index != CHIP_INFO_SIZE + 3) { + logError(1, "%s readChipInfo: index = %d different from %d ERROR %02X\n", tag, index, CHIP_INFO_SIZE+3, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + logError(1, "%s Chip Info Read DONE!\n", tag); + return OK; + +FAIL: + defaultChipInfo(isI2cError(ret)); + return ret; + +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsCompensation.h b/drivers/input/touchscreen/st/fts_lib/ftsCompensation.h new file mode 100644 index 000000000000..c4cc5913fc18 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsCompensation.h @@ -0,0 +1,146 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS functions for getting Initialization Data * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsCrossCompile.h" +#include "ftsSoftware.h" + +#define COMP_DATA_READ_RETRY 2 + +/* Bytes dimension of Compensation Data Format */ + +#define COMP_DATA_HEADER 8 +#define COMP_DATA_GLOBAL 8 + +#define HEADER_SIGNATURE 0xA5 + +/* Possible Compensation/Frame Data Type */ +#define GENERAL_TUNING 0x0100 +#define MS_TOUCH_ACTIVE 0x0200 +#define MS_TOUCH_LOW_POWER 0x0400 +#define MS_TOUCH_ULTRA_LOW_POWER 0x0800 +#define MS_KEY 0x1000 +#define SS_TOUCH 0x2000 +#define SS_KEY 0x4000 +#define SS_HOVER 0x8000 +#define SS_PROXIMITY 0x0001 +#define CHIP_INFO 0xFFFF + +#define TIMEOUT_REQU_COMP_DATA 1000 /* ms */ + +/* CHIP INFO */ +#define CHIP_INFO_SIZE 138/* bytes to read from framebuffer (exclude the signature and the type because already checked during the reading) */ +#define EXTERNAL_RELEASE_INFO_SIZE 8/* bytes */ + +typedef struct { + int force_node, sense_node; + u16 type; +} DataHeader; + +typedef struct { + DataHeader header; + u8 tuning_ver; + u8 cx1; + u8 *node_data; + int node_data_size; +} MutualSenseData; + +typedef struct { + DataHeader header; + u8 tuning_ver; + u8 f_ix1, s_ix1; + u8 f_cx1, s_cx1; + u8 f_max_n, s_max_n; + + u8 *ix2_fm; + u8 *ix2_sn; + u8 *cx2_fm; + u8 *cx2_sn; + +} SelfSenseData; + +typedef struct { + DataHeader header; + u8 ftsd_lp_timer_cal0; + u8 ftsd_lp_timer_cal1; + u8 ftsd_lp_timer_cal2; + + u8 ftsd_lp_timer_cal3; + u8 ftsa_lp_timer_cal0; + u8 ftsa_lp_timer_cal1; + +} GeneralData; + +typedef struct { + u8 u8_loadCnt; /* 03 - Load Counter */ + u8 u8_infoVer; /* 04 - New chip info version */ + u16 u16_ftsdId; /* 05 - FTSD ID */ + u8 u8_ftsdVer; /* 07 - FTSD version */ + u8 u8_ftsaId; /* 08 - FTSA ID */ + u8 u8_ftsaVer; /* 09 - FTSA version */ + u8 u8_tchRptVer; /* 0A - Touch report version (e.g. ST, Samsung etc) */ + u8 u8_extReleaseInfo[EXTERNAL_RELEASE_INFO_SIZE]; /* 0B - External release information */ + u8 u8_custInfo[12]; /* 13 - Customer information */ + u16 u16_fwVer; /* 1F - Firmware version */ + u16 u16_cfgId; /* 21 - Configuration ID */ + u32 u32_projId; /* 23 - Project ID */ + u16 u16_scrXRes; /* 27 - X resolution on main screen */ + u16 u16_scrYRes; /* 29 - Y resolution on main screen */ + u8 u8_scrForceLen; /* 2B - Number of force channel on main screen */ + u8 u8_scrSenseLen; /* 2C - Number of sense channel on main screen */ + u8 u64_scrForceEn[8]; /* 2D - Force channel enabled on main screen */ + u8 u64_scrSenseEn[8]; /* 35 - Sense channel enabled on main screen */ + u8 u8_msKeyLen; /* 3D - Number of MS Key channel */ + u8 u64_msKeyForceEn[8]; /* 3E - MS Key force channel enable */ + u8 u64_msKeySenseEn[8]; /* 46 - MS Key sense channel enable */ + u8 u8_ssKeyLen; /* 4E - Number of SS Key channel */ + u8 u64_ssKeyForceEn[8]; /* 4F - SS Key force channel enable */ + u8 u64_ssKeySenseEn[8]; /* 57 - SS Key sense channel enable */ + u8 u8_frcTchXLen; /* 5F - Number of force touch force channel */ + u8 u8_frcTchYLen; /* 60 - Number of force touch sense channel */ + u8 u64_frcTchForceEn[8]; /* 61 - Force touch force channel enable */ + u8 u64_frcTchSenseEn[8]; /* 69 - Force touch sense channel enable */ + u8 u8_msScrConfigTuneVer; /* 71 - MS screen tuning version in config */ + u8 u8_msScrLpConfigTuneVer; /* 72 - MS screen LP mode tuning version in config */ + u8 u8_msScrHwulpConfigTuneVer; /* 73 - MS screen ultra low power mode tuning version in config */ + u8 u8_msKeyConfigTuneVer; /* 74 - MS Key tuning version in config */ + u8 u8_ssTchConfigTuneVer; /* 75 - SS touch tuning version in config */ + u8 u8_ssKeyConfigTuneVer; /* 76 - SS Key tuning version in config */ + u8 u8_ssHvrConfigTuneVer; /* 77 - SS hover tuning version in config */ + u8 u8_frcTchConfigTuneVer; /* 78 - Force touch tuning version in config */ + u8 u8_msScrCxmemTuneVer; /* 79 - MS screen tuning version in cxmem */ + u8 u8_msScrLpCxmemTuneVer; /* 7A - MS screen LP mode tuning version in cxmem */ + u8 u8_msScrHwulpCxmemTuneVer; /* 7B - MS screen ultra low power mode tuning version in cxmem */ + u8 u8_msKeyCxmemTuneVer; /* 7C - MS Key tuning version in cxmem */ + u8 u8_ssTchCxmemTuneVer; /* 7D - SS touch tuning version in cxmem */ + u8 u8_ssKeyCxmemTuneVer; /* 7E - SS Key tuning version in cxmem */ + u8 u8_ssHvrCxmemTuneVer; /* 7F - SS hover tuning version in cxmem */ + u8 u8_frcTchCxmemTuneVer; /* 80 - Force touch tuning version in cxmem */ + u32 u32_mpPassFlag; /* 81 - Mass production pass flag */ + u32 u32_featEn; /* 85 - Supported features */ + u32 u32_echoEn; /* 89 - enable of particular features: first bit is Echo Enables */ +} chipInfo; + +int requestCompensationData(u16 type); +int readCompensationDataHeader(u16 type, DataHeader *header, u16 *address); +int readMutualSenseGlobalData(u16 *address, MutualSenseData *global); +int readMutualSenseNodeData(u16 address, MutualSenseData *node); +int readMutualSenseCompensationData(u16 type, MutualSenseData *data); +int readSelfSenseGlobalData(u16 *address, SelfSenseData *global); +int readSelfSenseNodeData(u16 address, SelfSenseData *node); +int readSelfSenseCompensationData(u16 type, SelfSenseData *data); +int readGeneralGlobalData(u16 address, GeneralData *global); +int readGeneralCompensationData(u16 type, GeneralData *data); +int defaultChipInfo(int i2cError); +int readChipInfo(int doRequest); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsCrossCompile.c b/drivers/input/touchscreen/st/fts_lib/ftsCrossCompile.c new file mode 100644 index 000000000000..502dace75e4f --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsCrossCompile.c @@ -0,0 +1,43 @@ +#include "ftsCrossCompile.h" +#include "ftsError.h" + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/spi/spidev.h> +#include <linux/fcntl.h> +#include <linux/syscalls.h> + +/* static char tag[8]="[ FTS ]\0"; */ +void *stmalloc(size_t size) +{ + return kmalloc(size, GFP_KERNEL); + +} + +void stfree(void *ptr) +{ + kfree(ptr); +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsCrossCompile.h b/drivers/input/touchscreen/st/fts_lib/ftsCrossCompile.h new file mode 100644 index 000000000000..8b287dd342f8 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsCrossCompile.h @@ -0,0 +1,34 @@ +/* #define NDK */ +/* #define DEBUG */ + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/spi/spidev.h> +#include <linux/fcntl.h> +#include <linux/syscalls.h> + +void *stmalloc(size_t size); +void stfree(void *ptr); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsError.c b/drivers/input/touchscreen/st/fts_lib/ftsError.c new file mode 100644 index 000000000000..844e5019fec6 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsError.c @@ -0,0 +1,105 @@ +/* +*************************************************************************** +* STMicroelectronics +************************************************************************** +* marco.cali@st.com +************************************************************************** +* +* FTS error/info kernel log reporting +* +************************************************************************** +************************************************************************** +*/ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/mt.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/completion.h> + +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> + +#include "../fts.h" +#include "ftsCrossCompile.h" +#include "ftsError.h" +#include "ftsIO.h" +#include "ftsTool.h" + +void logError(int force, const char *msg, ...) +{ + if (force == 1 +#ifdef DEBUG + || 1 +#endif + ) { + va_list args; + va_start(args, msg); + vprintk(msg, args); + va_end(args); + } +} + +int isI2cError(int error) +{ + if (((error & 0x000000FF) >= (ERROR_I2C_R & 0x000000FF)) && ((error & 0x000000FF) <= (ERROR_I2C_O & 0x000000FF))) + return 1; + else + return 0; +} + +int errorHandler(u8 *event, int size) +{ + int res = OK; + struct fts_ts_info *info = NULL; + + if (getClient() != NULL) + info = i2c_get_clientdata(getClient()); + + if (info != NULL && event != NULL && size > 1 && event[0] == EVENTID_ERROR_EVENT) { + logError(1, "%s errorHandler: Starting handling...\n", tag); + switch (event[1]) + /* TODO: write an error log for undefinied command subtype 0xBA*/ + { + case EVENT_TYPE_ESD_ERROR: /* esd */ + res = fts_chip_powercycle(info); + if (res < OK) { + logError(1, "%s errorHandler: Error performing powercycle ERROR %08X\n", tag, res); + } + + res = fts_system_reset(); + if (res < OK) { + logError(1, "%s errorHandler: Cannot reset the device ERROR %08X\n", tag, res); + } + res = (ERROR_HANDLER_STOP_PROC|res); + break; + + case EVENT_TYPE_WATCHDOG_ERROR: /* watchdog */ + res = fts_system_reset(); + if (res < OK) { + logError(1, "%s errorHandler: Cannot reset the device ERROR %08X\n", tag, res); + } + res = (ERROR_HANDLER_STOP_PROC|res); + break; + + default: + logError(1, "%s errorHandler: No Action taken! \n", tag); + break; + + } + logError(1, "%s errorHandler: handling Finished! res = %08X\n", tag, res); + return res; + } + logError(1, "%s errorHandler: event Null or not correct size! ERROR %08X \n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsError.h b/drivers/input/touchscreen/st/fts_lib/ftsError.h new file mode 100644 index 000000000000..05b8ba9e4612 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsError.h @@ -0,0 +1,76 @@ +/* +************************************************************************** +** STMicroelectronics +************************************************************************** +** marco.cali@st.com +************************************************************************** +* +* FTS error/info kernel log reporting +* +************************************************************************** +************************************************************************** +*/ + +#include <linux/types.h> + +/*FIRST LEVEL ERROR CODE*/ +#define OK ((int)0x00000000) /*No ERROR*/ +#define ERROR_ALLOC ((int)0x80000001) /*allocation of memory failed*/ +#define ERROR_I2C_R ((int)0x80000002) /*i2c read failed*/ +#define ERROR_I2C_W ((int)0x80000003) /*i2c write failed*/ +#define ERROR_I2C_WR ((int)0x80000004) /*i2c write/read failed*/ +#define ERROR_I2C_O ((int)0x80000005) /*error during opening a i2c device*/ +#define ERROR_OP_NOT_ALLOW ((int)0x80000006) /*operation not allowed*/ +#define ERROR_TIMEOUT ((int)0x80000007) /*timeout expired! exceed the max number of retries or the max waiting time*/ +#define ERROR_FILE_NOT_FOUND ((int)0x80000008) /*the file that i want to open is not found*/ +#define ERROR_FILE_PARSE ((int)0x80000009) /*error during parsing the file*/ +#define ERROR_FILE_READ ((int)0x8000000A) /*error during reading the file*/ +#define ERROR_LABEL_NOT_FOUND ((int)0x8000000B) /*label not found*/ +#define ERROR_FW_NO_UPDATE ((int)0x8000000C) /*fw in the chip newer than the one in the memmh*/ +#define ERROR_FLASH_UNKNOWN ((int)0x8000000D) /*flash status busy or unknown*/ + +/*SECOND LEVEL ERROR CODE*/ +#define ERROR_DISABLE_INTER ((int)0x80000200) /*unable to disable the interrupt*/ +#define ERROR_ENABLE_INTER ((int)0x80000300) /*unable to activate the interrup*/ +#define ERROR_READ_B2 ((int)0x80000400) /*B2 command failed*/ +#define ERROR_GET_OFFSET ((int)0x80000500) /*unable to read an offset from memory*/ +#define ERROR_GET_FRAME_DATA ((int)0x80000600) /*unable to retrieve the data of a required frame*/ +#define ERROR_DIFF_COMP_TYPE ((int)0x80000700) /*FW answers with an event that has a different address respect the request done*/ +#define ERROR_WRONG_COMP_SIGN ((int)0x80000800) /*the signature of the compensation data is not A5*/ +#define ERROR_SENSE_ON_FAIL ((int)0x80000900) /*the command Sense On failed*/ +#define ERROR_SENSE_OFF_FAIL ((int)0x80000A00) /*the command Sense Off failed*/ +#define ERROR_SYSTEM_RESET_FAIL ((int)0x80000B00) /*the command SYSTEM RESET failed*/ +#define ERROR_FLASH_NOT_READY ((int)0x80000C00) /*flash status not ready within a timeout*/ +#define ERROR_FW_VER_READ ((int)0x80000D00) /*unable to retrieve fw_vers or the config_id*/ +#define ERROR_GESTURE_ENABLE_FAIL ((int)0x80000E00) /*unable to enable/disable the gesture*/ +#define ERROR_GESTURE_START_ADD ((int)0x80000F00) /*unable to start to add custom gesture*/ +#define ERROR_GESTURE_FINISH_ADD ((int)0x80001000) /*unable to finish to add custom gesture*/ +#define ERROR_GESTURE_DATA_ADD ((int)0x80001100) /*unable to add custom gesture data*/ +#define ERROR_GESTURE_REMOVE ((int)0x80001200) /*unable to remove custom gesture data*/ +#define ERROR_FEATURE_ENABLE_DISABLE ((int)0x80001300) /*unable to enable/disable a feature mode in the IC*/ +/*THIRD LEVEL ERROR CODE*/ +#define ERROR_CH_LEN ((int)0x80010000) /*unable to retrieve the force and/or sense length*/ +#define ERROR_REQU_COMP_DATA ((int)0x80020000) /*compensation data request failed*/ +#define ERROR_COMP_DATA_HEADER ((int)0x80030000) /*unable to retrieve the compensation data header*/ +#define ERROR_COMP_DATA_GLOBAL ((int)0x80040000) /*unable to retrieve the global compensation data*/ +#define ERROR_COMP_DATA_NODE ((int)0x80050000) /*unable to retrieve the compensation data for each node*/ +#define ERROR_TEST_CHECK_FAIL ((int)0x80060000) /*check of production limits or of fw answers failed*/ +#define ERROR_MEMH_READ ((int)0x80070000) /*memh reading failed*/ +#define ERROR_FLASH_BURN_FAILED ((int)0x80080000) /*flash burn failed*/ +#define ERROR_MS_TUNING ((int)0x80090000) /*ms tuning failed*/ +#define ERROR_SS_TUNING ((int)0x800A0000) /*ss tuning failed*/ +#define ERROR_LP_TIMER_TUNING ((int)0x800B0000) /*lp timer calibration failed*/ +#define ERROR_SAVE_CX_TUNING ((int)0x800C0000) /*save cx data to flash failed*/ +#define ERROR_HANDLER_STOP_PROC ((int)0x800D0000) /*stop the poll of the FIFO if particular errors are found*/ +#define ERROR_CHECK_ECHO_FAIL ((int)0x800E0000) /*unable to retrieve echo event*/ + +/*FOURTH LEVEL ERROR CODE*/ +#define ERROR_PROD_TEST_DATA ((int)0x81000000) /*production data test failed*/ +#define ERROR_FLASH_PROCEDURE ((int)0x82000000) /*complete flash procedure failed*/ +#define ERROR_PROD_TEST_ITO ((int)0x83000000) /*production ito test failed*/ +#define ERROR_PROD_TEST_INITIALIZATION ((int)0x84000000) /*production initialization test failed*/ +#define ERROR_GET_INIT_STATUS ((int)0x85000000) /*mismatch of the MS or SS tuning_version*/ + +void logError(int force, const char *msg, ...); +int isI2cError(int error); +int errorHandler(u8 *event, int size); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsFlash.c b/drivers/input/touchscreen/st/fts_lib/ftsFlash.c new file mode 100644 index 000000000000..f3becac79102 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsFlash.c @@ -0,0 +1,1071 @@ +/* + + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for Flashing the IC * + * * + ************************************************************************** + ************************************************************************** + + */ + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFlash.h" +#include "ftsFrame.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTest.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h" /* needed for the FW_H_FILE define */ + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ + +#ifdef FW_H_FILE +#include <../fts_fw.h> +#endif + +/* static char tag[8] = "[ FTS ]\0"; */ +extern chipInfo ftsInfo; + +int getFirmwareVersion(u16 *fw_vers, u16 *config_id) +{ + u8 fwvers[DCHIP_FW_VER_BYTE]; + u8 confid[CONFIG_ID_BYTE]; + int res; + + res = readCmdU16(FTS_CMD_HW_REG_R, DCHIP_FW_VER_ADDR, fwvers, DCHIP_FW_VER_BYTE, DUMMY_HW_REG); + if (res < OK) { + logError(1, "%s getFirmwareVersion: unable to read fw_version ERROR %02X\n", tag, ERROR_FW_VER_READ); + return (res | ERROR_FW_VER_READ); + } + + u8ToU16(fwvers, fw_vers); /* fw version use big endian */ + if (*fw_vers != 0) { /* if fw_version is 00 00 means that there is no firmware running in the chip therefore will be impossible find the config_id */ + res = readB2(CONFIG_ID_ADDR, confid, CONFIG_ID_BYTE); + if (res < OK) { + logError(1, "%s getFirmwareVersion: unable to read config_id ERROR %02X\n", tag, ERROR_FW_VER_READ); + return (res | ERROR_FW_VER_READ); + } + u8ToU16(confid, config_id); /* config id use little endian */ + } else { + *config_id = 0x0000; + } + + logError(0, "%s FW VERS = %04X\n", tag, *fw_vers); + logError(0, "%s CONFIG ID = %04X\n", tag, *config_id); + return OK; + +} + +#ifdef FTM3_CHIP + +int flash_status(void) +{ + u8 cmd[2] = {FLASH_CMD_READSTATUS, 0x00}; + u8 readData = 0; + + logError(0, "%s Reading flash_status...\n", tag); + if (fts_readCmd(cmd, 2, &readData, FLASH_STATUS_BYTES) < 0) { + logError(1, "%s flash_status: ERROR % 02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + + readData &= 0x01; + /* logError(0, "%s flash_status = %d\n", tag,readData); */ + return (int) readData; + +} + +int flash_status_ready(void) +{ + + int status = flash_status(); + + if (status == ERROR_I2C_R) { + logError(1, "%s flash_status_ready: ERROR % 02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + + if (status != FLASH_READY) { + logError(1, "%s flash_status_ready: flash busy or unknown STATUS = % 02X\n", tag, status); + return ERROR_FLASH_UNKNOWN; + } + + return FLASH_READY; + +} + +int wait_for_flash_ready(void) +{ + int status; + int(*code)(void); + + code = flash_status_ready; + + logError(0, "%s Waiting for flash ready...\n", tag); + status = attempt_function(code, FLASH_WAIT_BEFORE_RETRY, FLASH_RETRY_COUNT); + + if (status != FLASH_READY) { + logError(1, "%s wait_for_flash_ready: ERROR % 02X\n", tag, ERROR_FLASH_NOT_READY); + return (status | ERROR_FLASH_NOT_READY); + } + + logError(0, "%s Flash ready!\n", tag); + return OK; +} + +int flash_unlock(void) +{ + + int status; + u8 cmd[3] = {FLASH_CMD_UNLOCK, FLASH_UNLOCK_CODE0, FLASH_UNLOCK_CODE1}; /* write the comand to perform the unlock */ + + logError(0, "%s Try to unlock flash...\n", tag); + status = wait_for_flash_ready(); + + if (status != OK) { + logError(1, "%s flash_unlock: ERROR % 02X\n", tag, ERROR_FLASH_NOT_READY); + return (status | ERROR_FLASH_NOT_READY); /* Flash not ready within the choosen time, better exit! */ + } + + logError(0, "%s Command unlock ...\n", tag); + if (fts_writeCmd(cmd, sizeof (cmd)) < 0) { + logError(1, "%s flash_unlock: ERROR % 02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + status = wait_for_flash_ready(); + + if (status != OK) { + logError(1, "%s flash_unlock: ERROR % 02X\n", tag, ERROR_FLASH_NOT_READY); + return (status | ERROR_FLASH_NOT_READY); /* Flash not ready within the choosen time, better exit! */ + } + + logError(0, "%s Unlock flash DONE!\n", tag); + + return OK; + +} + +/*int parseMemhFile(const char *pathToFile, u8** data, int* length, int dimension) +{ + + int i = 0; + unsigned long ul; + u8* buff = NULL; + int fd = -1; + int n, size, pointer = 0; + char *data_file, *line; + const struct firmware *fw = NULL; + struct device *dev = NULL; + + line = (char *) kmalloc(11 * sizeof (char), GFP_KERNEL); + if (line == NULL) { + logError(1, "%s parseMemhFile: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + logError(0, "%s parseMemhFile: allocating %d bytes\n", tag, dimension); + buff = (u8*) kmalloc(dimension * sizeof (u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s parseMemhFile: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + dev = getDev(); + if (dev != NULL) + fd = request_firmware(&fw, pathToFile, dev); + + if (fd == 0) { + size = fw->size; + logError(0, "%s The size of the firmware file is %d bytes...\n", tag, size); + data_file = (char *) fw->data; + logError(0, "%s Start to reading %s...\n", tag, pathToFile); + + while (size - pointer > 0 && (i * 4 + 4) <= dimension) { + if (readLine(&data_file[pointer], &line, size - pointer, &n) < 0) { + break; + } + pointer += n; + logError(0, "%s Pointer= %d riga = %s\n", tag, pointer, line); + ul = simple_strtoul(line, NULL, 16); + + buff[i * 4] = (u8) ((ul & 0x000000FF) >> 0); + buff[i * 4 + 1] = (u8) ((ul & 0x0000FF00) >> 8); + buff[i * 4 + 2] = (u8) ((ul & 0x00FF0000) >> 16); + buff[i * 4 + 3] = (u8) ((ul & 0xFF000000) >> 24); + i++; + } + + kfree(line); + + *length = i * 4; + if (*length < dimension) { + logError(1, "%s parseMemhFile: Read only %d instead of %d... ERROR %02X\n", tag, *length, dimension, ERROR_FILE_PARSE); + release_firmware(fw); + return ERROR_FILE_PARSE; + } + *data = buff; + + logError(0, "%s READ DONE %d bytes!\n", tag, *length); + release_firmware(fw); + return OK; + } else { + logError(1, "%s parseProductionTestLimits: ERROR %02X\n", tag, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + +}*/ + +int parseBinFile(const char *pathToFile, u8 **data, int *length, int dimension) +{ + + int fd = -1; + int fw_size = 0; + u8 *fw_data = NULL; + +#ifndef FW_H_FILE + const struct firmware *fw = NULL; + struct device *dev = NULL; + dev = getDev(); + + if (dev != NULL) + fd = request_firmware(&fw, pathToFile, dev); + else { + logError(1, "%s parseBinFile: No device found! ERROR %02X\n", ERROR_FILE_PARSE); + return ERROR_FILE_PARSE; + } +#else + fd = 0; +#endif + + if (fd == 0) { +#ifndef FW_H_FILE + fw_size = fw->size; + fw_data = (u8 *) (fw->data); +#else + fw_size = FW_SIZE_NAME; + fw_data = (u8 *) FW_ARRAY_NAME; +#endif + if (fw_size - FW_HEADER_SIZE != FW_SIZE) { + logError(1, "%s parseBinFile: Read only %d instead of %d... ERROR %02X\n", tag, fw_size, FW_SIZE, ERROR_FILE_PARSE); +#ifndef FW_H_FILE + release_firmware(fw); +#endif + return ERROR_FILE_PARSE; + } + *data = (u8 *) kmalloc(dimension * sizeof (u8), GFP_KERNEL); + if (*data == NULL) { + logError(1, "%s parseBinFile: ERROR %02X\n", tag, ERROR_ALLOC); +#ifndef FW_H_FILE + release_firmware(fw); +#endif + return ERROR_ALLOC; + } + + memcpy(*data, ((u8 *) (fw_data) + FW_HEADER_SIZE), dimension); + *length = dimension; + + logError(0, "%s READ FW DONE %d bytes!\n", tag, *length); +#ifndef FW_H_FILE + release_firmware(fw); +#endif + return OK; + } + logError(1, "%s parseBinFile: File Not Found! ERROR %02X\n", tag, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; +} + +int readFwFile(const char *path, Firmware *fw, int keep_cx) +{ + int res; + int size; + + if (keep_cx) { + size = FW_SIZE - FW_CX_SIZE; + logError(1, "%s readFwFile: Selected 124k Configuration!\n", tag); + } else { + size = FW_SIZE; + logError(1, "%s readFwFile: Selected 128k Configuration!\n", tag); + } + + /* res = parseMemhFile(path, &(fw->data), &(fw->data_size), size); */ + res = parseBinFile(path, &(fw->data), &(fw->data_size), size); + if (res < OK) { + logError(1, "%s readFwFile: ERROR %02X\n", tag, ERROR_MEMH_READ); + return (res | ERROR_MEMH_READ); + } + + fw->fw_ver = (u16) (((fw->data[FW_VER_MEMH_BYTE1] & 0x00FF) << 8) + (fw->data[FW_VER_MEMH_BYTE0] & 0x00FF)); + fw->config_id = (u16) (((fw->data[(FW_CODE_SIZE) + FW_OFF_CONFID_MEMH_BYTE1] & 0x00FF) << 8) + (fw->data[(FW_CODE_SIZE) + FW_OFF_CONFID_MEMH_BYTE0] & 0x00FF)); + + logError(0, "%s FW VERS File = %04X\n", tag, fw->fw_ver); + logError(0, "%s CONFIG ID File = %04X\n", tag, fw->config_id); + return OK; + +} + +int fillMemory(u32 address, u8 *data, int size) +{ + + int remaining = size; + int toWrite = 0; + + u8 *buff = (u8 *) kmalloc((MEMORY_CHUNK + 3) * sizeof (u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s fillMemory: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + if (remaining >= MEMORY_CHUNK) { + if ((address + MEMORY_CHUNK) < FLASH_ADDR_SWITCH_CMD) { + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = MEMORY_CHUNK; + remaining -= MEMORY_CHUNK; + } else { + if (address < FLASH_ADDR_SWITCH_CMD) { + int delta = FLASH_ADDR_SWITCH_CMD - address; + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = delta; + remaining -= delta; + } else { + buff[0] = FLASH_CMD_WRITE_UPPER_64; + toWrite = MEMORY_CHUNK; + remaining -= MEMORY_CHUNK; + } + } + } else { + if ((address + remaining) < FLASH_ADDR_SWITCH_CMD) { + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = remaining; + remaining = 0; + } else { + if (address < FLASH_ADDR_SWITCH_CMD) { + int delta = FLASH_ADDR_SWITCH_CMD - address; + buff[0] = FLASH_CMD_WRITE_LOWER_64; + toWrite = delta; + remaining -= delta; + } else { + buff[0] = FLASH_CMD_WRITE_UPPER_64; + toWrite = remaining; + remaining = 0; + } + } + } + + buff[1] = (u8) ((address & 0x0000FF00) >> 8); + buff[2] = (u8) (address & 0x000000FF); + memcpy(buff + 3, data, toWrite); + logError(0, "%s Command = %02X , address = %02X %02X, bytes = %d\n", tag, buff[0], buff[1], buff[2], toWrite); + if (fts_writeCmd(buff, 3 + toWrite) < 0) { + logError(1, "%s fillMemory: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + address += toWrite; + data += toWrite; + + } + return OK; +} + +int flash_burn(Firmware fw, int force_burn) +{ + u8 cmd; + int res; + + if (!force_burn && (ftsInfo.u16_fwVer >= fw.fw_ver) && (ftsInfo.u16_cfgId >= fw.config_id)) { + logError(1, "%s flash_burn: Firmware in the chip newer or equal to the one to burn! NO UPDATE ERROR %02X\n", tag, ERROR_FW_NO_UPDATE); + return (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED); + } + + /* programming procedure start */ + + logError(0, "%s Programming Procedure for flashing started:\n\n", tag); + + logError(0, "%s 1) SYSTEM RESET:\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s system reset FAILED!\n", tag); + if (res != (ERROR_SYSTEM_RESET_FAIL | ERROR_TIMEOUT)) /* if there is no firmware i will not get the controller ready event and there will be a timeout but i can keep going, but if there is an I2C error i have to exit */ + return (res | ERROR_FLASH_BURN_FAILED); + } else + logError(0, "%s system reset COMPLETED!\n\n", tag); + + logError(0, "%s 2) FLASH UNLOCK:\n", tag); + res = flash_unlock(); + if (res < 0) { + logError(1, "%s flash unlock FAILED! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash unlock COMPLETED!\n\n", tag); + + /* Write the lower part of the Program RAM */ + logError(0, "%s 3) PREPARING DATA FOR FLASH BURN:\n", tag); + + res = fillMemory(FLASH_ADDR_CODE, fw.data, fw.data_size); + if (res < 0) { + logError(1, "%s Error During filling the memory! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s Data copy COMPLETED!\n\n", tag); + + logError(0, "%s 4) ERASE FLASH:\n", tag); + res = wait_for_flash_ready(); + if (res < 0) { + logError(1, "%s Flash not ready! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + logError(0, "%s Command erase ...\n", tag); + cmd = FLASH_CMD_ERASE; + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s Error during erasing flash! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (ERROR_I2C_W | ERROR_FLASH_BURN_FAILED); + } + + res = wait_for_flash_ready(); + if (res < 0) { + logError(1, "%s Flash not ready 2! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + logError(0, "%s Flash erase COMPLETED!\n\n", tag); + + logError(0, "%s 5) BURN FLASH:\n", tag); + logError(0, "%s Command burn ...\n", tag); + cmd = FLASH_CMD_BURN; + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s Error during burning data! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (ERROR_I2C_W | ERROR_FLASH_BURN_FAILED); + } + + res = wait_for_flash_ready(); + if (res < 0) { + logError(1, "%s Flash not ready! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + logError(0, "%s Flash burn COMPLETED!\n\n", tag); + + logError(0, "%s 6) SYSTEM RESET:\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s system reset FAILED! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s system reset COMPLETED!\n\n", tag); + + logError(0, "%s 7) FINAL CHECK:\n", tag); + res = readChipInfo(0); + if (res < 0) { + logError(1, "%s flash_burn: Unable to retrieve Chip INFO! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + if ((ftsInfo.u16_fwVer != fw.fw_ver) && (ftsInfo.u16_cfgId != fw.config_id)) { + logError(1, "%s Firmware in the chip different from the one that was burn! fw: %x != %x , conf: %x != %x\n", tag, ftsInfo.u16_fwVer, fw.fw_ver, ftsInfo.u16_cfgId, fw.config_id); + return ERROR_FLASH_BURN_FAILED; + } + + logError(0, "%s Final check OK! fw: %02X , conf: %02X\n", tag, ftsInfo.u16_fwVer, ftsInfo.u16_cfgId); + + return OK; +} + +int flashProcedure(const char *path, int force, int keep_cx) +{ + Firmware fw; + int res; + + fw.data = NULL; + logError(0, "%s Reading Fw file...\n", tag); + res = readFwFile(path, &fw, keep_cx); + if (res < OK) { + logError(1, "%s flashProcedure: ERROR %02X\n", tag, (res | ERROR_FLASH_PROCEDURE)); + kfree(fw.data); + return (res | ERROR_FLASH_PROCEDURE); + } + logError(0, "%s Fw file read COMPLETED!\n", tag); + + logError(0, "%s Starting flashing procedure...\n", tag); + res = flash_burn(fw, force); + if (res < OK && res != (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED)) { + logError(1, "%s flashProcedure: ERROR %02X\n", tag, ERROR_FLASH_PROCEDURE); + kfree(fw.data); + return (res | ERROR_FLASH_PROCEDURE); + } + logError(0, "%s flashing procedure Finished!\n", tag); + kfree(fw.data); + + /* cleanUp(0); //after talking with Kusuma, the SenseOn should be issued only at the very end of the initialization process, if senso on here it can trigger autotune protection */ + return res; +} + +#else + +int wait_for_flash_ready(u8 type) +{ + u8 cmd[2] = {FLASH_CMD_READ_REGISTER, type}; + u8 readData; + int i, res = -1; + + logError(0, "%s Waiting for flash ready ...\n", tag); + for (i = 0; i < FLASH_RETRY_COUNT && res != 0; i++) { + if (fts_readCmd(cmd, sizeof (cmd), &readData, 1) < 0) { + logError(1, "%s wait_for_flash_ready: ERROR % 02X\n", tag, ERROR_I2C_W); + } else{ + res = readData & 0x80; + /* logError(0, "%s flash status = %d \n", tag, res); */ + } + msleep(FLASH_WAIT_BEFORE_RETRY); + } + + if (i == FLASH_RETRY_COUNT && res != 0) { + logError(1, "%s Wait for flash TIMEOUT! ERROR %02X\n", tag, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + + logError(0, "%s Flash READY!\n", tag); + return OK; +} + +int fts_warm_boot(void) +{ + + u8 cmd[4] = {FTS_CMD_HW_REG_W, 0x00, 0x00, WARM_BOOT_VALUE}; /* write the command to perform the warm boot */ + u16ToU8_be(ADDR_WARM_BOOT, &cmd[1]); + + logError(0, "%s Command warm boot ...\n", tag); + if (fts_writeCmd(cmd, sizeof (cmd)) < 0) { + logError(1, "%s flash_unlock: ERROR % 02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + logError(0, "%s Warm boot DONE!\n", tag); + + return OK; +} + +int parseBinFile(const char *pathToFile, Firmware *fwData, int keep_cx) +{ + + int fd = -1; + int dimension, index = 0; + u32 temp; + u8 *data; + int res, i, fw_size; + +#ifndef FW_H_FILE + const struct firmware *fw = NULL; + struct device *dev = NULL; + dev = getDev(); + + if (dev != NULL) + fd = request_firmware(&fw, pathToFile, dev); + else { + logError(1, "%s parseBinFile: No device found! ERROR %02X\n", ERROR_FILE_PARSE); + return ERROR_FILE_PARSE; + } + + fw_size = fw->size; +#else +fd = 0; +fw_size = SIZE_NAME; +#endif + + if (fd == 0 && fw_size > 0) { /* the file should contain at least the header plus the content_crc */ + if (fw_size < FW_HEADER_SIZE+FW_BYTES_ALIGN) { + logError(1, "%s parseBinFile: Read only %d instead of %d... ERROR %02X\n", tag, fw_size, FW_HEADER_SIZE+FW_BYTES_ALIGN, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } else { + /* start parsing of bytes */ +#ifndef FW_H_FILE + data = (u8 *) (fw->data); +#else + data = (u8 *) (ARRAY_NAME); +#endif + u8ToU32(&data[index], &temp); + if (temp != FW_HEADER_SIGNATURE) { + logError(1, "%s parseBinFile: Wrong Signature %08X ... ERROR %02X\n", tag, temp, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + logError(0, "%s parseBinFile: Fw Signature OK!\n", tag); + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + if (temp != FW_FTB_VER) { + logError(1, "%s parseBinFile: Wrong ftb_version %08X ... ERROR %02X\n", tag, temp, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + logError(0, "%s parseBinFile: ftb_version OK!\n", tag); + index += FW_BYTES_ALIGN; + if (data[index] != DCHIP_ID_0 || data[index+1] != DCHIP_ID_1) { + logError(1, "%s parseBinFile: Wrong target %02X != %02X %02X != %02X ... ERROR %08X\n", tag, data[index], DCHIP_ID_0, data[index+1], DCHIP_ID_1, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + logError(1, "%s parseBinFile: Fw ID = %08X\n", tag, temp); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->fw_ver = temp; + logError(1, "%s parseBinFile: FILE Fw Version = %04X\n", tag, fwData->fw_ver); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->config_id = temp; + logError(1, "%s parseBinFile: FILE Config ID = %08X\n", tag, temp); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + logError(1, "%s parseBinFile: Config Version = %08X\n", tag, temp); + + index += FW_BYTES_ALIGN*2; /* skip reserved data */ + + index += FW_BYTES_ALIGN; + logError(1, "%s parseBinFile: File External Release = ", tag); + for (i = 0; i < EXTERNAL_RELEASE_INFO_SIZE; i++) { + fwData->externalRelease[i] = data[index++]; + logError(1, "%02X ", fwData->externalRelease[i]); + } + logError(1, "\n"); + + /* index += FW_BYTES_ALIGN; */ + u8ToU32(&data[index], &temp); + fwData->sec0_size = temp; + logError(1, "%s parseBinFile: sec0_size = %08X (%d bytes)\n", tag, fwData->sec0_size, fwData->sec0_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->sec1_size = temp; + logError(1, "%s parseBinFile: sec1_size = %08X (%d bytes)\n", tag, fwData->sec1_size, fwData->sec1_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->sec2_size = temp; + logError(1, "%s parseBinFile: sec2_size = %08X (%d bytes)\n", tag, fwData->sec2_size, fwData->sec2_size); + + index += FW_BYTES_ALIGN; + u8ToU32(&data[index], &temp); + fwData->sec3_size = temp; + logError(1, "%s parseBinFile: sec3_size = %08X (%d bytes)\n", tag, fwData->sec3_size, fwData->sec3_size); + + index += FW_BYTES_ALIGN; /* skip header crc */ + + if (!keep_cx) { + dimension = fwData->sec0_size + fwData->sec1_size + fwData->sec2_size + fwData->sec3_size; + temp = fw_size; + } else { + dimension = fwData->sec0_size + fwData->sec1_size; /* sec2 may contain cx data (future implementation) sec3 atm not used */ + temp = fw_size - fwData->sec2_size - fwData->sec3_size; + } + + if (dimension+FW_HEADER_SIZE+FW_BYTES_ALIGN != temp) { + logError(1, "%s parseBinFile: Read only %d instead of %d... ERROR %02X\n", tag, fw_size, dimension+FW_HEADER_SIZE+FW_BYTES_ALIGN, ERROR_FILE_PARSE); + res = ERROR_FILE_PARSE; + goto END; + } + + fwData->data = (u8 *) kmalloc(dimension * sizeof (u8), GFP_KERNEL); + if (fwData->data == NULL) { + logError(1, "%s parseBinFile: ERROR %02X\n", tag, ERROR_ALLOC); + res = ERROR_ALLOC; + goto END; + } + + index += FW_BYTES_ALIGN; + memcpy(fwData->data, &data[index], dimension); + fwData->data_size = dimension; + + logError(0, "%s READ FW DONE %d bytes!\n", tag, fwData->data_size); + res = OK; + goto END; + } + } else { + logError(1, "%s parseBinFile: File Not Found! ERROR %02X\n", tag, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + +END: +#ifndef FW_H_FILE + release_firmware(fw); +#endif + return res; +} + +int readFwFile(const char *path, Firmware *fw, int keep_cx) +{ + int res; + + res = parseBinFile(path, fw, keep_cx); + if (res < OK) { + logError(1, "%s readFwFile: ERROR %02X\n", tag, ERROR_MEMH_READ); + return (res | ERROR_MEMH_READ); + } + + return OK; + +} + +int flash_unlock(void) +{ + u8 cmd[3] = {FLASH_CMD_UNLOCK, FLASH_UNLOCK_CODE0, FLASH_UNLOCK_CODE1}; /* write the command to perform the unlock */ + + logError(0, "%s Command unlock ...\n", tag); + if (fts_writeCmd(cmd, sizeof (cmd)) < 0) { + logError(1, "%s flash_unlock: ERROR % 02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + /* msleep(FLASH_WAIT_TIME); */ + logError(0, "%s Unlock flash DONE!\n", tag); + + return OK; + +} + +int flash_erase_unlock(void) +{ + u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_UNLOCK_CODE0, FLASH_ERASE_UNLOCK_CODE1}; /* write the command to perform the unlock for erasing the flash */ + + logError(0, "%s Try to erase unlock flash...\n", tag); + + logError(0, "%s Command erase unlock ...\n", tag); + if (fts_writeCmd(cmd, sizeof (cmd)) < 0) { + logError(1, "%s flash_erase_unlock: ERROR % 02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + logError(0, "%s Erase Unlock flash DONE!\n", tag); + + return OK; + +} + +int flash_full_erase(void) +{ + + int status; + u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_ERASE_CODE0, FLASH_ERASE_CODE1}; + /* write the command to erase the flash */ + + logError(0, "%s Command full erase sent ...\n", tag); + if (fts_writeCmd(cmd, sizeof (cmd)) < 0) { + logError(1, "%s flash_full_erase: ERROR % 02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + status = wait_for_flash_ready(FLASH_ERASE_CODE0); + + if (status != OK) { + logError(1, "%s flash_full_erase: ERROR % 02X\n", tag, ERROR_FLASH_NOT_READY); + return (status | ERROR_FLASH_NOT_READY); + /* Flash not ready within the chosen time, better exit! */ + } + + logError(0, "%s Full Erase flash DONE!\n", tag); + + return OK; + +} + +int start_flash_dma(void) +{ + int status; + u8 cmd[3] = {FLASH_CMD_WRITE_REGISTER, FLASH_DMA_CODE0, FLASH_DMA_CODE1}; + /* write the command to erase the flash */ + + logError(0, "%s Command flash DMA ...\n", tag); + if (fts_writeCmd(cmd, sizeof (cmd)) < 0) { + logError(1, "%s start_flash_dma: ERROR % 02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + status = wait_for_flash_ready(FLASH_DMA_CODE0); + + if (status != OK) { + logError(1, "%s start_flash_dma: ERROR % 02X\n", tag, ERROR_FLASH_NOT_READY); + return (status | ERROR_FLASH_NOT_READY); + /* Flash not ready within the chosen time, better exit! */ + } + + logError(0, "%s flash DMA DONE!\n", tag); + + return OK; +} + +int fillFlash(u32 address, u8 *data, int size) +{ + + int remaining = size; + int toWrite = 0; + int byteBlock = 0; + int wheel = 0; + u32 addr = 0; + int res; + int delta; + + u8 *buff = (u8 *) kmalloc((DMA_CHUNK + 3) * sizeof (u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s fillFlash: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + byteBlock = 0; + addr = 0; + while (byteBlock < FLASH_CHUNK && remaining > 0) { + buff[0] = FLASH_CMD_WRITE_64K; + if (remaining >= DMA_CHUNK) { + if ((byteBlock + DMA_CHUNK) <= FLASH_CHUNK) { + /* logError(1, "%s fillFlash: 1\n", tag); */ + toWrite = DMA_CHUNK; + remaining -= DMA_CHUNK; + byteBlock += DMA_CHUNK; + } else { + /* logError(1, "%s fillFlash: 2\n", tag); */ + delta = FLASH_CHUNK - byteBlock; + toWrite = delta; + remaining -= delta; + byteBlock += delta; + } + } else { + if ((byteBlock + remaining) <= FLASH_CHUNK) { + /* logError(1, "%s fillFlash: 3\n", tag); */ + toWrite = remaining; + byteBlock += remaining; + remaining = 0; + + } else { + /* logError(1, "%s fillFlash: 4\n", tag); */ + delta = FLASH_CHUNK - byteBlock; + toWrite = delta; + remaining -= delta; + byteBlock += delta; + } + } + + buff[1] = (u8) ((addr & 0x0000FF00) >> 8); + buff[2] = (u8) (addr & 0x000000FF); + memcpy(&buff[3], data, toWrite); + if (fts_writeCmd(buff, 3 + toWrite) < 0) { + logError(1, "%s fillFlash: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + addr += toWrite; + data += toWrite; + } + + kfree(buff); + + /* configuring the DMA */ + byteBlock = byteBlock / 4 - 1; + + buff = (u8 *) kmalloc((9) * sizeof (u8), GFP_KERNEL); + buff[0] = FLASH_CMD_WRITE_REGISTER; + buff[1] = FLASH_DMA_CONFIG; + buff[2] = 0x00; + buff[3] = 0x00; + + addr = address + ((wheel * FLASH_CHUNK)/4); + buff[4] = (u8) ((addr & 0x000000FF)); + buff[5] = (u8) ((addr & 0x0000FF00) >> 8); + buff[6] = (u8) (byteBlock & 0x000000FF); + buff[7] = (u8) ((byteBlock & 0x0000FF00) >> 8); + buff[8] = 0x00; + + logError(0, "%s Command = %02X , address = %02X %02X, words = %02X %02X\n", tag, buff[0], buff[5], buff[4], buff[7], buff[6]); + if (fts_writeCmd(buff, 9) < OK) { + logError(1, "%s Error during filling Flash! ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + /* msleep(FLASH_WAIT_TIME); */ + res = start_flash_dma(); + if (res < OK) { + logError(1, "%s Error during flashing DMA! ERROR %02X\n", tag, res); + return res; + } + wheel++; + } + return OK; +} + +int flash_burn(Firmware fw, int force_burn) +{ + int res; + + if (!force_burn) { + for (res = EXTERNAL_RELEASE_INFO_SIZE-1; res >= 0; res--) { + if (fw.externalRelease[res] > ftsInfo.u8_extReleaseInfo[res]) + goto start; + } + logError(1, "%s flash_burn: Firmware in the chip newer or equal to the one to burn! NO UPDATE ERROR %02X\n", tag, ERROR_FW_NO_UPDATE); + return (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED); + } + + /* programming procedure start */ +start: + logError(0, "%s Programming Procedure for flashing started:\n\n", tag); + + logError(0, "%s 1) SYSTEM RESET:\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s system reset FAILED!\n", tag); + if (res != (ERROR_SYSTEM_RESET_FAIL | ERROR_TIMEOUT)) + /* if there is no firmware i will not get the controller + *ready event and there will be a timeout but i can keep going, + *but if there is an I2C error i have to exit + */ + return (res | ERROR_FLASH_BURN_FAILED); + } else + logError(0, "%s system reset COMPLETED!\n\n", tag); + + logError(0, "%s 2) WARM BOOT:\n", tag); + res = fts_warm_boot(); + if (res < OK) { + logError(1, "%s warm boot FAILED!\n", tag); + return (res | ERROR_FLASH_BURN_FAILED); + } else + logError(0, "%s warm boot COMPLETED!\n\n", tag); + + /* msleep(FLASH_WAIT_TIME); */ + logError(0, "%s 3) FLASH UNLOCK:\n", tag); + res = flash_unlock(); + if (res < OK) { + logError(1, "%s flash unlock FAILED! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash unlock COMPLETED!\n\n", tag); + + /* msleep(200); */ + logError(0, "%s 4) FLASH ERASE UNLOCK:\n", tag); + res = flash_erase_unlock(); + if (res < 0) { + logError(1, "%s flash unlock FAILED! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash unlock COMPLETED!\n\n", tag); + + /* msleep(FLASH_WAIT_TIME); */ + logError(0, "%s 5) FLASH ERASE:\n", tag); + res = flash_full_erase(); + if (res < 0) { + logError(1, "%s flash erase FAILED! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s flash erase COMPLETED!\n\n", tag); + + /* msleep(FLASH_WAIT_TIME); */ + logError(0, "%s 6) LOAD PROGRAM:\n", tag); + res = fillFlash(FLASH_ADDR_CODE, &fw.data[0], fw.sec0_size); + if (res < OK) { + logError(1, "%s load program ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(1, "%s load program DONE!\n", tag); + + logError(0, "%s 7) LOAD CONFIG:\n", tag); + res = fillFlash(FLASH_ADDR_CONFIG, &(fw.data[fw.sec0_size]), fw.sec1_size); + if (res < OK) { + logError(1, "%s load config ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(1, "%s load config DONE!\n", tag); + + logError(0, "%s Flash burn COMPLETED!\n\n", tag); + + logError(0, "%s 8) SYSTEM RESET:\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s system reset FAILED! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + logError(0, "%s system reset COMPLETED!\n\n", tag); + + logError(0, "%s 9) FINAL CHECK:\n", tag); + res = readChipInfo(0); + if (res < 0) { + logError(1, "%s flash_burn: Unable to retrieve Chip INFO! ERROR %02X\n", tag, ERROR_FLASH_BURN_FAILED); + return (res | ERROR_FLASH_BURN_FAILED); + } + + for (res = 0; res < EXTERNAL_RELEASE_INFO_SIZE; res++) { + if (fw.externalRelease[res] != ftsInfo.u8_extReleaseInfo[res]) { + /* external release is prined during readChipInfo */ + logError(1, "%s Firmware in the chip different from the one that was burn! fw: %x != %x , conf: %x != %x\n", tag, ftsInfo.u16_fwVer, fw.fw_ver, ftsInfo.u16_cfgId, fw.config_id); + return ERROR_FLASH_BURN_FAILED; + } + } + + logError(0, "%s Final check OK! fw: %02X, conf: %02X\n", tag, ftsInfo.u16_fwVer, ftsInfo.u16_cfgId); + + return OK; +} + +int flashProcedure(const char *path, int force, int keep_cx) +{ + Firmware fw; + int res; + + fw.data = NULL; + logError(0, "%s Reading Fw file...\n", tag); + res = readFwFile(path, &fw, keep_cx); + if (res < OK) { + logError(1, "%s flashProcedure: ERROR %02X\n", tag, (res | ERROR_FLASH_PROCEDURE)); + kfree(fw.data); + return (res | ERROR_FLASH_PROCEDURE); + } + logError(0, "%s Fw file read COMPLETED!\n", tag); + + logError(0, "%s Starting flashing procedure...\n", tag); + res = flash_burn(fw, force); + if (res < OK && res != (ERROR_FW_NO_UPDATE | ERROR_FLASH_BURN_FAILED)) { + logError(1, "%s flashProcedure: ERROR %02X\n", tag, ERROR_FLASH_PROCEDURE); + kfree(fw.data); + return (res | ERROR_FLASH_PROCEDURE); + } + logError(0, "%s flashing procedure Finished!\n", tag); + kfree(fw.data); + return res; +} + +#endif diff --git a/drivers/input/touchscreen/st/fts_lib/ftsFlash.h b/drivers/input/touchscreen/st/fts_lib/ftsFlash.h new file mode 100644 index 000000000000..69635e07a9f4 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsFlash.h @@ -0,0 +1,79 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS API for Flashing the IC * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsSoftware.h" + +/* Flash possible status */ +#define FLASH_READY 0 +#define FLASH_BUSY 1 +#define FLASH_UNKNOWN -1 + +#define FLASH_STATUS_BYTES 1 + +/* Flash timing parameters */ +#define FLASH_RETRY_COUNT 1000 +#define FLASH_WAIT_BEFORE_RETRY 50 /* ms */ + +#define FLASH_WAIT_TIME 200 /* ms */ + +/* PATHS FW FILES */ +/* #define PATH_FILE_FW "fw.memh" */ +#ifdef FTM3_CHIP +#define PATH_FILE_FW "st_fts.bin" +#else +#define PATH_FILE_FW "st_fts.ftb" /* new bin file structure */ +#endif + +#ifndef FTM3_CHIP +#define FLASH_CHUNK (64*1024) +#define DMA_CHUNK 32 +#endif + +typedef struct { + u8 *data; + u16 fw_ver; + u16 config_id; + u8 externalRelease[EXTERNAL_RELEASE_INFO_SIZE]; + int data_size; +#ifndef FTM3_CHIP + u32 sec0_size; + u32 sec1_size; + u32 sec2_size; + u32 sec3_size; +#endif +} Firmware; + +#ifdef FTM3_CHIP +int flash_status(void); +int flash_status_ready(void); +int wait_for_flash_ready(void); +int parseBinFile(const char *pathToFile, u8 **data, int *length, int dimension); +/* int parseMemhFile(const char* pathToFile, u8** data, int* length, int dimension); */ +#else +int wait_for_flash_ready(u8 type); +int fts_warm_boot(void); +int parseBinFile(const char *pathToFile, Firmware *fw, int keep_cx); +int flash_erase_unlock(void); +int flash_full_erase(void); +int start_flash_dma(void); +int fillFlash(u32 address, u8 *data, int size); +#endif + +int flash_unlock(void); +int fillMemory(u32 address, u8 *data, int size); +int getFirmwareVersion(u16 *fw_vers, u16 *config_id); +int readFwFile(const char *path, Firmware *fw, int keep_cx); +int flash_burn(Firmware fw, int force_burn); +int flashProcedure(const char *path, int force, int keep_cx); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsFrame.c b/drivers/input/touchscreen/st/fts_lib/ftsFrame.c new file mode 100644 index 000000000000..c502559319a0 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsFrame.c @@ -0,0 +1,569 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS functions for getting frames * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTool.h" +#include "ftsTime.h" + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ + +static char tag[8] = "[ FTS ]\0"; +static int sense_len, force_len; + +/*int getOffsetFrame(u16 address, u16 *offset) +{ + + u8 data[2]; + u8 cmd = { FTS_CMD_FRAMEBUFFER_R }; + + if (readCmdU16(cmd, address, data, OFFSET_LENGTH, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s getOffsetFrame: ERROR %02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + else { + u8ToU16(data, offset); + logError(0, "%s %s", tag, printHex("Offest = ", data, OFFSET_LENGTH)); + return OK; + } + +}*/ + +int getChannelsLength(void) +{ + + int ret; + u8 *data = (u8 *)kmalloc(2*sizeof(u8), GFP_KERNEL); + + if (data == NULL) { + logError(1, "%s getChannelsLength: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = readB2(ADDR_SENSE_LEN, data, 2); + if (ret < OK) { + logError(1, "%s getChannelsLength: ERROR %02X\n", tag, ERROR_READ_B2); + return (ret|ERROR_READ_B2); + } + + sense_len = (int)data[0]; + force_len = (int)data[1]; + + logError(0, "%s Force_len = %d Sense_Len = %d\n", tag, force_len, sense_len); + + kfree(data); + + return OK; +} + +int getFrameData(u16 address, int size, short **frame) +{ + int i, j, ret; + u8 *data = (u8 *)kmalloc(size*sizeof(u8), GFP_KERNEL); + if (data == NULL) { + logError(1, "%s getFrameData: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = readCmdU16(FTS_CMD_FRAMEBUFFER_R, address, data, size, DUMMY_FRAMEBUFFER); + if (ret < OK) { + logError(1, "%s getFrameData: ERROR %02X\n", tag, ERROR_I2C_R); + kfree(data); + return ERROR_I2C_R; + } + j = 0; + for (i = 0; i < size; i += 2) { + (*frame)[j] = (short)((data[i + 1] << 8) + data[i]); + j++; + } + kfree(data); + return OK; +} + +/*int getMSFrame(u16 type, short **frame, int keep_first_row) +{ + u16 offset; + int size, ret; + + if (getSenseLen() == 0 || getForceLen() == 0) { + ret=getChannelsLength(); + if (ret<OK) { + logError(1, "%s getMSFrame: ERROR %02X\n", tag,ERROR_CH_LEN); + return (ret|ERROR_CH_LEN); + } + } + + ret = getOffsetFrame(type, &offset); + if (ret<OK) { + logError(1, "%s getMSFrame: ERROR %02X\n", tag, ERROR_GET_OFFSET); + return (ret | ERROR_GET_OFFSET); + } + + switch (type) { + case ADDR_RAW_TOUCH: + case ADDR_FILTER_TOUCH: + case ADDR_NORM_TOUCH: + case ADDR_CALIB_TOUCH: + if (keep_first_row ==1) + size = ((force_len+1)*sense_len); + else { + size = ((force_len)*sense_len); + offset+= (sense_len * BYTES_PER_NODE); + } + break; + + default: + logError(1, "%s getMSFrame: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *frame = (short*)kmalloc(size*sizeof(short), GFP_KERNEL); + if (*frame==NULL) { + logError(1, "%s getMSFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret=getFrameData(offset, size*BYTES_PER_NODE, frame); + if (ret<OK) { + logError(1, "%s getMSFrame: ERROR %02X\n", tag, ERROR_GET_FRAME_DATA); + return (ret|ERROR_GET_FRAME_DATA); + } + + logError(0, "%s Frame acquired!\n", tag); + return size; + +} + +int getMSKeyFrame(u16 type, short **frame) { + u16 offset; + int size, ret; + u16 address; + MutualSenseData data; + + if (type != ADDR_RAW_MS_KEY) { + logError(1, "%s getMSKeyFrame: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = getOffsetFrame(type, &offset); + if (ret<OK) { + logError(1, "%s getMSKeyFrame: ERROR %02X\n", tag, ERROR_GET_OFFSET); + return (ret | ERROR_GET_OFFSET); + } + + ret = requestCompensationData(MS_KEY); + if (ret < OK) { + logError(1, "%s getMSKeyFrame: readMutualSenseCompensationData ERROR %02X\n", tag, ERROR_REQU_COMP_DATA); + return (ret | ERROR_REQU_COMP_DATA); + } + + ret = readCompensationDataHeader(MS_KEY, &(data.header), &address); + if (ret < OK) { + logError(1, "%s getMSKeyFrame: readMutualSenseCompensationData ERROR %02X\n", tag, ERROR_COMP_DATA_HEADER); + return (ret | ERROR_COMP_DATA_HEADER); + } + + if (data.header.force_node>data.header.sense_node) + size = data.header.force_node; + else + size = data.header.sense_node; + + *frame = (short*)kmalloc(size*sizeof(short), GFP_KERNEL); + if (frame == NULL) { + logError(1, "%s getMSKeyFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = getFrameData(offset, size*BYTES_PER_NODE, frame); + if (ret<OK) { + logError(1, "%s getMSKeyFrame: ERROR %02X\n", tag, ERROR_GET_FRAME_DATA); + return (ret | ERROR_GET_FRAME_DATA); + } + + logError(0, "%s Frame acquired!\n", tag); +} + +int getSSFrame(u16 type, short **frame) { + u16 offset; + int size, ret; + + if (getSenseLen() == 0 || getForceLen() == 0) { + ret = getChannelsLength(); + if (ret<0) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_CH_LEN); + return (ret | ERROR_CH_LEN); + } + } + + switch (type) { + case ADDR_RAW_HOVER_FORCE: + case ADDR_FILTER_HOVER_FORCE: + case ADDR_NORM_HOVER_FORCE: + case ADDR_CALIB_HOVER_FORCE: + case ADDR_RAW_PRX_FORCE: + case ADDR_FILTER_PRX_FORCE: + case ADDR_NORM_PRX_FORCE: + case ADDR_CALIB_PRX_FORCE: + size = ((force_len)* 1); + break; + + case ADDR_RAW_HOVER_SENSE: + case ADDR_FILTER_HOVER_SENSE: + case ADDR_NORM_HOVER_SENSE: + case ADDR_CALIB_HOVER_SENSE: + case ADDR_RAW_PRX_SENSE: + case ADDR_FILTER_PRX_SENSE: + case ADDR_NORM_PRX_SENSE: + case ADDR_CALIB_PRX_SENSE: + size = ((1)*sense_len); + break; + + default: + logError(1, "%s getSSFrame: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + + } + + ret = getOffsetFrame(type, &offset); + if (ret<OK) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_GET_OFFSET); + return (ret | ERROR_GET_OFFSET); + } + + *frame = (short*)kmalloc(size*sizeof(short), GFP_KERNEL); + if (*frame == NULL) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = getFrameData(offset, size*BYTES_PER_NODE, frame); + if (ret<OK) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_GET_FRAME_DATA); + return (ret | ERROR_GET_FRAME_DATA); + } + + logError(0, "%s Frame acquired!\n", tag); + return size; + +} + +int getNmsFrame(u16 type, short ***frames, int *size, int keep_first_row, int fs, int n) { + int i; + StopWatch global, local; + int temp; + + *frames = (short **)kmalloc(n*sizeof(short *), GFP_KERNEL); + + if (*frames == NULL) { + logError(1, "%s getNmsFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + fs = (1*1000 / fs) ; + + startStopWatch(&global); + for (i = 0; i < n; i++) { + + startStopWatch(&local); + + *size = getMSFrame(type, ((*frames)+i), keep_first_row); + if (*size < OK) { + logError(1, "%s getNFrame: getFrame failed\n",tag); + return *size; + } + + stopStopWatch(&local); + temp = elapsedMillisecond(&local); + logError(0, "%s Iteration %d performed in %d ms... the process wait for %ld ms\n\n", tag, i, temp, (unsigned long)(fs - temp)); + + if (temp < fs) + msleep((unsigned long)(fs - temp)); + + } + + stopStopWatch(&global); + temp = elapsedMillisecond(&global); + logError(0, "%s Global Iteration performed in %d ms\n", tag, temp); + temp /= n; + logError(0, "%s Mean Iteration performed in %d ms\n", tag, temp); + return (1000 / (temp)); + +}*/ + +int getSenseLen(void) +{ + int ret; + if (sense_len != 0) + return sense_len; + ret = getChannelsLength(); + if (ret < OK) + return ret; + else + return sense_len; +} + +int getForceLen(void) +{ + int ret; + if (force_len != 0) + return force_len; + ret = getChannelsLength(); + if (ret < OK) + return ret; + else + return force_len; +} + +int requestFrame(u16 type) +{ + int retry = 0; + int ret; + u16 answer; + + int event_to_search[1]; + u8 readEvent[FIFO_EVENT_SIZE]; + + u8 cmd[3] = { FTS_CMD_REQU_FRAME_DATA, 0x00, 0x00 }; + /* B7 is the command for asking frame data */ + event_to_search[0] = (int)EVENTID_FRAME_DATA_READ; + + u16ToU8(type, &cmd[1]); + + while (retry < FRAME_DATA_READ_RETRY) { + logError(0, "%s %s", tag, printHex("Command = ", cmd, 3)); + ret = fts_writeFwCmd(cmd, 3); + /* send the request to the chip to load in memory the Frame Data */ + if (ret < OK) { + logError(1, "%s requestFrame: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + ret = pollForEvent(event_to_search, 1, readEvent, TIMEOUT_REQU_COMP_DATA); + if (ret < OK) { + logError(0, "%s Event did not Found at %d attemp!\n", tag, retry + 1); + retry += 1; + } else { + retry = 0; + break; + } + } + + if (retry == FRAME_DATA_READ_RETRY) { + logError(1, "%s requestFrame: ERROR %02X\n", tag, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + + u8ToU16_le(&readEvent[1], &answer); + + if (answer == type) + return OK; + logError(1, "%s The event found has a different type of Frame data ERROR %02X\n", tag, ERROR_DIFF_COMP_TYPE); + return ERROR_DIFF_COMP_TYPE; + +} + +int readFrameDataHeader(u16 type, DataHeader *header) +{ + + u16 offset = ADDR_FRAMEBUFFER_DATA; + u16 answer; + u8 data[FRAME_DATA_HEADER]; + + if (readCmdU16(FTS_CMD_FRAMEBUFFER_R, offset, data, FRAME_DATA_HEADER, DUMMY_FRAMEBUFFER) < 0) { + logError(1, "%s readFrameDataHeader: ERROR %02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + logError(0, "%s Read Data Header done!\n", tag); + + if (data[0] != FRAME_HEADER_SIGNATURE) { + logError(1, "%s readFrameDataHeader: ERROR %02X The Header Signature was wrong! %02X != %02X\n", tag, ERROR_WRONG_COMP_SIGN, data[0], HEADER_SIGNATURE); + return ERROR_WRONG_COMP_SIGN; + } + + u8ToU16_le(&data[1], &answer); + + if (answer != type) { + logError(1, "%s readFrameDataHeader: ERROR %02X\n", tag, ERROR_DIFF_COMP_TYPE); + return ERROR_DIFF_COMP_TYPE; + } + + logError(0, "%s Type of Frame data OK!\n", tag); + + header->type = type; + header->force_node = (int)data[4]; + header->sense_node = (int)data[5]; + + return OK; + +} + +int getMSFrame2(u16 type, MutualSenseFrame *frame) +{ + u16 offset = ADDR_FRAMEBUFFER_DATA+FRAME_DATA_HEADER; + int size, ret; + + if (!(type == MS_TOUCH_ACTIVE || type == MS_TOUCH_LOW_POWER || type == MS_TOUCH_ULTRA_LOW_POWER || type == MS_KEY)) { + logError(1, "%s getMSFrame: Choose a MS type of frame data ERROR %02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestFrame(type); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensationData: ERROR %02X\n", tag, ERROR_REQU_COMP_DATA); + return (ret | ERROR_REQU_COMP_DATA); + } + + ret = readFrameDataHeader(type, &(frame->header)); + if (ret < 0) { + logError(1, "%s readMutualSenseCompensationData: ERROR %02X\n", tag, ERROR_COMP_DATA_HEADER); + return (ret | ERROR_COMP_DATA_HEADER); + } + + switch (type) { + case MS_TOUCH_ACTIVE: + case MS_TOUCH_LOW_POWER: + case MS_TOUCH_ULTRA_LOW_POWER: + size = frame->header.force_node*frame->header.sense_node; + break; + case MS_KEY: + if (frame->header.force_node > frame->header.sense_node) + /* or use directly the number in the ftsChip */ + size = frame->header.force_node; + else + size = frame->header.sense_node; + frame->header.force_node = 1; + frame->header.sense_node = size; + break; + + default: + logError(1, "%s getMSFrame: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + frame->node_data = (short *)kmalloc(size*sizeof(short), GFP_KERNEL); + if (frame->node_data == NULL) { + logError(1, "%s getMSFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = getFrameData(offset, size*BYTES_PER_NODE, &(frame->node_data)); + if (ret < OK) { + logError(1, "%s getMSFrame: ERROR %02X\n", tag, ERROR_GET_FRAME_DATA); + return (ret | ERROR_GET_FRAME_DATA); + } + /* if you want to access one node i,j, you should compute the offset like: offset = i*columns + j = > frame[i, j] */ + + logError(0, "%s Frame acquired!\n", tag); + frame->node_data_size = size; + return size; /* return the number of data put inside frame */ + +} + +int getSSFrame2(u16 type, SelfSenseFrame *frame) +{ + u16 offset = ADDR_FRAMEBUFFER_DATA + FRAME_DATA_HEADER; + int size, ret; + short *temp = NULL; + + if (!(type == SS_TOUCH || type == SS_KEY || type == SS_HOVER || type == SS_PROXIMITY)) { + logError(1, "%s getSSFrame: Choose a SS type of frame data ERROR %02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + ret = requestFrame(type); + if (ret < 0) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_REQU_COMP_DATA); + return (ret | ERROR_REQU_COMP_DATA); + } + + ret = readFrameDataHeader(type, &(frame->header)); + if (ret < 0) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_COMP_DATA_HEADER); + return (ret | ERROR_COMP_DATA_HEADER); + } + + switch (type) { + case SS_TOUCH: + case SS_HOVER: + case SS_PROXIMITY: + size = frame->header.force_node+frame->header.sense_node; + break; + + default: + logError(1, "%s getSSFrame: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + temp = (short *)kmalloc(size*sizeof(short), GFP_KERNEL); + if (temp == NULL) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + ret = getFrameData(offset, size*BYTES_PER_NODE, &temp); + if (ret < OK) { + logError(1, "%s getMSFrame: ERROR %02X\n", tag, ERROR_GET_FRAME_DATA); + return (ret | ERROR_GET_FRAME_DATA); + } + + frame->force_data = (short *)kmalloc(frame->header.force_node*sizeof(short), GFP_KERNEL); + if (frame->force_data == NULL) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + memcpy(frame->force_data, temp, frame->header.force_node*sizeof(short)); + + frame->sense_data = (short *)kmalloc(frame->header.sense_node*sizeof(short), GFP_KERNEL); + if (frame->sense_data == NULL) { + logError(1, "%s getSSFrame: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + memcpy(frame->sense_data, &temp[frame->header.force_node], frame->header.sense_node*sizeof(short)); + + logError(0, "%s Frame acquired!\n", tag); + kfree(temp); + return size; /* return the number of data put inside frame */ + +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsFrame.h b/drivers/input/touchscreen/st/fts_lib/ftsFrame.h new file mode 100644 index 000000000000..89f4e5080a53 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsFrame.h @@ -0,0 +1,49 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS functions for getting frames * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsSoftware.h" + +/* Number of data bytes for each node */ +#define BYTES_PER_NODE 2 +#define OFFSET_LENGTH 2 +#define FRAME_DATA_HEADER 8 +#define FRAME_HEADER_SIGNATURE 0xB5 +#define FRAME_DATA_READ_RETRY 2 + +typedef struct { + DataHeader header; + short *node_data; + int node_data_size; +} MutualSenseFrame; + +typedef struct { + DataHeader header; + short *force_data; + short *sense_data; +} SelfSenseFrame; + +/* int getOffsetFrame(u16 address, u16 *offset); */ +int getChannelsLength(void); +int getFrameData(u16 address, int size, short **frame); +/* int getMSFrame(u16 type, short **frame, int keep_first_row); */ +/* int getMSKeyFrame(u16 type, short **frame); */ +/* int getSSFrame(u16 type, short **frame); */ +/* int getNmsFrame(u16 type, short ***frames, int * sizes, int keep_first_row, int fs, int n); */ +int getSenseLen(void); +int getForceLen(void); +int requestFrame(u16 type); +int readFrameDataHeader(u16 type, DataHeader *header); +int getMSFrame2(u16 type, MutualSenseFrame *frame); +int getSSFrame2(u16 type, SelfSenseFrame *frame); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsGesture.c b/drivers/input/touchscreen/st/fts_lib/ftsGesture.c new file mode 100644 index 000000000000..89f84efc08e2 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsGesture.c @@ -0,0 +1,398 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS Gesture Utilities * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsSoftware.h" +#include "ftsError.h" +#include "ftsGesture.h" +#include "ftsIO.h" +#include "ftsTool.h" + +static char tag[8] = "[ FTS ]\0"; + +static u8 gesture_mask[GESTURE_MASK_SIZE] = { 0 }; +static u8 custom_gestures[GESTURE_CUSTOM_NUMBER][GESTURE_CUSTOM_POINTS]; +static u8 custom_gesture_index[GESTURE_CUSTOM_NUMBER] = { 0 }; + +int enableGesture(u8 *mask, int size) +{ + u8 cmd[size+2]; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int i, res; + int event_to_search[4] = {EVENTID_GESTURE, EVENT_TYPE_ENB, 0x00, GESTURE_ENABLE}; + + logError(0, "%s Trying to enable gesture...\n", tag); + cmd[0] = FTS_CMD_GESTURE_CMD; + cmd[1] = GESTURE_ENABLE; + + if (size <= GESTURE_MASK_SIZE) { + if (mask != NULL) { + for (i = 0; i < size; i++) { + cmd[i + 2] = mask[i]; + gesture_mask[i] = gesture_mask[i]|mask[i]; + /* back up of the gesture enabled */ + } + while (i < GESTURE_MASK_SIZE) { + cmd[i + 2] = gesture_mask[i]; + i++; + } + } else { + for (i = 0; i < GESTURE_MASK_SIZE; i++) { + cmd[i + 2] = gesture_mask[i]; + } + } + + res = fts_writeFwCmd(cmd, GESTURE_MASK_SIZE + 2); + if (res < OK) { + logError(1, "%s enableGesture: ERROR %08X\n", tag, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s enableGesture: pollForEvent ERROR %08X\n", tag, res); + return res; + } + + if (readData[4] != 0x00) { + logError(1, "%s enableGesture: ERROR %08X\n", tag, ERROR_GESTURE_ENABLE_FAIL); + return ERROR_GESTURE_ENABLE_FAIL; + } + + logError(0, "%s enableGesture DONE!\n", tag); + return OK; + } else { + logError(1, "%s enableGesture: Size not valid! %d > %d ERROR %08X\n", tag, size, GESTURE_MASK_SIZE); + return ERROR_OP_NOT_ALLOW; + } + +} + +int disableGesture(u8 *mask, int size) +{ + u8 cmd[2+GESTURE_MASK_SIZE]; + u8 readData[FIFO_EVENT_SIZE] = {0}; + u8 temp; + int i, res; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, 0x00, GESTURE_DISABLE }; + + logError(0, "%s Trying to disable gesture...\n", tag); + cmd[0] = FTS_CMD_GESTURE_CMD; + cmd[1] = GESTURE_DISABLE; + + if (size <= GESTURE_MASK_SIZE) { + if (mask != NULL) { + for (i = 0; i < size; i++) { + cmd[i + 2] = mask[i]; + temp = gesture_mask[i] ^ mask[i]; + /* enabled XOR disabled */ + gesture_mask[i] = temp & gesture_mask[i]; + /* disable the gestures that were enabled */ + } + while (i < GESTURE_MASK_SIZE) { + cmd[i + 2] = gesture_mask[i]; + /* disable all the other gesture not specified */ + gesture_mask[i] = 0x00; + i++; + } + } else { + for (i = 0; i < GESTURE_MASK_SIZE; i++) { + cmd[i + 2] = gesture_mask[i]; + } + } + + res = fts_writeFwCmd(cmd, 2 + GESTURE_MASK_SIZE); + if (res < OK) { + logError(1, "%s disableGesture: ERROR %08X\n", tag, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s disableGesture: pollForEvent ERROR %08X\n", tag, res); + return res; + } + + if (readData[4] != 0x00) { + logError(1, "%s disableGesture: ERROR %08X\n", tag, ERROR_GESTURE_ENABLE_FAIL); + return ERROR_GESTURE_ENABLE_FAIL; + } + + logError(0, "%s disableGesture DONE!\n", tag); + return OK; + } else { + logError(1, "%s disableGesture: Size not valid! %d > %d ERROR %08X\n", tag, size, GESTURE_MASK_SIZE); + return ERROR_OP_NOT_ALLOW; + } +} + +int startAddCustomGesture(u8 gestureID) +{ + u8 cmd[3] = { FTS_CMD_GESTURE_CMD, GESTURE_START_ADD, gestureID }; + int res; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, gestureID, GESTURE_START_ADD }; + + res = fts_writeFwCmd(cmd, 3); + if (res < OK) { + logError(1, "%s startAddCustomGesture: Impossible to start adding custom gesture ID = %02X! ERROR %08X\n", tag, gestureID, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s startAddCustomGesture: start add event not found! ERROR %08X\n", tag, res); + return res; + } + + if (readData[2] != gestureID || readData[4] != 0x00) { /* check of gestureID is redundant */ + logError(1, "%s startAddCustomGesture: start add event status not OK! ERROR %08X\n", tag, readData[4]); + return ERROR_GESTURE_START_ADD; + } + + return OK; +} + +int finishAddCustomGesture(u8 gestureID) +{ + u8 cmd[3] = { FTS_CMD_GESTURE_CMD, GESTURE_FINISH_ADD, gestureID }; + int res; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, gestureID, GESTURE_FINISH_ADD }; + + res = fts_writeFwCmd(cmd, 3); + if (res < OK) { + logError(1, "%s finishAddCustomGesture: Impossible to finish adding custom gesture ID = %02X! ERROR %08X\n", tag, gestureID, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s finishAddCustomGesture: finish add event not found! ERROR %08X\n", tag, res); + return res; + } + + if (readData[2] != gestureID || readData[4] != 0x00) { /* check of gestureID is redundant */ + logError(1, "%s finishAddCustomGesture: finish add event status not OK! ERROR %08X\n", tag, readData[4]); + return ERROR_GESTURE_FINISH_ADD; + } + + return OK; + +} + +int loadCustomGesture(u8 *template, u8 gestureID) +{ + int res, i; + int remaining = GESTURE_CUSTOM_POINTS; + int toWrite, offset = 0; + u8 cmd[TEMPLATE_CHUNK + 5]; + int event_to_search[4] = { EVENTID_GESTURE, EVENT_TYPE_ENB, gestureID, GESTURE_DATA_ADD }; + u8 readData[FIFO_EVENT_SIZE] = {0}; + + logError(0, "%s Starting adding custom gesture procedure...\n", tag); + + res = startAddCustomGesture(gestureID); + if (res < OK) { + logError(1, "%s loadCustomGesture: unable to start adding procedure! ERROR %08X\n", tag, res); + return res; + } + + cmd[0] = FTS_CMD_GESTURE_CMD; + cmd[1] = GESTURE_DATA_ADD; + cmd[2] = gestureID; + while (remaining > 0) { + if (remaining > TEMPLATE_CHUNK) { + toWrite = TEMPLATE_CHUNK; + } else { + toWrite = remaining; + } + + cmd[3] = toWrite; + cmd[4] = offset; + for (i = 0; i < toWrite; i++) { + cmd[i + 5] = template[i]; + } + + res = fts_writeFwCmd(cmd, toWrite + 5); + if (res < OK) { + logError(1, "%s loadCustomGesture: unable to start adding procedure! ERROR %08X\n", tag, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s loadCustomGesture: add event not found! ERROR %08X\n", tag, res); + return res; + } + + if (readData[2] != gestureID || readData[4] != 0x00) { /* check of gestureID is redundant */ + logError(1, "%s loadCustomGesture: add event status not OK! ERROR %08X\n", tag, readData[4]); + return ERROR_GESTURE_DATA_ADD; + } + + remaining -= toWrite; + offset += toWrite / 2; + } + + res = finishAddCustomGesture(gestureID); + if (res < OK) { + logError(1, "%s loadCustomGesture: unable to finish adding procedure! ERROR %08X\n", tag, res); + return res; + } + + logError(0, "%s Adding custom gesture procedure DONE!\n", tag); + return OK; + +} + +int reloadCustomGesture(void) +{ + int res, i; + + logError(0, "%s Starting reload Gesture Template...\n", tag); + + for (i = 0; i < GESTURE_CUSTOM_NUMBER; i++) { + if (custom_gesture_index[i] == 1) { + res = loadCustomGesture(custom_gestures[i], GESTURE_CUSTOM_OFFSET+i); + if (res < OK) { + logError(1, "%s reloadCustomGesture: Impossible to load custom gesture ID = %02X! ERROR %08X\n", tag, GESTURE_CUSTOM_OFFSET + i, res); + return res; + } + } + } + + logError(0, "%s Reload Gesture Template DONE!\n", tag); + return OK; + +} + +int enterGestureMode(int reload) +{ + u8 cmd = FTS_CMD_GESTURE_MODE; + int res, ret; + + res = fts_disableInterrupt(); + if (res < OK) { + logError(1, "%s enterGestureMode: ERROR %08X\n", tag, res|ERROR_DISABLE_INTER); + return res | ERROR_DISABLE_INTER; + } + + if (reload == 1) { + + res = reloadCustomGesture(); + if (res < OK) { + logError(1, "%s enterGestureMode: impossible reload custom gesture! ERROR %08X\n", tag, res); + goto END; + } + + res = disableGesture(NULL, 0); + if (res < OK) { + logError(1, "%s enterGestureMode: disableGesture ERROR %08X\n", tag, res); + goto END; + } + + res = enableGesture(NULL, 0); + if (res < OK) { + logError(1, "%s enterGestureMode: enableGesture ERROR %08X\n", tag, res); + goto END; + } + } + + res = fts_writeFwCmd(&cmd, 1); + if (res < OK) { + logError(1, "%s enterGestureMode: enter gesture mode ERROR %08X\n", tag, res); + goto END; + } + + res = OK; +END: + ret = fts_enableInterrupt(); + if (ret < OK) { + logError(1, "%s enterGestureMode: fts_enableInterrupt ERROR %08X\n", tag, res | ERROR_ENABLE_INTER); + res |= ret | ERROR_ENABLE_INTER; + } + + return res; +} + +int addCustomGesture(u8 *data, int size, u8 gestureID) +{ + int index, res, i; + + index = gestureID - GESTURE_CUSTOM_OFFSET; + + logError(0, "%s Starting Custom Gesture Adding procedure...\n", tag); + if ((size != GESTURE_CUSTOM_POINTS) && (gestureID != GES_ID_CUST1) + && (gestureID != GES_ID_CUST2) && (gestureID != GES_ID_CUST3) + && (gestureID != GES_ID_CUST4) && (gestureID != GES_ID_CUST5)) { + logError(1, "%s addCustomGesture: Invalid size (%d) or Custom GestureID (%02X)! ERROR %08X\n", tag, size, gestureID, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + for (i = 0; i < GESTURE_CUSTOM_POINTS; i++) { + custom_gestures[index][i] = data[i]; + } + + res = loadCustomGesture(custom_gestures[index], gestureID); + if (res < OK) { + logError(1, "%s addCustomGesture: impossible to load the custom gesture! ERROR %08X\n", tag, res); + return res; + } + + custom_gesture_index[index] = 1; + logError(0, "%s Custom Gesture Adding procedure DONE!\n", tag); + return OK; +} + +int removeCustomGesture(u8 gestureID) +{ + int res, index; + u8 cmd[3] = { FTS_CMD_GESTURE_CMD, GETURE_REMOVE_CUSTOM, gestureID }; + int event_to_search[4] = {EVENTID_GESTURE, EVENT_TYPE_ENB, gestureID, GETURE_REMOVE_CUSTOM }; + u8 readData[FIFO_EVENT_SIZE] = {0}; + + index = gestureID - GESTURE_CUSTOM_OFFSET; + + logError(0, "%s Starting Custom Gesture Removing procedure...\n", tag); + if ((gestureID != GES_ID_CUST1) && (gestureID != GES_ID_CUST2) + && (gestureID != GES_ID_CUST3) && (gestureID != GES_ID_CUST4) + && (gestureID != GES_ID_CUST5)) { + logError(1, "%s removeCustomGesture: Invalid size (%d) or Custom GestureID (%02X)! ERROR %08X\n", tag, gestureID, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + res = fts_writeFwCmd(cmd, 3);/* when a gesture is removed, it is also disabled automatically */ + if (res < OK) { + logError(1, "%s removeCustomGesture: Impossible to remove custom gesture ID = %02X! ERROR %08X\n", tag, gestureID, res); + return res; + } + + res = pollForEvent(event_to_search, 4, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s removeCustomGesture: remove event not found! ERROR %08X\n", tag, res); + return res; + } + + /* check of gestureID is redundant */ + if ((readData[2] != gestureID) || (readData[4] != 0x00)) { + logError(1, "%s removeCustomGesture: remove event status not OK! ERROR %08X\n", tag, readData[4]); + return ERROR_GESTURE_REMOVE; + } + + custom_gesture_index[index] = 0; + logError(0, "%s Custom Gesture Remove procedure DONE!\n", tag); + return OK; + +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsGesture.h b/drivers/input/touchscreen/st/fts_lib/ftsGesture.h new file mode 100644 index 000000000000..19a47b06d8f5 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsGesture.h @@ -0,0 +1,76 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS Gesture Utilities * +* * +************************************************************************** +************************************************************************** + +*/ + +#include <linux/types.h> + +#define GESTURE_MASK_SIZE 8 + +#define GESTURE_CUSTOM_POINTS (30*2) +/* for each custom gesture should be provided 30 points (each point is a couple of x,y) */ +#define GESTURE_CUSTOM_NUMBER 5 /* fw support up to 5 custom gestures */ + +#define TEMPLATE_CHUNK (10*2) +/* number of points to transfer with each I2C transaction */ + +/* Gesture IDs */ +#define GES_ID_DBLTAP 0x01 /* Double Tap */ +#define GES_ID_O 0x02 /* 'O' */ +#define GES_ID_C 0x03 /* 'C' */ +#define GES_ID_M 0x04 /* 'M' */ +#define GES_ID_W 0x05 /* 'W' */ +#define GES_ID_E 0x06 /* 'e' */ +#define GES_ID_HFLIP_L2R 0x07 /* Left to right line */ +#define GES_ID_HFLIP_R2L 0x08 /* Right to left line */ +#define GES_ID_VFLIP_T2D 0x09 /* Top to bottom line */ +#define GES_ID_VFLIP_D2T 0x0A /* Bottom to Top line */ +#define GES_ID_L 0x0B /* 'L' */ +#define GES_ID_F 0x0C /* 'F' */ +#define GES_ID_V 0x0D /* 'V' */ +#define GES_ID_AT 0x0E /* '@' */ +#define GES_ID_S 0x0F /* 'S' */ +#define GES_ID_Z 0x10 /* 'Z' */ +#define GES_ID_CUST1 0x11 /* Custom gesture 1 */ +#define GES_ID_CUST2 0x12 /* Custom gesture 2 */ +#define GES_ID_CUST3 0x13 /* Custom gesture 3 */ +#define GES_ID_CUST4 0x14 /* Custom gesture 4 */ +#define GES_ID_CUST5 0x15 /* Custom gesture 5 */ +#define GES_ID_LEFTBRACE 0x20 /* '<' */ +#define GES_ID_RIGHTBRACE 0x21 /* '>' */ + +#define GESTURE_CUSTOM_OFFSET GES_ID_CUST1 + +/* Command sub-type */ +#define GESTURE_ENABLE 0x01 +#define GESTURE_DISABLE 0x02 +#define GESTURE_ENB_CHECK 0x03 +#define GESTURE_START_ADD 0x10 +#define GESTURE_DATA_ADD 0x11 +#define GESTURE_FINISH_ADD 0x12 +#define GETURE_REMOVE_CUSTOM 0x13 +#define GESTURE_CHECK_CUSTOM 0x14 + +/* Event sub-type */ +#define EVENT_TYPE_ENB 0x04 +#define EVENT_TYPE_CHECK_ENB 0x03 +#define EVENT_TYPE_GESTURE_DTC1 0x01 +#define EVENT_TYPE_GESTURE_DTC2 0x02 + +int disableGesture(u8 *mask, int size); +int enableGesture(u8 *mask, int size); +int startAddCustomGesture(u8 gestureID); +int finishAddCustomGesture(u8 gestureID); +int loadCustomGesture(u8 *template, u8 gestureID); +int reloadCustomGesture(void); +int enterGestureMode(int reload); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsHardware.h b/drivers/input/touchscreen/st/fts_lib/ftsHardware.h new file mode 100644 index 000000000000..933059e671b4 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsHardware.h @@ -0,0 +1,177 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* HW related data * +* * +************************************************************************** +************************************************************************** + +*/ + +#define FTM3_CHIP + +/* DUMMY BYTES DATA */ +#define DUMMY_HW_REG 1 +#define DUMMY_FRAMEBUFFER 1 +#define DUMMY_MEMORY 1 + +/* DIGITAL CHIP INFO */ +#ifdef FTM3_CHIP +#define DCHIP_ID_0 0x39 +#define DCHIP_ID_1 0x6C +#else +#define DCHIP_ID_0 0x36 +#define DCHIP_ID_1 0x70 +#endif + +#ifdef FTM3_CHIP +#define DCHIP_ID_ADDR 0x0007 +#define DCHIP_FW_VER_ADDR 0x000A +#else +#define DCHIP_ID_ADDR 0x0004 +#define DCHIP_FW_VER_ADDR 0x000B +#endif + +#define DCHIP_FW_VER_BYTE 2 + +/* CHUNKS */ +#define READ_CHUNK (2*1024) +#define WRITE_CHUNK (2*1024) +#define MEMORY_CHUNK (2*1024) + +/* PROTOCOL INFO */ +#ifdef FTM3_CHIP +#define I2C_SAD 0x49 +#else +#define I2C_SAD 0x48 +#endif + +#define I2C_INTERFACE /* comment if the chip use SPI */ +#define ICR_ADDR 0x0024 +#define ICR_SPI_VALUE 0x02 + +/* SYSTEM RESET INFO */ +#ifdef FTM3_CHIP +#define SYSTEM_RESET_ADDRESS 0x0023 +#define SYSTEM_RESET_VALUE 0x01 +#else +#define SYSTEM_RESET_ADDRESS 0x0028 +#define SYSTEM_RESET_VALUE 0x80 +#endif + +/* INTERRUPT INFO */ +#ifdef FTM3_CHIP +#define IER_ADDR 0x001C +#else +#define IER_ADDR 0x002C +#endif + +#define IER_ENABLE 0x41 +#define IER_DISABLE 0x00 + +/* FLASH COMMAND */ + +#define FLASH_CMD_UNLOCK 0xF7 + +#ifdef FTM3_CHIP +#define FLASH_CMD_WRITE_LOWER_64 0xF0 +#define FLASH_CMD_WRITE_UPPER_64 0xF1 +#define FLASH_CMD_BURN 0xF2 +#define FLASH_CMD_ERASE 0xF3 +#define FLASH_CMD_READSTATUS 0xF4 +#else +#define FLASH_CMD_WRITE_64K 0xF8 +#define FLASH_CMD_READ_REGISTER 0xF9 +#define FLASH_CMD_WRITE_REGISTER 0xFA +#endif + +/* FLASH UNLOCK PARAMETER */ +#define FLASH_UNLOCK_CODE0 0x74 +#define FLASH_UNLOCK_CODE1 0x45 + +#ifndef FTM3_CHIP +/* FLASH ERASE and DMA PARAMETER */ +#define FLASH_ERASE_UNLOCK_CODE0 0x72 +#define FLASH_ERASE_UNLOCK_CODE1 0x03 +#define FLASH_ERASE_UNLOCK_CODE2 0x02 +#define FLASH_ERASE_CODE0 0x02 +#define FLASH_ERASE_CODE1 0xC0 +#define FLASH_DMA_CODE0 0x05 +#define FLASH_DMA_CODE1 0xC0 +#define FLASH_DMA_CONFIG 0x06 +#endif + +/* FLASH ADDRESS */ +#ifdef FTM3_CHIP +#define FLASH_ADDR_SWITCH_CMD 0x00010000 +#define FLASH_ADDR_CODE 0x00000000 +#define FLASH_ADDR_CONFIG 0x0001E800 +#define FLASH_ADDR_CX 0x0001F000 +#else +#define ADDR_WARM_BOOT 0x001E +#define WARM_BOOT_VALUE 0x38 +#define FLASH_ADDR_CODE 0x00000000 +#define FLASH_ADDR_CONFIG 0x0000FC00 +#endif + +/* CRC ADDR */ +#ifdef FTM3_CHIP +#define ADDR_CRC_BYTE0 0x00 +#define ADDR_CRC_BYTE1 0x86 +#define CRC_MASK 0x02 +#else +#define ADDR_CRC_BYTE0 0x00 +#define ADDR_CRC_BYTE1 0x74 +#define CRC_MASK 0x03 +#endif + +/* SIZES FW, CODE, CONFIG, MEMH */ +#ifdef FTM3_CHIP +#define FW_HEADER_SIZE 32 +#define FW_SIZE (int)(128*1024) +#define FW_CODE_SIZE (int)(122*1024) +#define FW_CONFIG_SIZE (int)(2*1024) +#define FW_CX_SIZE (int)(FW_SIZE-FW_CODE_SIZE-FW_CONFIG_SIZE) +#define FW_VER_MEMH_BYTE1 193 +#define FW_VER_MEMH_BYTE0 192 +#define FW_OFF_CONFID_MEMH_BYTE1 2 +#define FW_OFF_CONFID_MEMH_BYTE0 1 +#define FW_BIN_VER_OFFSET 4 +#define FW_BIN_CONFIG_VER_OFFSET (FW_HEADER_SIZE+FW_CODE_SIZE+1) +#else +#define FW_HEADER_SIZE 64 +#define FW_HEADER_SIGNATURE 0xAA55AA55 +#define FW_FTB_VER 0x00000001 +#define FW_BYTES_ALIGN 4 +#define FW_BIN_VER_OFFSET 16 +#define FW_BIN_CONFIG_VER_OFFSET 20 +#endif + +/* FIFO */ +#define FIFO_EVENT_SIZE 8 +#ifdef FTM3_CHIP +#define FIFO_DEPTH 32 +#else +#define FIFO_DEPTH 64 +#endif + +#define FIFO_CMD_READONE 0x85 +#define FIFO_CMD_READALL 0x86 +#define FIFO_CMD_LAST 0x87 +#define FIFO_CMD_FLUSH 0xA1 + +/* CONSTANT TOTAL CX */ +#define CX1_WEIGHT 4 +#define CX2_WEIGHT 1 + +/* OP CODES FOR MEMORY (based on protocol) */ + +#define FTS_CMD_HW_REG_R 0xB6 +#define FTS_CMD_HW_REG_W 0xB6 +#define FTS_CMD_FRAMEBUFFER_R 0xD0 +#define FTS_CMD_FRAMEBUFFER_W 0xD0 diff --git a/drivers/input/touchscreen/st/fts_lib/ftsIO.c b/drivers/input/touchscreen/st/fts_lib/ftsIO.c new file mode 100644 index 000000000000..96ca8cfecd8a --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsIO.c @@ -0,0 +1,403 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* I2C/SPI Communication * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsSoftware.h" +#include "ftsCrossCompile.h" + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/spi/spidev.h> +static struct i2c_client *client; +static u16 I2CSAD; + +#include "ftsError.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsTool.h" + +static char tag[8] = "[ FTS ]\0"; + +int openChannel(struct i2c_client *clt) +{ + client = clt; + I2CSAD = clt->addr; + logError(1, "%s openChannel: SAD: %02X\n", tag, I2CSAD); + return OK; +} + +struct device *getDev(void) +{ + if (client != NULL) + return &(client->dev); + else + return NULL; +} + +struct i2c_client *getClient(void) +{ + if (client != NULL) + return client; + else + return NULL; +} + +int fts_readCmd(u8 *cmd, int cmdLength, u8 *outBuf, int byteToRead) +{ + int ret = -1; + int retry = 0; + struct i2c_msg I2CMsg[2]; + + /* write msg */ + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = (__u8 *)cmd; + + /* read msg */ + I2CMsg[1].addr = (__u16)I2CSAD; + I2CMsg[1].flags = I2C_M_RD; + I2CMsg[1].len = byteToRead; + I2CMsg[1].buf = (__u8 *)outBuf; + + if (client == NULL) + return ERROR_I2C_O; + while (retry < I2C_RETRY && ret < OK) { + ret = i2c_transfer(client->adapter, I2CMsg, 2); + if (ret >= OK) + break; + retry++; + msleep(I2C_WAIT_BEFORE_RETRY); + } + if (ret < 0) { + logError(1, "%s fts_readCmd: ERROR %02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + return OK; +} + +int fts_writeCmd(u8 *cmd, int cmdLength) +{ + int ret = -1; + int retry = 0; + struct i2c_msg I2CMsg[2]; + + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = (__u8 *)cmd; + + if (client == NULL) +return ERROR_I2C_O; + while (retry < I2C_RETRY && ret < OK) { + ret = i2c_transfer(client->adapter, I2CMsg, 1); + if (ret >= OK) + break; + retry++; + msleep(I2C_WAIT_BEFORE_RETRY); + /* logError(1, "%s fts_writeCmd: attempt %d\n", tag, retry); */ + } + if (ret < 0) { + logError(1, "%s fts_writeCmd: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + return OK; +} + +int fts_writeFwCmd(u8 *cmd, int cmdLength) +{ + int ret = -1; + int ret2 = -1; + int retry = 0; + struct i2c_msg I2CMsg[2]; + + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)cmdLength; + I2CMsg[0].buf = (__u8 *)cmd; + + if (client == NULL) +return ERROR_I2C_O; + while (retry < I2C_RETRY && (ret < OK || ret2 < OK)) { + ret = i2c_transfer(client->adapter, I2CMsg, 1); + retry++; + if (ret >= 0) { + ret2 = checkEcho(cmd, cmdLength); + break; + } + msleep(I2C_WAIT_BEFORE_RETRY); + /* logError(1, "%s fts_writeCmd: attempt %d\n", tag, retry); */ + } + if (ret < 0) { + logError(1, "%s fts_writeFwCmd: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + if (ret2 < OK) { + logError(1, "%s fts_writeFwCmd: check echo ERROR %02X\n", tag, ret2); + return (ret|ERROR_I2C_W); + } + return OK; +} + +int writeReadCmd(u8 *writeCmd1, int writeCmdLength, u8 *readCmd1, + int readCmdLength, u8 *outBuf, int byteToRead) +{ + int ret = -1; + int retry = 0; + struct i2c_msg I2CMsg[3]; + + /* write msg */ + I2CMsg[0].addr = (__u16)I2CSAD; + I2CMsg[0].flags = (__u16)0; + I2CMsg[0].len = (__u16)writeCmdLength; + I2CMsg[0].buf = (__u8 *)writeCmd1; + + /* write msg */ + I2CMsg[1].addr = (__u16)I2CSAD; + I2CMsg[1].flags = (__u16)0; + I2CMsg[1].len = (__u16)readCmdLength; + I2CMsg[1].buf = (__u8 *)readCmd1; + + /* read msg */ + I2CMsg[2].addr = (__u16)I2CSAD; + I2CMsg[2].flags = I2C_M_RD; + I2CMsg[2].len = byteToRead; + I2CMsg[2].buf = (__u8 *)outBuf; + + if (client == NULL) + return ERROR_I2C_O; + while (retry < I2C_RETRY && ret < OK) { + ret = i2c_transfer(client->adapter, I2CMsg, 3); + if (ret >= OK) + break; + retry++; + msleep(I2C_WAIT_BEFORE_RETRY); + } + + if (ret < 0) { + logError(1, "%s writeReadCmd: ERROR %02X\n", tag, ERROR_I2C_WR); + return ERROR_I2C_WR; + } + return OK; + +} + +int readCmdU16(u8 cmd, u16 address, u8 *outBuf, int byteToRead, int hasDummyByte) +{ + + int remaining = byteToRead; + int toRead = 0; + u8 rCmd[3] = { cmd, 0x00, 0x00 }; + + u8 *buff = (u8 *)kmalloc((READ_CHUNK + 1)*sizeof(u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s readCmdU16: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + if (remaining >= READ_CHUNK) { + toRead = READ_CHUNK; + remaining -= READ_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + rCmd[1] = (u8)((address & 0xFF00) >> 8); + rCmd[2] = (u8)(address & 0xFF); + + if (hasDummyByte) { + if (fts_readCmd(rCmd, 3, buff, toRead + 1) < 0) { + logError(1, "%s readCmdU16: ERROR %02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; + } + memcpy(outBuf, buff + 1, toRead); + } else { + if (fts_readCmd(rCmd, 3, buff, toRead) < 0) + return ERROR_I2C_R; + memcpy(outBuf, buff, toRead); + } + + address += toRead; + + outBuf += toRead; + + } + kfree(buff); + + return OK; +} + +int writeCmdU16(u8 WriteCmd, u16 address, u8 *dataToWrite, int byteToWrite) +{ + + int remaining = byteToWrite; + int toWrite = 0; + + u8 *buff = (u8 *)kmalloc((WRITE_CHUNK + 3)*sizeof(u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s writeCmdU16: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + buff[0] = WriteCmd; + + while (remaining > 0) { + if (remaining >= WRITE_CHUNK) { + toWrite = WRITE_CHUNK; + remaining -= WRITE_CHUNK; + } else { + toWrite = remaining; + remaining = 0; + } + + buff[1] = (u8)((address & 0xFF00) >> 8); + buff[2] = (u8)(address & 0xFF); + memcpy(buff + 3, dataToWrite, toWrite); + if (fts_writeCmd(buff, 3 + toWrite) < 0) { + logError(1, "%s writeCmdU16: ERROR %02\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + address += toWrite; + dataToWrite += toWrite; + + } + + return OK; +} + +int writeCmdU32(u8 writeCmd1, u8 writeCmd2, u32 address, u8 *dataToWrite, int byteToWrite) +{ + + int remaining = byteToWrite; + int toWrite = 0; + + u8 buff1[3] = { writeCmd1, 0x00, 0x00 }; + u8 *buff2 = (u8 *)kmalloc((WRITE_CHUNK + 3)*sizeof(u8), GFP_KERNEL); + if (buff2 == NULL) { + logError(1, "%s writeCmdU32: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + buff2[0] = writeCmd2; + + while (remaining > 0) { + if (remaining >= WRITE_CHUNK) { + toWrite = WRITE_CHUNK; + remaining -= WRITE_CHUNK; + } else { + toWrite = remaining; + remaining = 0; + } + + buff1[1] = (u8)((address & 0xFF000000) >> 24); + buff1[2] = (u8)((address & 0x00FF0000) >> 16); + buff2[1] = (u8)((address & 0x0000FF00) >> 8); + buff2[2] = (u8)(address & 0xFF); + memcpy(buff2 + 3, dataToWrite, toWrite); + + if (fts_writeCmd(buff1, 3) < 0) { + logError(1, "%s writeCmdU32: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + if (fts_writeCmd(buff2, 3 + toWrite) < 0) { + logError(1, "%s writeCmdU32: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + address += toWrite; + dataToWrite += toWrite; + + } + + return OK; +} + +int writeReadCmdU32(u8 wCmd, u8 rCmd, u32 address, u8 *outBuf, int byteToRead, int hasDummyByte) +{ + + int remaining = byteToRead; + int toRead = 0; + u8 reaCmd[3]; + u8 wriCmd[3]; + + u8 *buff = (u8 *)kmalloc((READ_CHUNK + 1)*sizeof(u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s writereadCmd32: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + reaCmd[0] = rCmd; + wriCmd[0] = wCmd; + + while (remaining > 0) { + if (remaining >= READ_CHUNK) { + toRead = READ_CHUNK; + remaining -= READ_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + wriCmd[1] = (u8)((address & 0xFF000000) >> 24); + wriCmd[2] = (u8)((address & 0x00FF0000) >> 16); + + reaCmd[1] = (u8)((address & 0x0000FF00) >> 8); + reaCmd[2] = (u8)(address & 0x000000FF); + + if (hasDummyByte) { + if (writeReadCmd(wriCmd, 3, reaCmd, 3, buff, toRead + 1) < 0) { + logError(1, "%s writeCmdU32: ERROR %02X\n", tag, ERROR_I2C_WR); + return ERROR_I2C_WR; + } + memcpy(outBuf, buff + 1, toRead); + } else { + if (writeReadCmd(wriCmd, 3, reaCmd, 3, buff, toRead) < 0) + return ERROR_I2C_WR; + memcpy(outBuf, buff, toRead); + } + + address += toRead; + + outBuf += toRead; + + } + + return OK; +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsIO.h b/drivers/input/touchscreen/st/fts_lib/ftsIO.h new file mode 100644 index 000000000000..7bdeda2f9a2d --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsIO.h @@ -0,0 +1,35 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* I2C/SPI Communication * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsSoftware.h" +#include "ftsCrossCompile.h" + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_RETRY 3 /* number */ +#define I2C_WAIT_BEFORE_RETRY 10 /* ms */ + +int openChannel(struct i2c_client *clt); +struct device *getDev(void); +struct i2c_client *getClient(void); +int fts_readCmd(u8 *cmd, int cmdLenght, u8 *outBuf, int byteToRead); +int fts_writeCmd(u8 *cmd, int cmdLenght); +int fts_writeFwCmd(u8 *cmd, int cmdLenght); +int writeReadCmd(u8 *writeCmd, int writeCmdLenght, u8 *readCmd, int readCmdLenght, u8 *outBuf, int byteToRead); +int readCmdU16(u8 cmd, u16 address, u8 *outBuf, int byteToRead, int hasDummyByte); +int writeCmdU16(u8 WriteCmd, u16 address, u8 *dataToWrite, int byteToWrite); +int writeCmdU32(u8 writeCmd1, u8 writeCmd2, u32 address, u8 *dataToWrite, int byteToWrite); +int writeReadCmdU32(u8 wCmd, u8 rCmd, u32 address, u8 *outBuf, int byteToRead, int hasDummyByte); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsSoftware.h b/drivers/input/touchscreen/st/fts_lib/ftsSoftware.h new file mode 100644 index 000000000000..080661d134d0 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsSoftware.h @@ -0,0 +1,127 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FW related data * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsHardware.h" + +#define ECHO_ENABLED 0x00000001 + +/* chipInfo ftsInfo; */ + +/* FTS FW COMAND */ +#define FTS_CMD_MS_MT_SENSE_OFF 0x92 +#define FTS_CMD_MS_MT_SENSE_ON 0x93 +#define FTS_CMD_SS_HOVER_OFF 0x94 +#define FTS_CMD_SS_HOVER_ON 0x95 +#define FTS_CMD_LP_TIMER_CALIB 0x97 +#define FTS_CMD_MS_KEY_OFF 0x9A +#define FTS_CMD_MS_KEY_ON 0x9B +#define FTS_CMD_MS_COMP_TUNING 0xA3 +#define FTS_CMD_SS_COMP_TUNING 0xA4 +#define FTS_CMD_FULL_INITIALIZATION 0xA5 +#define FTS_CMD_ITO_CHECK 0xA7 +#define FTS_CMD_RELEASE_INFO 0xAA +#define FTS_CMD_GESTURE_MODE 0xAD +#define FTS_CMD_REQU_FW_CONF 0xB2 +#define FTS_CMD_REQU_FRAME_DATA 0xB7 +#define FTS_CMD_REQU_COMP_DATA 0xB8 +#define FTS_CMD_WRITE_MP_FLAG 0xC0 +#define FTS_CMD_FEATURE_ENABLE 0xC1 +#define FTS_CMD_FEATURE_DISABLE 0xC2 +#define FTS_CMD_GESTURE_CMD 0xC3 +#define FTS_CMD_SAVE_CX_TUNING 0xFC + +/* Event ID */ +#define EVENTID_NO_EVENT 0x00 +#define EVENTID_ERROR_EVENT 0x0F +#define EVENTID_CONTROL_READY 0x10 +#define EVENTID_FW_CONFIGURATION 0x12 +#define EVENTID_COMP_DATA_READ 0x13 +#define EVENTID_STATUS_UPDATE 0x16 +#define EVENTID_RELEASE_INFO 0x1C +#define EVENTID_ENTER_POINTER 0x03 +#define EVENTID_LEAVE_POINTER 0x04 +#define EVENTID_MOTION_POINTER 0x05 +#define EVENTID_HOVER_ENTER_POINTER 0x07 +#define EVENTID_HOVER_LEAVE_POINTER 0x08 +#define EVENTID_HOVER_MOTION_POINTER 0x09 +#define EVENTID_PROXIMITY_ENTER 0x0B +#define EVENTID_PROXIMITY_LEAVE 0x0C +#define EVENTID_KEY_STATUS 0x0E +#define EVENTID_GESTURE 0x22 +#define EVENTID_FRAME_DATA_READ 0x25 +#define EVENTID_ECHO 0xEC +#define EVENTID_LAST (EVENTID_FRAME_DATA_READ+1) + +/* EVENT TYPE */ +#define EVENT_TYPE_MS_TUNING_CMPL 0x01 +#define EVENT_TYPE_SS_TUNING_CMPL 0x02 +#define EVENT_TYPE_COMP_DATA_SAVED 0x04 +#define EVENT_TYPE_ITO 0x05 +#define EVENT_TYPE_FULL_INITIALIZATION 0x07 +#define EVENT_TYPE_LPTIMER_TUNING_CMPL 0x20 +#define EVENT_TYPE_ESD_ERROR 0x0A +#define EVENT_TYPE_WATCHDOG_ERROR 0x01 + +/* CONFIG ID INFO */ +#define CONFIG_ID_ADDR 0x0001 +#define CONFIG_ID_BYTE 2 + +/* ADDRESS OFFSET IN SYSINFO */ +#define ADDR_RAW_TOUCH 0x0000 +#define ADDR_FILTER_TOUCH 0x0002 +#define ADDR_NORM_TOUCH 0x0004 +#define ADDR_CALIB_TOUCH 0x0006 +#define ADDR_RAW_HOVER_FORCE 0x000A +#define ADDR_RAW_HOVER_SENSE 0x000C +#define ADDR_FILTER_HOVER_FORCE 0x000E +#define ADDR_FILTER_HOVER_SENSE 0x0010 +#define ADDR_NORM_HOVER_FORCE 0x0012 +#define ADDR_NORM_HOVER_SENSE 0x0014 +#define ADDR_CALIB_HOVER_FORCE 0x0016 +#define ADDR_CALIB_HOVER_SENSE 0x0018 +#define ADDR_RAW_PRX_FORCE 0x001A +#define ADDR_RAW_PRX_SENSE 0x001C +#define ADDR_FILTER_PRX_FORCE 0x001E +#define ADDR_FILTER_PRX_SENSE 0x0020 +#define ADDR_NORM_PRX_FORCE 0x0022 +#define ADDR_NORM_PRX_SENSE 0x0024 +#define ADDR_CALIB_PRX_FORCE 0x0026 +#define ADDR_CALIB_PRX_SENSE 0x0028 +#define ADDR_RAW_MS_KEY 0x0032 +#define ADDR_COMP_DATA 0x0050 +#define ADDR_FRAMEBUFFER_DATA 0x8000 + +/* ADDRESS FW REGISTER */ +#define ADDR_SENSE_LEN 0x0014 +#define ADDR_FORCE_LEN 0x0015 +#define ADDR_MS_TUNING_VER 0x0729 +#define ADDR_SS_TUNING_VER 0x074E + +/* B2 INFO */ +#define B2_DATA_BYTES 4 +#define B2_CHUNK ((FIFO_DEPTH/2)*B2_DATA_BYTES) /* number of bytes */ + +/* FEATURES */ +#define FEAT_GESTURE 0x00 +#define FEAT_GLOVE 0x01 +#define FEAT_STYLUS 0x02 +#define FEAT_COVER 0x04 +#define FEAT_CHARGER 0x08 +#define FEAT_VR 0x10 +#define FEAT_EDGE_REJECTION 0x20 + +/* MP_FLAG_VALUE */ +#define INIT_MP 0xA5A5A501 +#define INIT_FIELD 0xA5A5A502 diff --git a/drivers/input/touchscreen/st/fts_lib/ftsTest.c b/drivers/input/touchscreen/st/fts_lib/ftsTest.c new file mode 100644 index 000000000000..3810fd02001a --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsTest.c @@ -0,0 +1,2324 @@ +/* + + ************************************************************************** + ** STMicroelectronics ** + ************************************************************************** + ** marco.cali@st.com ** + ************************************************************************** + * * + * FTS API for MP test * + * * + ************************************************************************** + ************************************************************************** + + */ + +#include "ftsCrossCompile.h" +#include "ftsCompensation.h" +#include "ftsError.h" +#include "ftsFrame.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTest.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h" + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ + +#ifdef LIMITS_H_FILE +#include <../fts_limits.h> +#endif + +/* static char tag[8] = "[ FTS ]\0"; */ + +int computeAdjHoriz(u8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + logError(1, "%s computeAdjHoriz: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *) kmalloc(size * sizeof (u8), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s computeAdjHoriz: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) { + for (j = 1; j < column; j++) { + *(*result + (i * (column - 1) + (j - 1))) = abs(data[i * column + j] - data[i * column + (j - 1)]); + } + } + + return OK; + +} + +int computeAdjHorizTotal(u16 *data, int row, int column, u16 **result) +{ + int i, j; + int size = row * (column - 1); + + if (column < 2) { + logError(1, "%s computeAdjHorizTotal: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *) kmalloc(size * sizeof (u16), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s computeAdjHorizTotal: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) { + for (j = 1; j < column; j++) { + *(*result + (i * (column - 1) + (j - 1))) = abs(data[i * column + j] - data[i * column + (j - 1)]); + } + } + + return OK; + +} + +int computeAdjVert(u8 *data, int row, int column, u8 **result) +{ + int i, j; + int size = (row - 1)*(column); + + if (row < 2) { + logError(1, "%s computeAdjVert: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u8 *) kmalloc(size * sizeof (u8), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s computeAdjVert: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) { + for (j = 0; j < column; j++) { + *(*result + ((i - 1) * column + j)) = abs(data[i * column + j] - data[(i - 1) * column + j]); + } + } + + return OK; +} + +int computeAdjVertTotal(u16 *data, int row, int column, u16 **result) +{ + int i, j; + int size = (row - 1)*(column); + + if (row < 2) { + logError(1, "%s computeAdjVertTotal: ERROR % 02X\n", tag, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + *result = (u16 *) kmalloc(size * sizeof (u16), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s computeAdjVertTotal: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 1; i < row; i++) { + for (j = 0; j < column; j++) { + *(*result + ((i - 1) * column + j)) = abs(data[i * column + j] - data[(i - 1) * column + j]); + } + } + + return OK; +} + +int computeTotal(u8 *data, u8 main, int row, int column, int m, int n, u16 **result) +{ + int i, j; + int size = (row)*(column); + + *result = (u16 *) kmalloc(size * sizeof (u16), GFP_KERNEL); + if (*result == NULL) { + logError(1, "%s computeTotal : ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + *(*result + (i * column + j)) = m * main + n * data[i * column + j]; + } + } + + return OK; +} + +int checkLimitsMinMax(short *data, int row, int column, int min, int max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min || data[i * column + j] > max) { + logError(1, "%s checkLimitsMinMax: Node[%d,%d] = %d exceed limit [%d, %d]\n", tag, i, j, data[i * column + j], min, max); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +int checkLimitsGap(short *data, int row, int column, int threshold) +{ + int i, j; + int min_node; + int max_node; + + if (row == 0 || column == 0) { + logError(1, "%s checkLimitsGap: invalid number of rows = %d or columns = %d ERROR %02\n", tag, row, column, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + + min_node = data[0]; + max_node = data[0]; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min_node) { + min_node = data[i * column + j]; + } else { + if (data[i * column + j] > max_node) + max_node = data[i * column + j]; + } + } + } + + if (max_node - min_node > threshold) { + logError(1, "%s checkLimitsGap: GAP = %d exceed limit %d\n", tag, max_node - min_node, threshold); + return ERROR_TEST_CHECK_FAIL; + } else + return OK; + +} + +int checkLimitsMap(u8 *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] || data[i * column + j] > max[i * column + j]) { + logError(1, "%s checkLimitsMap: Node[%d,%d] = %d exceed limit [%d, %d]\n", tag, i, j, data[i * column + j], min[i * column + j], max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +int checkLimitsMapTotal(u16 *data, int row, int column, int *min, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] < min[i * column + j] || data[i * column + j] > max[i * column + j]) { + logError(1, "%s checkLimitsMapTotal: Node[%d,%d] = %d exceed limit [%d, %d]\n", tag, i, j, data[i * column + j], min[i * column + j], max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +int checkLimitsMapAdj(u8 *data, int row, int column, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] > max[i * column + j]) { + logError(1, "%s checkLimitsMapAdj: Node[%d,%d] = %d exceed limit > %d\n", tag, i, j, data[i * column + j], max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +int checkLimitsMapAdjTotal(u16 *data, int row, int column, int *max) +{ + int i, j; + int count = 0; + + for (i = 0; i < row; i++) { + for (j = 0; j < column; j++) { + if (data[i * column + j] > max[i * column + j]) { + logError(1, "%s checkLimitsMapAdjTotal: Node[%d,%d] = %d exceed limit > %d\n", tag, i, j, data[i * column + j], max[i * column + j]); + count++; + } + } + } + + return count; /* if count is 0 = OK, test completed successfully */ +} + +int production_test_ito(void) +{ + int res = OK; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_ERROR_EVENT, EVENT_TYPE_ITO}; /* look for ito event */ + + logError(0, "%s ITO Production test is starting...\n", tag); + + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s production_test_ito: ERROR %02X\n", tag, ERROR_PROD_TEST_ITO); + return (res | ERROR_PROD_TEST_ITO); + } + + cmd = FTS_CMD_ITO_CHECK; + + logError(0, "%s ITO Check command sent...\n", tag); + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s production_test_ito: ERROR %02X\n", tag, (ERROR_I2C_W | ERROR_PROD_TEST_ITO)); + return (ERROR_I2C_W | ERROR_PROD_TEST_ITO); + } + + logError(0, "%s Looking for ITO Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, TIMEOUT_ITO_TEST_RESULT); + if (res < 0) { + logError(1, "%s production_test_ito: ITO Production test failed... ERROR %02X\n", tag, ERROR_PROD_TEST_ITO); + return (res | ERROR_PROD_TEST_ITO); + } + + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s ITO Production testes finished!.................FAILED ERROR %02X\n", tag, (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_ITO)); + res = (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_ITO); + } else { + logError(0, "%s ITO Production test finished!.................OK\n", tag); + res = OK; + } + + res |= fts_system_reset(); + if (res < 0) { + logError(1, "%s production_test_ito: ERROR %02X\n", tag, ERROR_PROD_TEST_ITO); + res = (res | ERROR_PROD_TEST_ITO); + } + return res; +} + +int production_test_initialization(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, EVENT_TYPE_FULL_INITIALIZATION}; + + logError(0, "%s INITIALIZATION Production test is starting...\n", tag); + + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s production_test_initialization: ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s INITIALIZATION command sent...\n", tag); + cmd = FTS_CMD_FULL_INITIALIZATION; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s production_test_initialization: ERROR %02X\n", tag, (ERROR_I2C_W | ERROR_PROD_TEST_INITIALIZATION)); + return (ERROR_I2C_W | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s Looking for INITIALIZATION Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s production_test_initialization: INITIALIZATION Production test failed... ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + if (readData[2] != 0x00) { + logError(0, "%s INITIALIZATION Production testes finished!.................FAILED ERROR %02X\n", tag, (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_INITIALIZATION)); + res = (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_INITIALIZATION); + } else { + logError(0, "%s INITIALIZATION Production test.................OK\n", tag); + res = OK; + } + + logError(0, "%s Refresh Chip Info...\n", tag); + res |= readChipInfo(1); + /* need to update the chipInfo in order to refresh the tuning_versione */ + + if (res < 0) { + logError(1, "%s production_test_initialization: read chip info ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + res = (res | ERROR_PROD_TEST_INITIALIZATION); + } + + return res; + +} + +int ms_compensation_tuning(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, EVENT_TYPE_MS_TUNING_CMPL}; + + logError(0, "%s MS INITIALIZATION command sent...\n", tag); + cmd = FTS_CMD_MS_COMP_TUNING; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s ms_compensation_tuning 2: ERROR %02X\n", tag, (ERROR_I2C_W | ERROR_MS_TUNING)); + return (ERROR_I2C_W | ERROR_MS_TUNING); + } + + logError(0, "%s Looking for MS INITIALIZATION Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s ms_compensation_tuning: MS INITIALIZATION Production test failed... ERROR %02X\n", tag, ERROR_MS_TUNING); + return (res | ERROR_MS_TUNING); + } + + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s MS INITIALIZATION Production test finished!.................FAILED ERROR %02X\n", tag, ERROR_MS_TUNING); + res = ERROR_MS_TUNING; + } else { + logError(0, "%s MS INITIALIZATION Production test finished!.................OK\n", tag); + res = OK; + } + + return res; +} + +int ss_compensation_tuning(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, EVENT_TYPE_SS_TUNING_CMPL}; + + logError(0, "%s SS INITIALIZATION command sent...\n", tag); + cmd = FTS_CMD_SS_COMP_TUNING; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s ss_compensation_tuning 2: ERROR %02X\n", tag, (ERROR_I2C_W | ERROR_SS_TUNING)); + return (ERROR_I2C_W | ERROR_SS_TUNING); + } + + logError(0, "%s Looking for SS INITIALIZATION Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s ms_compensation_tuning: SS INITIALIZATION Production test failed... ERROR %02X\n", tag, ERROR_SS_TUNING); + return (res | ERROR_SS_TUNING); + } + + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s SS INITIALIZATION Production test finished!.................FAILED ERROR %02X\n", tag, ERROR_SS_TUNING); + res = ERROR_SS_TUNING; + } else { + logError(0, "%s SS INITIALIZATION Production test finished!.................OK\n", tag); + res = OK; + } + + return res; +} + +int lp_timer_calibration(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, EVENT_TYPE_LPTIMER_TUNING_CMPL}; + + logError(0, "%s LP TIMER CALIBRATION command sent...\n", tag); + cmd = FTS_CMD_LP_TIMER_CALIB; + if (fts_writeFwCmd(&cmd, 1) < 0) { + logError(1, "%s lp_timer_calibration 2: ERROR %02X\n", tag, (ERROR_I2C_W | ERROR_LP_TIMER_TUNING)); + return (ERROR_I2C_W | ERROR_LP_TIMER_TUNING); + } + + logError(0, "%s Looking for LP TIMER CALIBRATION Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s lp_timer_calibration: LP TIMER CALIBRATION Production test failed... ERROR %02X\n", tag, ERROR_LP_TIMER_TUNING); + return (res | ERROR_LP_TIMER_TUNING); + } + + if (readData[2] != 0x00 || readData[3] != 0x01) { + logError(0, "%s LP TIMER CALIBRATION Production test finished!.................FAILED ERROR %02X\n", tag, ERROR_LP_TIMER_TUNING); + res = ERROR_LP_TIMER_TUNING; + } else { + logError(0, "%s LP TIMER CALIBRATION Production test finished!.................OK\n", tag); + res = OK; + } + + return res; +} + +int save_cx_tuning(void) +{ + int res; + u8 cmd; + u8 readData[FIFO_EVENT_SIZE] = {0}; + int eventToSearch[2] = {EVENTID_STATUS_UPDATE, EVENT_TYPE_COMP_DATA_SAVED}; + + logError(0, "%s SAVE CX command sent...\n", tag); + cmd = FTS_CMD_SAVE_CX_TUNING; + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s save_cx_tuning 2: ERROR %02X\n", tag, (ERROR_I2C_W | ERROR_SAVE_CX_TUNING)); + return (ERROR_I2C_W | ERROR_SAVE_CX_TUNING); + } + + logError(0, "%s Looking for SAVE CX Event...\n", tag); + res = pollForEvent(eventToSearch, 2, readData, TIMEOUT_INITIALIZATION_TEST_RESULT); + if (res < 0) { + logError(1, "%s save_cx_tuning: SAVE CX failed... ERROR %02X\n", tag, ERROR_SAVE_CX_TUNING); + return (res | ERROR_SAVE_CX_TUNING); + } + + if (readData[2] != 0x00 || readData[3] != 0x00) { + logError(0, "%s SAVE CX finished!.................FAILED ERROR %02X\n", tag, ERROR_SAVE_CX_TUNING); + res = ERROR_SAVE_CX_TUNING; + } else { + logError(0, "%s SAVE CX finished!.................OK\n", tag); + res = OK; + } + + return res; +} + +int production_test_splited_initialization(int saveToFlash) +{ + int res; + + logError(0, "%s Splitted Initialization test is starting...\n", tag); + res = fts_system_reset(); + if (res < 0) { + logError(1, "%s production_test_initialization: ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + logError(0, "%s LP INITIALIZATION TEST:\n", tag); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + + logError(0, "%s MS INITIALIZATION TEST:\n", tag); + res = ms_compensation_tuning(); + if (res < 0) { + logError(0, "%s production_test_splited_initialization: MS INITIALIZATION TEST FAILED! ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + logError(0, "%s MS INITIALIZATION TEST OK!\n", tag); + + logError(0, "%s\n", tag); + + logError(0, "%s SS INITIALIZATION TEST:\n", tag); + res = ss_compensation_tuning(); + if (res < 0) { + logError(0, "%s production_test_splited_initialization: SS INITIALIZATION TEST FAILED! ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + logError(0, "%s SS INITIALIZATION TEST OK!\n", tag); + + logError(0, "%s\n", tag); + + logError(0, "%s LP INITIALIZATION TEST:\n", tag); + res = lp_timer_calibration(); + if (res < 0) { + logError(0, "%s production_test_splited_initialization: LP INITIALIZATION TEST FAILED! ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + logError(0, "%s LP INITIALIZATION TEST OK!\n", tag); + if (saveToFlash) { + logError(0, "%s\n", tag); + logError(0, "%s SAVE CX TEST:\n", tag); + res = save_cx_tuning(); + if (res < 0) { + logError(0, "%s production_test_splited_initialization: SAVE CX TEST FAILED! ERROR %02X\n", tag, res); + return (res | ERROR_PROD_TEST_INITIALIZATION); + } + logError(0, "%s SAVE CX TEST OK!\n", tag); + } + logError(0, "%s Refresh Chip Info...\n", tag); + res |= readChipInfo(1); + if (res < 0) { + logError(1, "%s production_test_initialization: read chip info ERROR %02X\n", tag, ERROR_PROD_TEST_INITIALIZATION); + res = (res | ERROR_PROD_TEST_INITIALIZATION); + } else + logError(0, "%s Splited Initialization test finished!.................OK\n", tag); + return res; +} + +int production_test_main(char *pathThresholds, int stop_on_fail, + int saveInit, TestToDo *todo, u32 signature) +{ + int res, ret; + + logError(0, "%s MAIN Production test is starting...\n", tag); + + logError(0, "%s\n", tag); + + logError(0, "%s ITO TEST:\n", tag); + res = production_test_ito(); + if (res < 0) { + logError(0, "%s Error during ITO TEST! ERROR %02X\n", tag, (u8) res); + goto END; /* in case of ITO TEST failure is no sense keep going */ + } else { + logError(0, "%s ITO TEST OK!\n", tag); + } + + logError(0, "%s\n", tag); + + logError(0, "%s INITIALIZATION TEST :\n", tag); + if (saveInit == 1) { + res = production_test_initialization(); + if (res < 0) { + logError(0, "%s Error during INITIALIZATION TEST! ERROR %02X\n", tag, (u8) res); + if (stop_on_fail) + goto END; + } else { + logError(0, "%s INITIALIZATION TEST OK!\n", tag); + } + } else + logError(0, "%s INITIALIZATION TEST :................. SKIPPED\n", tag); + + logError(0, "%s\n", tag); + + if (saveInit == 1) { + logError(0, "%s Cleaning up...\n", tag); + ret = cleanUp(0); + if (ret < 0) { + logError(1, "%s production_test_main: clean up ERROR %02X\n", tag, ret); + res |= ret; + if (stop_on_fail) + goto END; + } + logError(0, "%s\n", tag); + } + + logError(0, "%s PRODUCTION DATA TEST:\n", tag); + ret = production_test_data(pathThresholds, stop_on_fail, todo); + if (ret < 0) { + logError(0, "%s Error during PRODUCTION DATA TEST! ERROR %02X\n", tag, ret); + } else { + logError(0, "%s PRODUCTION DATA TEST OK!\n", tag); + } + + res |= ret; + /* the OR is important because if the data test is OK but the inizialization test fail, + * the main production test result should = FAIL + */ + + if (ret == OK && saveInit == 1) { + logError(0, "%s SAVE FLAG:\n", tag); + ret = save_mp_flag(signature); + if (ret < OK) + logError(0, "%s SAVE FLAG:................. FAIL! ERROR %08X\n", tag, ret); + else + logError(0, "%s SAVE FLAG:................. OK!\n", tag); + res |= ret; + } + + logError(0, "%s\n", tag); +END: + if (res < 0) { + logError(0, "%s MAIN Production test finished.................FAILED\n", tag); + return res; + } + logError(0, "%s MAIN Production test finished.................OK\n", tag); + return OK; +} + +int production_test_ms_raw(char *path_limits, int stop_on_fail, TestToDo *todo) +{ + + int ret, count_fail = 0; + MutualSenseFrame msRawFrame; + + int *thresholds = NULL; + int trows, tcolumns; + + /*********************** Mutual Sense Test **********************/ + logError(0, "%s\n", tag); + logError(0, "%s MS RAW DATA TEST is starting...\n", tag); + if (todo->MutualRaw == 1 || todo->MutualRawGap == 1) { + + ret = getMSFrame2(MS_TOUCH_ACTIVE, &msRawFrame); + if (ret < 0) { + logError(1, "%s production_test_data: getMSFrame failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s MS RAW MIN MAX TEST:\n", tag); + if (todo->MutualRaw == 1) { + ret = parseProductionTestLimits(path_limits, MS_RAW_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_RAW_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMinMax(msRawFrame.node_data, msRawFrame.header.force_node, msRawFrame.header.sense_node, thresholds[0], thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax MS RAW failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS RAW MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + } + kfree(thresholds); + logError(0, "%s MS RAW MIN MAX TEST:.................OK\n", tag); + } else + logError(0, "%s MS RAW MIN MAX TEST:.................SKIPPED\n", tag); + + logError(0, "%s\n", tag); + logError(0, "%s MS RAW GAP TEST:\n", tag); + if (todo->MutualRawGap == 1) { + ret = parseProductionTestLimits(path_limits, MS_RAW_GAP, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_RAW_GAP failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsGap(msRawFrame.node_data, msRawFrame.header.force_node, msRawFrame.header.sense_node, thresholds[0]); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsGap MS RAW failed... ERROR = %02X\n", tag, ret); + count_fail += 1; + if (stop_on_fail == 1) + goto ERROR; + + } else + logError(0, "%s MS RAW GAP TEST:.................OK\n\n", tag); + kfree(thresholds); + } else + logError(0, "%s MS RAW GAP TEST:.................SKIPPED\n", tag); + + kfree(msRawFrame.node_data); + } else + logError(0, "%s MS RAW FRAME TEST:.................SKIPPED\n", tag); + + logError(0, "%s\n", tag); + logError(0, "%s MS KEY RAW TEST:\n", tag); + if (todo->MutualKeyRaw == 1) { + ret = production_test_ms_key_raw(path_limits); + if (ret < 0) { + logError(1, "%s production_test_data: production_test_ms_key_raw failed... ERROR = %02X\n", tag, ret); + count_fail += 1; + } + } else + logError(0, "%s MS KEY RAW TEST:.................SKIPPED\n", tag); + +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, "%s MS RAW DATA TEST finished!.................OK\n", tag); + return OK; + } + print_frame_short("MS Raw frame =", array1dTo2d_short(msRawFrame.node_data, msRawFrame.node_data_size, msRawFrame.header.sense_node), msRawFrame.header.force_node, msRawFrame.header.sense_node); + logError(0, "%s MS RAW DATA TEST:.................FAIL fails_count = %d\n\n", tag, count_fail); + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); +} + +int production_test_ms_key_raw(char *path_limits) +{ + + int ret; + MutualSenseFrame msRawFrame; + + int *thresholds = NULL; + int trows, tcolumns; + + /******************************* Mutual Sense Test *******************************/ + logError(0, "%s MS KEY RAW DATA TEST is starting...\n", tag); + + ret = getMSFrame2(MS_KEY, &msRawFrame); + if (ret < 0) { + logError(1, "%s production_test_data: getMSKeyFrame failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, MS_KEY_RAW_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_KEY_RAW_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMinMax(msRawFrame.node_data, msRawFrame.header.force_node, msRawFrame.header.sense_node, thresholds[0], thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax MS KEY RAW failed... ERROR COUNT = %d\n", tag, ret); + goto ERROR; + } else + logError(0, "%s MS KEY RAW TEST:.................OK\n\n", tag); + + kfree(thresholds); + + kfree(msRawFrame.node_data); + + return OK; + +ERROR: + print_frame_short("MS Key Raw frame =", array1dTo2d_short(msRawFrame.node_data, msRawFrame.node_data_size, msRawFrame.header.sense_node), msRawFrame.header.force_node, msRawFrame.header.sense_node); + logError(0, "%s MS KEY RAW TEST:.................FAIL\n\n", tag); + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); + +} + +int production_test_ms_cx(char *path_limits, int stop_on_fail, TestToDo *todo) +{ + + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + MutualSenseData msCompData; + + u8 *adjhor = NULL; + + u8 *adjvert = NULL; + + u16 container; + u16 *total_cx = NULL; + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + /* MS CX TEST */ + logError(0, "%s\n", tag); + logError(0, "%s MS CX Testes are starting...\n", tag); + + ret = readMutualSenseCompensationData(MS_TOUCH_ACTIVE, &msCompData); /* read MS compensation data */ + if (ret < 0) { + logError(1, "%s production_test_data: readMutualSenseCompensationData failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s MS CX1 TEST:\n", tag); + if (todo->MutualCx1 == 1) { + + ret = parseProductionTestLimits(path_limits, MS_CX1_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_CX1_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + container = (u16) msCompData.cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], thresholds[1]); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax MS CX1 failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS CX1 TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX1 TEST:.................OK\n\n", tag); + } else + logError(0, "%s MS CX1 TEST:.................SKIPPED\n\n", tag); + + kfree(thresholds); + + logError(0, "%s MS CX2 MIN MAX TEST:\n", tag); + if (todo->MutualCx2 == 1) { + ret = parseProductionTestLimits(path_limits, MS_CX2_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load min thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_CX2_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, MS_CX2_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load max thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_CX2_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMap(msCompData.node_data, msCompData.header.force_node, msCompData.header.sense_node, thresholds_min, thresholds_max); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap MS CX2 MIN MAX failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS CX2 MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX2 MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s MS CX2 MIN MAX TEST:.................SKIPPED\n\n", tag); + + logError(0, "%s MS CX2 ADJ TEST:\n", tag); + if (todo->MutualCx2Adj == 1) { + /* MS CX2 ADJ HORIZ */ + logError(0, "%s MS CX2 ADJ HORIZ TEST:\n", tag); + + ret = computeAdjHoriz(msCompData.node_data, msCompData.header.force_node, msCompData.header.sense_node, &adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjHoriz failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s MS CX2 ADJ HORIZ computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, MS_CX2_ADJH_MAP_MAX, &thresholds_max, &trows, &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node || tcolumns != msCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_CX2_ADJH_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdj(adjhor, msCompData.header.force_node, msCompData.header.sense_node - 1, thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj CX2 ADJH failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS CX2 ADJ HORIZ TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX2 ADJ HORIZ TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(adjhor); + + /* MS CX2 ADJ VERT */ + logError(0, "%s MS CX2 ADJ VERT TEST:\n", tag); + + ret = computeAdjVert(msCompData.node_data, msCompData.header.force_node, msCompData.header.sense_node, &adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjVert failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s MS CX2 ADJ VERT computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, MS_CX2_ADJV_MAP_MAX, &thresholds_max, &trows, &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node - 1 || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_CX2_ADJV_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdj(adjvert, msCompData.header.force_node - 1, msCompData.header.sense_node - 1, thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj CX2 ADJV failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS CX2 ADJ HORIZ TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS CX2 ADJ VERT TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(adjvert); + } else + logError(0, "%s MS CX2 ADJ TEST:.................SKIPPED\n\n", tag); + + /* START OF TOTAL CHECK */ + logError(0, "%s MS TOTAL CX TEST:\n", tag); + + if (todo->MutualCxTotal == 1 || todo->MutualCxTotalAdj == 1) { + ret = computeTotal(msCompData.node_data, msCompData.cx1, msCompData.header.force_node, msCompData.header.sense_node, CX1_WEIGHT, CX2_WEIGHT, &total_cx); + if (ret < 0) { + logError(1, "%s production_test_data: computeTotalCx failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s MS TOTAL CX MIN MAX TEST:\n", tag); + if (todo->MutualCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, MS_TOTAL_CX_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load min thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_TOTAL_CX_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, MS_TOTAL_CX_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load max thresholds */ + if (ret < 0 || (trows != msCompData.header.force_node || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_TOTAL_CX_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapTotal(total_cx, msCompData.header.force_node, msCompData.header.sense_node, thresholds_min, thresholds_max); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap MS TOTAL CX TEST failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS TOTAL CX MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS TOTAL CX MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s MS TOTAL CX MIN MAX TEST:.................SKIPPED\n\n", tag); + + logError(0, "%s MS TOTAL CX ADJ TEST:\n", tag); + if (todo->MutualCxTotalAdj == 1) { + /* MS TOTAL CX ADJ HORIZ */ + logError(0, "%s MS TOTAL CX ADJ HORIZ TEST:\n", tag); + + /* thresholds_max = NULL; */ + ret = computeAdjHorizTotal(total_cx, msCompData.header.force_node, msCompData.header.sense_node, &total_adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjHoriz failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s MS TOTAL CX ADJ HORIZ computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, MS_TOTAL_CX_ADJH_MAP_MAX, &thresholds_max, &trows, &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node || tcolumns != msCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_TOTAL_CX_ADJH_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdjTotal(total_adjhor, msCompData.header.force_node, msCompData.header.sense_node - 1, thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj MS TOTAL CX ADJH failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS TOTAL CX ADJ HORIZ TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS TOTAL CX ADJ HORIZ TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(total_adjhor); + + /* MS TOTAL CX ADJ VERT */ + logError(0, "%s MS TOTAL CX ADJ VERT TEST:\n", tag); + + ret = computeAdjVertTotal(total_cx, msCompData.header.force_node, msCompData.header.sense_node, &total_adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjVert failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s MS TOTAL CX ADJ VERT computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, MS_TOTAL_CX_ADJV_MAP_MAX, &thresholds_max, &trows, &tcolumns); + if (ret < 0 || (trows != msCompData.header.force_node - 1 || tcolumns != msCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_TOTAL_CX_ADJV_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdjTotal(total_adjvert, msCompData.header.force_node - 1, msCompData.header.sense_node - 1, thresholds_max); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj MS TOTAL CX ADJV failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS TOTAL CX ADJ HORIZ TEST:.................FAIL\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS TOTAL CX ADJ VERT TEST:.................OK\n", tag); + + kfree(thresholds_max); + kfree(total_adjvert); + } else + logError(0, "%s MS TOTAL CX ADJ TEST:.................SKIPPED\n", tag); + + kfree(total_cx); + } else + logError(0, "%s MS TOTAL CX TEST:.................SKIPPED\n", tag); + + kfree(msCompData.node_data); + + if ((todo->MutualKeyCx1 | todo->MutualKeyCx2 | todo->MutualKeyCxTotal) == 1) { + ret = production_test_ms_key_cx(path_limits, stop_on_fail, todo); + if (ret < 0) { + count_fail += 1; + logError(1, "%s production_test_data: production_test_ms_key_cx failed... ERROR = %02X\n", tag, ret); + logError(0, "%s MS CX testes finished!.................FAILED fails_count = %d\n\n", tag, count_fail); + return ret; + } + } else + logError(0, "%s MS KEY CX TEST:.................SKIPPED\n", tag); + +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, "%s MS CX testes finished!.................OK\n", tag); + return OK; + } + print_frame_u8("MS Init Data (Cx2) =", array1dTo2d_u8(msCompData.node_data, msCompData.node_data_size, msCompData.header.sense_node), msCompData.header.force_node, msCompData.header.sense_node); + logError(0, "%s MS CX testes finished!.................FAILED fails_count = %d\n\n", tag, count_fail); + return (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); + +} + +int production_test_ms_key_cx(char *path_limits, int stop_on_fail, TestToDo *todo) +{ + + int ret; + int count_fail = 0; + int num_keys = 0; + + int *thresholds = NULL; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + int trows, tcolumns; + + MutualSenseData msCompData; + + u16 container; + u16 *total_cx = NULL; + + /* MS CX TEST */ + logError(0, "%s MS KEY CX Testes are starting...\n", tag); + + ret = readMutualSenseCompensationData(MS_KEY, &msCompData); /* read MS compensation data */ + if (ret < 0) { + logError(1, "%s production_test_data: readMutualSenseCompensationData failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + if (msCompData.header.force_node > msCompData.header.sense_node) +/* the meaningful data are only in the first row, the other rows are only a copy of the first one */ + num_keys = msCompData.header.force_node; + else + num_keys = msCompData.header.sense_node; + + logError(0, "%s MS KEY CX1 TEST:\n", tag); + if (todo->MutualKeyCx1 == 1) { + + ret = parseProductionTestLimits(path_limits, MS_KEY_CX1_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_KEY_CX1_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + container = (u16) msCompData.cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], thresholds[1]); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax MS CX1 failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS KEY CX1 TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS KEY CX1 TEST:.................OK\n\n", tag); + } else + logError(0, "%s MS KEY CX1 TEST:.................SKIPPED\n\n", tag); + + kfree(thresholds); + + logError(0, "%s MS KEY CX2 TEST:\n", tag); + if (todo->MutualKeyCx2 == 1) { + ret = parseProductionTestLimits(path_limits, MS_KEY_CX2_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_KEY_CX2_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, MS_KEY_CX2_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_KEY_CX2_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMap(msCompData.node_data, 1, num_keys, thresholds_min, thresholds_max); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap MS KEY CX2 failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS KEY CX2 TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS KEY CX2 TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s MS CX2 TEST:.................SKIPPED\n\n", tag); + + /* START OF TOTAL CHECK */ + logError(0, "%s MS KEY TOTAL CX TEST:\n", tag); + + if (todo->MutualKeyCxTotal == 1) { + ret = computeTotal(msCompData.node_data, msCompData.cx1, 1, num_keys, CX1_WEIGHT, CX2_WEIGHT, &total_cx); + if (ret < 0) { + logError(1, "%s production_test_data: computeTotalCx failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, MS_KEY_TOTAL_CX_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_KEY_TOTAL_CX_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, MS_KEY_TOTAL_CX_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != num_keys)) { + logError(1, "%s production_test_data: parseProductionTestLimits MS_KEY_TOTAL_CX_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapTotal(total_cx, 1, num_keys, thresholds_min, thresholds_max); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap MS TOTAL KEY CX TEST failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s MS KEY TOTAL CX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s MS KEY TOTAL CX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + + kfree(total_cx); + } else + logError(0, "%s MS KEY TOTAL CX TEST:.................SKIPPED\n", tag); + + kfree(msCompData.node_data); +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, "%s MS KEY CX testes finished!.................OK\n", tag); + return OK; + } + print_frame_u8("MS Key Init Data (Cx2) =", array1dTo2d_u8(msCompData.node_data, msCompData.node_data_size, msCompData.header.sense_node), 1, msCompData.header.sense_node); + logError(0, "%s MS Key CX testes finished!.................FAILED fails_count = %d\n\n", tag, count_fail); + return (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); +} + +int production_test_ss_raw(char *path_limits, int stop_on_fail, TestToDo *todo) +{ + int ret; + int count_fail = 0; + int rows, columns; + + /* short *ssRawFrame = NULL; */ + SelfSenseFrame ssRawFrame; + + int *thresholds = NULL; + int trows, tcolumns; + + /* MS SS TEST */ + logError(0, "%s\n", tag); + logError(0, "%s SS RAW Testes are starting...\n", tag); + + /******************************* Self Sense Test *******************************/ + + logError(0, "%s Getting SS Frame...\n", tag); + ret = getSSFrame2(SS_TOUCH, &ssRawFrame); + if (ret < 0) { + logError(1, "%s production_test_data: getSSFrame failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + /* SS RAW (PROXIMITY) FORCE TEST */ + logError(0, "%s SS RAW (PROXIMITY) FORCE TEST:\n", tag); + + if (todo->SelfForceRaw == 1 || todo->SelfForceRawGap == 1) { + + columns = 1; + /* there are no data for the sense channels due to the fact that the force frame is analized */ + rows = ssRawFrame.header.force_node; + + logError(0, "%s SS RAW (PROXIMITY) FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceRaw == 1) { + + ret = parseProductionTestLimits(path_limits, SS_RAW_FORCE_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_RAW_FORCE_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMinMax(ssRawFrame.force_data, rows, columns, thresholds[0], thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax SS RAW (PROXIMITY) FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS RAW (PROXIMITY) FORCE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + print_frame_short("SS Raw force frame =", array1dTo2d_short(ssRawFrame.force_data, rows*columns, columns), rows, columns); + if (stop_on_fail) + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); + } else + logError(0, "%s SS RAW (PROXIMITY) FORCE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds); + } else + logError(0, "%s SS RAW (PROXIMITY) FORCE MIN MAX TEST:.................SKIPPED\n\n", tag); + + logError(0, "%s\n", tag); + logError(0, "%s SS RAW (PROXIMITY) FORCE GAP TEST:\n", tag); + if (todo->SelfForceRawGap == 1) { + + ret = parseProductionTestLimits(path_limits, SS_RAW_FORCE_GAP, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_RAW_FORCE_GAP failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsGap(ssRawFrame.force_data, rows, columns, thresholds[0]); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsGap SS RAW (PROXIMITY) FORCE GAP failed... ERROR = %02X\n", tag, ret); + logError(0, "%s SS RAW (PROXIMITY) FORCE GAP TEST:.................FAIL\n\n", tag); + count_fail += 1; + print_frame_short("SS Raw force frame =", array1dTo2d_short(ssRawFrame.force_data, rows*columns, columns), rows, columns); + if (stop_on_fail) + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); + } else + logError(0, "%s SS RAW (PROXIMITY) FORCE GAP TEST:.................OK\n\n", tag); + + kfree(thresholds); + } else + logError(0, "%s SS RAW (PROXIMITY) FORCE GAP TEST:.................SKIPPED\n\n", tag); + + kfree(ssRawFrame.force_data); + } else + logError(0, "%s SS RAW (PROXIMITY) FORCE TEST:.................SKIPPED\n\n", tag); + + logError(0, "%s\n", tag); + /* SS RAW (PROXIMITY) SENSE TEST */ + logError(0, "%s SS RAW (PROXIMITY) SENSE TEST:\n", tag); + + if (todo->SelfSenseRaw == 1 || todo->SelfSenseRawGap == 1) { + columns = ssRawFrame.header.sense_node; + rows = 1; /* there are no data for the force channels due to the fact that the sense frame is analized */ + + logError(0, "%s SS RAW (PROXIMITY) SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseRaw == 1) { + ret = parseProductionTestLimits(path_limits, SS_RAW_SENSE_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_RAW_SENSE_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMinMax(ssRawFrame.sense_data, rows, columns, thresholds[0], thresholds[1]); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax SS RAW (PROXIMITY) SENSE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS RAW (PROXIMITY) SENSE MIN MAX TEST:.................FAIL\n", tag); + count_fail += 1; + print_frame_short("SS Raw sense frame =", array1dTo2d_short(ssRawFrame.sense_data, rows*columns, columns), rows, columns); + if (stop_on_fail) + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); + } else + logError(0, "%s SS RAW (PROXIMITY) SENSE MIN MAX TEST:.................OK\n", tag); + + kfree(thresholds); + } else + logError(0, "%s SS RAW (PROXIMITY) SENSE MIN MAX TEST:.................SKIPPED\n", tag); + + logError(0, "%s\n", tag); + logError(0, "%s SS RAW (PROXIMITY) SENSE GAP TEST:\n", tag); + if (todo->SelfSenseRawGap == 1) { + ret = parseProductionTestLimits(path_limits, SS_RAW_SENSE_GAP, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_RAW_SENSE_GAP failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsGap(ssRawFrame.sense_data, rows, columns, thresholds[0]); + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsGap SS RAW (PROXIMITY) SENSE GAP failed... ERROR = %02X\n", tag, ret); + logError(0, "%s SS RAW (PROXIMITY) SENSE GAP TEST:.................FAIL\n", tag); + count_fail += 1; + print_frame_short("SS Raw sense frame =", array1dTo2d_short(ssRawFrame.sense_data, rows*columns, columns), rows, columns); + if (stop_on_fail) + return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); + } else + logError(0, "%s SS RAW (PROXIMITY) SENSE GAP TEST:.................OK\n", tag); + + kfree(thresholds); + } else + logError(0, "%s SS RAW (PROXIMITY) SENSE GAP TEST:.................SKIPPED\n", tag); + + kfree(ssRawFrame.sense_data); + } + + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, "%s SS RAW testes finished!.................OK\n\n", tag); + return OK; + } + logError(0, "%s SS RAW testes finished!.................FAILED fails_count = %d\n\n", tag, count_fail); + return (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); +} + +int production_test_ss_ix_cx(char *path_limits, int stop_on_fail, TestToDo *todo) +{ + + int ret; + int count_fail = 0; + + int *thresholds = NULL; + int trows, tcolumns; + int *thresholds_min = NULL; + int *thresholds_max = NULL; + + SelfSenseData ssCompData; + + u8 *adjhor = NULL; + u8 *adjvert = NULL; + + u16 container; + int *ix1_w, *ix2_w; + u16 *total_ix = NULL; + u16 *total_cx = NULL; + + u16 *total_adjhor = NULL; + u16 *total_adjvert = NULL; + + logError(0, "%s\n", tag); + logError(0, "%s SS IX CX testes are starting...\n", tag); + ret = readSelfSenseCompensationData(SS_TOUCH, &ssCompData); /* read the SS compensation data */ + if (ret < 0) { + logError(1, "%s production_test_data: readSelfSenseCompensationData failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + /*************************** SS FORCE IX *************************************/ + /* SS IX1 FORCE TEST */ + logError(0, "%s SS IX1 FORCE TEST:\n", tag); + if (todo->SelfForceIx1 == 1) { + + ret = parseProductionTestLimits(path_limits, SS_IX1_FORCE_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX1_FORCE_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + container = (u16) ssCompData.f_ix1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], thresholds[1]); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax SS IX1 FORCE TEST failed... ERROR COUNT = %d\n", tag, ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS IX1 FORCE TEST:.................OK\n\n", tag); + } else + logError(0, "%s SS IX1 FORCE TEST:.................SKIPPED\n\n", tag); + + kfree(thresholds); + /* SS IX2 FORCE TEST */ + logError(0, "%s SS IX2 FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceIx2 == 1) { + ret = parseProductionTestLimits(path_limits, SS_IX2_FORCE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX2_FORCE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_IX2_FORCE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX2_FORCE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMap(ssCompData.ix2_fm, ssCompData.header.force_node, 1, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS IX2 FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS IX2 FORCE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS IX2 FORCE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS IX2 FORCE MIN MAX TEST:.................SKIPPED\n\n", tag); + + logError(0, "%s SS IX2 FORCE ADJ TEST:\n", tag); + if (todo->SelfForceIx2Adj == 1) { + /* SS IX2 FORCE ADJV TEST */ + logError(0, "%s SS IX2 FORCE ADJVERT TEST:\n", tag); + ret = computeAdjVert(ssCompData.ix2_fm, ssCompData.header.force_node, 1, &adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjVert SS IX2 FORCE ADJV failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS IX2 FORCE ADJV computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_IX2_FORCE_ADJV_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX2_FORCE_ADJV_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdj(adjvert, ssCompData.header.force_node - 1, 1, thresholds_max); + /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS IX2 FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS IX2 FORCE ADJV TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS IX2 FORCE ADJV TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(adjvert); + + } else + logError(0, "%s SS IX2 FORCE ADJ TEST:.................SKIPPED\n\n", tag); + + /* SS TOTAL FORCE IX */ + logError(0, "%s SS TOTAL IX FORCE TEST:\n", tag); + if (todo->SelfForceIxTotal == 1 || todo->SelfForceIxTotalAdj == 1) { + logError(0, "%s Reading TOTAL IX FORCE Weights...\n", tag); + ret = parseProductionTestLimits(path_limits, SS_IX1_FORCE_W, &ix1_w, &trows, &tcolumns); /* load the IX1 weight */ + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX1_FORCE_W failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_IX2_FORCE_W, &ix2_w, &trows, &tcolumns); /* load the IX2 weight */ + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX1_FORCE_W failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s Weights: IX1_W = %d IX2_W = %d\n", tag, *ix1_w, *ix2_w); + + ret = computeTotal(ssCompData.ix2_fm, ssCompData.f_ix1, ssCompData.header.force_node, 1, *ix1_w, *ix2_w, &total_ix); + if (ret < 0) { + logError(1, "%s production_test_data: computeTotal Ix Force failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s SS TOTAL IX FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceIxTotal == 1) { + ret = parseProductionTestLimits(path_limits, SS_TOTAL_IX_FORCE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_IX_FORCE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_IX_FORCE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_IX_FORCE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapTotal(total_ix, ssCompData.header.force_node, 1, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS TOTAL IX FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL IX FORCE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL IX FORCE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS TOTAL IX FORCE MIN MAX TEST:.................SKIPPED\n", tag); + + logError(0, "%s SS TOTAL IX FORCE ADJ TEST:\n", tag); + if (todo->SelfForceIxTotalAdj == 1) { + /* SS TOTAL IX FORCE ADJV TEST */ + logError(0, "%s SS TOTAL IX FORCE ADJVERT TEST:\n", tag); + ret = computeAdjVertTotal(total_ix, ssCompData.header.force_node, 1, &total_adjvert); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjVert SS TOTAL IX FORCE ADJV failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS TOTAL IX FORCE ADJV computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_IX_FORCE_ADJV_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_IX_FORCE_ADJV_MAP_MAX... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdjTotal(total_adjvert, ssCompData.header.force_node - 1, 1, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS TOTAL IX FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL IX FORCE ADJV TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL IX FORCE ADJV TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(total_adjvert); + } else + logError(0, "%s SS TOTAL IX FORCE ADJ TEST:.................SKIPPED\n", tag); + + kfree(total_ix); + } else + logError(0, "%s SS TOTAL IX FORCE TEST:.................SKIPPED\n\n", tag); + + /******************* SS SENSE IX **************************/ + /* SS IX1 SENSE TEST */ + logError(0, "%s SS IX1 SENSE TEST:\n", tag); + if (todo->SelfSenseIx1 == 1) { + + ret = parseProductionTestLimits(path_limits, SS_IX1_SENSE_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX1_SENSE_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + container = (u16) ssCompData.s_ix1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], thresholds[1]); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax SS IX1 SENSE TEST failed... ERROR COUNT = %d\n", tag, ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS IX1 SENSE TEST:.................OK\n\n", tag); + } else + logError(0, "%s SS IX1 SENSE TEST:.................SKIPPED\n\n", tag); + + kfree(thresholds); + /* SS IX2 SENSE TEST */ + logError(0, "%s SS IX2 SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseIx2 == 1) { + ret = parseProductionTestLimits(path_limits, SS_IX2_SENSE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX2_SENSE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_IX2_SENSE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX2_SENSE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMap(ssCompData.ix2_sn, 1, ssCompData.header.sense_node, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS IX2 SENSE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS IX2 SENSE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS IX2 SENSE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS IX2 SENSE MIN MAX TEST:.................SKIPPED\n\n", tag); + + logError(0, "%s SS IX2 SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseIx2Adj == 1) { + /* SS IX2 SENSE ADJH TEST */ + logError(0, "%s SS IX2 SENSE ADJHORIZ TEST:\n", tag); + ret = computeAdjHoriz(ssCompData.ix2_sn, 1, ssCompData.header.sense_node, &adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjHoriz SS IX2 SENSE ADJH failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS IX2 SENSE ADJ HORIZ computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_IX2_SENSE_ADJH_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX2_SENSE_ADJH_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdj(adjhor, 1, ssCompData.header.sense_node - 1, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj SS IX2 SENSE ADJH failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS IX2 SENSE ADJH TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS IX2 SENSE ADJH TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(adjhor); + } else + logError(0, "%s SS IX2 SENSE ADJ TEST:.................SKIPPED\n", tag); + + /* SS TOTAL IX SENSE */ + logError(0, "%s SS TOTAL IX SENSE TEST:\n", tag); + if (todo->SelfSenseIxTotal == 1 || todo->SelfSenseIxTotalAdj == 1) { + logError(0, "%s Reading TOTAL IX SENSE Weights...\n", tag); + ret = parseProductionTestLimits(path_limits, SS_IX1_SENSE_W, &ix1_w, &trows, &tcolumns); /* load the IX1 weight */ + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX1_SENSE_W failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_IX2_SENSE_W, &ix2_w, &trows, &tcolumns); /* load the IX2 weight */ + if (ret < 0 || (trows != 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX1_SENSE_W failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s Weights: IX1_W = %d IX2_W = %d\n", tag, *ix1_w, *ix2_w); + + ret = computeTotal(ssCompData.ix2_sn, ssCompData.s_ix1, 1, ssCompData.header.sense_node, *ix1_w, *ix2_w, &total_ix); + if (ret < 0) { + logError(1, "%s production_test_data: computeTotal Ix Sense failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s SS TOTAL IX SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseIxTotal == 1) { + ret = parseProductionTestLimits(path_limits, SS_TOTAL_IX_SENSE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_IX_SENSE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_IX_SENSE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_IX_SENSE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapTotal(total_ix, 1, ssCompData.header.sense_node, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS TOTAL IX SENSE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL IX SENSE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL IX SENSE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS TOTAL IX SENSE MIN MAX TEST:.................SKIPPED\n", tag); + + logError(0, "%s SS TOTAL IX SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseIxTotalAdj == 1) { + /* SS TOTAL IX SENSE ADJH TEST */ + logError(0, "%s SS TOTAL IX SENSE ADJHORIZ TEST:\n", tag); + ret = computeAdjHorizTotal(total_ix, 1, ssCompData.header.sense_node, &total_adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjHoriz SS TOTAL IX SENSE ADJH failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS TOTAL IX SENSE ADJ HORIZ computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_IX_SENSE_ADJH_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_IX_SENSE_ADJH_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdjTotal(total_adjhor, 1, ssCompData.header.sense_node - 1, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj SS TOTAL IX SENSE ADJH failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL IX SENSE ADJH TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL IX SENSE ADJH TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(total_adjhor); + } else + logError(0, "%s SS TOTAL IX SENSE ADJ TEST:.................SKIPPED\n", tag); + kfree(total_ix); + } else + logError(0, "%s SS TOTAL IX SENSE TEST:.................SKIPPED\n", tag); + + /*********************** SS SENSE CX ******************************/ + /* SS CX1 FORCE TEST */ + logError(0, "%s SS CX1 FORCE TEST:\n", tag); + if (todo->SelfForceCx1 == 1) { + + ret = parseProductionTestLimits(path_limits, SS_CX1_FORCE_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_CX1_FORCE_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + container = (u16) ssCompData.f_cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], thresholds[1]); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax SS CX1 FORCE TEST failed... ERROR COUNT = %d\n", tag, ret); + count_fail += 1; + /* if (stop_on_fail) return (ERROR_PROD_TEST_DATA | ERROR_TEST_CHECK_FAIL); */ + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS CX1 FORCE TEST:.................OK\n\n", tag); + kfree(thresholds); + } else + logError(0, "%s SS CX1 FORCE TEST:.................SKIPPED\n\n", tag); + + /* SS CX2 FORCE TEST */ + logError(0, "%s SS CX2 FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceCx2 == 1) { + ret = parseProductionTestLimits(path_limits, SS_CX2_FORCE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_CX2_FORCE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_CX2_FORCE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_CX2_FORCE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMap(ssCompData.cx2_fm, ssCompData.header.force_node, 1, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS CX2 FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS CX2 FORCE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS CX2 FORCE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS CX2 FORCE MIN MAX TEST:.................SKIPPED\n", tag); + + logError(0, "%s SS CX2 FORCE ADJ TEST:\n", tag); + if (todo->SelfForceCx2Adj == 1) { + /* SS CX2 FORCE ADJV TEST */ + logError(0, "%s SS CX2 FORCE ADJVERT TEST:\n", tag); + ret = computeAdjVert(ssCompData.cx2_fm, ssCompData.header.force_node, + 1, &adjvert); /* comepute the ADJV for CX2 FORCE */ + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjVert SS CX2 FORCE ADJV failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS CX2 FORCE ADJV computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_CX2_FORCE_ADJV_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_CX2_FORCE_ADJV_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdj(adjvert, ssCompData.header.force_node - 1, 1, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS IX2 FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS CX2 FORCE ADJV TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS CX2 FORCE ADJV TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(adjvert); + } else + logError(0, "%s SS CX2 FORCE ADJ TEST:.................SKIPPED\n\n", tag); + + /* SS TOTAL CX FORCE */ + logError(0, "%s SS TOTAL CX FORCE TEST:\n", tag); + if (todo->SelfForceCxTotal == 1 || todo->SelfForceCxTotalAdj == 1) { + ret = computeTotal(ssCompData.cx2_fm, ssCompData.f_cx1, ssCompData.header.force_node, 1, CX1_WEIGHT, CX2_WEIGHT, &total_cx); + if (ret < 0) { + logError(1, "%s production_test_data: computeTotal Cx Force failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s SS TOTAL CX FORCE MIN MAX TEST:\n", tag); + if (todo->SelfForceCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, SS_TOTAL_CX_FORCE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_CX_FORCE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_CX_FORCE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_CX_FORCE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapTotal(total_cx, ssCompData.header.force_node, 1, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS TOTAL FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL FORCE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL FORCE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS TOTAL CX FORCE MIN MAX TEST:.................SKIPPED\n", tag); + + /* SS TOTAL CX FORCE ADJV TEST */ + logError(0, "%s SS TOTAL CX FORCE ADJ TEST:\n", tag); + if (todo->SelfForceCxTotalAdj == 1) { + logError(0, "%s SS TOTAL CX FORCE ADJVERT TEST:\n", tag); + ret = computeAdjVertTotal(total_cx, ssCompData.header.force_node, 1, &total_adjvert); /* comepute the ADJV for CX2 FORCE */ + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjVert SS TOTAL CX FORCE ADJV failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS TOTAL CX FORCE ADJV computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_CX_FORCE_ADJV_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != ssCompData.header.force_node - 1 || tcolumns != 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_CX_FORCE_ADJV_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdjTotal(total_adjvert, ssCompData.header.force_node - 1, 1, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS TOTAL CX FORCE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL CX FORCE ADJV TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL CX FORCE ADJV TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(total_adjvert); + + } else + logError(0, "%s SS TOTAL CX FORCE ADJ TEST:.................SKIPPED\n", tag); + kfree(total_cx); + } else + logError(0, "%s SS TOTAL CX FORCE TEST:.................SKIPPED\n\n", tag); + + /* ********************** SS SENSE CX ************************************/ + /* SS CX1 SENSE TEST */ + logError(0, "%s SS CX1 SENSE TEST:\n", tag); + if (todo->SelfSenseCx1 == 1) { + + ret = parseProductionTestLimits(path_limits, SS_CX1_SENSE_MIN_MAX, &thresholds, &trows, &tcolumns); + if (ret < 0 || (trows != 1 || tcolumns != 2)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_CX1_SENSE_MIN_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + container = (u16) ssCompData.s_cx1; + ret = checkLimitsMinMax(&container, 1, 1, thresholds[0], thresholds[1]); /* check the limits */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMinMax SS CX1 SENSE TEST failed... ERROR COUNT = %d\n", tag, ret); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS CX1 SENSE TEST:.................OK\n\n", tag); + kfree(thresholds); + } else + logError(0, "%s SS CX1 SENSE TEST:.................SKIPPED\n\n", tag); + + /* SS CX2 SENSE TEST */ + logError(0, "%s SS CX2 SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseCx2 == 1) { + ret = parseProductionTestLimits(path_limits, SS_CX2_SENSE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_CX2_SENSE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_CX2_SENSE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_CX2_SENSE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMap(ssCompData.cx2_sn, 1, ssCompData.header.sense_node, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS CX2 SENSE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS CX2 SENSE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS CX2 SENSE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS CX2 SENSE MIN MAX TEST:.................SKIPPED\n", tag); + + logError(0, "%s SS CX2 SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseCx2Adj == 1) { + /* SS CX2 SENSE ADJH TEST */ + logError(0, "%s SS CX2 SENSE ADJHORIZ TEST:\n", tag); + ret = computeAdjHoriz(ssCompData.ix2_sn, 1, ssCompData.header.sense_node, &adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjHoriz SS CX2 SENSE ADJH failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS CX2 SENSE ADJH computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_CX2_SENSE_ADJH_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_IX2_SENSE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdj(adjhor, 1, ssCompData.header.sense_node - 1, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj SS CX2 SENSE ADJH failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS CX2 SENSE ADJH TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS CX2 SENSE ADJH TEST:.................OK\n", tag); + + kfree(thresholds_max); + kfree(adjhor); + } else + logError(0, "%s SS CX2 SENSE ADJ TEST:.................SKIPPED\n\n", tag); + + /* SS TOTAL CX SENSE */ + logError(0, "%s SS TOTAL CX SENSE TEST:\n", tag); + if (todo->SelfSenseCxTotal == 1 || todo->SelfSenseCxTotalAdj == 1) { + ret = computeTotal(ssCompData.cx2_sn, ssCompData.s_cx1, 1, ssCompData.header.sense_node, CX1_WEIGHT, CX2_WEIGHT, &total_cx); + if (ret < 0) { + logError(1, "%s production_test_data: computeTotal Cx Sense failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s SS TOTAL CX SENSE MIN MAX TEST:\n", tag); + if (todo->SelfSenseCxTotal == 1) { + ret = parseProductionTestLimits(path_limits, SS_TOTAL_CX_SENSE_MAP_MIN, &thresholds_min, &trows, &tcolumns); /* load the min thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_CX_SENSE_MAP_MIN failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_CX_SENSE_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_CX_SENSE_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapTotal(total_cx, 1, ssCompData.header.sense_node, thresholds_min, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMap SS TOTAL CX SENSE failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL CX SENSE MIN MAX TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL CX SENSE MIN MAX TEST:.................OK\n\n", tag); + + kfree(thresholds_min); + kfree(thresholds_max); + } else + logError(0, "%s SS TOTAL CX SENSE MIN MAX TEST:.................SKIPPED\n", tag); + + /* SS TOTAL IX SENSE ADJH TEST */ + logError(0, "%s SS TOTAL CX SENSE ADJ TEST:\n", tag); + if (todo->SelfSenseCxTotalAdj == 1) { + logError(0, "%s SS TOTAL CX SENSE ADJHORIZ TEST:\n", tag); + ret = computeAdjHorizTotal(total_cx, 1, ssCompData.header.sense_node, &total_adjhor); + if (ret < 0) { + logError(1, "%s production_test_data: computeAdjHoriz SS TOTAL CX SENSE ADJH failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + logError(0, "%s SS TOTAL CX SENSE ADJ HORIZ computed!\n", tag); + + ret = parseProductionTestLimits(path_limits, SS_TOTAL_CX_SENSE_ADJH_MAP_MAX, &thresholds_max, &trows, &tcolumns); /* load the max thresholds */ + if (ret < 0 || (trows != 1 || tcolumns != ssCompData.header.sense_node - 1)) { + logError(1, "%s production_test_data: parseProductionTestLimits SS_TOTAL_CX_SENSE_ADJH_MAP_MAX failed... ERROR %02X\n", tag, ERROR_PROD_TEST_DATA); + return (ret | ERROR_PROD_TEST_DATA); + } + + ret = checkLimitsMapAdjTotal(total_adjhor, 1, ssCompData.header.sense_node - 1, thresholds_max); /* check the values with thresholds */ + if (ret != OK) { + logError(1, "%s production_test_data: checkLimitsMapAdj SS TOTAL CX SENSE ADJH failed... ERROR COUNT = %d\n", tag, ret); + logError(0, "%s SS TOTAL CX SENSE ADJH TEST:.................FAIL\n\n", tag); + count_fail += 1; + if (stop_on_fail) + goto ERROR; + } else + logError(0, "%s SS TOTAL CX SENSE ADJH TEST:.................OK\n\n", tag); + + kfree(thresholds_max); + kfree(total_adjhor); + } else + logError(0, "%s SS TOTAL CX SENSE ADJ TEST:.................SKIPPED\n", tag); + kfree(total_cx); + } else + logError(0, "%s SS TOTAL CX SENSE TEST:.................SKIPPED\n", tag); + + /* SS compensation data are no usefull anymore */ + + kfree(ssCompData.ix2_fm); + kfree(ssCompData.ix2_sn); + kfree(ssCompData.cx2_fm); + kfree(ssCompData.cx2_sn); + +ERROR: + logError(0, "%s\n", tag); + if (count_fail == 0) { + logError(0, "%s SS IX CX testes finished!.................OK\n\n", tag); + return OK; + } + /* print all kind of data in just one row for readability reason */ + print_frame_u8("SS Init Data Ix2_fm = ", array1dTo2d_u8(ssCompData.ix2_fm, ssCompData.header.force_node, ssCompData.header.force_node), 1, ssCompData.header.force_node); + print_frame_u8("SS Init Data Cx2_fm = ", array1dTo2d_u8(ssCompData.cx2_fm, ssCompData.header.force_node, ssCompData.header.force_node), 1, ssCompData.header.force_node); + print_frame_u8("SS Init Data Ix2_sn = ", array1dTo2d_u8(ssCompData.ix2_sn, ssCompData.header.sense_node, ssCompData.header.sense_node), 1, ssCompData.header.sense_node); + print_frame_u8("SS Init Data Cx2_sn = ", array1dTo2d_u8(ssCompData.cx2_sn, ssCompData.header.sense_node, ssCompData.header.sense_node), 1, ssCompData.header.sense_node); + logError(0, "%s SS IX CX testes finished!.................FAILED fails_count = %d\n\n", tag, count_fail); + return (ERROR_TEST_CHECK_FAIL | ERROR_PROD_TEST_DATA); +} + +int production_test_data(char *path_limits, int stop_on_fail, TestToDo *todo) +{ + int res = OK, ret; + + if (todo == NULL) { + logError(1, "%s production_test_data: No TestToDo specified!! ERROR = %02X\n", tag, (ERROR_OP_NOT_ALLOW | ERROR_PROD_TEST_DATA)); + return (ERROR_OP_NOT_ALLOW | ERROR_PROD_TEST_DATA); + } + + logError(0, "%s DATA Production test is starting...\n", tag); + + ret = production_test_ms_raw(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + logError(1, "%s production_test_data: production_test_ms_raw failed... ERROR = %02X\n", tag, ret); + if (stop_on_fail == 1) + goto END; + } + + ret = production_test_ms_cx(path_limits, stop_on_fail, todo); + res |= ret; + if (res < 0) { + logError(1, "%s production_test_data: production_test_ms_cx failed... ERROR = %02X\n", tag, ret); + if (stop_on_fail == 1) + goto END; + } + + ret = production_test_ss_raw(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + logError(1, "%s production_test_data: production_test_ss_raw failed... ERROR = %02X\n", tag, ret); + if (stop_on_fail == 1) + goto END; + } + + ret = production_test_ss_ix_cx(path_limits, stop_on_fail, todo); + res |= ret; + if (ret < 0) { + logError(1, "%s production_test_data: production_test_ss_ix_cx failed... ERROR = %02X\n", tag, ret); + if (stop_on_fail == 1) + goto END; + } + +END: + if (res < OK) + logError(0, "%s DATA Production test failed!\n", tag); + else + logError(0, "%s DATA Production test finished!\n", tag); + return res; +} + +int save_mp_flag(u32 signature) +{ + int res = -1, ret; + int i; + u8 cmd[6] = {FTS_CMD_WRITE_MP_FLAG, 0x00, 0x00, 0x00, 0x00, 0x00}; + + u32ToU8(signature, &cmd[2]); + + logError(0, "%s Starting Saving Flag with signature = %08X ...\n", tag, signature); + + for (i = 0; i < SAVE_FLAG_RETRY && res < OK; i++) { + logError(0, "%s Attempt number %d to save mp flag !\n", tag, i+1); + logError(0, "%s Command write flag sent...\n", tag); + res = fts_writeFwCmd(cmd, 6); + if (res >= OK) + res = save_cx_tuning(); + + } + + ret = readChipInfo(1); /* need to update the MP Flag */ + if (ret < OK) { + logError(1, "%s save_mp_flag: read chip info ERROR %08X\n", tag, ret); + } + + res |= ret; + if (res < OK) { + logError(1, "%s save_mp_flag: ERROR %08X ...\n", tag, res); + return res; + } + logError(1, "%s Saving Flag DONE!\n", tag); + return OK; +} + +int parseProductionTestLimits(char *path, char *label, int **data, int *row, int *column) +{ + + int find = 0; + char *token = NULL; + int i = 0; + int j = 0; + int z = 0; + + char *line = NULL; + int fd = -1; + char *buf = NULL; + int n, size, pointer = 0, ret; + char *data_file = NULL; +#ifndef LIMITS_H_FILE + const struct firmware *fw = NULL; + struct device *dev = NULL; + + dev = getDev(); + if (dev != NULL) + fd = request_firmware(&fw, path, dev); +#else + fd = 0; +#endif + + line = (char *)kmalloc(1800*sizeof(char), GFP_KERNEL); + buf = line; + + if (fd == 0) { +#ifndef LIMITS_H_FILE + size = fw->size; + data_file = (char *)fw->data; + logError(0, "%s Start to reading %s...\n", tag, path); +#else + size = LIMITS_SIZE_NAME; + data_file = (char *)(LIMITS_ARRAY_NAME); +#endif + + logError(0, "%s The size of the limits file is %d bytes...\n", tag, size); + + while (find == 0) { + /* start to look for the wanted label */ + if (readLine(&data_file[pointer], &line, size-pointer, &n) < 0) { + find = -1; + break; + } + pointer += n; + if (line[0] == '*') { /* each header row start with * ex. *label,n_row,n_colum */ + buf = line; + line += 1; + token = strsep(&line, ","); + if (strcmp(token, label) == 0) { + /* if the row is the wanted one i retrieve rows and columns info */ + find = 1; + token = strsep(&line, ","); + if (token != NULL) { + sscanf(token, "%d", row); + logError(0, "%s Row = %d\n", tag, *row); + } else { + logError(1, "%s parseProductionTestLimits 1: ERROR %02X\n", tag, ERROR_FILE_PARSE); + /* release_firmware(fw); */ + /* return ERROR_FILE_PARSE; */ + ret = ERROR_FILE_PARSE; + goto END; + } + token = strsep(&line, ","); + if (token != NULL) { + sscanf(token, "%d", column); + logError(0, "%s Column = %d\n", tag, *column); + } else { + logError(1, "%s parseProductionTestLimits 2: ERROR %02X\n", tag, ERROR_FILE_PARSE); + /* release_firmware(fw); */ + /* return ERROR_FILE_PARSE; */ + ret = ERROR_FILE_PARSE; + goto END; + } + + *data = (int *)kmalloc(((*row)*(*column))*sizeof(int), GFP_KERNEL); + /* allocate the memory for containing the data */ + j = 0; + if (*data == NULL) { + logError(1, "%s parseProductionTestLimits: ERROR %02X\n", tag, ERROR_ALLOC); + /* release_firmware(fw); */ + /* return ERROR_ALLOC; */ + ret = ERROR_ALLOC; + goto END; + } + + /* start to read the data */ + for (i = 0; i < *row; i++) { + line = buf; + if (readLine(&data_file[pointer], &line, size-pointer, &n) < 0) { + logError(1, "%s parseProductionTestLimits : ERROR %02X\n", tag, ERROR_FILE_READ); + /* release_firmware(fw); */ + /* return ERROR_FILE_READ; */ + ret = ERROR_FILE_READ; + goto END; + } + pointer += n; + token = strsep(&line, ","); + for (z = 0; (z < *column) && (token != NULL); z++) { + sscanf(token, "%d", ((*data) + j)); + j++; + token = strsep(&line, ","); + } + } + if (j == ((*row)*(*column))) { /* check that all the data are read */ + logError(0, "%s READ DONE!\n", tag); + /* release_firmware(fw); */ + /* return OK; */ + ret = OK; + goto END; + } + logError(1, "%s parseProductionTestLimits 3: ERROR %02X\n", tag, ERROR_FILE_PARSE); + /* release_firmware(fw); */ + /* return ERROR_FILE_PARSE; */ + ret = ERROR_FILE_PARSE; + goto END; + } + } + + } + logError(1, "%s parseProductionTestLimits: ERROR %02X\n", tag, ERROR_LABEL_NOT_FOUND); + ret = ERROR_LABEL_NOT_FOUND; +END: +#ifndef LIMITS_H_FILE + release_firmware(fw); +#endif + return ret; + + } else { + logError(1, "%s parseProductionTestLimits: ERROR %02X\n", tag, ERROR_FILE_NOT_FOUND); + return ERROR_FILE_NOT_FOUND; + } + +} + +int readLine(char *data, char **line, int size, int *n) +{ + int i = 0; + if (size < 1) + return 1; + + while (data[i] != '\n' && i < size) { + *(*line + i) = data[i]; + i++; + } + *n = i+1; + *(*line + i) = '\0'; + + return OK; + +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsTest.h b/drivers/input/touchscreen/st/fts_lib/ftsTest.h new file mode 100644 index 000000000000..93da901c9f42 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsTest.h @@ -0,0 +1,158 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS API for MP test * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsSoftware.h" + +#define LIMITS_FILE "stm_fts_production_limits.csv" + +#define WAIT_FOR_FRESH_FRAMES 100 /* ms */ +#define WAIT_AFTER_SENSEOFF 50 /* ms */ +#define TIMEOUT_ITO_TEST_RESULT 200 /* ms */ +#define TIMEOUT_INITIALIZATION_TEST_RESULT 5000 /* ms */ + +/* LABELS PRODUCTION TEST LIMITS FILE */ +#define MS_RAW_MIN_MAX "MS_RAW_DATA_MIN_MAX" +#define MS_RAW_GAP "MS_RAW_DATA_GAP" +#define MS_CX1_MIN_MAX "MS_TOUCH_ACTIVE_CX1_MIN_MAX" +#define MS_CX2_MAP_MIN "MS_TOUCH_ACTIVE_CX2_MIN" +#define MS_CX2_MAP_MAX "MS_TOUCH_ACTIVE_CX2_MAX" +#define MS_CX2_ADJH_MAP_MAX "MS_TOUCH_ACTIVE_CX2_ADJ_HORIZONTAL" +#define MS_CX2_ADJV_MAP_MAX "MS_TOUCH_ACTIVE_CX2_ADJ_VERTICAL" +#define MS_TOTAL_CX_MAP_MIN "MS_TOUCH_ACTIVE_TOTAL_CX_MIN" +#define MS_TOTAL_CX_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_MAX" +#define MS_TOTAL_CX_ADJH_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_ADJ_HORIZONTAL" +#define MS_TOTAL_CX_ADJV_MAP_MAX "MS_TOUCH_ACTIVE_TOTAL_CX_ADJ_VERTICAL" +#define SS_RAW_FORCE_MIN_MAX "SS_RAW_DATA_FORCE_MIN_MAX" +#define SS_RAW_SENSE_MIN_MAX "SS_RAW_DATA_SENSE_MIN_MAX" +#define SS_RAW_FORCE_GAP "SS_RAW_DATA_FORCE_GAP" +#define SS_RAW_SENSE_GAP "SS_RAW_DATA_SENSE_GAP" +#define SS_IX1_FORCE_MIN_MAX "SS_TOUCH_ACTIVE_IX1_FORCE_MIN_MAX" +#define SS_IX1_SENSE_MIN_MAX "SS_TOUCH_ACTIVE_IX1_SENSE_MIN_MAX" +#define SS_CX1_FORCE_MIN_MAX "SS_TOUCH_ACTIVE_CX1_FORCE_MIN_MAX" +#define SS_CX1_SENSE_MIN_MAX "SS_TOUCH_ACTIVE_CX1_SENSE_MIN_MAX" +#define SS_IX2_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_IX2_FORCE_MIN" +#define SS_IX2_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_IX2_FORCE_MAX" +#define SS_IX2_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_IX2_SENSE_MIN" +#define SS_IX2_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_IX2_SENSE_MAX" +#define SS_IX2_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_IX2_ADJ_VERTICAL" +#define SS_IX2_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_IX2_ADJ_HORIZONTAL" +#define SS_CX2_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_CX2_FORCE_MIN" +#define SS_CX2_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_CX2_FORCE_MAX" +#define SS_CX2_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_CX2_SENSE_MIN" +#define SS_CX2_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_CX2_SENSE_MAX" +#define SS_CX2_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_CX2_ADJ_VERTICAL" +#define SS_CX2_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_CX2_ADJ_HORIZONTAL" + +/* TOTAL SS */ +#define SS_TOTAL_IX_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_IX_FORCE_MIN" +#define SS_TOTAL_IX_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_FORCE_MAX" +#define SS_TOTAL_IX_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_IX_SENSE_MIN" +#define SS_TOTAL_IX_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_SENSE_MAX" +#define SS_TOTAL_IX_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_ADJ_VERTICAL" +#define SS_TOTAL_IX_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_IX_ADJ_HORIZONTAL" +#define SS_TOTAL_CX_FORCE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_CX_FORCE_MIN" +#define SS_TOTAL_CX_FORCE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_FORCE_MAX" +#define SS_TOTAL_CX_SENSE_MAP_MIN "SS_TOUCH_ACTIVE_TOTAL_CX_SENSE_MIN" +#define SS_TOTAL_CX_SENSE_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_SENSE_MAX" +#define SS_TOTAL_CX_FORCE_ADJV_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_ADJ_VERTICAL" +#define SS_TOTAL_CX_SENSE_ADJH_MAP_MAX "SS_TOUCH_ACTIVE_TOTAL_CX_ADJ_HORIZONTAL" + +/* KEYS */ +#define MS_KEY_RAW_MIN_MAX "MS_KEY_RAW_DATA_MIN_MAX" +#define MS_KEY_CX1_MIN_MAX "MS_KEY_CX1_MIN_MAX" +#define MS_KEY_CX2_MAP_MIN "MS_KEY_CX2_MIN" +#define MS_KEY_CX2_MAP_MAX "MS_KEY_CX2_MAX" +#define MS_KEY_TOTAL_CX_MAP_MIN "MS_KEY_TOTAL_CX_MIN" +#define MS_KEY_TOTAL_CX_MAP_MAX "MS_KEY_TOTAL_CX_MAX" + +/* CONSTANT TOTAL IX */ +#define SS_IX1_FORCE_W "SS_IX1_FORCE_W" +#define SS_IX2_FORCE_W "SS_IX2_FORCE_W" +#define SS_IX1_SENSE_W "SS_IX1_SENSE_W" +#define SS_IX2_SENSE_W "SS_IX2_SENSE_W" + +#define SAVE_FLAG_RETRY 3 + +typedef struct { + int MutualRaw; + int MutualRawGap; + int MutualCx1; + int MutualCx2; + int MutualCx2Adj; + int MutualCxTotal; + int MutualCxTotalAdj; + + int MutualKeyRaw; + int MutualKeyCx1; + int MutualKeyCx2; + int MutualKeyCxTotal; + + int SelfForceRaw; + int SelfForceRawGap; + int SelfForceIx1; + int SelfForceIx2; + int SelfForceIx2Adj; + int SelfForceIxTotal; + int SelfForceIxTotalAdj; + int SelfForceCx1; + int SelfForceCx2; + int SelfForceCx2Adj; + int SelfForceCxTotal; + int SelfForceCxTotalAdj; + + int SelfSenseRaw; + int SelfSenseRawGap; + int SelfSenseIx1; + int SelfSenseIx2; + int SelfSenseIx2Adj; + int SelfSenseIxTotal; + int SelfSenseIxTotalAdj; + int SelfSenseCx1; + int SelfSenseCx2; + int SelfSenseCx2Adj; + int SelfSenseCxTotal; + int SelfSenseCxTotalAdj; + +} TestToDo; + +int computeAdjHoriz(u8 *data, int row, int column, u8 **result); +int computeAdjHorizTotal(u16 *data, int row, int column, u16 **result); +int computeAdjVert(u8 *data, int row, int column, u8 **result); +int computeAdjVertTotal(u16 *data, int row, int column, u16 **result); +int computeTotal(u8 *data, u8 main, int row, int column, int m, int n, u16 **result); +int checkLimitsMinMax(short *data, int row, int column, int min, int max); +int checkLimitsMap(u8 *data, int row, int column, int *min, int *max); +int checkLimitsMapTotal(u16 *data, int row, int column, int *min, int *max); +int checkLimitsMapAdj(u8 *data, int row, int column, int *max); +int checkLimitsMapAdjTotal(u16 *data, int row, int column, int *max); +int production_test_ito(void); +int production_test_initialization(void); +int ms_compensation_tuning(void); +int ss_compensation_tuning(void); +int lp_timer_calibration(void); +int save_cx_tuning(void); +int production_test_splited_initialization(int saveToFlash); +int production_test_main(char *pathThresholds, int stop_on_fail, + int saveInit, TestToDo *todo, u32 signature); +int production_test_ms_raw(char *path_limits, int stop_on_fail, TestToDo *todo); +int production_test_ms_cx(char *path_limits, int stop_on_fail, TestToDo *todo); +int production_test_ss_raw(char *path_limits, int stop_on_fail, TestToDo *todo); +int production_test_ss_ix_cx(char *path_limits, int stop_on_fail, TestToDo *todo); +int production_test_data(char *path_limits, int stop_on_fail, TestToDo *todo); +int production_test_ms_key_cx(char *path_limits, int stop_on_fail, TestToDo *todo); +int production_test_ms_key_raw(char *path_limits); +int save_mp_flag(u32 signature); +int parseProductionTestLimits(char *path, char *label, int **data, int *row, int *column); +int readLine(char *data, char **line, int size, int *n); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsTime.c b/drivers/input/touchscreen/st/fts_lib/ftsTime.c new file mode 100644 index 000000000000..03a2a39fe10b --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsTime.c @@ -0,0 +1,84 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS Utility for mesuring/handling the time * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsCrossCompile.h" +#include "ftsTime.h" + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/time.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +/* #include <linux/sec_sysfs.h> */ + +void startStopWatch(StopWatch *w) +{ + w->start = current_kernel_time(); +} + +void stopStopWatch(StopWatch *w) +{ + w->end = current_kernel_time(); +} + +int elapsedMillisecond(StopWatch *w) +{ + int result; + + result = ((w->end.tv_sec - w->start.tv_sec)*1000) + (w->end.tv_nsec - w->start.tv_nsec) / 1000000; + return result; +} + +int elapsedNanosecond(StopWatch *w) +{ + int result; + + result = ((w->end.tv_sec - w->start.tv_sec)*1000000000) + (w->end.tv_nsec - w->start.tv_nsec); + return result; +} + +char *timestamp(void) +{ + char *result = NULL; + result = (char *)kmalloc((1)*sizeof(char), GFP_KERNEL); + if (result == NULL) + return NULL; + result[0] = ' '; + return result; +} + +void stdelay(unsigned long ms) +{ + msleep(ms); +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsTime.h b/drivers/input/touchscreen/st/fts_lib/ftsTime.h new file mode 100644 index 000000000000..5eeca6eace97 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsTime.h @@ -0,0 +1,29 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS Utility for mesuring/handling the time * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsCrossCompile.h" + +#include <linux/time.h> + +typedef struct { + struct timespec start, end; +} StopWatch; + +void startStopWatch(StopWatch *w); +void stopStopWatch(StopWatch *w); +int elapsedMillisecond(StopWatch *w); +int elapsedNanosecond(StopWatch *w); +char *timestamp(void); +void stdelay(unsigned long ms); diff --git a/drivers/input/touchscreen/st/fts_lib/ftsTool.c b/drivers/input/touchscreen/st/fts_lib/ftsTool.c new file mode 100644 index 000000000000..4c5d54f17ea7 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsTool.c @@ -0,0 +1,706 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS Utility Functions * +* * +************************************************************************** +************************************************************************** + +*/ + +#include "ftsCompensation.h" +#include "ftsCrossCompile.h" +#include "ftsError.h" +#include "ftsHardware.h" +#include "ftsIO.h" +#include "ftsSoftware.h" +#include "ftsTime.h" +#include "ftsTool.h" +#include "../fts.h" /* needed for the PHONE_KEY define */ + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <stdarg.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/pm.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/power_supply.h> +#include <linux/firmware.h> +#include <linux/gpio.h> + +/* static char tag[8]="[ FTS ]\0"; */ +static int reset_gpio = GPIO_NOT_DEFINED; +static int system_resetted_up; +static int system_resetted_down; +extern chipInfo ftsInfo; + +int readB2(u16 address, u8 *outBuf, int len) +{ + int remaining = len; + int toRead = 0; + int retry = 0; + int ret; + int event_to_search[3]; + u8 *readEvent = (u8 *)kmalloc(FIFO_EVENT_SIZE*sizeof(u8), GFP_KERNEL); + u8 cmd[4] = { FTS_CMD_REQU_FW_CONF, 0x00, 0x00, (u8)len }; + + if (readEvent == NULL) { + logError(1, "%s readB2: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + u16ToU8_be(address, &cmd[1]); + + logError(0, "%s %s", tag, printHex("Command B2 = ", cmd, 4)); + do { + remaining = len; + ret = fts_writeFwCmd(cmd, 4); + if (ret < 0) { + logError(1, "%s readB2: ERROR %02X\n", tag, ERROR_I2C_W); + return ret; + } /* ask to the FW the data */ + logError(0, "%s Command to FW sent!\n", tag); + event_to_search[0] = (int)EVENTID_FW_CONFIGURATION; + + while (remaining > OK) { + event_to_search[1] = (int)((address & 0xFF00)>>8); + event_to_search[2] = (int) (address & 0x00FF); + if (remaining > B2_DATA_BYTES) { + toRead = B2_DATA_BYTES; + remaining -= B2_DATA_BYTES; + } else { + toRead = remaining; + remaining = 0; + } + + ret = pollForEvent(event_to_search, 3, readEvent, GENERAL_TIMEOUT); + if (ret >= OK) { /* start the polling for reading the reply */ + memcpy(outBuf, &readEvent[3], toRead); + retry = 0; + outBuf += toRead; + + } else { + retry += 1; + break; + } + address += B2_DATA_BYTES; + } + + } while (retry < B2_RETRY && retry != 0); + + kfree(readEvent); + if (retry == B2_RETRY) { + logError(1, "%s readB2: ERROR %02X\n", tag, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } + logError(0, "%s B2 read %d bytes\n", tag, len); + + return OK; +} + +int readB2U16(u16 address, u8 *outBuf, int byteToRead) +{ + + int remaining = byteToRead; + int toRead = 0; + int ret; + + u8 *buff = (u8 *)kmalloc((B2_CHUNK + 1)*sizeof(u8), GFP_KERNEL); + if (buff == NULL) { + logError(1, "%s readB2U16: ERROR %02X\n", tag, ERROR_ALLOC); + return ERROR_ALLOC; + } + + while (remaining > 0) { + if (remaining >= B2_CHUNK) { + toRead = B2_CHUNK; + remaining -= B2_CHUNK; + } else { + toRead = remaining; + remaining = 0; + } + + ret = readB2(address, buff, toRead); + if (ret < 0) + return ret; + memcpy(outBuf, buff, toRead); + + address += toRead; + + outBuf += toRead; + + } + + kfree(buff); + return OK; +} + +int releaseInformation(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_RELEASE_INFO }; + int event_to_search[1]; + u8 readEvent[FIFO_EVENT_SIZE]; + + event_to_search[0] = (int)EVENTID_RELEASE_INFO; + + logError(0, "%s releaseInformation started... Chip INFO:\n", tag); + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s releaseInformation: ERROR %02X\n", tag, ret); + return ret; + } + + ret = pollForEvent(event_to_search, 1, &readEvent[0], RELEASE_INFO_TIMEOUT); + /* start the polling for reading the reply */ + if (ret < OK) { + logError(1, "%s releaseInformation: ERROR %02X\n", tag, ret); + return ret; + } + + logError(0, "%s releaseInformation: Finished!\n", tag, ret); + return OK; + +} + +char *printHex(char *label, u8 *buff, int count) +{ + int i, offset; + char *result = NULL; + + offset = strlen(label); + result = (char *)kmalloc(((offset + 3 * count) + 1)*sizeof(char), GFP_KERNEL); + if (result != NULL) { + strlcpy(result, label, sizeof(result)); + + for (i = 0; i < count; i++) { + snprintf(&result[offset + i * 3], 4, "%02X ", buff[i]); + } + strlcat(result, "\n", sizeof(result)); + } + return result; +} + +int pollForEvent(int *event_to_search, int event_bytes, u8 *readData, int time_to_wait) +{ + int i, find, retry, count_err; + int time_to_count; + int err_handling = OK; + StopWatch clock; + + u8 cmd[1] = { FIFO_CMD_READONE }; + char *temp = NULL; + + find = 0; + retry = 0; + count_err = 0; + time_to_count = time_to_wait / TIMEOUT_RESOLUTION; + + startStopWatch(&clock); + while (find != 1 && retry < time_to_count && fts_readCmd(cmd, 1, readData, FIFO_EVENT_SIZE) >= 0) { + /* Log of errors */ + if (readData[0] == EVENTID_ERROR_EVENT) { + logError(1, "%s %s", tag, printHex("ERROR EVENT = ", readData, FIFO_EVENT_SIZE)); + count_err++; + err_handling = errorHandler(readData, FIFO_EVENT_SIZE); + if ((err_handling&0xF0FF0000) == ERROR_HANDLER_STOP_PROC) { + logError(1, "%s pollForEvent: forced to be stopped! ERROR %08X\n", tag, err_handling); + return err_handling; + } + } else { + if (readData[0] != EVENTID_NO_EVENT) { + logError(1, "%s %s", tag, printHex("READ EVENT = ", readData, FIFO_EVENT_SIZE)); + } + if (readData[0] == EVENTID_CONTROL_READY && event_to_search[0] != EVENTID_CONTROL_READY) { + logError(1, "%s pollForEvent: Unmanned Controller Ready Event! Setting reset flags...\n", tag); + setSystemResettedUp(1); + setSystemResettedDown(1); + } + } + + find = 1; + + for (i = 0; i < event_bytes; i++) { + + if (event_to_search[i] != -1 && (int)readData[i] != event_to_search[i]) { + find = 0; + break; + } + } + + retry++; + msleep(TIMEOUT_RESOLUTION); + } + stopStopWatch(&clock); + if ((retry >= time_to_count) && find != 1) { + logError(1, "%s pollForEvent: ERROR %02X\n", tag, ERROR_TIMEOUT); + return ERROR_TIMEOUT; + } else if (find == 1) { + temp = printHex("FOUND EVENT = ", readData, FIFO_EVENT_SIZE); + if (temp != NULL) + logError(0, "%s %s", tag, temp); + kfree(temp); + logError(0, "%s Event found in %d ms (%d iterations)! Number of errors found = %d\n", tag, elapsedMillisecond(&clock), retry, count_err); + return count_err; + } + logError(1, "%s pollForEvent: ERROR %02X\n", tag, ERROR_I2C_R); + return ERROR_I2C_R; +} + +int flushFIFO(void) +{ + + u8 cmd = FIFO_CMD_FLUSH; /* flush the FIFO */ + if (fts_writeCmd(&cmd, 1) < 0) { + logError(1, "%s flushFIFO: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + + logError(0, "%s FIFO flushed!\n", tag); + return OK; + +} + +int fts_disableInterrupt(void) +{ + u8 cmd[4] = { FTS_CMD_HW_REG_W, 0x00, 0x00, IER_DISABLE }; /* disable interrupt */ + u16ToU8_be(IER_ADDR, &cmd[1]); + + if (fts_writeCmd(cmd, 4) < OK) { + logError(1, "%s fts_disableInterrupt: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + logError(0, "%s Interrupt Disabled!\n", tag); + return OK; +} + +int fts_enableInterrupt(void) +{ + u8 cmd[4] = { FTS_CMD_HW_REG_W, 0x00, 0x00, IER_ENABLE }; /* enable interrupt */ + u16ToU8_be(IER_ADDR, &cmd[1]); + if (fts_writeCmd(cmd, 4) < 0) { + logError(1, "%s fts_enableInterrupt: ERROR %02X\n", tag, ERROR_I2C_W); + return ERROR_I2C_W; + } + logError(0, "%s Interrupt Enabled!\n", tag); + return OK; +} + +int u8ToU16n(u8 *src, int src_length, u16 *dst) +{ + int i, j; + + if (src_length % 2 != 0) { + return 0; + } + j = 0; + dst = (u16 *)kmalloc((src_length / 2)*sizeof(u16), GFP_KERNEL); + for (i = 0; i < src_length; i += 2) { + dst[j] = ((src[i+1] & 0x00FF) << 8) + (src[i] & 0x00FF); + j++; + } + + return (src_length / 2); +} + +int u8ToU16(u8 *src, u16 *dst) +{ + *dst = (u16)(((src[1] & 0x00FF) << 8) + (src[0] & 0x00FF)); + return 0; +} + +int u8ToU16_le(u8 *src, u16 *dst) +{ + *dst = (u16)(((src[0] & 0x00FF) << 8) + (src[1] & 0x00FF)); + return 0; +} + +int u16ToU8n(u16 *src, int src_length, u8 *dst) +{ + int i, j; + dst = (u8 *)kmalloc((2 * src_length)*sizeof(u8), GFP_KERNEL); + j = 0; + for (i = 0; i < src_length; i++) { + dst[j] = (u8) (src[i] & 0xFF00)>>8; + dst[j+1] = (u8) (src[i] & 0x00FF); + j += 2; + } + + return src_length * 2; + +} + +int u16ToU8(u16 src, u8 *dst) +{ + dst[0] = (u8)((src & 0xFF00) >> 8); + dst[1] = (u8)(src & 0x00FF); + return 0; +} + +int u16ToU8_be(u16 src, u8 *dst) +{ + dst[0] = (u8)((src & 0xFF00) >> 8); + dst[1] = (u8)(src & 0x00FF); + return 0; +} + +int u16ToU8_le(u16 src, u8 *dst) +{ + dst[1] = (u8)((src & 0xFF00) >> 8); + dst[0] = (u8)(src & 0x00FF); + return 0; +} + +int u8ToU32(u8 *src, u32 *dst) +{ + *dst = (u32)(((src[3] & 0x000000FF) << 24) + ((src[2] & 0x000000FF) << 16) + ((src[1] & 0x000000FF) << 8) + (src[0] & 0x000000FF)); + return 0; +} + +int u32ToU8(u32 src, u8 *dst) +{ + dst[3] = (u8)((src & 0xFF000000) >> 24); + dst[2] = (u8)((src & 0x00FF0000) >> 16); + dst[1] = (u8)((src & 0x0000FF00) >> 8); + dst[0] = (u8)(src & 0x000000FF); + return 0; +} + +int attempt_function(int(*code)(void), unsigned long wait_before_retry, int retry_count) +{ + int result; + int count = 0; + + do { + result = code(); + count++; + msleep(wait_before_retry); + } while (count < retry_count && result < 0); + + if (count == retry_count) + return (result | ERROR_TIMEOUT); + else + return result; + +} + +void setResetGpio(int gpio) +{ + reset_gpio = gpio; + logError(1, "%s setResetGpio: reset_gpio = %d\n", tag, reset_gpio); +} + +int fts_system_reset(void) +{ + u8 readData[FIFO_EVENT_SIZE]; + int event_to_search; + int res = -1; + int i; + u8 cmd[4] = { FTS_CMD_HW_REG_W, 0x00, 0x00, SYSTEM_RESET_VALUE }; + event_to_search = (int)EVENTID_CONTROL_READY; + + u16ToU8_be(SYSTEM_RESET_ADDRESS, &cmd[1]); + logError(0, "%s System resetting...\n", tag); + for (i = 0; i < SYSTEM_RESET_RETRY && res < 0; i++) { + + if (reset_gpio == GPIO_NOT_DEFINED) { + res = fts_writeCmd(cmd, 4); + } else { + gpio_set_value(reset_gpio, 0); + msleep(10); + gpio_set_value(reset_gpio, 1); + res = OK; + } + if (res < OK) { + logError(1, "%s fts_system_reset: ERROR %02X\n", tag, ERROR_I2C_W); + } else { + res = pollForEvent(&event_to_search, 1, readData, GENERAL_TIMEOUT); + if (res < OK) { + logError(1, "%s fts_system_reset: ERROR %02X\n", tag, res); + } + } + } + if (res < OK) { + logError(1, "%s fts_system_reset...failed after 3 attempts: ERROR %02X\n", tag, (res | ERROR_SYSTEM_RESET_FAIL)); + return (res | ERROR_SYSTEM_RESET_FAIL); + } + logError(0, "%s System reset DONE!\n", tag); + system_resetted_down = 1; + system_resetted_up = 1; + return OK; + +} + +int isSystemResettedDown(void) +{ + return system_resetted_down; +} + +int isSystemResettedUp(void) +{ + return system_resetted_up; +} + +void setSystemResettedDown(int val) +{ + system_resetted_down = val; +} + +void setSystemResettedUp(int val) +{ + system_resetted_up = val; +} + +int senseOn(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_MT_SENSE_ON }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s senseOn: ERROR %02X\n", tag, ERROR_SENSE_ON_FAIL); + return (ret|ERROR_SENSE_ON_FAIL); + } + + logError(0, "%s senseOn: SENSE ON\n", tag); + return OK; +} + +int senseOff(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_MT_SENSE_OFF }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s senseOff: ERROR %02X\n", tag, ERROR_SENSE_OFF_FAIL); + return (ret | ERROR_SENSE_OFF_FAIL); + } + + logError(0, "%s senseOff: SENSE OFF\n", tag); + return OK; + +} + +int keyOn(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_KEY_ON }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s keyOn: ERROR %02X\n", tag, ERROR_SENSE_ON_FAIL); + return (ret | ERROR_SENSE_ON_FAIL); + } + + logError(0, "%s keyOn: KEY ON\n", tag); + return OK; + +} + +int keyOff(void) +{ + int ret; + u8 cmd[1] = { FTS_CMD_MS_KEY_OFF }; + + ret = fts_writeFwCmd(cmd, 1); + if (ret < OK) { + logError(1, "%s keyOff: ERROR %02X\n", tag, ERROR_SENSE_OFF_FAIL); + return (ret | ERROR_SENSE_OFF_FAIL); + } + + logError(0, "%s keyOff: KEY OFF\n", tag); + return OK; + +} + +int cleanUp(int enableTouch) +{ + int res; + + logError(0, "%s cleanUp: system reset...\n", tag); + res = fts_system_reset(); + if (res < OK) + return res; + if (enableTouch) { + logError(0, "%s cleanUp: enabling touches...\n", tag); + res = senseOn(); + if (res < OK) + return res; +#ifdef PHONE_KEY + res = keyOn(); + if (res < OK) + return res; +#endif + logError(0, "%s cleanUp: enabling interrupts...\n", tag); + res = fts_enableInterrupt(); + if (res < OK) + return res; + } + return OK; + +} + +int checkEcho(u8 *cmd, int size) +{ + int ret, i; + int event_to_search[size+1]; + u8 readData[FIFO_EVENT_SIZE]; + + if ((ftsInfo.u32_echoEn & 0x00000001) != ECHO_ENABLED) { + logError(1, "%s ECHO Not Enabled!\n", tag); + return OK; + } + if (size < 1) { + logError(1, "%s checkEcho: Error Size = %d not valid! or ECHO not Enabled! ERROR %08X\n", tag, size, ERROR_OP_NOT_ALLOW); + return ERROR_OP_NOT_ALLOW; + } + if ((size+2) > FIFO_EVENT_SIZE) + size = FIFO_EVENT_SIZE-2; + /* Echo event EC xx xx xx xx xx xx fifo_status therefore for command + *with more than 6 bytes will echo only the first 6 + */ + event_to_search[0] = EVENTID_ECHO; + for (i = 1; i <= size; i++) { + event_to_search[i] = cmd[i-1]; + } + ret = pollForEvent(event_to_search, size+1, readData, GENERAL_TIMEOUT); + if (ret < OK) { + logError(1, "%s checkEcho: Echo Event not found! ERROR %02X\n", tag, ret); + return (ret | ERROR_CHECK_ECHO_FAIL); + } + + logError(0, "%s ECHO OK!\n", tag); + return OK; +} + +int featureEnableDisable(int on_off, u8 feature) +{ + int ret; + u8 cmd[2] = { 0x00, feature }; + + if (on_off == FEAT_ENABLE) { + cmd[0] = FTS_CMD_FEATURE_ENABLE; + logError(0, "%s featureEnableDisable: Enabling feature %02X ...\n", tag, feature); + } else { + cmd[0] = FTS_CMD_FEATURE_DISABLE; + logError(0, "%s featureEnableDisable: Disabling feature %02X ...\n", tag, feature); + } + + ret = fts_writeCmd(cmd, 2); /* not use writeFwCmd because this function can be called also during interrupt enable and should be fast */ + if (ret < OK) { + logError(1, "%s featureEnableDisable: ERROR %02X\n", tag, ret); + return (ret | ERROR_FEATURE_ENABLE_DISABLE); + } + + logError(0, "%s featureEnableDisable: DONE!\n", tag); + return OK; + +} + +short **array1dTo2d_short(short *data, int size, int columns) +{ + + int i; + short **matrix = (short **)kmalloc(((int)(size / columns))*sizeof(short *), GFP_KERNEL); + if (matrix != NULL) { + for (i = 0; i < (int)(size / columns); i++) { + matrix[i] = (short *)kmalloc(columns*sizeof(short), GFP_KERNEL); + } + + for (i = 0; i < size; i++) + matrix[i / columns][i % columns] = data[i]; + } + + return matrix; +} + +u8 **array1dTo2d_u8(u8 *data, int size, int columns) +{ + + int i; + u8 **matrix = (u8 **)kmalloc(((int)(size / columns))*sizeof(u8 *), GFP_KERNEL); + if (matrix != NULL) { + for (i = 0; i < (int)(size / columns); i++) { + matrix[i] = (u8 *)kmalloc(columns*sizeof(u8), GFP_KERNEL); + } + + for (i = 0; i < size; i++) + matrix[i / columns][i % columns] = data[i]; + } + + return matrix; +} + +void print_frame_short(char *label, short **matrix, int row, int column) +{ + int i, j; + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) { + printk("%d ", matrix[i][j]); + } + logError(0, "\n"); + kfree(matrix[i]); + } +} + +void print_frame_u8(char *label, u8 **matrix, int row, int column) +{ + int i, j; + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) { + printk("%d ", matrix[i][j]); + } + logError(0, "\n"); + kfree(matrix[i]); + } +} + +void print_frame_u32(char *label, u32 **matrix, int row, int column) +{ + int i, j; + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) { + printk("%d ", matrix[i][j]); + } + logError(0, "\n"); + kfree(matrix[i]); + } +} + +void print_frame_int(char *label, int **matrix, int row, int column) +{ + int i, j; + logError(0, "%s %s\n", tag, label); + for (i = 0; i < row; i++) { + logError(0, "%s ", tag); + for (j = 0; j < column; j++) { + printk("%d ", matrix[i][j]); + } + logError(0, "\n"); + kfree(matrix[i]); + } +} diff --git a/drivers/input/touchscreen/st/fts_lib/ftsTool.h b/drivers/input/touchscreen/st/fts_lib/ftsTool.h new file mode 100644 index 000000000000..a90e79fc5607 --- /dev/null +++ b/drivers/input/touchscreen/st/fts_lib/ftsTool.h @@ -0,0 +1,64 @@ +/* + +************************************************************************** +** STMicroelectronics ** +************************************************************************** +** marco.cali@st.com ** +************************************************************************** +* * +* FTS Utility Functions * +* * +************************************************************************** +************************************************************************** + +*/ +#define GPIO_NOT_DEFINED -1 + +#define TIMEOUT_RESOLUTION 10 /* ms */ +#define GENERAL_TIMEOUT (50*TIMEOUT_RESOLUTION) /* ms */ +#define RELEASE_INFO_TIMEOUT (15*TIMEOUT_RESOLUTION) /* ms */ + +#define FEAT_ENABLE 1 +#define FEAT_DISABLE 0 + +#define SYSTEM_RESET_RETRY 3 + +#define B2_RETRY 2 + +int readB2(u16 address, u8 *outBuf, int len); +int readB2U16(u16 address, u8 *outBuf, int byteToRead); +int releaseInformation(void); +char *printHex(char *label, u8 *buff, int count); +int pollForEvent(int *event_to_search, int event_bytes, u8 *readData, int time_to_wait); +int fts_disableInterrupt(void); +int fts_enableInterrupt(void); +int u8ToU16(u8 *src, u16 *dst); +int u8ToU16_le(u8 *src, u16 *dst); +int u8ToU16n(u8 *src, int src_length, u16 *dst); +int u16ToU8(u16 src, u8 *dst); +int u16ToU8_le(u16 src, u8 *dst); +int u16ToU8_be(u16 src, u8 *dst); +int u16ToU8n(u16 *src, int src_length, u8 *dst); +int u8ToU32(u8 *src, u32 *dst); +int u32ToU8(u32 src, u8 *dst); +int attempt_function(int(*code)(void), unsigned long wait_before_retry, int retry_count); +void setResetGpio(int gpio); +int fts_system_reset(void); +int isSystemResettedUp(void); +int isSystemResettedDown(void); +void setSystemResettedUp(int val); +void setSystemResettedDown(int val); +int senseOn(void); +int senseOff(void); +int keyOn(void); +int keyOff(void); +int featureEnableDisable(int on_off, u8 feature); +int checkEcho(u8 *cmd, int size); +void print_frame_short(char *label, short **matrix, int row, int column); +short **array1dTo2d_short(short *data, int size, int columns); +u8 **array1dTo2d_u8(u8 *data, int size, int columns); +void print_frame_u8(char *label, u8 **matrix, int row, int column); +void print_frame_u32(char *label, u32 **matrix, int row, int column); +void print_frame_int(char *label, int **matrix, int row, int column); +int cleanUp(int enableTouch); +int flushFIFO(void); diff --git a/drivers/input/touchscreen/st/fts_limits.h b/drivers/input/touchscreen/st/fts_limits.h new file mode 100644 index 000000000000..d3be1a2b1e1a --- /dev/null +++ b/drivers/input/touchscreen/st/fts_limits.h @@ -0,0 +1,10 @@ +#ifndef FTS_LIMITS_H +#define FTS_LIMITS_H +/* This is an auto generated header file +* --->Remember to change the name of the two variables!<--- */ +const uint32_t myArray2_size; + +const uint8_t myArray2[] = { +}; + +#endif diff --git a/drivers/input/touchscreen/synaptics_dsx/Kconfig b/drivers/input/touchscreen/synaptics_dsx/Kconfig new file mode 100644 index 000000000000..18d473969261 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx/Kconfig @@ -0,0 +1,64 @@ +# +# Synaptics DSX touchscreen driver configuration +# +menuconfig TOUCHSCREEN_SYNAPTICS_DSX_v21 + bool "Synaptics DSX touchscreen" + default y + help + Say Y here if you have a Synaptics DSX touchscreen connected + to your system. + + If unsure, say N. + +if TOUCHSCREEN_SYNAPTICS_DSX_v21 + +choice + default TOUCHSCREEN_SYNAPTICS_DSX_I2C_v21 + prompt "Synaptics DSX touchscreen bus interface" + +config TOUCHSCREEN_SYNAPTICS_DSX_I2C_v21 + bool "I2C" + depends on I2C + help + Say Y here if you have a Synaptics DSX touchscreen interfaced + to the host processor over I2C + + If unsure, say N. + + This module uses the services of DSX CORE + +config TOUCHSCREEN_SYNAPTICS_DSX_SPI_v21 + bool "SPI" + depends on SPI_MASTER + help + Say Y here if you have a Synaptics DSX touchscreen interfaced + to the host processor over SPI + + If unsure, say N. + + This module uses the services of DSX CORE +endchoice + +config TOUCHSCREEN_SYNAPTICS_DSX_CORE_v21 + tristate "Synaptics DSX core driver module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_I2C_v21 || TOUCHSCREEN_SYNAPTICS_DSX_SPI_v21 + help + Say Y here to enable basic touch reporting functionalities. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_core. + +config TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_v21 + tristate "Synaptics DSX touchscreen firmware update module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE_v21 + help + Say Y here to enable support for carrying out firmware update. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_fw_update. + +endif diff --git a/drivers/input/touchscreen/synaptics_dsx/Makefile b/drivers/input/touchscreen/synaptics_dsx/Makefile new file mode 100644 index 000000000000..0bffb8da94ea --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the Synaptics DSX touchscreen driver. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_I2C_v21) += synaptics_dsx_i2c.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_SPI_v21) += synaptics_dsx_spi.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_CORE_v21) += synaptics_dsx_core.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_v21) += synaptics_dsx_fw_update.o diff --git a/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.c b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.c new file mode 100644 index 000000000000..6f6cb35d90da --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.c @@ -0,0 +1,4506 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012 Synaptics Incorporated + * + * Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com> + * Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com> + * Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/input/synaptics_dsx_v2.h> +#include "synaptics_dsx_core.h" +#ifdef KERNEL_ABOVE_2_6_38 +#include <linux/input/mt.h> +#endif +#if defined(CONFIG_SECURE_TOUCH) +#include <linux/errno.h> +#include <soc/qcom/scm.h> +enum subsystem { + TZ = 1, + APSS = 3 +}; + +#define TZ_BLSP_MODIFY_OWNERSHIP_ID 3 +#endif + +#define INPUT_PHYS_NAME "synaptics_dsx/input0" +#define DEBUGFS_DIR_NAME "ts_debug" + +#ifdef KERNEL_ABOVE_2_6_38 +#define TYPE_B_PROTOCOL +#endif + +#define NO_0D_WHILE_2D +#define REPORT_2D_Z +#define REPORT_2D_W + +#define F12_DATA_15_WORKAROUND + +/* +#define IGNORE_FN_INIT_FAILURE +*/ + +#define RPT_TYPE (1 << 0) +#define RPT_X_LSB (1 << 1) +#define RPT_X_MSB (1 << 2) +#define RPT_Y_LSB (1 << 3) +#define RPT_Y_MSB (1 << 4) +#define RPT_Z (1 << 5) +#define RPT_WX (1 << 6) +#define RPT_WY (1 << 7) +#define RPT_DEFAULT (RPT_TYPE | RPT_X_LSB | RPT_X_MSB | RPT_Y_LSB | RPT_Y_MSB) + +#define EXP_FN_WORK_DELAY_MS 1000 /* ms */ +#define MAX_F11_TOUCH_WIDTH 15 + +#define CHECK_STATUS_TIMEOUT_MS 100 + +#define F01_STD_QUERY_LEN 21 +#define F01_BUID_ID_OFFSET 18 +#define F11_STD_QUERY_LEN 9 +#define F11_STD_CTRL_LEN 10 +#define F11_STD_DATA_LEN 12 + +#define STATUS_NO_ERROR 0x00 +#define STATUS_RESET_OCCURRED 0x01 +#define STATUS_INVALID_CONFIG 0x02 +#define STATUS_DEVICE_FAILURE 0x03 +#define STATUS_CONFIG_CRC_FAILURE 0x04 +#define STATUS_FIRMWARE_CRC_FAILURE 0x05 +#define STATUS_CRC_IN_PROGRESS 0x06 + +#define NORMAL_OPERATION (0 << 0) +#define SENSOR_SLEEP (1 << 0) +#define NO_SLEEP_OFF (0 << 2) +#define NO_SLEEP_ON (1 << 2) +#define CONFIGURED (1 << 7) + +#define SYNA_F11_MAX 4096 +#define SYNA_F12_MAX 65536 + +#define SYNA_S332U_PACKAGE_ID 332 +#define SYNA_S332U_PACKAGE_ID_REV 85 + +static int synaptics_rmi4_f12_set_enables(struct synaptics_rmi4_data *rmi4_data, + unsigned short ctrl28); + +static int synaptics_rmi4_free_fingers(struct synaptics_rmi4_data *rmi4_data); +static int synaptics_rmi4_reinit_device(struct synaptics_rmi4_data *rmi4_data); +static int synaptics_rmi4_reset_device(struct synaptics_rmi4_data *rmi4_data); + +static ssize_t synaptics_rmi4_full_pm_cycle_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_full_pm_cycle_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +#if defined(CONFIG_FB) +static void fb_notify_resume_work(struct work_struct *work); +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data); +#elif defined(CONFIG_HAS_EARLYSUSPEND) +static void synaptics_rmi4_early_suspend(struct early_suspend *h); + +static void synaptics_rmi4_late_resume(struct early_suspend *h); +#endif + +static int synaptics_rmi4_suspend(struct device *dev); + +static int synaptics_rmi4_resume(struct device *dev); + +static ssize_t synaptics_rmi4_f01_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_set_abs_x_axis(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_set_abs_y_axis(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_f01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_f01_buildid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_f01_flashprog_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_0dbutton_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_0dbutton_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static irqreturn_t synaptics_rmi4_irq(int irq, void *data); + +#if defined(CONFIG_SECURE_TOUCH) +static ssize_t synaptics_secure_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_secure_touch_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_secure_touch_show(struct device *dev, + struct device_attribute *attr, char *buf); +#endif + +struct synaptics_rmi4_f01_device_status { + union { + struct { + unsigned char status_code:4; + unsigned char reserved:2; + unsigned char flash_prog:1; + unsigned char unconfigured:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f12_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + struct { + unsigned char ctrl24_is_present:1; + unsigned char ctrl25_is_present:1; + unsigned char ctrl26_is_present:1; + unsigned char ctrl27_is_present:1; + unsigned char ctrl28_is_present:1; + unsigned char ctrl29_is_present:1; + unsigned char ctrl30_is_present:1; + unsigned char ctrl31_is_present:1; + } __packed; + }; + unsigned char data[5]; + }; +}; + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + struct { + unsigned char data8_is_present:1; + unsigned char data9_is_present:1; + unsigned char data10_is_present:1; + unsigned char data11_is_present:1; + unsigned char data12_is_present:1; + unsigned char data13_is_present:1; + unsigned char data14_is_present:1; + unsigned char data15_is_present:1; + } __packed; + }; + unsigned char data[3]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_8 { + union { + struct { + unsigned char max_x_coord_lsb; + unsigned char max_x_coord_msb; + unsigned char max_y_coord_lsb; + unsigned char max_y_coord_msb; + unsigned char rx_pitch_lsb; + unsigned char rx_pitch_msb; + unsigned char tx_pitch_lsb; + unsigned char tx_pitch_msb; + unsigned char low_rx_clip; + unsigned char high_rx_clip; + unsigned char low_tx_clip; + unsigned char high_tx_clip; + unsigned char num_of_rx; + unsigned char num_of_tx; + }; + unsigned char data[14]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_23 { + union { + struct { + unsigned char obj_type_enable; + unsigned char max_reported_objects; + }; + unsigned char data[2]; + }; +}; + +struct synaptics_rmi4_f12_finger_data { + unsigned char object_type_and_status; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; +#ifdef REPORT_2D_Z + unsigned char z; +#endif +#ifdef REPORT_2D_W + unsigned char wx; + unsigned char wy; +#endif +}; + +struct synaptics_rmi4_f1a_query { + union { + struct { + unsigned char max_button_count:3; + unsigned char reserved:5; + unsigned char has_general_control:1; + unsigned char has_interrupt_enable:1; + unsigned char has_multibutton_select:1; + unsigned char has_tx_rx_map:1; + unsigned char has_perbutton_threshold:1; + unsigned char has_release_threshold:1; + unsigned char has_strongestbtn_hysteresis:1; + unsigned char has_filter_strength:1; + } __packed; + unsigned char data[2]; + }; +}; + +struct synaptics_rmi4_f1a_control_0 { + union { + struct { + unsigned char multibutton_report:2; + unsigned char filter_mode:2; + unsigned char reserved:4; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f1a_control { + struct synaptics_rmi4_f1a_control_0 general_control; + unsigned char button_int_enable; + unsigned char multi_button; + unsigned char *txrx_map; + unsigned char *button_threshold; + unsigned char button_release_threshold; + unsigned char strongest_button_hysteresis; + unsigned char filter_strength; +}; + +struct synaptics_rmi4_f1a_handle { + int button_bitmask_size; + unsigned char max_count; + unsigned char valid_button_count; + unsigned char *button_data_buffer; + unsigned char *button_map; + struct synaptics_rmi4_f1a_query button_query; + struct synaptics_rmi4_f1a_control button_control; +}; + +struct synaptics_rmi4_exp_fhandler { + struct synaptics_rmi4_exp_fn *exp_fn; + bool insert; + bool remove; + struct list_head link; +}; + +struct synaptics_rmi4_exp_fn_data { + bool initialized; + bool queue_work; + struct mutex mutex; + struct list_head list; + struct delayed_work work; + struct workqueue_struct *workqueue; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct synaptics_rmi4_exp_fn_data exp_data; + +static struct device_attribute attrs[] = { + __ATTR(full_pm_cycle, (S_IRUGO | S_IWUSR | S_IWGRP), + synaptics_rmi4_full_pm_cycle_show, + synaptics_rmi4_full_pm_cycle_store), + __ATTR(reset, (S_IWUSR | S_IWGRP), + NULL, + synaptics_rmi4_f01_reset_store), + __ATTR(set_abs_x_axis, (S_IWUSR | S_IWGRP), + NULL, + synaptics_rmi4_set_abs_x_axis), + __ATTR(set_abs_y_axis, (S_IWUSR | S_IWGRP), + NULL, + synaptics_rmi4_set_abs_y_axis), + __ATTR(productinfo, S_IRUGO, + synaptics_rmi4_f01_productinfo_show, + synaptics_rmi4_store_error), + __ATTR(buildid, S_IRUGO, + synaptics_rmi4_f01_buildid_show, + synaptics_rmi4_store_error), + __ATTR(flashprog, S_IRUGO, + synaptics_rmi4_f01_flashprog_show, + synaptics_rmi4_store_error), + __ATTR(0dbutton, (S_IRUGO | S_IWUSR | S_IWGRP), + synaptics_rmi4_0dbutton_show, + synaptics_rmi4_0dbutton_store), +#if defined(CONFIG_SECURE_TOUCH) + __ATTR(secure_touch_enable, (S_IRUGO | S_IWUSR | S_IWGRP), + synaptics_secure_touch_enable_show, + synaptics_secure_touch_enable_store), + __ATTR(secure_touch, S_IRUGO , + synaptics_secure_touch_show, + NULL), +#endif +}; + +#define MAX_BUF_SIZE 256 +#define VKEY_VER_CODE "0x01" + +#define HEIGHT_SCALE_NUM 8 +#define HEIGHT_SCALE_DENOM 10 + +/* numerator and denomenator for border equations */ +#define BORDER_ADJUST_NUM 3 +#define BORDER_ADJUST_DENOM 4 + +static struct kobject *vkey_kobj; +static char *vkey_buf; + +static ssize_t vkey_show(struct kobject *obj, + struct kobj_attribute *attr, char *buf) +{ + strlcpy(buf, vkey_buf, MAX_BUF_SIZE); + return strnlen(buf, MAX_BUF_SIZE); +} + +static struct kobj_attribute vkey_obj_attr = { + .attr = { + .mode = S_IRUGO, + .name = "virtualkeys."PLATFORM_DRIVER_NAME, + }, + .show = vkey_show, +}; + +static struct attribute *vkey_attr[] = { + &vkey_obj_attr.attr, + NULL, +}; + +static struct attribute_group vkey_grp = { + .attrs = vkey_attr, +}; + +static int synaptics_rmi4_debug_suspend_set(void *_data, u64 val) +{ + struct synaptics_rmi4_data *rmi4_data = _data; + + if (val) + synaptics_rmi4_suspend(&rmi4_data->input_dev->dev); + else + synaptics_rmi4_resume(&rmi4_data->input_dev->dev); + + return 0; +} + +static int synaptics_rmi4_debug_suspend_get(void *_data, u64 *val) +{ + struct synaptics_rmi4_data *rmi4_data = _data; + + *val = rmi4_data->suspended; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(debug_suspend_fops, synaptics_rmi4_debug_suspend_get, + synaptics_rmi4_debug_suspend_set, "%lld\n"); + +#if defined(CONFIG_SECURE_TOUCH) +static int synaptics_i2c_change_pipe_owner( + struct synaptics_rmi4_data *rmi4_data, enum subsystem subsystem) +{ + /*scm call descriptor */ + struct scm_desc desc = {0}; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + int ret = 0; + + /* number of arguments */ + desc.arginfo = SCM_ARGS(2); + /* BLSPID (1-12) */ + desc.args[0] = i2c->adapter->nr - 1; + /* Owner if TZ or APSS */ + desc.args[1] = subsystem; + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_TZ, TZ_BLSP_MODIFY_OWNERSHIP_ID), + &desc); + if (ret) + return ret; + + return desc.ret[0]; +} + +static void synaptics_secure_touch_init(struct synaptics_rmi4_data *data) +{ + data->st_initialized = 0; + init_completion(&data->st_powerdown); + init_completion(&data->st_irq_processed); + /* Get clocks */ + data->core_clk = clk_get(data->pdev->dev.parent, "core_clk"); + if (IS_ERR(data->core_clk)) { + data->core_clk = NULL; + dev_warn(data->pdev->dev.parent, + "%s: core_clk is not defined\n", __func__); + } + + data->iface_clk = clk_get(data->pdev->dev.parent, "iface_clk"); + if (IS_ERR(data->iface_clk)) { + data->iface_clk = NULL; + dev_warn(data->pdev->dev.parent, + "%s: iface_clk is not defined\n", __func__); + } + + data->st_initialized = 1; +} +static void synaptics_secure_touch_notify(struct synaptics_rmi4_data *rmi4_data) +{ + sysfs_notify(&rmi4_data->input_dev->dev.kobj, NULL, "secure_touch"); +} +static irqreturn_t synaptics_filter_interrupt( + struct synaptics_rmi4_data *rmi4_data) +{ + if (atomic_read(&rmi4_data->st_enabled)) { + if (atomic_cmpxchg(&rmi4_data->st_pending_irqs, 0, 1) == 0) { + reinit_completion(&rmi4_data->st_irq_processed); + synaptics_secure_touch_notify(rmi4_data); + wait_for_completion_interruptible( + &rmi4_data->st_irq_processed); + } + return IRQ_HANDLED; + } + return IRQ_NONE; +} +static void synaptics_secure_touch_stop( + struct synaptics_rmi4_data *rmi4_data, + int blocking) +{ + if (atomic_read(&rmi4_data->st_enabled)) { + atomic_set(&rmi4_data->st_pending_irqs, -1); + synaptics_secure_touch_notify(rmi4_data); + if (blocking) + wait_for_completion_interruptible( + &rmi4_data->st_powerdown); + } +} +#else +static void synaptics_secure_touch_init(struct synaptics_rmi4_data *rmi4_data) +{ +} +static irqreturn_t synaptics_filter_interrupt( + struct synaptics_rmi4_data *rmi4_data) +{ + return IRQ_NONE; +} +static void synaptics_secure_touch_stop( + struct synaptics_rmi4_data *rmi4_data, + int blocking) +{ +} +#endif + +#if defined(CONFIG_SECURE_TOUCH) +static ssize_t synaptics_secure_touch_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + return scnprintf( + buf, + PAGE_SIZE, + "%d", + atomic_read(&rmi4_data->st_enabled)); +} +/* + * Accept only "0" and "1" valid values. + * "0" will reset the st_enabled flag, then wake up the reading process and + * the interrupt handler. + * The bus driver is notified via pm_runtime that it is not required to stay + * awake anymore. + * It will also make sure the queue of events is emptied in the controller, + * in case a touch happened in between the secure touch being disabled and + * the local ISR being ungated. + * "1" will set the st_enabled flag and clear the st_pending_irqs flag. + * The bus driver is requested via pm_runtime to stay awake. + */ +static ssize_t synaptics_secure_touch_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + unsigned long value; + int err = 0; + + if (count > 2) + return -EINVAL; + + err = kstrtoul(buf, 10, &value); + if (err != 0) + return err; + + if (!rmi4_data->st_initialized) + return -EIO; + + err = count; + + switch (value) { + case 0: + if (atomic_read(&rmi4_data->st_enabled) == 0) + break; + + synaptics_i2c_change_pipe_owner(rmi4_data, APSS); + synaptics_rmi4_bus_put(rmi4_data); + atomic_set(&rmi4_data->st_enabled, 0); + synaptics_secure_touch_notify(rmi4_data); + complete(&rmi4_data->st_irq_processed); + synaptics_rmi4_irq(rmi4_data->irq, rmi4_data); + complete(&rmi4_data->st_powerdown); + + break; + case 1: + if (atomic_read(&rmi4_data->st_enabled)) { + err = -EBUSY; + break; + } + + synchronize_irq(rmi4_data->irq); + + if (synaptics_rmi4_bus_get(rmi4_data) < 0) { + dev_err( + rmi4_data->pdev->dev.parent, + "synaptics_rmi4_bus_get failed\n"); + err = -EIO; + break; + } + synaptics_i2c_change_pipe_owner(rmi4_data, TZ); + reinit_completion(&rmi4_data->st_powerdown); + reinit_completion(&rmi4_data->st_irq_processed); + atomic_set(&rmi4_data->st_enabled, 1); + atomic_set(&rmi4_data->st_pending_irqs, 0); + break; + default: + dev_err( + rmi4_data->pdev->dev.parent, + "unsupported value: %lu\n", value); + err = -EINVAL; + break; + } + return err; +} + +/* + * This function returns whether there are pending interrupts, or + * other error conditions that need to be signaled to the userspace library, + * according tot he following logic: + * - st_enabled is 0 if secure touch is not enabled, returning -EBADF + * - st_pending_irqs is -1 to signal that secure touch is in being stopped, + * returning -EINVAL + * - st_pending_irqs is 1 to signal that there is a pending irq, returning + * the value "1" to the sysfs read operation + * - st_pending_irqs is 0 (only remaining case left) if the pending interrupt + * has been processed, so the interrupt handler can be allowed to continue. + */ +static ssize_t synaptics_secure_touch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + int val = 0; + if (atomic_read(&rmi4_data->st_enabled) == 0) + return -EBADF; + + if (atomic_cmpxchg(&rmi4_data->st_pending_irqs, -1, 0) == -1) + return -EINVAL; + + if (atomic_cmpxchg(&rmi4_data->st_pending_irqs, 1, 0) == 1) + val = 1; + else + complete(&rmi4_data->st_irq_processed); + + return scnprintf(buf, PAGE_SIZE, "%u", val); + +} +#endif + +static ssize_t synaptics_rmi4_full_pm_cycle_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->full_pm_cycle); +} + +static ssize_t synaptics_rmi4_full_pm_cycle_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + rmi4_data->full_pm_cycle = input > 0 ? 1 : 0; + + return count; +} + +static ssize_t synaptics_rmi4_set_abs_x_axis(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 0) + return -EINVAL; + + input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_X, + 0, input, 0, 0); + + return count; +} + +static ssize_t synaptics_rmi4_set_abs_y_axis(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 0) + return -EINVAL; + + input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_Y, + 0, input, 0, 0); + + return count; +} + +static ssize_t synaptics_rmi4_f01_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int reset; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (sscanf(buf, "%u", &reset) != 1) + return -EINVAL; + + if (reset != 1) + return -EINVAL; + + retval = synaptics_rmi4_reset_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command, error = %d\n", + __func__, retval); + return retval; + } + + return count; +} + +static ssize_t synaptics_rmi4_f01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "0x%02x 0x%02x\n", + (rmi4_data->rmi4_mod_info.product_info[0]), + (rmi4_data->rmi4_mod_info.product_info[1])); +} + +static ssize_t synaptics_rmi4_f01_buildid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->firmware_id); +} + +static ssize_t synaptics_rmi4_f01_flashprog_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct synaptics_rmi4_f01_device_status device_status; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + device_status.data, + sizeof(device_status.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device status, error = %d\n", + __func__, retval); + return retval; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", + device_status.flash_prog); +} + +static ssize_t synaptics_rmi4_0dbutton_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->button_0d_enabled); +} + +static ssize_t synaptics_rmi4_0dbutton_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + unsigned char ii; + unsigned char intr_enable; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + input = input > 0 ? 1 : 0; + + if (rmi4_data->button_0d_enabled == input) + return count; + + if (list_empty(&rmi->support_fn_list)) + return -ENODEV; + + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F1A) { + ii = fhandler->intr_reg_num; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr + 1 + ii, + &intr_enable, + sizeof(intr_enable)); + if (retval < 0) + return retval; + + if (input == 1) + intr_enable |= fhandler->intr_mask; + else + intr_enable &= ~fhandler->intr_mask; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr + 1 + ii, + &intr_enable, + sizeof(intr_enable)); + if (retval < 0) + return retval; + } + } + + rmi4_data->button_0d_enabled = input; + + return count; +} + + /** + * synaptics_rmi4_f11_abs_report() + * + * Called by synaptics_rmi4_report_touch() when valid Function $11 + * finger data has been detected. + * + * This function reads the Function $11 data registers, determines the + * status of each finger supported by the Function, processes any + * necessary coordinate manipulation, reports the finger data to + * the input subsystem, and returns the number of fingers detected. + */ +static int synaptics_rmi4_f11_abs_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; /* number of touch points */ + unsigned char reg_index; + unsigned char finger; + unsigned char fingers_supported; + unsigned char num_of_finger_status_regs; + unsigned char finger_shift; + unsigned char finger_status; + unsigned char data_reg_blk_size; + unsigned char finger_status_reg[3]; + unsigned char data[F11_STD_DATA_LEN]; + unsigned short data_addr; + unsigned short data_offset; + int x; + int y; + int wx; + int wy; + int temp; + + /* + * The number of finger status registers is determined by the + * maximum number of fingers supported - 2 bits per finger. So + * the number of finger status registers to read is: + * register_count = ceil(max_num_of_fingers / 4) + */ + fingers_supported = fhandler->num_of_data_points; + num_of_finger_status_regs = (fingers_supported + 3) / 4; + data_addr = fhandler->full_addr.data_base; + data_reg_blk_size = fhandler->size_of_data_register_block; + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr, + finger_status_reg, + num_of_finger_status_regs); + if (retval < 0) + return 0; + + for (finger = 0; finger < fingers_supported; finger++) { + reg_index = finger / 4; + finger_shift = (finger % 4) * 2; + finger_status = (finger_status_reg[reg_index] >> finger_shift) + & MASK_2BIT; + + /* + * Each 2-bit finger status field represents the following: + * 00 = finger not present + * 01 = finger present and data accurate + * 10 = finger present but data may be inaccurate + * 11 = reserved + */ +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, finger_status); +#endif + + if (finger_status) { + data_offset = data_addr + + num_of_finger_status_regs + + (finger * data_reg_blk_size); + retval = synaptics_rmi4_reg_read(rmi4_data, + data_offset, + data, + data_reg_blk_size); + if (retval < 0) + return 0; + + x = (data[0] << 4) | (data[2] & MASK_4BIT); + y = (data[1] << 4) | ((data[2] >> 4) & MASK_4BIT); + wx = (data[3] & MASK_4BIT); + wy = (data[3] >> 4) & MASK_4BIT; + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = x; + x = y; + y = temp; + temp = wx; + wx = wy; + wy = temp; + } + + if (rmi4_data->hw_if->board_data->x_flip) + x = rmi4_data->sensor_max_x - x; + if (rmi4_data->hw_if->board_data->y_flip) + y = rmi4_data->sensor_max_y - y; + + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 1); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_Y, y); +#ifdef REPORT_2D_W + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, max(wx, wy)); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, min(wx, wy)); +#endif +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d:\n" + "status = 0x%02x\n" + "x = %d\n" + "y = %d\n" + "wx = %d\n" + "wy = %d\n", + __func__, finger, + finger_status, + x, y, wx, wy); + + touch_count++; + } + } + + if (touch_count == 0) { + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + } + + input_sync(rmi4_data->input_dev); + + return touch_count; +} + + /** + * synaptics_rmi4_f12_abs_report() + * + * Called by synaptics_rmi4_report_touch() when valid Function $12 + * finger data has been detected. + * + * This function reads the Function $12 data registers, determines the + * status of each finger supported by the Function, processes any + * necessary coordinate manipulation, reports the finger data to + * the input subsystem, and returns the number of fingers detected. + */ +static int synaptics_rmi4_f12_abs_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; /* number of touch points */ + unsigned char finger; + unsigned char fingers_to_process; + unsigned char finger_status; + unsigned char size_of_2d_data; + unsigned short data_addr; + int x; + int y; + int wx; + int wy; + int temp; + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_f12_finger_data *data; + struct synaptics_rmi4_f12_finger_data *finger_data; +#ifdef F12_DATA_15_WORKAROUND + static unsigned char fingers_already_present; +#endif + + fingers_to_process = fhandler->num_of_data_points; + data_addr = fhandler->full_addr.data_base; + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + size_of_2d_data = sizeof(struct synaptics_rmi4_f12_finger_data); + + + /* Determine the total number of fingers to process */ + if (extra_data->data15_size) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data15_offset, + extra_data->data15_data, + extra_data->data15_size); + if (retval < 0) + return 0; + + /* Start checking from the highest bit */ + temp = extra_data->data15_size - 1; /* Highest byte */ + if ((temp >= 0) && (fingers_to_process > 0) && + (temp < ((F12_FINGERS_TO_SUPPORT + 7) / 8))) { + finger = (fingers_to_process - 1) % 8; /* Highest bit */ + do { + if (extra_data->data15_data[temp] + & (1 << finger)) + break; + + if (finger) { + finger--; + } else { + /* Move to the next lower byte */ + temp--; + finger = 7; + } + + fingers_to_process--; + } while (fingers_to_process && (temp >= 0)); + } + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Number of fingers to process = %d\n", + __func__, fingers_to_process); + } + +#ifdef F12_DATA_15_WORKAROUND + fingers_to_process = max(fingers_to_process, fingers_already_present); +#endif + + if (!fingers_to_process) { + synaptics_rmi4_free_fingers(rmi4_data); + return 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data1_offset, + (unsigned char *)fhandler->data, + fingers_to_process * size_of_2d_data); + if (retval < 0) + return 0; + + data = (struct synaptics_rmi4_f12_finger_data *)fhandler->data; + + for (finger = 0; finger < fingers_to_process; finger++) { + finger_data = data + finger; + finger_status = finger_data->object_type_and_status; + + if (finger_status == F12_FINGER_STATUS) { +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 1); +#endif + +#ifdef F12_DATA_15_WORKAROUND + fingers_already_present = finger + 1; +#endif + + x = (finger_data->x_msb << 8) | (finger_data->x_lsb); + y = (finger_data->y_msb << 8) | (finger_data->y_lsb); +#ifdef REPORT_2D_W + wx = finger_data->wx; + wy = finger_data->wy; +#endif + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = x; + x = y; + y = temp; + temp = wx; + wx = wy; + wy = temp; + } + + if (rmi4_data->hw_if->board_data->x_flip) + x = rmi4_data->sensor_max_x - x; + if (rmi4_data->hw_if->board_data->y_flip) + y = rmi4_data->sensor_max_y - y; + + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 1); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_Y, y); +#ifdef REPORT_2D_W + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, max(wx, wy)); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, min(wx, wy)); +#endif +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d:\n" + "status = 0x%02x\n" + "x = %d\n" + "y = %d\n" + "wx = %d\n" + "wy = %d\n", + __func__, finger, + finger_status, + x, y, wx, wy); + + touch_count++; + } else { +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 0); +#endif + } + } + + if (touch_count == 0) { + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + } + + input_sync(rmi4_data->input_dev); + + return touch_count; +} + +static void synaptics_rmi4_f1a_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; + unsigned char button; + unsigned char index; + unsigned char shift; + unsigned char status; + unsigned char *data; + unsigned short data_addr = fhandler->full_addr.data_base; + struct synaptics_rmi4_f1a_handle *f1a = fhandler->data; + static unsigned char do_once = 1; + static bool current_status[MAX_NUMBER_OF_BUTTONS]; +#ifdef NO_0D_WHILE_2D + static bool before_2d_status[MAX_NUMBER_OF_BUTTONS]; + static bool while_2d_status[MAX_NUMBER_OF_BUTTONS]; +#endif + + if (do_once) { + memset(current_status, 0, sizeof(current_status)); +#ifdef NO_0D_WHILE_2D + memset(before_2d_status, 0, sizeof(before_2d_status)); + memset(while_2d_status, 0, sizeof(while_2d_status)); +#endif + do_once = 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr, + f1a->button_data_buffer, + f1a->button_bitmask_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read button data registers\n", + __func__); + return; + } + + data = f1a->button_data_buffer; + + for (button = 0; button < f1a->valid_button_count; button++) { + index = button / 8; + shift = button % 8; + status = ((data[index] >> shift) & MASK_1BIT); + + if (current_status[button] == status) + continue; + else + current_status[button] = status; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Button %d (code %d) ->%d\n", + __func__, button, + f1a->button_map[button], + status); +#ifdef NO_0D_WHILE_2D + if (rmi4_data->fingers_on_2d == false) { + if (status == 1) { + before_2d_status[button] = 1; + } else { + if (while_2d_status[button] == 1) { + while_2d_status[button] = 0; + continue; + } else { + before_2d_status[button] = 0; + } + } + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); + } else { + if (before_2d_status[button] == 1) { + before_2d_status[button] = 0; + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); + } else { + if (status == 1) + while_2d_status[button] = 1; + else + while_2d_status[button] = 0; + } + } +#else + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); +#endif + } + + if (touch_count) + input_sync(rmi4_data->input_dev); + + return; +} + + /** + * synaptics_rmi4_report_touch() + * + * Called by synaptics_rmi4_sensor_report(). + * + * This function calls the appropriate finger data reporting function + * based on the function handler it receives and returns the number of + * fingers detected. + */ +static void synaptics_rmi4_report_touch(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + unsigned char touch_count_2d; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x reporting\n", + __func__, fhandler->fn_number); + + switch (fhandler->fn_number) { + case SYNAPTICS_RMI4_F11: + touch_count_2d = synaptics_rmi4_f11_abs_report(rmi4_data, + fhandler); + + if (touch_count_2d) + rmi4_data->fingers_on_2d = true; + else + rmi4_data->fingers_on_2d = false; + break; + case SYNAPTICS_RMI4_F12: + touch_count_2d = synaptics_rmi4_f12_abs_report(rmi4_data, + fhandler); + + if (touch_count_2d) + rmi4_data->fingers_on_2d = true; + else + rmi4_data->fingers_on_2d = false; + break; + case SYNAPTICS_RMI4_F1A: + synaptics_rmi4_f1a_report(rmi4_data, fhandler); + break; + default: + break; + } + + return; +} + + /** + * synaptics_rmi4_sensor_report() + * + * Called by synaptics_rmi4_irq(). + * + * This function determines the interrupt source(s) from the sensor + * and calls synaptics_rmi4_report_touch() with the appropriate + * function handler for each function with valid data inputs. + */ +static void synaptics_rmi4_sensor_report(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char data[MAX_INTR_REGISTERS + 1]; + unsigned char *intr = &data[1]; + struct synaptics_rmi4_f01_device_status status; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + /* + * Get interrupt status information from F01 Data1 register to + * determine the source(s) that are flagging the interrupt. + */ + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + data, + rmi4_data->num_of_intr_regs + 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read interrupt status\n", + __func__); + return; + } + + status.data[0] = data[0]; + if (status.unconfigured && !status.flash_prog) { + pr_notice("%s: spontaneous reset detected\n", __func__); + retval = synaptics_rmi4_reinit_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to reinit device\n", + __func__); + } + return; + } + + /* + * Traverse the function handler list and service the source(s) + * of the interrupt accordingly. + */ + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + if (fhandler->intr_mask & + intr[fhandler->intr_reg_num]) { + synaptics_rmi4_report_touch(rmi4_data, + fhandler); + } + } + } + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) { + if (!exp_fhandler->insert && + !exp_fhandler->remove && + (exp_fhandler->exp_fn->attn != NULL)) + exp_fhandler->exp_fn->attn(rmi4_data, intr[0]); + } + } + mutex_unlock(&exp_data.mutex); + + return; +} + + /** + * synaptics_rmi4_irq() + * + * Called by the kernel when an interrupt occurs (when the sensor + * asserts the attention irq). + * + * This function is the ISR thread and handles the acquisition + * and the reporting of finger data when the presence of fingers + * is detected. + */ +static irqreturn_t synaptics_rmi4_irq(int irq, void *data) +{ + struct synaptics_rmi4_data *rmi4_data = data; + + if (IRQ_HANDLED == synaptics_filter_interrupt(data)) + return IRQ_HANDLED; + + synaptics_rmi4_sensor_report(rmi4_data); + + return IRQ_HANDLED; +} + + /** + * synaptics_rmi4_irq_enable() + * + * Called by synaptics_rmi4_probe() and the power management functions + * in this driver and also exported to other expansion Function modules + * such as rmi_dev. + * + * This function handles the enabling and disabling of the attention + * irq including the setting up of the ISR thread. + */ +static int synaptics_rmi4_irq_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval = 0; + unsigned char intr_status[MAX_INTR_REGISTERS]; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (enable) { + if (rmi4_data->irq_enabled) + return retval; + + /* Clear interrupts first */ + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr + 1, + intr_status, + rmi4_data->num_of_intr_regs); + if (retval < 0) + return retval; + + retval = request_threaded_irq(rmi4_data->irq, NULL, + synaptics_rmi4_irq, bdata->irq_flags, + PLATFORM_DRIVER_NAME, rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create irq thread\n", + __func__); + return retval; + } + + rmi4_data->irq_enabled = true; + } else { + if (rmi4_data->irq_enabled) { + disable_irq(rmi4_data->irq); + free_irq(rmi4_data->irq, rmi4_data); + rmi4_data->irq_enabled = false; + } + } + + return retval; +} + +static int synaptics_rmi4_set_intr_mask(struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + unsigned char ii; + unsigned char intr_offset; + + fhandler->intr_reg_num = (intr_count + 7) / 8; + if (fhandler->intr_reg_num >= MAX_INTR_REGISTERS) { + fhandler->intr_reg_num = 0; + fhandler->num_of_data_sources = 0; + fhandler->intr_mask = 0; + return -EINVAL; + } + if (fhandler->intr_reg_num != 0) + fhandler->intr_reg_num -= 1; + + /* Set an enable bit for each data source */ + intr_offset = intr_count % 8; + fhandler->intr_mask = 0; + for (ii = intr_offset; + ii < ((fd->intr_src_count & MASK_3BIT) + + intr_offset); + ii++) + fhandler->intr_mask |= 1 << ii; + + return 0; +} + +static int synaptics_rmi4_f01_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + fhandler->data = NULL; + fhandler->extra = NULL; + + retval = synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + if (retval < 0) + return retval; + + + rmi4_data->f01_query_base_addr = fd->query_base_addr; + rmi4_data->f01_ctrl_base_addr = fd->ctrl_base_addr; + rmi4_data->f01_data_base_addr = fd->data_base_addr; + rmi4_data->f01_cmd_base_addr = fd->cmd_base_addr; + + return 0; +} + + /** + * synaptics_rmi4_f11_set_coords() + * + * Set panel resolution for f11 to match display resolution. + * + */ +static int synaptics_rmi4_f11_set_coords(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char control[F11_STD_CTRL_LEN]; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (!rmi4_data->update_coords) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: No need to update panel resolution\n", __func__); + return 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base, + control, + sizeof(control)); + if (retval < 0) + return retval; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = ((control[6] & MASK_8BIT) << 0) | + ((control[7] & MASK_4BIT) << 8); + rmi4_data->sensor_max_y = ((control[8] & MASK_8BIT) << 0) | + ((control[9] & MASK_4BIT) << 8); + + if (bdata->panel_maxx && bdata->panel_maxy && + (rmi4_data->sensor_max_x != bdata->panel_maxx || + rmi4_data->sensor_max_y != bdata->panel_maxy)) { + if (bdata->panel_maxx > SYNA_F11_MAX || + bdata->panel_maxy > SYNA_F11_MAX) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid panel resolution\n", __func__); + return -EINVAL; + } + + rmi4_data->sensor_max_x = bdata->panel_maxx; + rmi4_data->sensor_max_y = bdata->panel_maxy; + control[6] = rmi4_data->sensor_max_x & MASK_8BIT; + control[7] = (rmi4_data->sensor_max_x >> 8) & MASK_4BIT; + control[8] = rmi4_data->sensor_max_y & MASK_8BIT; + control[9] = (rmi4_data->sensor_max_y >> 8) & MASK_4BIT; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fhandler->full_addr.ctrl_base, + control, + sizeof(control)); + if (retval < 0) + return retval; + rmi4_data->update_coords = true; + } else { + rmi4_data->update_coords = false; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x max x = %d max y = %d\n", + __func__, fhandler->fn_number, + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + + rmi4_data->max_touch_width = MAX_F11_TOUCH_WIDTH; + + return 0; +} + + /** + * synaptics_rmi4_f11_init() + * + * Called by synaptics_rmi4_query_device(). + * + * This funtion parses information from the Function 11 registers + * and determines the number of fingers supported, x and y data ranges, + * offset to the associated interrupt status register, interrupt bit + * mask, and gathers finger data acquisition capabilities from the query + * registers. + */ +static int synaptics_rmi4_f11_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval; + unsigned char abs_data_size; + unsigned char abs_data_blk_size; + unsigned char query[F11_STD_QUERY_LEN]; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base, + query, + sizeof(query)); + if (retval < 0) + return retval; + + /* Maximum number of fingers supported */ + if ((query[1] & MASK_3BIT) <= 4) + fhandler->num_of_data_points = (query[1] & MASK_3BIT) + 1; + else if ((query[1] & MASK_3BIT) == 5) + fhandler->num_of_data_points = 10; + + rmi4_data->num_of_fingers = fhandler->num_of_data_points; + + retval = synaptics_rmi4_f11_set_coords(rmi4_data, fhandler); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + if (retval < 0) + return retval; + + abs_data_size = query[5] & MASK_2BIT; + abs_data_blk_size = 3 + (2 * (abs_data_size == 0 ? 1 : 0)); + fhandler->size_of_data_register_block = abs_data_blk_size; + fhandler->data = NULL; + fhandler->extra = NULL; + + return retval; +} + +static int synaptics_rmi4_f12_set_enables(struct synaptics_rmi4_data *rmi4_data, + unsigned short ctrl28) +{ + int retval; + static unsigned short ctrl_28_address; + + if (ctrl28) + ctrl_28_address = ctrl28; + + retval = synaptics_rmi4_reg_write(rmi4_data, + ctrl_28_address, + &rmi4_data->report_enable, + sizeof(rmi4_data->report_enable)); + if (retval < 0) + return retval; + + return retval; +} + + /** + * synaptics_rmi4_f12_set_coords() + * + * Set panel resolution for f12 to match display resolution. + * + */ +static int synaptics_rmi4_f12_set_coords(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + struct synaptics_rmi4_f12_query_5 query_5; + struct synaptics_rmi4_f12_ctrl_8 ctrl_8; + unsigned char ctrl_8_offset; + int retval; + + if (!rmi4_data->update_coords) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: No need to update panel resolution\n", __func__); + return 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 5, + query_5.data, + sizeof(query_5.data)); + if (retval < 0) + return retval; + + ctrl_8_offset = query_5.ctrl0_is_present + + query_5.ctrl1_is_present + + query_5.ctrl2_is_present + + query_5.ctrl3_is_present + + query_5.ctrl4_is_present + + query_5.ctrl5_is_present + + query_5.ctrl6_is_present + + query_5.ctrl7_is_present; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_8_offset, + ctrl_8.data, + sizeof(ctrl_8.data)); + if (retval < 0) + return retval; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = + ((unsigned short)ctrl_8.max_x_coord_lsb << 0) | + ((unsigned short)ctrl_8.max_x_coord_msb << 8); + rmi4_data->sensor_max_y = + ((unsigned short)ctrl_8.max_y_coord_lsb << 0) | + ((unsigned short)ctrl_8.max_y_coord_msb << 8); + + if (bdata->panel_maxx && bdata->panel_maxy && + (rmi4_data->sensor_max_x != bdata->panel_maxx || + rmi4_data->sensor_max_y != bdata->panel_maxy)) { + + if (bdata->panel_maxx > SYNA_F12_MAX || + bdata->panel_maxy > SYNA_F12_MAX) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid panel resolution\n", __func__); + retval = -EINVAL; + return retval; + } + + rmi4_data->sensor_max_x = bdata->panel_maxx; + rmi4_data->sensor_max_y = bdata->panel_maxy; + ctrl_8.max_x_coord_lsb = rmi4_data->sensor_max_x & MASK_8BIT; + ctrl_8.max_x_coord_msb = (rmi4_data->sensor_max_x >> 8) & + MASK_4BIT; + ctrl_8.max_y_coord_lsb = rmi4_data->sensor_max_y & MASK_8BIT; + ctrl_8.max_y_coord_msb = (rmi4_data->sensor_max_y >> 8) & + MASK_4BIT; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_8_offset, + ctrl_8.data, + sizeof(ctrl_8.data)); + if (retval < 0) + return retval; + rmi4_data->update_coords = true; + } else { + rmi4_data->update_coords = false; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x max x = %d max y = %d\n", + __func__, fhandler->fn_number, + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + + rmi4_data->num_of_rx = ctrl_8.num_of_rx; + rmi4_data->num_of_tx = ctrl_8.num_of_tx; + rmi4_data->max_touch_width = max(rmi4_data->num_of_rx, + rmi4_data->num_of_tx); + + return 0; +} + + /** + * synaptics_rmi4_f12_init() + * + * Called by synaptics_rmi4_query_device(). + * + * This funtion parses information from the Function 12 registers and + * determines the number of fingers supported, offset to the data1 + * register, x and y data ranges, offset to the associated interrupt + * status register, interrupt bit mask, and allocates memory resources + * for finger data acquisition. + */ +static int synaptics_rmi4_f12_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval; + unsigned char size_of_2d_data; + unsigned char size_of_query8; + unsigned char ctrl_8_offset; + unsigned char ctrl_23_offset; + unsigned char ctrl_28_offset; + unsigned char num_of_fingers; + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_f12_query_5 query_5; + struct synaptics_rmi4_f12_query_8 query_8; + struct synaptics_rmi4_f12_ctrl_23 ctrl_23; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + size_of_2d_data = sizeof(struct synaptics_rmi4_f12_finger_data); + + fhandler->extra = kmalloc(sizeof(*extra_data), GFP_KERNEL); + if (!fhandler->extra) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for function handler\n", + __func__); + return -ENOMEM; + } + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 5, + query_5.data, + sizeof(query_5.data)); + if (retval < 0) + goto free_function_handler_mem; + + ctrl_8_offset = query_5.ctrl0_is_present + + query_5.ctrl1_is_present + + query_5.ctrl2_is_present + + query_5.ctrl3_is_present + + query_5.ctrl4_is_present + + query_5.ctrl5_is_present + + query_5.ctrl6_is_present + + query_5.ctrl7_is_present; + + ctrl_23_offset = ctrl_8_offset + + query_5.ctrl8_is_present + + query_5.ctrl9_is_present + + query_5.ctrl10_is_present + + query_5.ctrl11_is_present + + query_5.ctrl12_is_present + + query_5.ctrl13_is_present + + query_5.ctrl14_is_present + + query_5.ctrl15_is_present + + query_5.ctrl16_is_present + + query_5.ctrl17_is_present + + query_5.ctrl18_is_present + + query_5.ctrl19_is_present + + query_5.ctrl20_is_present + + query_5.ctrl21_is_present + + query_5.ctrl22_is_present; + + ctrl_28_offset = ctrl_23_offset + + query_5.ctrl23_is_present + + query_5.ctrl24_is_present + + query_5.ctrl25_is_present + + query_5.ctrl26_is_present + + query_5.ctrl27_is_present; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_23_offset, + ctrl_23.data, + sizeof(ctrl_23.data)); + if (retval < 0) + goto free_function_handler_mem; + + /* Maximum number of fingers supported */ + fhandler->num_of_data_points = min(ctrl_23.max_reported_objects, + (unsigned char)F12_FINGERS_TO_SUPPORT); + + num_of_fingers = fhandler->num_of_data_points; + rmi4_data->num_of_fingers = num_of_fingers; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 7, + &size_of_query8, + sizeof(size_of_query8)); + if (retval < 0) + goto free_function_handler_mem; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 8, + query_8.data, + size_of_query8); + if (retval < 0) + goto free_function_handler_mem; + + /* Determine the presence of the Data0 register */ + extra_data->data1_offset = query_8.data0_is_present; + + if ((size_of_query8 >= 3) && (query_8.data15_is_present)) { + extra_data->data15_offset = query_8.data0_is_present + + query_8.data1_is_present + + query_8.data2_is_present + + query_8.data3_is_present + + query_8.data4_is_present + + query_8.data5_is_present + + query_8.data6_is_present + + query_8.data7_is_present + + query_8.data8_is_present + + query_8.data9_is_present + + query_8.data10_is_present + + query_8.data11_is_present + + query_8.data12_is_present + + query_8.data13_is_present + + query_8.data14_is_present; + extra_data->data15_size = (num_of_fingers + 7) / 8; + } else { + extra_data->data15_size = 0; + } + + rmi4_data->report_enable = RPT_DEFAULT; +#ifdef REPORT_2D_Z + rmi4_data->report_enable |= RPT_Z; +#endif +#ifdef REPORT_2D_W + rmi4_data->report_enable |= (RPT_WX | RPT_WY); +#endif + + retval = synaptics_rmi4_f12_set_enables(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_28_offset); + if (retval < 0) + goto free_function_handler_mem; + + + retval = synaptics_rmi4_f12_set_coords(rmi4_data, fhandler); + if (retval < 0) + goto free_function_handler_mem; + + retval = synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + if (retval < 0) + return retval; + + /* Allocate memory for finger data storage space */ + fhandler->data_size = num_of_fingers * size_of_2d_data; + fhandler->data = kmalloc(fhandler->data_size, GFP_KERNEL); + if (!fhandler->data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for function handler data\n", + __func__); + retval = -ENOMEM; + goto free_function_handler_mem; + } + + return retval; + +free_function_handler_mem: + kfree(fhandler->extra); + return retval; +} + +static int synaptics_rmi4_f1a_alloc_mem(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + struct synaptics_rmi4_f1a_handle *f1a; + + f1a = kzalloc(sizeof(*f1a), GFP_KERNEL); + if (!f1a) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for function handle\n", + __func__); + return -ENOMEM; + } + + fhandler->data = (void *)f1a; + fhandler->extra = NULL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base, + f1a->button_query.data, + sizeof(f1a->button_query.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read query registers\n", + __func__); + return retval; + } + + f1a->max_count = f1a->button_query.max_button_count + 1; + + f1a->button_control.txrx_map = kzalloc(f1a->max_count * 2, GFP_KERNEL); + if (!f1a->button_control.txrx_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for tx rx mapping\n", + __func__); + return -ENOMEM; + } + + f1a->button_bitmask_size = (f1a->max_count + 7) / 8; + + f1a->button_data_buffer = kcalloc(f1a->button_bitmask_size, + sizeof(*(f1a->button_data_buffer)), GFP_KERNEL); + if (!f1a->button_data_buffer) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for data buffer\n", + __func__); + return -ENOMEM; + } + + f1a->button_map = kcalloc(f1a->max_count, + sizeof(*(f1a->button_map)), GFP_KERNEL); + if (!f1a->button_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for button map\n", + __func__); + return -ENOMEM; + } + + return 0; +} + +static int synaptics_rmi4_f1a_button_map(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char ii; + unsigned char mapping_offset = 0; + struct synaptics_rmi4_f1a_handle *f1a = fhandler->data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + mapping_offset = f1a->button_query.has_general_control + + f1a->button_query.has_interrupt_enable + + f1a->button_query.has_multibutton_select; + + if (f1a->button_query.has_tx_rx_map) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + mapping_offset, + f1a->button_control.txrx_map, + sizeof(f1a->button_control.txrx_map)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read tx rx mapping\n", + __func__); + return retval; + } + + rmi4_data->button_txrx_mapping = f1a->button_control.txrx_map; + } + + if (!bdata->cap_button_map) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: cap_button_map is NULL in board file\n", + __func__); + } else if (!bdata->cap_button_map->map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Button map is missing in board file\n", + __func__); + } else { + if (bdata->cap_button_map->nbuttons != f1a->max_count) { + f1a->valid_button_count = min(f1a->max_count, + bdata->cap_button_map->nbuttons); + } else { + f1a->valid_button_count = f1a->max_count; + } + + for (ii = 0; ii < f1a->valid_button_count; ii++) + f1a->button_map[ii] = bdata->cap_button_map->map[ii]; + } + + return 0; +} + +static void synaptics_rmi4_f1a_kfree(struct synaptics_rmi4_fn *fhandler) +{ + struct synaptics_rmi4_f1a_handle *f1a = fhandler->data; + + if (f1a) { + kfree(f1a->button_control.txrx_map); + kfree(f1a->button_data_buffer); + kfree(f1a->button_map); + kfree(f1a); + fhandler->data = NULL; + } + + return; +} + +static int synaptics_rmi4_f1a_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + + retval = synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_f1a_alloc_mem(rmi4_data, fhandler); + if (retval < 0) + goto error_exit; + + retval = synaptics_rmi4_f1a_button_map(rmi4_data, fhandler); + if (retval < 0) + goto error_exit; + + rmi4_data->button_0d_enabled = 1; + + return 0; + +error_exit: + synaptics_rmi4_f1a_kfree(fhandler); + + return retval; +} + +static void synaptics_rmi4_empty_fn_list(struct synaptics_rmi4_data *rmi4_data) +{ + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_fn *fhandler_temp; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry_safe(fhandler, + fhandler_temp, + &rmi->support_fn_list, + link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F1A) { + synaptics_rmi4_f1a_kfree(fhandler); + } else { + kfree(fhandler->extra); + kfree(fhandler->data); + } + list_del(&fhandler->link); + kfree(fhandler); + } + } + INIT_LIST_HEAD(&rmi->support_fn_list); + + return; +} + +static int synaptics_rmi4_check_status(struct synaptics_rmi4_data *rmi4_data, + bool *was_in_bl_mode) +{ + int retval; + int timeout = CHECK_STATUS_TIMEOUT_MS; + unsigned char command = 0x01; + unsigned char intr_status; + struct synaptics_rmi4_f01_device_status status; + + /* Do a device reset first */ + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_cmd_base_addr, + &command, + sizeof(command)); + if (retval < 0) + return retval; + + msleep(rmi4_data->hw_if->board_data->reset_delay_ms); + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) + return retval; + + while (status.status_code == STATUS_CRC_IN_PROGRESS) { + if (timeout > 0) + msleep(20); + else + return -1; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) + return retval; + + timeout -= 20; + } + + if (timeout != CHECK_STATUS_TIMEOUT_MS) + *was_in_bl_mode = true; + + if (status.flash_prog == 1) { + rmi4_data->flash_prog_mode = true; + pr_notice("%s: In flash prog mode, status = 0x%02x\n", + __func__, + status.status_code); + } else { + rmi4_data->flash_prog_mode = false; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr + 1, + &intr_status, + sizeof(intr_status)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read interrupt status\n", + __func__); + return retval; + } + + return 0; +} + +static void synaptics_rmi4_set_configured(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char device_ctrl; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set configured\n", + __func__); + return; + } + + rmi4_data->no_sleep_setting = device_ctrl & NO_SLEEP_ON; + device_ctrl |= CONFIGURED; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set configured\n", + __func__); + } + + return; +} + +static int synaptics_rmi4_alloc_fh(struct synaptics_rmi4_fn **fhandler, + struct synaptics_rmi4_fn_desc *rmi_fd, int page_number) +{ + *fhandler = kmalloc(sizeof(**fhandler), GFP_KERNEL); + if (!(*fhandler)) + return -ENOMEM; + + (*fhandler)->full_addr.data_base = + (rmi_fd->data_base_addr | + (page_number << 8)); + (*fhandler)->full_addr.ctrl_base = + (rmi_fd->ctrl_base_addr | + (page_number << 8)); + (*fhandler)->full_addr.cmd_base = + (rmi_fd->cmd_base_addr | + (page_number << 8)); + (*fhandler)->full_addr.query_base = + (rmi_fd->query_base_addr | + (page_number << 8)); + + return 0; +} + +static int synaptics_rmi4_read_configid(struct synaptics_rmi4_data *rmi4_data, + unsigned char ctrl_base_addr) +{ + unsigned int device_config_id; + + /* + * We may get an error while trying to read config id if it is + * not provisioned by vendor + */ + if (synaptics_rmi4_reg_read(rmi4_data, ctrl_base_addr, + (unsigned char *)(&device_config_id), + sizeof(device_config_id)) < 0) + dev_err(rmi4_data->pdev->dev.parent, "Failed to read device config ID from CTP\n"); + + if (rmi4_data->hw_if->board_data->config_id) + dev_info(rmi4_data->pdev->dev.parent, + "CTP Config ID=%pI4\tDT Config ID=%pI4\n", + &device_config_id, + &rmi4_data->hw_if->board_data->config_id); + else + dev_info(rmi4_data->pdev->dev.parent, + "CTP Config ID=%pI4\n", &device_config_id); + + return 0; +} + + /** + * synaptics_rmi4_query_device() + * + * Called by synaptics_rmi4_probe(). + * + * This funtion scans the page description table, records the offsets + * to the register types of Function $01, sets up the function handlers + * for Function $11 and Function $12, determines the number of interrupt + * sources from the sensor, adds valid Functions with data inputs to the + * Function linked list, parses information from the query registers of + * Function $01, and enables the interrupt sources from the valid Functions + * with data inputs. + */ +static int synaptics_rmi4_query_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char page_number; + unsigned char intr_count; + unsigned char f01_query[F01_STD_QUERY_LEN]; + unsigned short pdt_entry_addr; + unsigned short intr_addr; + bool f01found; + bool was_in_bl_mode; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + unsigned char pkg_id[PACKAGE_ID_SIZE]; + rmi = &(rmi4_data->rmi4_mod_info); + +rescan_pdt: + f01found = false; + was_in_bl_mode = false; + intr_count = 0; + INIT_LIST_HEAD(&rmi->support_fn_list); + + /* Scan the page description tables of the pages to service */ + for (page_number = 0; page_number < PAGES_TO_SERVICE; page_number++) { + for (pdt_entry_addr = PDT_START; pdt_entry_addr > PDT_END; + pdt_entry_addr -= PDT_ENTRY_SIZE) { + pdt_entry_addr |= (page_number << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + pdt_entry_addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + pdt_entry_addr &= ~(MASK_8BIT << 8); + + fhandler = NULL; + + if (rmi_fd.fn_number == 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Reached end of PDT\n", + __func__); + break; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: F%02x found (page %d)\n", + __func__, rmi_fd.fn_number, + page_number); + + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F34: + /* + * Though function F34 is an interrupt source, + * but it is not a data source, hence do not + * add its handler to support_fn_list + */ + synaptics_rmi4_read_configid(rmi4_data, + rmi_fd.ctrl_base_addr); + break; + case SYNAPTICS_RMI4_F01: + if (rmi_fd.intr_src_count == 0) + break; + + f01found = true; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f01_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_check_status(rmi4_data, + &was_in_bl_mode); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to check status\n", + __func__); + return retval; + } + + if (was_in_bl_mode) { + kfree(fhandler); + fhandler = NULL; + goto rescan_pdt; + } + + if (rmi4_data->flash_prog_mode) + goto flash_prog_mode; + + break; + case SYNAPTICS_RMI4_F11: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f11_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + break; + case SYNAPTICS_RMI4_F12: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f12_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + break; + case SYNAPTICS_RMI4_F1A: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f1a_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) { +#ifdef IGNORE_FN_INIT_FAILURE + kfree(fhandler); + fhandler = NULL; +#else + return retval; +#endif + } + break; + } + + /* Accumulate the interrupt count */ + intr_count += (rmi_fd.intr_src_count & MASK_3BIT); + + if (fhandler && rmi_fd.intr_src_count) { + list_add_tail(&fhandler->link, + &rmi->support_fn_list); + } + } + } + + if (!f01found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F01\n", + __func__); + return -EINVAL; + } + +flash_prog_mode: + rmi4_data->num_of_intr_regs = (intr_count + 7) / 8; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Number of interrupt registers = %d\n", + __func__, rmi4_data->num_of_intr_regs); + if (rmi4_data->num_of_intr_regs >= MAX_INTR_REGISTERS) + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr, + f01_query, + sizeof(f01_query)); + if (retval < 0) + return retval; + + /* RMI Version 4.0 currently supported */ + rmi->version_major = 4; + rmi->version_minor = 0; + + rmi->manufacturer_id = f01_query[0]; + rmi->product_props = f01_query[1]; + rmi->product_info[0] = f01_query[2] & MASK_7BIT; + rmi->product_info[1] = f01_query[3] & MASK_7BIT; + rmi->date_code[0] = f01_query[4] & MASK_5BIT; + rmi->date_code[1] = f01_query[5] & MASK_4BIT; + rmi->date_code[2] = f01_query[6] & MASK_5BIT; + rmi->tester_id = ((f01_query[7] & MASK_7BIT) << 8) | + (f01_query[8] & MASK_7BIT); + rmi->serial_number = ((f01_query[9] & MASK_7BIT) << 8) | + (f01_query[10] & MASK_7BIT); + memcpy(rmi->product_id_string, &f01_query[11], 10); + + if (rmi->manufacturer_id != 1) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Non-Synaptics device found, manufacturer ID = %d\n", + __func__, rmi->manufacturer_id); + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr + F01_PACKAGE_ID_OFFSET, + pkg_id, sizeof(pkg_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device package id (code %d)\n", + __func__, retval); + return retval; + } + + rmi->package_id = (pkg_id[1] << 8) | pkg_id[0]; + rmi->package_id_rev = (pkg_id[3] << 8) | pkg_id[2]; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr + F01_BUID_ID_OFFSET, + rmi->build_id, + sizeof(rmi->build_id)); + if (retval < 0) + return retval; + + rmi4_data->firmware_id = (unsigned int)rmi->build_id[0] + + (unsigned int)rmi->build_id[1] * 0x100 + + (unsigned int)rmi->build_id[2] * 0x10000; + + memset(rmi4_data->intr_mask, 0x00, sizeof(rmi4_data->intr_mask)); + + /* + * Map out the interrupt bit masks for the interrupt sources + * from the registered function handlers. + */ + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + rmi4_data->intr_mask[fhandler->intr_reg_num] |= + fhandler->intr_mask; + } + } + } + + /* Enable the interrupt sources */ + for (ii = 0; ii < rmi4_data->num_of_intr_regs; ii++) { + if (rmi4_data->intr_mask[ii] != 0x00) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Interrupt enable mask %d = 0x%02x\n", + __func__, ii, rmi4_data->intr_mask[ii]); + intr_addr = rmi4_data->f01_ctrl_base_addr + 1 + ii; + retval = synaptics_rmi4_reg_write(rmi4_data, + intr_addr, + &(rmi4_data->intr_mask[ii]), + sizeof(rmi4_data->intr_mask[ii])); + if (retval < 0) + return retval; + } + } + + synaptics_rmi4_set_configured(rmi4_data); + + return 0; +} + +static void synaptics_rmi4_set_params(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char ii; + struct synaptics_rmi4_f1a_handle *f1a; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (bdata->disp_maxx && bdata->disp_maxy) { + input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_X, + 0, bdata->disp_maxx, 0, 0); + input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_Y, + 0, bdata->disp_maxy, 0, 0); + } else { + input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_X, + 0, rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_Y, + 0, rmi4_data->sensor_max_y, 0, 0); + } + +#ifdef REPORT_2D_W + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, 0, + rmi4_data->max_touch_width, 0, 0); + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, 0, + rmi4_data->max_touch_width, 0, 0); +#endif + +#ifdef TYPE_B_PROTOCOL + input_mt_init_slots(rmi4_data->input_dev, + rmi4_data->num_of_fingers, 0); +#endif + + f1a = NULL; + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F1A) + f1a = fhandler->data; + } + } + + if (f1a) { + for (ii = 0; ii < f1a->valid_button_count; ii++) { + set_bit(f1a->button_map[ii], + rmi4_data->input_dev->keybit); + input_set_capability(rmi4_data->input_dev, + EV_KEY, f1a->button_map[ii]); + } + } + + return; +} + +static int synaptics_dsx_virtual_keys_init(struct device *dev, + struct synaptics_dsx_board_data *rmi4_pdata) +{ + int width, height, center_x, center_y; + int x1 = 0, x2 = 0, i, c = 0, rc = 0, border; + + vkey_buf = devm_kzalloc(dev, MAX_BUF_SIZE, GFP_KERNEL); + if (!vkey_buf) { + dev_err(dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + border = (rmi4_pdata->panel_maxx - rmi4_pdata->disp_maxx) * 2; + width = ((rmi4_pdata->disp_maxx - + (border * (rmi4_pdata->virtual_key_map->nkeys - 1))) + / rmi4_pdata->virtual_key_map->nkeys); + height = (rmi4_pdata->panel_maxy - rmi4_pdata->disp_maxy); + center_y = rmi4_pdata->disp_maxy + (height / 2); + height = height * HEIGHT_SCALE_NUM / HEIGHT_SCALE_DENOM; + + x2 -= border * BORDER_ADJUST_NUM / BORDER_ADJUST_DENOM; + + for (i = 0; i < rmi4_pdata->virtual_key_map->nkeys; i++) { + x1 = x2 + border; + x2 = x2 + border + width; + center_x = x1 + (x2 - x1) / 2; + c += snprintf(vkey_buf + c, MAX_BUF_SIZE - c, + "%s:%d:%d:%d:%d:%d\n", VKEY_VER_CODE, + rmi4_pdata->virtual_key_map->map[i], + center_x, center_y, width, height); + } + + vkey_buf[c] = '\0'; + + vkey_kobj = kobject_create_and_add("board_properties", NULL); + if (!vkey_kobj) { + dev_err(dev, "unable to create kobject\n"); + return -ENOMEM; + } + + rc = sysfs_create_group(vkey_kobj, &vkey_grp); + if (rc) { + dev_err(dev, "failed to create attributes\n"); + kobject_put(vkey_kobj); + } + + return rc; +} + +static int synaptics_dsx_get_virtual_keys(struct device *dev, + struct property *prop, char *name, + struct synaptics_dsx_board_data *rmi4_pdata, + struct device_node *np) +{ + u32 num_keys; + int rc; + + num_keys = prop->length / sizeof(u32); + + rmi4_pdata->virtual_key_map = devm_kzalloc(dev, + sizeof(*rmi4_pdata->virtual_key_map), + GFP_KERNEL); + if (!rmi4_pdata->virtual_key_map) + return -ENOMEM; + + rmi4_pdata->virtual_key_map->map = devm_kzalloc(dev, + sizeof(*rmi4_pdata->virtual_key_map->map) * + num_keys, GFP_KERNEL); + if (!rmi4_pdata->virtual_key_map->map) + return -ENOMEM; + + rc = of_property_read_u32_array(np, name, + rmi4_pdata->virtual_key_map->map, + num_keys); + if (rc) { + dev_err(dev, "Failed to read key codes\n"); + return -EINVAL; + } + rmi4_pdata->virtual_key_map->nkeys = num_keys; + + return 0; +} + +static int synaptics_dsx_get_button_map(struct device *dev, + struct property *prop, char *name, + struct synaptics_dsx_board_data *rmi4_pdata, + struct device_node *np) +{ + int rc, i; + u32 num_buttons; + u32 button_map[MAX_NUMBER_OF_BUTTONS]; + + num_buttons = prop->length / sizeof(u32); + + rmi4_pdata->cap_button_map = devm_kzalloc(dev, + sizeof(*rmi4_pdata->cap_button_map), + GFP_KERNEL); + if (!rmi4_pdata->cap_button_map) + return -ENOMEM; + + rmi4_pdata->cap_button_map->map = devm_kzalloc(dev, + sizeof(*rmi4_pdata->cap_button_map->map) * + num_buttons, GFP_KERNEL); + if (!rmi4_pdata->cap_button_map->map) + return -ENOMEM; + + if (num_buttons <= MAX_NUMBER_OF_BUTTONS) { + rc = of_property_read_u32_array(np, + name, button_map, num_buttons); + if (rc) { + dev_err(dev, "Unable to read key codes\n"); + return rc; + } + for (i = 0; i < num_buttons; i++) + rmi4_pdata->cap_button_map->map[i] = + button_map[i]; + rmi4_pdata->cap_button_map->nbuttons = num_buttons; + } else { + return -EINVAL; + } + + return 0; +} + +static int synaptics_rmi4_parse_dt_children(struct device *dev, + struct synaptics_dsx_board_data *rmi4_pdata, + struct synaptics_rmi4_data *rmi4_data) +{ + struct synaptics_rmi4_device_info *rmi = &rmi4_data->rmi4_mod_info; + struct device_node *node = dev->of_node, *child = NULL; + int rc = 0; + struct synaptics_rmi4_fn *fhandler = NULL; + struct property *prop; + + for_each_child_of_node(node, child) { + rc = of_property_read_u32(child, "synaptics,package-id", + &rmi4_pdata->package_id); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read package_id\n"); + return rc; + } else if (rc == -EINVAL) { + rmi4_pdata->package_id = 0x00; + } + + if (rmi4_pdata->package_id) { + if (rmi4_pdata->package_id != rmi->package_id) { + dev_err(dev, + "%s: Synaptics package id don't match %d %d\n", + __func__, + rmi4_pdata->package_id, rmi->package_id); + /* + * Iterate over next child if package + * id does not match + */ + continue; + } else if (of_property_read_bool(child, + "synaptics,bypass-sensor-coords-check") && + of_find_property(child, + "synaptics,panel-coords", NULL)) { + /* + * Some unprogrammed panels from touch vendor + * and wrongly programmed panels from factory + * may return incorrect sensor coordinate range + * when their query registers are read, but + * they normally work fine in field. In such + * a scenario, driver can bypass the comparison + * of coordinate range read from sensor and read + * from DT and continue normal operation. + */ + synaptics_dsx_get_dt_coords(dev, + "synaptics,panel-coords", + rmi4_pdata, child); + dev_info(dev, + "%s Synaptics package id matches %d %d," + "but bypassing the comparison of sensor" + "coordinates.\n", __func__, + rmi4_pdata->package_id, + rmi->package_id); + dev_info(dev, "Pmax_x Pmax_y = %d:%d\n", + rmi4_pdata->panel_maxx, + rmi4_pdata->panel_maxy); + dev_info(dev, "Smax_x Smax_y = %d:%d\n", + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + } else { + /* + * If package id read from DT matches the + * package id value read from touch controller, + * also check if sensor dimensions read from DT + * match those read from controller, before + * moving further. For this first check if touch + * panel coordinates are defined in DT or not. + */ + if (of_find_property(child, + "synaptics,panel-coords", NULL)) { + synaptics_dsx_get_dt_coords(dev, + "synaptics,panel-coords", + rmi4_pdata, child); + dev_info(dev, "Pmax_x Pmax_y = %d:%d\n", + rmi4_pdata->panel_maxx, + rmi4_pdata->panel_maxy); + dev_info(dev, "Smax_x Smax_y = %d:%d\n", + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + if ((rmi4_pdata->panel_maxx != + rmi4_data->sensor_max_x) || + (rmi4_pdata->panel_maxy != + rmi4_data->sensor_max_y)) + continue; + } else { + dev_info(dev, "Smax_x Smax_y = %d:%d\n", + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + } + } + } + + rc = synaptics_dsx_get_dt_coords(dev, + "synaptics,display-coords", rmi4_pdata, child); + if (rc && (rc != -EINVAL)) + return rc; + + prop = of_find_property(child, "synaptics,button-map", NULL); + if (prop) { + rc = synaptics_dsx_get_button_map(dev, prop, + "synaptics,button-map", rmi4_pdata, child); + if (rc < 0) { + dev_err(dev, "Unable to read button map\n"); + return rc; + } + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, + &rmi->support_fn_list, link) { + if (fhandler->fn_number == + SYNAPTICS_RMI4_F1A) + break; + } + } + + if (fhandler && fhandler->fn_number == + SYNAPTICS_RMI4_F1A) { + rc = synaptics_rmi4_f1a_button_map(rmi4_data, + fhandler); + if (rc < 0) { + dev_err(dev, + "Fail to register F1A %d\n", + rc); + return rc; + } + } + } + + prop = of_find_property(child, "synaptics,key-codes", NULL); + if (prop) { + rc = synaptics_dsx_get_virtual_keys(dev, prop, + "synaptics,key-codes", rmi4_pdata, child); + if (!rc) { + rc = synaptics_dsx_virtual_keys_init(dev, + rmi4_pdata); + if (!rc) + rmi4_data->support_vkeys = true; + + } else { + dev_err(dev, + "Unable to read virtual key codes\n"); + return rc; + } + } + + break; + } + + return 0; +} + +static int synaptics_rmi4_set_input_dev(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + int temp; + + rmi4_data->input_dev = input_allocate_device(); + if (rmi4_data->input_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate input device\n", + __func__); + retval = -ENOMEM; + goto err_input_device; + } + + retval = synaptics_rmi4_query_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to query device\n", + __func__); + goto err_query_device; + } + + if (rmi4_data->hw_if->board_data->detect_device) { + retval = synaptics_rmi4_parse_dt_children( + rmi4_data->pdev->dev.parent, + rmi4_data->hw_if->board_data, + rmi4_data); + if (retval < 0) + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to parse device tree property\n", + __func__); + } + + rmi4_data->input_dev->name = PLATFORM_DRIVER_NAME; + rmi4_data->input_dev->phys = INPUT_PHYS_NAME; + rmi4_data->input_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + rmi4_data->input_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + rmi4_data->input_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(rmi4_data->input_dev, rmi4_data); + + set_bit(EV_SYN, rmi4_data->input_dev->evbit); + set_bit(EV_KEY, rmi4_data->input_dev->evbit); + set_bit(EV_ABS, rmi4_data->input_dev->evbit); + set_bit(BTN_TOUCH, rmi4_data->input_dev->keybit); + set_bit(BTN_TOOL_FINGER, rmi4_data->input_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, rmi4_data->input_dev->propbit); +#endif + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = rmi4_data->sensor_max_x; + rmi4_data->sensor_max_x = rmi4_data->sensor_max_y; + rmi4_data->sensor_max_y = temp; + } + + synaptics_rmi4_set_params(rmi4_data); + + retval = input_register_device(rmi4_data->input_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register input device\n", + __func__); + goto err_register_input; + } + + return 0; + +err_register_input: + if (rmi4_data->support_vkeys) { + sysfs_remove_group(vkey_kobj, &vkey_grp); + kobject_put(vkey_kobj); + } +err_query_device: + synaptics_rmi4_empty_fn_list(rmi4_data); + input_free_device(rmi4_data->input_dev); + +err_input_device: + return retval; +} + +static int synaptics_rmi4_set_gpio(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + int power_on; + int reset_on; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + power_on = bdata->power_on_state; + reset_on = bdata->reset_on_state; + + retval = bdata->gpio_config( + bdata->irq_gpio, + true, 0, 0); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure attention GPIO\n", + __func__); + goto err_gpio_irq; + } + + if (bdata->power_gpio >= 0) { + retval = bdata->gpio_config( + bdata->power_gpio, + true, 1, !power_on); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure power GPIO\n", + __func__); + goto err_gpio_power; + } + } + + if (bdata->reset_gpio >= 0) { + retval = bdata->gpio_config( + bdata->reset_gpio, + true, 1, !reset_on); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure reset GPIO\n", + __func__); + goto err_gpio_reset; + } + } + + if (bdata->power_gpio >= 0) { + gpio_set_value(bdata->power_gpio, power_on); + msleep(bdata->power_delay_ms); + } + + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, reset_on); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !reset_on); + msleep(bdata->reset_delay_ms); + } + + return 0; + +err_gpio_reset: + if (bdata->power_gpio >= 0) { + bdata->gpio_config( + bdata->power_gpio, + false, 0, 0); + } + +err_gpio_power: + bdata->gpio_config( + bdata->irq_gpio, + false, 0, 0); + +err_gpio_irq: + return retval; +} + +static int synaptics_dsx_pinctrl_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + /* Get pinctrl if target uses pinctrl */ + rmi4_data->ts_pinctrl = devm_pinctrl_get((rmi4_data->pdev->dev.parent)); + if (IS_ERR_OR_NULL(rmi4_data->ts_pinctrl)) { + retval = PTR_ERR(rmi4_data->ts_pinctrl); + dev_dbg(rmi4_data->pdev->dev.parent, + "Target does not use pinctrl %d\n", retval); + goto err_pinctrl_get; + } + + rmi4_data->pinctrl_state_active + = pinctrl_lookup_state(rmi4_data->ts_pinctrl, "pmx_ts_active"); + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_active)) { + retval = PTR_ERR(rmi4_data->pinctrl_state_active); + dev_err(rmi4_data->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_ACTIVE, retval); + goto err_pinctrl_lookup; + } + + rmi4_data->pinctrl_state_suspend + = pinctrl_lookup_state(rmi4_data->ts_pinctrl, "pmx_ts_suspend"); + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_suspend)) { + retval = PTR_ERR(rmi4_data->pinctrl_state_suspend); + dev_dbg(rmi4_data->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_SUSPEND, retval); + goto err_pinctrl_lookup; + } + + rmi4_data->pinctrl_state_release + = pinctrl_lookup_state(rmi4_data->ts_pinctrl, "pmx_ts_release"); + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_release)) { + retval = PTR_ERR(rmi4_data->pinctrl_state_release); + dev_dbg(rmi4_data->pdev->dev.parent, + "Can not lookup %s pinstate %d\n", + PINCTRL_STATE_RELEASE, retval); + } + + return 0; + +err_pinctrl_lookup: + devm_pinctrl_put(rmi4_data->ts_pinctrl); +err_pinctrl_get: + rmi4_data->ts_pinctrl = NULL; + return retval; +} + +static int synaptics_dsx_gpio_configure(struct synaptics_rmi4_data *rmi4_data, + bool on) +{ + int retval = 0; + struct synaptics_rmi4_device_info *rmi; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + rmi = &(rmi4_data->rmi4_mod_info); + + if (on) { + if (gpio_is_valid(bdata->irq_gpio)) { + /* configure touchscreen irq gpio */ + retval = gpio_request(bdata->irq_gpio, + "rmi4_irq_gpio"); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "unable to request gpio [%d]\n", + bdata->irq_gpio); + goto err_irq_gpio_req; + } + retval = gpio_direction_input(bdata->irq_gpio); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "unable to set dir for gpio[%d]\n", + bdata->irq_gpio); + goto err_irq_gpio_dir; + } + } else { + dev_err(rmi4_data->pdev->dev.parent, + "irq gpio not provided\n"); + goto err_irq_gpio_req; + } + + if (gpio_is_valid(bdata->reset_gpio)) { + /* configure touchscreen reset out gpio */ + retval = gpio_request(bdata->reset_gpio, + "rmi4_reset_gpio"); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "unable to request gpio [%d]\n", + bdata->reset_gpio); + goto err_irq_gpio_dir; + } + + retval = gpio_direction_output(bdata->reset_gpio, 1); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "unable to set dir for gpio [%d]\n", + bdata->reset_gpio); + goto err_reset_gpio_dir; + } + + gpio_set_value(bdata->reset_gpio, 1); + msleep(bdata->reset_delay_ms); + } + + return 0; + } else { + if (bdata->disable_gpios) { + if (gpio_is_valid(bdata->irq_gpio)) + gpio_free(bdata->irq_gpio); + if (gpio_is_valid(bdata->reset_gpio)) { + /* + * This is intended to save leakage current + * only. Even if the call(gpio_direction_input) + * fails, only leakage current will be more but + * functionality will not be affected. + */ + if (rmi->package_id == + SYNA_S332U_PACKAGE_ID && + rmi->package_id_rev == + SYNA_S332U_PACKAGE_ID_REV) { + gpio_set_value(bdata-> + reset_gpio, + 0); + } else { + retval = gpio_direction_input( + bdata->reset_gpio); + if (retval) { + dev_err(rmi4_data->pdev-> + dev.parent, + "unable to set direction for gpio [%d]\n", + bdata->irq_gpio); + } + } + gpio_free(bdata->reset_gpio); + } + } + + return 0; + } + +err_reset_gpio_dir: + if (gpio_is_valid(bdata->reset_gpio)) + gpio_free(bdata->reset_gpio); +err_irq_gpio_dir: + if (gpio_is_valid(bdata->irq_gpio)) + gpio_free(bdata->irq_gpio); +err_irq_gpio_req: + return retval; +} + +static int synaptics_rmi4_free_fingers(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char ii; + +#ifdef TYPE_B_PROTOCOL + for (ii = 0; ii < rmi4_data->num_of_fingers; ii++) { + input_mt_slot(rmi4_data->input_dev, ii); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 0); + } +#endif + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + input_sync(rmi4_data->input_dev); + + rmi4_data->fingers_on_2d = false; + + return 0; +} + +static int synaptics_rmi4_reinit_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned short intr_addr; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + synaptics_rmi4_free_fingers(rmi4_data); + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F12) { + synaptics_rmi4_f12_set_coords(rmi4_data, + fhandler); + synaptics_rmi4_f12_set_enables(rmi4_data, 0); + break; + } else if (fhandler->fn_number == SYNAPTICS_RMI4_F11) { + synaptics_rmi4_f11_set_coords(rmi4_data, + fhandler); + break; + } + + } + } + + for (ii = 0; ii < rmi4_data->num_of_intr_regs; ii++) { + if (rmi4_data->intr_mask[ii] != 0x00) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Interrupt enable mask %d = 0x%02x\n", + __func__, ii, rmi4_data->intr_mask[ii]); + intr_addr = rmi4_data->f01_ctrl_base_addr + 1 + ii; + retval = synaptics_rmi4_reg_write(rmi4_data, + intr_addr, + &(rmi4_data->intr_mask[ii]), + sizeof(rmi4_data->intr_mask[ii])); + if (retval < 0) + goto exit; + } + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->reinit != NULL) + exp_fhandler->exp_fn->reinit(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + synaptics_rmi4_set_configured(rmi4_data); + + retval = 0; + +exit: + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + return retval; +} + +static int synaptics_rmi4_reset_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + int temp; + unsigned char command = 0x01; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + rmi4_data->touch_stopped = true; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_cmd_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command, error = %d\n", + __func__, retval); + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + return retval; + } + + msleep(rmi4_data->hw_if->board_data->reset_delay_ms); + + synaptics_rmi4_free_fingers(rmi4_data); + + synaptics_rmi4_empty_fn_list(rmi4_data); + + retval = synaptics_rmi4_query_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to query device\n", + __func__); + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + return retval; + } + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = rmi4_data->sensor_max_x; + rmi4_data->sensor_max_x = rmi4_data->sensor_max_y; + rmi4_data->sensor_max_y = temp; + } + + synaptics_rmi4_set_params(rmi4_data); + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->reset != NULL) + exp_fhandler->exp_fn->reset(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->touch_stopped = false; + + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + + return 0; +} + +/** +* synaptics_rmi4_exp_fn_work() +* +* Called by the kernel at the scheduled time. +* +* This function is a work thread that checks for the insertion and +* removal of other expansion Function modules such as rmi_dev and calls +* their initialization and removal callback functions accordingly. +*/ +static void synaptics_rmi4_exp_fn_work(struct work_struct *work) +{ + int retval; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler_temp; + struct synaptics_rmi4_data *rmi4_data = exp_data.rmi4_data; + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry_safe(exp_fhandler, + exp_fhandler_temp, + &exp_data.list, + link) { + if ((exp_fhandler->exp_fn->init != NULL) && + exp_fhandler->insert) { + retval = exp_fhandler->exp_fn->init(rmi4_data); + if (retval < 0) { + list_del(&exp_fhandler->link); + kfree(exp_fhandler); + } else { + exp_fhandler->insert = false; + } + } else if ((exp_fhandler->exp_fn->remove != NULL) && + exp_fhandler->remove) { + exp_fhandler->exp_fn->remove(rmi4_data); + list_del(&exp_fhandler->link); + kfree(exp_fhandler); + } + } + } + mutex_unlock(&exp_data.mutex); + + return; +} + +/** +* synaptics_rmi4_dsx_new_function() +* +* Called by other expansion Function modules in their module init and +* module exit functions. +* +* This function is used by other expansion Function modules such as +* rmi_dev to register themselves with the driver by providing their +* initialization and removal callback function pointers so that they +* can be inserted or removed dynamically at module init and exit times, +* respectively. +*/ +void synaptics_rmi4_dsx_new_function(struct synaptics_rmi4_exp_fn *exp_fn, + bool insert) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + + if (!exp_data.initialized) { + mutex_init(&exp_data.mutex); + INIT_LIST_HEAD(&exp_data.list); + exp_data.initialized = true; + } + + mutex_lock(&exp_data.mutex); + if (insert) { + exp_fhandler = kzalloc(sizeof(*exp_fhandler), GFP_KERNEL); + if (!exp_fhandler) { + pr_err("%s: Failed to alloc mem for expansion function\n", + __func__); + goto exit; + } + exp_fhandler->exp_fn = exp_fn; + exp_fhandler->insert = true; + exp_fhandler->remove = false; + list_add_tail(&exp_fhandler->link, &exp_data.list); + } else if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) { + if (exp_fhandler->exp_fn->fn_type == exp_fn->fn_type) { + exp_fhandler->insert = false; + exp_fhandler->remove = true; + goto exit; + } + } + } + +exit: + mutex_unlock(&exp_data.mutex); + + if (exp_data.queue_work) { + queue_delayed_work(exp_data.workqueue, + &exp_data.work, + msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + } + + return; +} +EXPORT_SYMBOL(synaptics_rmi4_dsx_new_function); + +static int synaptics_dsx_regulator_configure(struct synaptics_rmi4_data + *rmi4_data) +{ + int retval; + u32 voltage_supply[2]; + u32 current_supply; + + /* Regulator VDD */ + rmi4_data->regulator_vdd = regulator_get(rmi4_data->pdev->dev.parent, + "vdd"); + if (IS_ERR(rmi4_data->regulator_vdd)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get regulator vdd\n", + __func__); + retval = PTR_ERR(rmi4_data->regulator_vdd); + return retval; + } + + /* Read and set vdd regulator voltage and current */ + retval = of_property_read_u32(rmi4_data->pdev->dev.parent->of_node, + "synaptics,vdd-current", ¤t_supply); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get regulator vdd current\n", + __func__); + goto err_vdd_regulator; + } + rmi4_data->regulator_vdd_current = current_supply; + + retval = regulator_set_load(rmi4_data->regulator_vdd, + rmi4_data->regulator_vdd_current); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regulator current vdd\n", + __func__); + goto err_vdd_regulator; + } + + retval = of_property_read_u32_array( + rmi4_data->pdev->dev.parent->of_node, + "synaptics,vdd-voltage", voltage_supply, 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get regulator vdd voltage\n", + __func__); + goto err_vdd_regulator; + } + rmi4_data->regulator_vdd_vmin = voltage_supply[0]; + rmi4_data->regulator_vdd_vmax = voltage_supply[1]; + + retval = regulator_set_voltage(rmi4_data->regulator_vdd, + rmi4_data->regulator_vdd_vmin, + rmi4_data->regulator_vdd_vmax); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regulator voltage vdd\n", + __func__); + goto err_vdd_regulator; + } + + /* Regulator AVDD */ + rmi4_data->regulator_avdd = regulator_get(rmi4_data->pdev->dev.parent, + "avdd"); + if (IS_ERR(rmi4_data->regulator_avdd)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get regulator avdd\n", + __func__); + retval = PTR_ERR(rmi4_data->regulator_avdd); + goto err_vdd_regulator; + } + + /* Read and set avdd regulator voltage and current */ + retval = of_property_read_u32(rmi4_data->pdev->dev.parent->of_node, + "synaptics,avdd-current", ¤t_supply); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get regulator avdd current\n", + __func__); + goto err_avdd_regulator; + } + rmi4_data->regulator_avdd_current = current_supply; + + retval = regulator_set_load(rmi4_data->regulator_avdd, + rmi4_data->regulator_avdd_current); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regulator current avdd\n", + __func__); + goto err_avdd_regulator; + } + + retval = of_property_read_u32_array( + rmi4_data->pdev->dev.parent->of_node, + "synaptics,avdd-voltage", voltage_supply, 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get regulator avdd voltage\n", + __func__); + goto err_avdd_regulator; + } + rmi4_data->regulator_avdd_vmin = voltage_supply[0]; + rmi4_data->regulator_avdd_vmax = voltage_supply[1]; + + retval = regulator_set_voltage(rmi4_data->regulator_avdd, + rmi4_data->regulator_avdd_vmin, + rmi4_data->regulator_avdd_vmax); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set regultor voltage avdd\n", + __func__); + goto err_avdd_regulator; + } + + return 0; + +err_avdd_regulator: + regulator_put(rmi4_data->regulator_avdd); +err_vdd_regulator: + regulator_put(rmi4_data->regulator_vdd); + + return retval; +}; + +static int synaptics_dsx_regulator_enable(struct synaptics_rmi4_data + *rmi4_data, bool on) +{ + int retval; + + if (on) { + retval = regulator_enable(rmi4_data->regulator_vdd); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable regulator vdd\n", + __func__); + return retval; + } + retval = regulator_enable(rmi4_data->regulator_avdd); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable regulator avdd\n", + __func__); + regulator_disable(rmi4_data->regulator_vdd); + return retval; + } + msleep(rmi4_data->hw_if->board_data->power_delay_ms); + } else { + regulator_disable(rmi4_data->regulator_vdd); + regulator_disable(rmi4_data->regulator_avdd); + } + + return 0; +} + + /** + * synaptics_rmi4_probe() + * + * Called by the kernel when an association with an I2C device of the + * same name is made (after doing i2c_add_driver). + * + * This funtion allocates and initializes the resources for the driver + * as an input driver, turns on the power to the sensor, queries the + * sensor for its supported Functions and characteristics, registers + * the driver to the input subsystem, sets up the interrupt, handles + * the registration of the early_suspend and late_resume functions, + * and creates a work queue for detection of other expansion Function + * modules. + */ +static int synaptics_rmi4_probe(struct platform_device *pdev) +{ + int retval, len; + unsigned char attr_count; + struct synaptics_rmi4_data *rmi4_data; + const struct synaptics_dsx_hw_interface *hw_if; + const struct synaptics_dsx_board_data *bdata; + struct dentry *temp; + + hw_if = pdev->dev.platform_data; + if (!hw_if) { + dev_err(&pdev->dev, + "%s: No hardware interface found\n", + __func__); + return -EINVAL; + } + + bdata = hw_if->board_data; + if (!bdata) { + dev_err(&pdev->dev, + "%s: No board data found\n", + __func__); + return -EINVAL; + } + + rmi4_data = kzalloc(sizeof(*rmi4_data), GFP_KERNEL); + if (!rmi4_data) { + dev_err(&pdev->dev, + "%s: Failed to alloc mem for rmi4_data\n", + __func__); + return -ENOMEM; + } + + rmi4_data->pdev = pdev; + rmi4_data->current_page = MASK_8BIT; + rmi4_data->hw_if = hw_if; + rmi4_data->touch_stopped = false; + rmi4_data->sensor_sleep = false; + rmi4_data->irq_enabled = false; + rmi4_data->fw_updating = false; + rmi4_data->fingers_on_2d = false; + rmi4_data->update_coords = true; + + rmi4_data->irq_enable = synaptics_rmi4_irq_enable; + rmi4_data->reset_device = synaptics_rmi4_reset_device; + + mutex_init(&(rmi4_data->rmi4_io_ctrl_mutex)); + mutex_init(&(rmi4_data->rmi4_reset_mutex)); + + retval = synaptics_dsx_regulator_configure(rmi4_data); + if (retval) { + dev_err(&pdev->dev, + "%s: regulator configuration failed\n", __func__); + goto err_regulator_configure; + } + retval = synaptics_dsx_regulator_enable(rmi4_data, true); + if (retval) { + dev_err(&pdev->dev, + "%s: regulator enable failed\n", __func__); + goto err_regulator_enable; + } + + platform_set_drvdata(pdev, rmi4_data); + + if (bdata->gpio_config) { + retval = synaptics_rmi4_set_gpio(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to set up GPIO's\n", + __func__); + goto err_set_gpio; + } + } else { + retval = synaptics_dsx_pinctrl_init(rmi4_data); + if (!retval && rmi4_data->ts_pinctrl) { + /* + * Pinctrl handle is optional. If pinctrl handle is found + * let pins to be configured in active state. If not + * found continue further without error. + */ + retval = pinctrl_select_state(rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_active); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to select %s pinstate %d\n", + __func__, PINCTRL_STATE_ACTIVE, retval); + } + } + + retval = synaptics_dsx_gpio_configure(rmi4_data, true); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to set up GPIO's\n", + __func__); + goto err_config_gpio; + } + } + + if (bdata->fw_name) { + len = strlen(bdata->fw_name); + if (len > SYNA_FW_NAME_MAX_LEN - 1) { + dev_err(&pdev->dev, "Invalid firmware name\n"); + goto err_set_input_dev; + } + + strlcpy(rmi4_data->fw_name, bdata->fw_name, len + 1); + } + + retval = synaptics_rmi4_set_input_dev(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to set up input device\n", + __func__); + goto err_set_input_dev; + } + +#ifdef CONFIG_FB + INIT_WORK(&rmi4_data->fb_notify_work, fb_notify_resume_work); + rmi4_data->fb_notif.notifier_call = fb_notifier_callback; + + retval = fb_register_client(&rmi4_data->fb_notif); + if (retval) + dev_err(rmi4_data->pdev->dev.parent, + "Unable to register fb_notifier: %d\n", retval); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + rmi4_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + rmi4_data->early_suspend.suspend = synaptics_rmi4_early_suspend; + rmi4_data->early_suspend.resume = synaptics_rmi4_late_resume; + register_early_suspend(&rmi4_data->early_suspend); +#endif + + rmi4_data->irq = gpio_to_irq(bdata->irq_gpio); + + if (!exp_data.initialized) { + mutex_init(&exp_data.mutex); + INIT_LIST_HEAD(&exp_data.list); + exp_data.initialized = true; + } + + retval = synaptics_rmi4_irq_enable(rmi4_data, true); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to enable attention interrupt\n", + __func__); + goto err_enable_irq; + } + + exp_data.workqueue = create_singlethread_workqueue("dsx_exp_workqueue"); + if (exp_data.workqueue == NULL) { + dev_err(&pdev->dev, + "%s: Failed to create workqueue\n", __func__); + retval = -ENOMEM; + goto err_create_wq; + } + + INIT_DELAYED_WORK(&exp_data.work, synaptics_rmi4_exp_fn_work); + exp_data.rmi4_data = rmi4_data; + exp_data.queue_work = true; + queue_delayed_work(exp_data.workqueue, + &exp_data.work, + msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + + rmi4_data->dir = debugfs_create_dir(DEBUGFS_DIR_NAME, NULL); + if (rmi4_data->dir == NULL || IS_ERR(rmi4_data->dir)) { + retval = rmi4_data->dir ? PTR_ERR(rmi4_data->dir) : -EIO; + dev_err(&pdev->dev, + "%s: Failed to create debugfs directory, rc = %d\n", + __func__, retval); + goto err_create_debugfs_dir; + } + + temp = debugfs_create_file("suspend", S_IRUSR | S_IWUSR, rmi4_data->dir, + rmi4_data, &debug_suspend_fops); + if (temp == NULL || IS_ERR(temp)) { + retval = temp ? PTR_ERR(temp) : -EIO; + dev_err(&pdev->dev, + "%s: Failed to create suspend debugfs file, rc = %d\n", + __func__, retval); + goto err_create_debugfs_file; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to create sysfs attributes\n", + __func__); + goto err_sysfs; + } + } + + synaptics_secure_touch_init(rmi4_data); + synaptics_secure_touch_stop(rmi4_data, 1); + + return retval; + +err_sysfs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } +err_create_debugfs_file: + debugfs_remove_recursive(rmi4_data->dir); +err_create_debugfs_dir: + cancel_delayed_work_sync(&exp_data.work); + flush_workqueue(exp_data.workqueue); + destroy_workqueue(exp_data.workqueue); +err_create_wq: + synaptics_rmi4_irq_enable(rmi4_data, false); + free_irq(rmi4_data->irq, rmi4_data); + +err_enable_irq: +#if defined(CONFIG_FB) + fb_unregister_client(&rmi4_data->fb_notif); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&rmi4_data->early_suspend); +#endif + + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + +err_set_input_dev: + if (bdata->gpio_config) { + bdata->gpio_config( + bdata->irq_gpio, + false, 0, 0); + + if (bdata->reset_gpio >= 0) { + bdata->gpio_config( + bdata->reset_gpio, + false, 0, 0); + } + + if (bdata->power_gpio >= 0) { + bdata->gpio_config( + bdata->power_gpio, + false, 0, 0); + } + } else { + synaptics_dsx_gpio_configure(rmi4_data, false); + } +err_config_gpio: + if (rmi4_data->ts_pinctrl) { + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_release)) { + devm_pinctrl_put(rmi4_data->ts_pinctrl); + rmi4_data->ts_pinctrl = NULL; + } else { + retval = pinctrl_select_state( + rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_release); + if (retval) + dev_err(&pdev->dev, + "%s: Failed to create sysfs attributes\n", + __func__); + } + } + +err_set_gpio: + regulator_disable(rmi4_data->regulator_vdd); + regulator_disable(rmi4_data->regulator_avdd); +err_regulator_enable: + regulator_put(rmi4_data->regulator_vdd); + regulator_put(rmi4_data->regulator_avdd); +err_regulator_configure: + kfree(rmi4_data); + + return retval; +} + + /** + * synaptics_rmi4_remove() + * + * Called by the kernel when the association with an I2C device of the + * same name is broken (when the driver is unloaded). + * + * This funtion terminates the work queue, stops sensor data acquisition, + * frees the interrupt, unregisters the driver from the input subsystem, + * turns off the power to the sensor, and frees other allocated resources. + */ +static int synaptics_rmi4_remove(struct platform_device *pdev) +{ + unsigned char attr_count; + struct synaptics_rmi4_data *rmi4_data = platform_get_drvdata(pdev); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + int err; + + if (rmi4_data->support_vkeys) { + sysfs_remove_group(vkey_kobj, &vkey_grp); + kobject_put(vkey_kobj); + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + debugfs_remove_recursive(rmi4_data->dir); + cancel_delayed_work_sync(&exp_data.work); + flush_workqueue(exp_data.workqueue); + destroy_workqueue(exp_data.workqueue); + + synaptics_rmi4_irq_enable(rmi4_data, false); + +#if defined(CONFIG_FB) + fb_unregister_client(&rmi4_data->fb_notif); +#elif defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&rmi4_data->early_suspend); +#endif + + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + + if (bdata->gpio_config) { + bdata->gpio_config( + bdata->irq_gpio, + false, 0, 0); + + if (bdata->reset_gpio >= 0) { + bdata->gpio_config( + bdata->reset_gpio, + false, 0, 0); + } + + if (bdata->power_gpio >= 0) { + bdata->gpio_config( + bdata->power_gpio, + false, 0, 0); + } + } else { + synaptics_dsx_gpio_configure(rmi4_data, false); + if (rmi4_data->ts_pinctrl) { + if (IS_ERR_OR_NULL(rmi4_data->pinctrl_state_release)) { + devm_pinctrl_put(rmi4_data->ts_pinctrl); + rmi4_data->ts_pinctrl = NULL; + } else { + err = pinctrl_select_state( + rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_release); + if (err) + dev_err(&pdev->dev, + "Failed to select release pinctrl state %d\n", + err); + } + } + } + + if (rmi4_data->regulator_vdd) { + regulator_disable(rmi4_data->regulator_vdd); + regulator_put(rmi4_data->regulator_vdd); + } + + if (rmi4_data->regulator_avdd) { + regulator_disable(rmi4_data->regulator_avdd); + regulator_put(rmi4_data->regulator_avdd); + } + + kfree(rmi4_data); + + return 0; +} + +#if defined(CONFIG_FB) +static void fb_notify_resume_work(struct work_struct *work) +{ + struct synaptics_rmi4_data *rmi4_data = + container_of(work, struct synaptics_rmi4_data, fb_notify_work); + synaptics_rmi4_resume(&(rmi4_data->input_dev->dev)); +} + +static int fb_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + int *blank; + struct synaptics_rmi4_data *rmi4_data = + container_of(self, struct synaptics_rmi4_data, fb_notif); + + if (evdata && evdata->data && rmi4_data) { + blank = evdata->data; + if (rmi4_data->hw_if->board_data->resume_in_workqueue) { + if (event == FB_EARLY_EVENT_BLANK) { + synaptics_secure_touch_stop(rmi4_data, 0); + if (*blank == FB_BLANK_UNBLANK) + schedule_work( + &(rmi4_data->fb_notify_work)); + } else if (event == FB_EVENT_BLANK && + *blank == FB_BLANK_POWERDOWN) { + flush_work( + &(rmi4_data->fb_notify_work)); + synaptics_rmi4_suspend( + &(rmi4_data->input_dev->dev)); + } + } else { + if (event == FB_EARLY_EVENT_BLANK) { + synaptics_secure_touch_stop(rmi4_data, 0); + } else if (event == FB_EVENT_BLANK) { + if (*blank == FB_BLANK_UNBLANK) + synaptics_rmi4_resume( + &(rmi4_data->input_dev->dev)); + else if (*blank == FB_BLANK_POWERDOWN) + synaptics_rmi4_suspend( + &(rmi4_data->input_dev->dev)); + } + } + } + + return 0; +} +#elif defined(CONFIG_HAS_EARLYSUSPEND) + /** + * synaptics_rmi4_early_suspend() + * + * Called by the kernel during the early suspend phase when the system + * enters suspend. + * + * This function calls synaptics_rmi4_sensor_sleep() to stop finger + * data acquisition and put the sensor to sleep. + */ +static void synaptics_rmi4_early_suspend(struct early_suspend *h) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = + container_of(h, struct synaptics_rmi4_data, + early_suspend); + + if (rmi4_data->stay_awake) { + rmi4_data->staying_awake = true; + return; + } else { + rmi4_data->staying_awake = false; + } + + synaptics_secure_touch_stop(rmi4_data, 0); + + rmi4_data->touch_stopped = true; + synaptics_rmi4_irq_enable(rmi4_data, false); + synaptics_rmi4_sensor_sleep(rmi4_data); + synaptics_rmi4_free_fingers(rmi4_data); + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->early_suspend != NULL) + exp_fhandler->exp_fn->early_suspend(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + if (rmi4_data->full_pm_cycle) + synaptics_rmi4_suspend(&(rmi4_data->input_dev->dev)); + + return; +} + + /** + * synaptics_rmi4_late_resume() + * + * Called by the kernel during the late resume phase when the system + * wakes up from suspend. + * + * This function goes through the sensor wake process if the system wakes + * up from early suspend (without going into suspend). + */ +static void synaptics_rmi4_late_resume(struct early_suspend *h) +{ + int retval; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = + container_of(h, struct synaptics_rmi4_data, + early_suspend); + + if (rmi4_data->staying_awake) + return; + + synaptics_secure_touch_stop(rmi4_data, 0); + + if (rmi4_data->full_pm_cycle) + synaptics_rmi4_resume(&(rmi4_data->input_dev->dev)); + + if (rmi4_data->sensor_sleep == true) { + synaptics_rmi4_sensor_wake(rmi4_data); + synaptics_rmi4_irq_enable(rmi4_data, true); + retval = synaptics_rmi4_reinit_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to reinit device\n", + __func__); + } + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->late_resume != NULL) + exp_fhandler->exp_fn->late_resume(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->touch_stopped = false; + + return; +} +#endif + +#ifdef CONFIG_PM + /** + * synaptics_rmi4_sensor_sleep() + * + * Called by synaptics_rmi4_early_suspend() and synaptics_rmi4_suspend(). + * + * This function stops finger data acquisition and puts the sensor to sleep. + */ +static void synaptics_rmi4_sensor_sleep(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char device_ctrl; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enter sleep mode\n", + __func__); + rmi4_data->sensor_sleep = false; + return; + } + + device_ctrl = (device_ctrl & ~MASK_3BIT); + device_ctrl = (device_ctrl | NO_SLEEP_OFF | SENSOR_SLEEP); + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enter sleep mode\n", + __func__); + rmi4_data->sensor_sleep = false; + return; + } else { + rmi4_data->sensor_sleep = true; + } + + return; +} + + /** + * synaptics_rmi4_sensor_wake() + * + * Called by synaptics_rmi4_resume() and synaptics_rmi4_late_resume(). + * + * This function wakes the sensor from sleep. + */ +static void synaptics_rmi4_sensor_wake(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char device_ctrl; + unsigned char no_sleep_setting = rmi4_data->no_sleep_setting; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wake from sleep mode\n", + __func__); + rmi4_data->sensor_sleep = true; + return; + } + + device_ctrl = (device_ctrl & ~MASK_3BIT); + device_ctrl = (device_ctrl | no_sleep_setting | NORMAL_OPERATION); + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wake from sleep mode\n", + __func__); + rmi4_data->sensor_sleep = true; + return; + } else { + rmi4_data->sensor_sleep = false; + } + + return; +} + + /** + * synaptics_rmi4_suspend() + * + * Called by the kernel during the suspend phase when the system + * enters suspend. + * + * This function stops finger data acquisition and puts the sensor to + * sleep (if not already done so during the early suspend phase), + * disables the interrupt, and turns off the power to the sensor. + */ +static int synaptics_rmi4_suspend(struct device *dev) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + int retval; + + if (rmi4_data->stay_awake) { + rmi4_data->staying_awake = true; + return 0; + } else { + rmi4_data->staying_awake = false; + } + + if (rmi4_data->suspended) { + dev_info(dev, "Already in suspend state\n"); + return 0; + } + + synaptics_secure_touch_stop(rmi4_data, 1); + + if (!rmi4_data->fw_updating) { + if (!rmi4_data->sensor_sleep) { + rmi4_data->touch_stopped = true; + synaptics_rmi4_irq_enable(rmi4_data, false); + synaptics_rmi4_sensor_sleep(rmi4_data); + synaptics_rmi4_free_fingers(rmi4_data); + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->suspend != NULL) + exp_fhandler->exp_fn->suspend(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + retval = synaptics_dsx_regulator_enable(rmi4_data, false); + if (retval < 0) { + dev_err(dev, "failed to enter low power mode\n"); + goto err_lpm_regulator; + } + } else { + dev_err(dev, + "Firmware updating, cannot go into suspend mode\n"); + return 0; + } + + if (bdata->disable_gpios) { + if (rmi4_data->ts_pinctrl) { + retval = pinctrl_select_state(rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_suspend); + if (retval < 0) { + dev_err(dev, "Cannot get idle pinctrl state\n"); + goto err_pinctrl_select_suspend; + } + } + + retval = synaptics_dsx_gpio_configure(rmi4_data, false); + if (retval < 0) { + dev_err(dev, "failed to put gpios in suspend state\n"); + goto err_gpio_configure; + } + } + + rmi4_data->suspended = true; + + return 0; + +err_gpio_configure: + if (rmi4_data->ts_pinctrl) { + retval = pinctrl_select_state(rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_active); + if (retval < 0) + dev_err(dev, "Cannot get default pinctrl state\n"); + } +err_pinctrl_select_suspend: + synaptics_dsx_regulator_enable(rmi4_data, true); +err_lpm_regulator: + if (rmi4_data->sensor_sleep) { + synaptics_rmi4_sensor_wake(rmi4_data); + synaptics_rmi4_irq_enable(rmi4_data, true); + rmi4_data->touch_stopped = false; + } + + return retval; +} + + /** + * synaptics_rmi4_resume() + * + * Called by the kernel during the resume phase when the system + * wakes up from suspend. + * + * This function turns on the power to the sensor, wakes the sensor + * from sleep, enables the interrupt, and starts finger data + * acquisition. + */ +static int synaptics_rmi4_resume(struct device *dev) +{ + int retval; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + struct synaptics_rmi4_device_info *rmi; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + rmi = &(rmi4_data->rmi4_mod_info); + if (rmi->package_id == SYNA_S332U_PACKAGE_ID && + rmi->package_id_rev == SYNA_S332U_PACKAGE_ID_REV) { + synaptics_rmi4_reset_device(rmi4_data); + } + + if (rmi4_data->staying_awake) + return 0; + + if (!rmi4_data->suspended) + return 0; + + synaptics_secure_touch_stop(rmi4_data, 1); + + synaptics_dsx_regulator_enable(rmi4_data, true); + + if (bdata->disable_gpios) { + if (rmi4_data->ts_pinctrl) { + retval = pinctrl_select_state(rmi4_data->ts_pinctrl, + rmi4_data->pinctrl_state_active); + if (retval < 0) + dev_err(dev, "Cannot get default pinctrl state\n"); + } + + retval = synaptics_dsx_gpio_configure(rmi4_data, true); + if (retval < 0) + dev_err(dev, "Failed to put gpios in active state\n"); + } + + synaptics_rmi4_sensor_wake(rmi4_data); + retval = synaptics_rmi4_reinit_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to reinit device\n", + __func__); + return retval; + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->resume != NULL) + exp_fhandler->exp_fn->resume(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->touch_stopped = false; + rmi4_data->suspended = false; + + synaptics_rmi4_irq_enable(rmi4_data, true); + + return 0; +} + +static const struct dev_pm_ops synaptics_rmi4_dev_pm_ops = { +#if (!defined(CONFIG_FB) && !defined(CONFIG_HAS_EARLYSUSPEND)) + .suspend = synaptics_rmi4_suspend, + .resume = synaptics_rmi4_resume, +#endif +}; +#else +static int synaptics_rmi4_suspend(struct device *dev) +{ + dev_err(dev, "PM not supported\n"); + return -EINVAL; +} + +static int synaptics_rmi4_resume(struct device *dev) +{ + dev_err(dev, "PM not supported\n"); + return -EINVAL; +} +#endif + +static struct platform_driver synaptics_rmi4_driver = { + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &synaptics_rmi4_dev_pm_ops, +#endif + }, + .probe = synaptics_rmi4_probe, + .remove = synaptics_rmi4_remove, +}; + + /** + * synaptics_rmi4_init() + * + * Called by the kernel during do_initcalls (if built-in) + * or when the driver is loaded (if a module). + * + * This function registers the driver to the I2C subsystem. + * + */ +static int __init synaptics_rmi4_init(void) +{ + int retval; + + retval = synaptics_rmi4_bus_init(); + if (retval) + return retval; + + return platform_driver_register(&synaptics_rmi4_driver); +} + + /** + * synaptics_rmi4_exit() + * + * Called by the kernel when the driver is unloaded. + * + * This funtion unregisters the driver from the I2C subsystem. + * + */ +static void __exit synaptics_rmi4_exit(void) +{ + platform_driver_unregister(&synaptics_rmi4_driver); + + synaptics_rmi4_bus_exit(); + + return; +} + +module_init(synaptics_rmi4_init); +module_exit(synaptics_rmi4_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX I2C Touch Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.h b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.h new file mode 100644 index 000000000000..7d7e045d7917 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.h @@ -0,0 +1,405 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012 Synaptics Incorporated + * + * Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com> + * Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com> + * Copyright (c) 2014, 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _SYNAPTICS_DSX_RMI4_H_ +#define _SYNAPTICS_DSX_RMI4_H_ + +#define SYNAPTICS_DS4 (1 << 0) +#define SYNAPTICS_DS5 (1 << 1) +#define SYNAPTICS_DSX_DRIVER_PRODUCT (SYNAPTICS_DS4 | SYNAPTICS_DS5) +#define SYNAPTICS_DSX_DRIVER_VERSION 0x2001 + +#include <linux/version.h> +#include <linux/debugfs.h> + +#if defined(CONFIG_FB) +#include <linux/notifier.h> +#include <linux/fb.h> +#elif defined(CONFIG_HAS_EARLYSUSPEND) +#include <linux/earlysuspend.h> +#endif +#if defined(CONFIG_SECURE_TOUCH) +#include <linux/completion.h> +#include <linux/atomic.h> +#include <linux/clk.h> +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38)) +#define KERNEL_ABOVE_2_6_38 +#endif + +#ifdef KERNEL_ABOVE_2_6_38 +#define sstrtoul(...) kstrtoul(__VA_ARGS__) +#else +#define sstrtoul(...) strict_strtoul(__VA_ARGS__) +#endif + +#define PDT_PROPS (0X00EF) +#define PDT_START (0x00E9) +#define PDT_END (0x00D0) +#define PDT_ENTRY_SIZE (0x0006) +#define PAGES_TO_SERVICE (10) +#define PAGE_SELECT_LEN (2) +#define ADDRESS_WORD_LEN (2) + +#define SYNAPTICS_RMI4_F01 (0x01) +#define SYNAPTICS_RMI4_F11 (0x11) +#define SYNAPTICS_RMI4_F12 (0x12) +#define SYNAPTICS_RMI4_F1A (0x1a) +#define SYNAPTICS_RMI4_F34 (0x34) +#define SYNAPTICS_RMI4_F51 (0x51) +#define SYNAPTICS_RMI4_F54 (0x54) +#define SYNAPTICS_RMI4_F55 (0x55) + +#define SYNAPTICS_RMI4_PRODUCT_INFO_SIZE 2 +#define SYNAPTICS_RMI4_DATE_CODE_SIZE 3 +#define SYNAPTICS_RMI4_PRODUCT_ID_SIZE 10 +#define SYNAPTICS_RMI4_BUILD_ID_SIZE 3 + +#define F01_PACKAGE_ID_OFFSET 17 +#define PACKAGE_ID_SIZE 4 + +#define F12_FINGERS_TO_SUPPORT 10 +#define F12_NO_OBJECT_STATUS 0x00 +#define F12_FINGER_STATUS 0x01 +#define F12_STYLUS_STATUS 0x02 +#define F12_PALM_STATUS 0x03 +#define F12_HOVERING_FINGER_STATUS 0x05 +#define F12_GLOVED_FINGER_STATUS 0x06 + +#define MAX_NUMBER_OF_BUTTONS 4 +#define MAX_INTR_REGISTERS 4 + +#define MASK_16BIT 0xFFFF +#define MASK_8BIT 0xFF +#define MASK_7BIT 0x7F +#define MASK_6BIT 0x3F +#define MASK_5BIT 0x1F +#define MASK_4BIT 0x0F +#define MASK_3BIT 0x07 +#define MASK_2BIT 0x03 +#define MASK_1BIT 0x01 + +#define PINCTRL_STATE_ACTIVE "pmx_ts_active" +#define PINCTRL_STATE_SUSPEND "pmx_ts_suspend" +#define PINCTRL_STATE_RELEASE "pmx_ts_release" + +#define SYNA_FW_NAME_MAX_LEN 50 + +enum exp_fn { + RMI_DEV = 0, + RMI_F54, + RMI_FW_UPDATER, + RMI_PROXIMITY, + RMI_ACTIVE_PEN, + RMI_LAST, +}; + +struct synaptics_dsx_hw_interface { + struct synaptics_dsx_board_data *board_data; + const struct synaptics_dsx_bus_access *bus_access; +}; + +/* + * struct synaptics_rmi4_fn_desc - function descriptor fields in PDT + * @query_base_addr: base address for query registers + * @cmd_base_addr: base address for command registers + * @ctrl_base_addr: base address for control registers + * @data_base_addr: base address for data registers + * @intr_src_count: number of interrupt sources + * @fn_number: function number + */ +struct synaptics_rmi4_fn_desc { + unsigned char query_base_addr; + unsigned char cmd_base_addr; + unsigned char ctrl_base_addr; + unsigned char data_base_addr; + unsigned char intr_src_count; + unsigned char fn_number; +}; + +/* + * synaptics_rmi4_fn_full_addr - full 16-bit base addresses + * @query_base: 16-bit base address for query registers + * @cmd_base: 16-bit base address for data registers + * @ctrl_base: 16-bit base address for command registers + * @data_base: 16-bit base address for control registers + */ +struct synaptics_rmi4_fn_full_addr { + unsigned short query_base; + unsigned short cmd_base; + unsigned short ctrl_base; + unsigned short data_base; +}; + +struct synaptics_rmi4_f12_extra_data { + unsigned char data1_offset; + unsigned char data15_offset; + unsigned char data15_size; + unsigned char data15_data[(F12_FINGERS_TO_SUPPORT + 7) / 8]; +}; + +/* + * struct synaptics_rmi4_fn - function handler data structure + * @fn_number: function number + * @num_of_data_sources: number of data sources + * @num_of_data_points: maximum number of fingers supported + * @size_of_data_register_block: data register block size + * @intr_reg_num: index to associated interrupt register + * @intr_mask: interrupt mask + * @full_addr: full 16-bit base addresses of function registers + * @link: linked list for function handlers + * @data_size: size of private data + * @data: pointer to private data + */ +struct synaptics_rmi4_fn { + unsigned char fn_number; + unsigned char num_of_data_sources; + unsigned char num_of_data_points; + unsigned char size_of_data_register_block; + unsigned char intr_reg_num; + unsigned char intr_mask; + struct synaptics_rmi4_fn_full_addr full_addr; + struct list_head link; + int data_size; + void *data; + void *extra; +}; + +/* + * struct synaptics_rmi4_device_info - device information + * @version_major: rmi protocol major version number + * @version_minor: rmi protocol minor version number + * @manufacturer_id: manufacturer id + * @product_props: product properties information + * @product_info: product info array + * @date_code: device manufacture date + * @tester_id: tester id array + * @serial_number: device serial number + * @product_id_string: device product id + * @support_fn_list: linked list for function handlers + */ +struct synaptics_rmi4_device_info { + unsigned int version_major; + unsigned int version_minor; + unsigned char manufacturer_id; + unsigned char product_props; + unsigned char product_info[SYNAPTICS_RMI4_PRODUCT_INFO_SIZE]; + unsigned char date_code[SYNAPTICS_RMI4_DATE_CODE_SIZE]; + unsigned short tester_id; + unsigned short serial_number; + unsigned char product_id_string[SYNAPTICS_RMI4_PRODUCT_ID_SIZE + 1]; + unsigned char build_id[SYNAPTICS_RMI4_BUILD_ID_SIZE]; + struct list_head support_fn_list; + unsigned int package_id; + unsigned int package_id_rev; +}; + +/* + * struct synaptics_rmi4_data - rmi4 device instance data + * @pdev: pointer to platform device + * @input_dev: pointer to associated input device + * @hw_if: pointer to hardware interface data + * @rmi4_mod_info: device information + * @regulator_vdd: pointer to associated vdd regulator + * @regulator_add: pointer to associated avdd regulator + * @regulator_vdd_vmin: minimum vdd regulator voltage + * @regulator_vdd_vmax: maximum vdd regulator voltage + * @regulator_vdd_current: vdd regulator current load + * @regulator_avdd_vmin: minimum avdd regulator voltage + * @regulator_avdd_vmax: maximum avdd regulator voltage + * @regulator_avdd_current: avdd regulator current load + * @rmi4_io_ctrl_mutex: mutex for i2c i/o control + * @early_suspend: instance to support early suspend power management + * @current_page: current page in sensor to acess + * @button_0d_enabled: flag for 0d button support + * @full_pm_cycle: flag for full power management cycle in early suspend stage + * @num_of_intr_regs: number of interrupt registers + * @f01_query_base_addr: query base address for f01 + * @f01_cmd_base_addr: command base address for f01 + * @f01_ctrl_base_addr: control base address for f01 + * @f01_data_base_addr: data base address for f01 + * @irq: attention interrupt + * @sensor_max_x: sensor maximum x value + * @sensor_max_y: sensor maximum y value + * @irq_enabled: flag for indicating interrupt enable status + * @fingers_on_2d: flag to indicate presence of fingers in 2d area + * @sensor_sleep: flag to indicate sleep state of sensor + * @wait: wait queue for touch data polling in interrupt thread + * @irq_enable: pointer to irq enable function + */ +struct synaptics_rmi4_data { + struct platform_device *pdev; + struct input_dev *input_dev; + const struct synaptics_dsx_hw_interface *hw_if; + struct synaptics_rmi4_device_info rmi4_mod_info; + struct regulator *regulator_vdd; + struct regulator *regulator_avdd; + int regulator_vdd_vmin; + int regulator_vdd_vmax; + int regulator_vdd_current; + int regulator_avdd_vmin; + int regulator_avdd_vmax; + int regulator_avdd_current; + struct mutex rmi4_reset_mutex; + struct mutex rmi4_io_ctrl_mutex; +#if defined(CONFIG_FB) + struct work_struct fb_notify_work; + struct notifier_block fb_notif; +#elif defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + struct dentry *dir; + unsigned char current_page; + unsigned char button_0d_enabled; + unsigned char full_pm_cycle; + unsigned char num_of_rx; + unsigned char num_of_tx; + unsigned char num_of_fingers; + unsigned char max_touch_width; + unsigned char report_enable; + unsigned char no_sleep_setting; + unsigned char intr_mask[MAX_INTR_REGISTERS]; + unsigned char *button_txrx_mapping; + unsigned short num_of_intr_regs; + unsigned short f01_query_base_addr; + unsigned short f01_cmd_base_addr; + unsigned short f01_ctrl_base_addr; + unsigned short f01_data_base_addr; + unsigned int firmware_id; + int irq; + int sensor_max_x; + int sensor_max_y; + bool flash_prog_mode; + bool irq_enabled; + bool touch_stopped; + bool fingers_on_2d; + bool sensor_sleep; + bool stay_awake; + bool staying_awake; + bool fw_updating; + bool support_vkeys; + bool update_coords; + int (*irq_enable)(struct synaptics_rmi4_data *rmi4_data, bool enable); + int (*reset_device)(struct synaptics_rmi4_data *rmi4_data); + + struct pinctrl *ts_pinctrl; + struct pinctrl_state *pinctrl_state_active; + struct pinctrl_state *pinctrl_state_suspend; + struct pinctrl_state *pinctrl_state_release; + char fw_name[SYNA_FW_NAME_MAX_LEN]; + bool suspended; +#if defined(CONFIG_SECURE_TOUCH) + atomic_t st_enabled; + atomic_t st_pending_irqs; + bool st_initialized; + struct completion st_powerdown; + struct completion st_irq_processed; + struct clk *core_clk; + struct clk *iface_clk; +#endif +}; + +struct synaptics_dsx_bus_access { + unsigned char type; + int (*read)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr, + unsigned char *data, unsigned short length); + int (*write)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr, + unsigned char *data, unsigned short length); +#if defined(CONFIG_SECURE_TOUCH) + int (*get)(struct synaptics_rmi4_data *rmi4_data); + void (*put)(struct synaptics_rmi4_data *rmi4_data); +#endif +}; + +struct synaptics_rmi4_exp_fn { + enum exp_fn fn_type; + int (*init)(struct synaptics_rmi4_data *rmi4_data); + void (*remove)(struct synaptics_rmi4_data *rmi4_data); + void (*reset)(struct synaptics_rmi4_data *rmi4_data); + void (*reinit)(struct synaptics_rmi4_data *rmi4_data); + void (*early_suspend)(struct synaptics_rmi4_data *rmi4_data); + void (*suspend)(struct synaptics_rmi4_data *rmi4_data); + void (*resume)(struct synaptics_rmi4_data *rmi4_data); + void (*late_resume)(struct synaptics_rmi4_data *rmi4_data); + void (*attn)(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask); +}; + +int synaptics_rmi4_bus_init(void); + +void synaptics_rmi4_bus_exit(void); + +void synaptics_rmi4_dsx_new_function(struct synaptics_rmi4_exp_fn *exp_fn_mod, + bool insert); + +int synaptics_dsx_fw_updater(unsigned char *fw_data); + +int synaptics_dsx_get_dt_coords(struct device *dev, char *name, + struct synaptics_dsx_board_data *pdata, + struct device_node *node); + +static inline int synaptics_rmi4_reg_read( + struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, + unsigned char *data, + unsigned short len) +{ + return rmi4_data->hw_if->bus_access->read(rmi4_data, addr, data, len); +} + +static inline int synaptics_rmi4_reg_write( + struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, + unsigned char *data, + unsigned short len) +{ + return rmi4_data->hw_if->bus_access->write(rmi4_data, addr, data, len); +} + +#if defined(CONFIG_SECURE_TOUCH) +static inline int synaptics_rmi4_bus_get(struct synaptics_rmi4_data *rmi4_data) +{ + return rmi4_data->hw_if->bus_access->get(rmi4_data); +} +static inline void synaptics_rmi4_bus_put(struct synaptics_rmi4_data *rmi4_data) +{ + rmi4_data->hw_if->bus_access->put(rmi4_data); +} +#endif + +static inline ssize_t synaptics_rmi4_store_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + dev_warn(dev, "%s Attempted to write to read-only attribute %s\n", + __func__, attr->attr.name); + return -EPERM; +} + +static inline void batohs(unsigned short *dest, unsigned char *src) +{ + *dest = src[1] * 0x100 + src[0]; +} + +static inline void hstoba(unsigned char *dest, unsigned short src) +{ + dest[0] = src % 0x100; + dest[1] = src / 0x100; +} +#endif diff --git a/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_fw_update.c b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_fw_update.c new file mode 100644 index 000000000000..13680130c2de --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_fw_update.c @@ -0,0 +1,2163 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012 Synaptics Incorporated + * + * Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com> + * Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/firmware.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx_v2.h> +#include "synaptics_dsx_core.h" + +#define STARTUP_FW_UPDATE_DELAY_MS 1000 /* ms */ +#define FORCE_UPDATE false +#define DO_LOCKDOWN false + +#define MAX_IMAGE_NAME_LEN 256 +#define MAX_FIRMWARE_ID_LEN 10 + +#define LOCKDOWN_OFFSET 0xb0 +#define FW_IMAGE_OFFSET 0x100 + +#define BOOTLOADER_ID_OFFSET 0 +#define BLOCK_NUMBER_OFFSET 0 + +#define V5_PROPERTIES_OFFSET 2 +#define V5_BLOCK_SIZE_OFFSET 3 +#define V5_BLOCK_COUNT_OFFSET 5 +#define V5_BLOCK_DATA_OFFSET 2 + +#define V6_PROPERTIES_OFFSET 1 +#define V6_BLOCK_SIZE_OFFSET 2 +#define V6_BLOCK_COUNT_OFFSET 3 +#define V6_BLOCK_DATA_OFFSET 1 +#define V6_FLASH_COMMAND_OFFSET 2 +#define V6_FLASH_STATUS_OFFSET 3 + +#define LOCKDOWN_BLOCK_COUNT 5 + +#define REG_MAP (1 << 0) +#define UNLOCKED (1 << 1) +#define HAS_CONFIG_ID (1 << 2) +#define HAS_PERM_CONFIG (1 << 3) +#define HAS_BL_CONFIG (1 << 4) +#define HAS_DISP_CONFIG (1 << 5) +#define HAS_CTRL1 (1 << 6) + +#define UI_CONFIG_AREA 0x00 +#define PERM_CONFIG_AREA 0x01 +#define BL_CONFIG_AREA 0x02 +#define DISP_CONFIG_AREA 0x03 + +#define CMD_WRITE_FW_BLOCK 0x2 +#define CMD_ERASE_ALL 0x3 +#define CMD_WRITE_LOCKDOWN_BLOCK 0x4 +#define CMD_READ_CONFIG_BLOCK 0x5 +#define CMD_WRITE_CONFIG_BLOCK 0x6 +#define CMD_ERASE_CONFIG 0x7 +#define CMD_ERASE_BL_CONFIG 0x9 +#define CMD_ERASE_DISP_CONFIG 0xa +#define CMD_ENABLE_FLASH_PROG 0xf + +#define SLEEP_MODE_NORMAL (0x00) +#define SLEEP_MODE_SENSOR_SLEEP (0x01) +#define SLEEP_MODE_RESERVED0 (0x02) +#define SLEEP_MODE_RESERVED1 (0x03) + +#define ENABLE_WAIT_MS (1 * 1000) +#define WRITE_WAIT_MS (3 * 1000) +#define ERASE_WAIT_MS (5 * 1000) + +#define MIN_SLEEP_TIME_US 50 +#define MAX_SLEEP_TIME_US 100 + +#define SYN_FW_CFG_GREATER(fwu, config_id) \ + ((fwu->config_data[0] == 0) && (config_id[0] == 0) && \ + (fwu->config_data[1] == config_id[1]) && \ + (((fwu->config_data[2] == config_id[2]) && \ + (fwu->config_data[3] > config_id[3])) || \ + (fwu->config_data[2] > config_id[2]))) + +#define SYN_FW_CFG_EQUAL(fwu, config_id) \ + ((fwu->config_data[0] == 0) && (config_id[0] == 0) && \ + (fwu->config_data[1] == config_id[1]) && \ + (fwu->config_data[2] == config_id[2]) && \ + (fwu->config_data[3] == config_id[3])) + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static ssize_t fwu_sysfs_show_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t fwu_sysfs_store_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t fwu_sysfs_force_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_do_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_write_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_read_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_config_area_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_image_name_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_image_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_image_size_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_block_size_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_firmware_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_configuration_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_perm_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_bl_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_disp_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_config_id_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_package_id_show(struct device *dev, + struct device_attribute *attr, char *buf); +#endif + +enum bl_version { + V5 = 5, + V6 = 6, +}; + +enum flash_area { + NONE, + UI_FIRMWARE, + CONFIG_AREA, +}; + +enum update_mode { + NORMAL = 1, + FORCE = 2, + LOCKDOWN = 8, +}; + +struct image_header { + /* 0x00 - 0x0f */ + unsigned char checksum[4]; + unsigned char reserved_04; + unsigned char reserved_05; + unsigned char options_firmware_id:1; + unsigned char options_contain_bootloader:1; + unsigned char options_reserved:6; + unsigned char bootloader_version; + unsigned char firmware_size[4]; + unsigned char config_size[4]; + /* 0x10 - 0x1f */ + unsigned char product_id[SYNAPTICS_RMI4_PRODUCT_ID_SIZE]; + unsigned char package_id[2]; + unsigned char package_id_revision[2]; + unsigned char product_info[SYNAPTICS_RMI4_PRODUCT_INFO_SIZE]; + /* 0x20 - 0x2f */ + unsigned char reserved_20_2f[16]; + /* 0x30 - 0x3f */ + unsigned char ds_id[16]; + /* 0x40 - 0x4f */ + unsigned char ds_info[10]; + unsigned char reserved_4a_4f[6]; + /* 0x50 - 0x53 */ + unsigned char firmware_id[4]; +}; + +struct image_header_data { + bool contains_firmware_id; + unsigned int firmware_id; + unsigned int checksum; + unsigned int firmware_size; + unsigned int config_size; + unsigned char bootloader_version; + unsigned char product_id[SYNAPTICS_RMI4_PRODUCT_ID_SIZE + 1]; + unsigned char product_info[SYNAPTICS_RMI4_PRODUCT_INFO_SIZE]; +}; + +struct pdt_properties { + union { + struct { + unsigned char reserved_1:6; + unsigned char has_bsr:1; + unsigned char reserved_2:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f01_device_status { + union { + struct { + unsigned char status_code:4; + unsigned char reserved:2; + unsigned char flash_prog:1; + unsigned char unconfigured:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f01_device_control { + union { + struct { + unsigned char sleep_mode:2; + unsigned char nosleep:1; + unsigned char reserved:2; + unsigned char charger_connected:1; + unsigned char report_rate:1; + unsigned char configured:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_fwu_handle { + enum bl_version bl_version; + bool initialized; + bool program_enabled; + bool has_perm_config; + bool has_bl_config; + bool has_disp_config; + bool force_update; + bool in_flash_prog_mode; + bool do_lockdown; + unsigned int data_pos; + unsigned int image_size; + unsigned char *image_name; + unsigned char *ext_data_source; + unsigned char *read_config_buf; + unsigned char intr_mask; + unsigned char command; + unsigned char bootloader_id[2]; + unsigned char flash_properties; + unsigned char flash_status; + unsigned char productinfo1; + unsigned char productinfo2; + unsigned char properties_off; + unsigned char blk_size_off; + unsigned char blk_count_off; + unsigned char blk_data_off; + unsigned char flash_cmd_off; + unsigned char flash_status_off; + unsigned short block_size; + unsigned short fw_block_count; + unsigned short config_block_count; + unsigned short lockdown_block_count; + unsigned short perm_config_block_count; + unsigned short bl_config_block_count; + unsigned short disp_config_block_count; + unsigned short config_size; + unsigned short config_area; + char product_id[SYNAPTICS_RMI4_PRODUCT_ID_SIZE + 1]; + const unsigned char *firmware_data; + const unsigned char *config_data; + const unsigned char *lockdown_data; + struct delayed_work fwu_work; + struct synaptics_rmi4_fn_desc f34_fd; + struct synaptics_rmi4_data *rmi4_data; +}; + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static struct bin_attribute dev_attr_data = { + .attr = { + .name = "data", + .mode = (S_IRUGO | S_IWUSR), + }, + .size = 0, + .read = fwu_sysfs_show_image, + .write = fwu_sysfs_store_image, +}; +#endif + + +static struct device_attribute attrs[] = { +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + __ATTR(force_update_fw, S_IWUSR | S_IWGRP, + NULL, + fwu_sysfs_force_reflash_store), + __ATTR(update_fw, S_IWUSR | S_IWGRP, + NULL, + fwu_sysfs_do_reflash_store), + __ATTR(writeconfig, S_IWUSR | S_IWGRP, + NULL, + fwu_sysfs_write_config_store), + __ATTR(readconfig, S_IWUSR | S_IWGRP, + NULL, + fwu_sysfs_read_config_store), + __ATTR(configarea, S_IWUSR | S_IWGRP, + NULL, + fwu_sysfs_config_area_store), + __ATTR(fw_name, S_IRUGO | S_IWUSR | S_IWGRP, + fwu_sysfs_image_name_show, + fwu_sysfs_image_name_store), + __ATTR(imagesize, S_IWUSR | S_IWGRP, + NULL, + fwu_sysfs_image_size_store), + __ATTR(blocksize, S_IRUGO, + fwu_sysfs_block_size_show, + synaptics_rmi4_store_error), + __ATTR(fwblockcount, S_IRUGO, + fwu_sysfs_firmware_block_count_show, + synaptics_rmi4_store_error), + __ATTR(configblockcount, S_IRUGO, + fwu_sysfs_configuration_block_count_show, + synaptics_rmi4_store_error), + __ATTR(permconfigblockcount, S_IRUGO, + fwu_sysfs_perm_config_block_count_show, + synaptics_rmi4_store_error), + __ATTR(blconfigblockcount, S_IRUGO, + fwu_sysfs_bl_config_block_count_show, + synaptics_rmi4_store_error), + __ATTR(dispconfigblockcount, S_IRUGO, + fwu_sysfs_disp_config_block_count_show, + synaptics_rmi4_store_error), + __ATTR(config_id, S_IRUGO, + fwu_sysfs_config_id_show, + synaptics_rmi4_store_error), + __ATTR(package_id, S_IRUGO, + fwu_sysfs_package_id_show, + synaptics_rmi4_store_error), +#endif +}; + +static struct synaptics_rmi4_fwu_handle *fwu; + +DECLARE_COMPLETION(fwu_dsx_remove_complete); +DEFINE_MUTEX(dsx_fwu_sysfs_mutex); + +static unsigned int extract_uint_le(const unsigned char *ptr) +{ + return (unsigned int)ptr[0] + + (unsigned int)ptr[1] * 0x100 + + (unsigned int)ptr[2] * 0x10000 + + (unsigned int)ptr[3] * 0x1000000; +} + +static void parse_header(struct image_header_data *header, + const unsigned char *fw_image) +{ + struct image_header *data = (struct image_header *)fw_image; + + header->checksum = extract_uint_le(data->checksum); + + header->bootloader_version = data->bootloader_version; + + header->firmware_size = extract_uint_le(data->firmware_size); + + header->config_size = extract_uint_le(data->config_size); + + memcpy(header->product_id, data->product_id, sizeof(data->product_id)); + header->product_id[sizeof(data->product_id)] = 0; + + memcpy(header->product_info, data->product_info, + sizeof(data->product_info)); + + header->contains_firmware_id = data->options_firmware_id; + if (header->contains_firmware_id) + header->firmware_id = extract_uint_le(data->firmware_id); + + return; +} + +static int fwu_read_f01_device_status(struct f01_device_status *status) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status->data, + sizeof(status->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F01 device status\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_read_f34_queries(void) +{ + int retval; + unsigned char count; + unsigned char buf[10]; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.query_base_addr + BOOTLOADER_ID_OFFSET, + fwu->bootloader_id, + sizeof(fwu->bootloader_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read bootloader ID\n", + __func__); + return retval; + } + + if (fwu->bootloader_id[1] == '5') { + fwu->bl_version = V5; + } else if (fwu->bootloader_id[1] == '6') { + fwu->bl_version = V6; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Unrecognized bootloader version\n", + __func__); + return -EINVAL; + } + + if (fwu->bl_version == V5) { + fwu->properties_off = V5_PROPERTIES_OFFSET; + fwu->blk_size_off = V5_BLOCK_SIZE_OFFSET; + fwu->blk_count_off = V5_BLOCK_COUNT_OFFSET; + fwu->blk_data_off = V5_BLOCK_DATA_OFFSET; + } else if (fwu->bl_version == V6) { + fwu->properties_off = V6_PROPERTIES_OFFSET; + fwu->blk_size_off = V6_BLOCK_SIZE_OFFSET; + fwu->blk_count_off = V6_BLOCK_COUNT_OFFSET; + fwu->blk_data_off = V6_BLOCK_DATA_OFFSET; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.query_base_addr + fwu->properties_off, + &fwu->flash_properties, + sizeof(fwu->flash_properties)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties\n", + __func__); + return retval; + } + + count = 4; + + if (fwu->flash_properties & HAS_PERM_CONFIG) { + fwu->has_perm_config = 1; + count += 2; + } + + if (fwu->flash_properties & HAS_BL_CONFIG) { + fwu->has_bl_config = 1; + count += 2; + } + + if (fwu->flash_properties & HAS_DISP_CONFIG) { + fwu->has_disp_config = 1; + count += 2; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.query_base_addr + fwu->blk_size_off, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block size info\n", + __func__); + return retval; + } + + batohs(&fwu->block_size, &(buf[0])); + + if (fwu->bl_version == V5) { + fwu->flash_cmd_off = fwu->blk_data_off + fwu->block_size; + fwu->flash_status_off = fwu->flash_cmd_off; + } else if (fwu->bl_version == V6) { + fwu->flash_cmd_off = V6_FLASH_COMMAND_OFFSET; + fwu->flash_status_off = V6_FLASH_STATUS_OFFSET; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.query_base_addr + fwu->blk_count_off, + buf, + count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block count info\n", + __func__); + return retval; + } + + batohs(&fwu->fw_block_count, &(buf[0])); + batohs(&fwu->config_block_count, &(buf[2])); + + count = 4; + + if (fwu->has_perm_config) { + batohs(&fwu->perm_config_block_count, &(buf[count])); + count += 2; + } + + if (fwu->has_bl_config) { + batohs(&fwu->bl_config_block_count, &(buf[count])); + count += 2; + } + + if (fwu->has_disp_config) + batohs(&fwu->disp_config_block_count, &(buf[count])); + + return 0; +} + +static int fwu_read_f34_flash_status(void) +{ + int retval; + unsigned char status; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->flash_status_off, + &status, + sizeof(status)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash status\n", + __func__); + return retval; + } + + fwu->program_enabled = status >> 7; + + if (fwu->bl_version == V5) + fwu->flash_status = (status >> 4) & MASK_3BIT; + else if (fwu->bl_version == V6) + fwu->flash_status = status & MASK_3BIT; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->flash_cmd_off, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash command\n", + __func__); + return retval; + } + + fwu->command = command & MASK_4BIT; + + return 0; +} + +static int fwu_write_f34_command(unsigned char cmd) +{ + int retval; + unsigned char command = cmd & MASK_4BIT; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + fwu->command = cmd; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->flash_cmd_off, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command 0x%02x\n", + __func__, command); + return retval; + } + + return 0; +} + +static int fwu_wait_for_idle(int timeout_ms) +{ + int count = 0; + int timeout_count = ((timeout_ms * 1000) / MAX_SLEEP_TIME_US) + 1; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + do { + usleep_range(MIN_SLEEP_TIME_US, MAX_SLEEP_TIME_US); + + count++; + if (count == timeout_count) + fwu_read_f34_flash_status(); + + if ((fwu->command == 0x00) && (fwu->flash_status == 0x00)) + return 0; + } while (count < timeout_count); + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for idle status\n", + __func__); + + return -ETIMEDOUT; +} + +static enum flash_area fwu_go_nogo(struct image_header_data *header) +{ + int retval; + enum flash_area flash_area = NONE; + unsigned char index = 0; + unsigned char config_id[4]; + unsigned int device_fw_id; + unsigned long image_fw_id; + char *strptr; + char *firmware_id; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->force_update) { + flash_area = UI_FIRMWARE; + goto exit; + } + + /* Update both UI and config if device is in bootloader mode */ + if (fwu->in_flash_prog_mode) { + flash_area = UI_FIRMWARE; + goto exit; + } + + /* Get device firmware ID */ + device_fw_id = rmi4_data->firmware_id; + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device firmware ID = %d\n", + __func__, device_fw_id); + + /* Get image firmware ID */ + if (header->contains_firmware_id) { + image_fw_id = header->firmware_id; + } else { + strptr = strnstr(fwu->image_name, "PR", + sizeof(fwu->image_name)); + if (!strptr) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No valid PR number (PRxxxxxxx) found in image file name (%s)\n", + __func__, fwu->image_name); + flash_area = NONE; + goto exit; + } + + strptr += 2; + firmware_id = kzalloc(MAX_FIRMWARE_ID_LEN, GFP_KERNEL); + if (!firmware_id) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for firmware id\n", + __func__); + flash_area = NONE; + goto exit; + } + + while ((index < MAX_FIRMWARE_ID_LEN - 1) && strptr[index] >= '0' + && strptr[index] <= '9') { + firmware_id[index] = strptr[index]; + index++; + } + firmware_id[index] = '\0'; + + retval = sstrtoul(firmware_id, 10, &image_fw_id); + kfree(firmware_id); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to obtain image firmware ID\n", + __func__); + flash_area = NONE; + goto exit; + } + } + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image firmware ID = %d\n", + __func__, (unsigned int)image_fw_id); + + if (!rmi4_data->hw_if->board_data->bypass_packrat_id_check) { + if (image_fw_id > device_fw_id) { + flash_area = UI_FIRMWARE; + goto exit; + } else if (image_fw_id < device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image firmware ID older than device firmware ID\n", + __func__); + flash_area = NONE; + goto exit; + } + } + + /* Get device config ID */ + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.ctrl_base_addr, + config_id, + sizeof(config_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device config ID\n", + __func__); + flash_area = NONE; + goto exit; + } + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device config ID = 0x%02x 0x%02x 0x%02x 0x%02x\n", + __func__, + config_id[0], + config_id[1], + config_id[2], + config_id[3]); + + /* Get image config ID */ + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image config ID = 0x%02x 0x%02x 0x%02x 0x%02x\n", + __func__, + fwu->config_data[0], + fwu->config_data[1], + fwu->config_data[2], + fwu->config_data[3]); + + if (SYN_FW_CFG_GREATER(fwu, config_id)) { + if (image_fw_id > device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image file has higher packrat id than device\n", + __func__); + /* + * If packrat id of the firmware file is greater than + * the firmware build id in the device(same as packrat + * id), then both firmware and config area need to be + * upgraded. + */ + flash_area = UI_FIRMWARE; + goto exit; + } else if (image_fw_id == device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image file has equal packrat id as is in device\n", + __func__); + /* + * If packrat id of the firmware file equals the + * firmware build id in the device(same as packrat id), + * then only config area needs to be upgraded. + */ + flash_area = CONFIG_AREA; + goto exit; + } else if (image_fw_id < device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image file has lesser packrat id than device, even though config id is greater\n", + __func__); + /* + * If packrat id of the firmware file is lesser than + * the firmware build id in the device(same as packrat + * id), then it is treated as an error + */ + flash_area = NONE; + goto exit; + } + } else if (SYN_FW_CFG_EQUAL(fwu, config_id)) { + if (image_fw_id > device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image file has higher packrat id than device, though config id is equal\n", + __func__); + /* + * If config id of the firmware file equals the config + * id in the device, but packrat id of the firmware is + * greater than the firmware build id in the device + * (same as packrat id), then both firmware and config + * area need to be upgraded. + */ + flash_area = UI_FIRMWARE; + goto exit; + } else if (image_fw_id == device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image file has equal packrat id and config id as are in device\n", + __func__); + /* + * If config id of the firmware file equals the config + * id in the device and if packrat id of the firmware + * is also equal to the firmware build id in the device + * (same as packrat id), then no update is needed. + */ + flash_area = NONE; + goto exit; + } else if (image_fw_id < device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image file has lesser packrat id than device, though config id is equal\n", + __func__); + /* + * If config id of the firmware file equals the config + * id in the device, but the packrat id of the firmware + * file is lesser than the firmware build id in the + * device(same as packrat id), then it is treated as an + * error and no update is needed. + */ + flash_area = NONE; + goto exit; + } + } + + flash_area = NONE; + +exit: + if (flash_area == NONE) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: No need to do reflash\n", + __func__); + } else { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Updating %s\n", + __func__, + flash_area == UI_FIRMWARE ? + "UI firmware" : + "config only"); + } + + return flash_area; +} + +static int fwu_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + bool f01found = false; + bool f34found = false; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + if (rmi_fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, rmi_fd.fn_number); + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F01: + f01found = true; + + rmi4_data->f01_query_base_addr = + rmi_fd.query_base_addr; + rmi4_data->f01_ctrl_base_addr = + rmi_fd.ctrl_base_addr; + rmi4_data->f01_data_base_addr = + rmi_fd.data_base_addr; + rmi4_data->f01_cmd_base_addr = + rmi_fd.cmd_base_addr; + break; + case SYNAPTICS_RMI4_F34: + f34found = true; + fwu->f34_fd.query_base_addr = + rmi_fd.query_base_addr; + fwu->f34_fd.ctrl_base_addr = + rmi_fd.ctrl_base_addr; + fwu->f34_fd.data_base_addr = + rmi_fd.data_base_addr; + + fwu->intr_mask = 0; + intr_src = rmi_fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < ((intr_src & MASK_3BIT) + + intr_off); + ii++) { + fwu->intr_mask |= 1 << ii; + } + break; + } + } else { + break; + } + + intr_count += (rmi_fd.intr_src_count & MASK_3BIT); + } + + if (!f01found || !f34found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find both F01 and F34\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_write_blocks(unsigned char *block_ptr, unsigned short block_cnt, + unsigned char command) +{ + int retval; + unsigned char block_offset[] = {0, 0}; + unsigned short block_num; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + unsigned int progress; + unsigned char command_str[10]; + + switch (command) { + case CMD_WRITE_CONFIG_BLOCK: + progress = 10; + strlcpy(command_str, "config", 10); + break; + case CMD_WRITE_FW_BLOCK: + progress = 100; + strlcpy(command_str, "firmware", 10); + break; + case CMD_WRITE_LOCKDOWN_BLOCK: + progress = 1; + strlcpy(command_str, "lockdown", 10); + break; + default: + progress = 1; + strlcpy(command_str, "unknown", 10); + break; + } + + block_offset[1] |= (fwu->config_area << 5); + + retval = synaptics_rmi4_reg_write(rmi4_data, + fwu->f34_fd.data_base_addr + BLOCK_NUMBER_OFFSET, + block_offset, + sizeof(block_offset)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write to block number registers\n", + __func__); + return retval; + } + + for (block_num = 0; block_num < block_cnt; block_num++) { + if (block_num % progress == 0) + dev_info(rmi4_data->pdev->dev.parent, + "%s: update %s %3d / %3d\n", + __func__, command_str, block_num, block_cnt); + + retval = synaptics_rmi4_reg_write(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->blk_data_off, + block_ptr, + fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block data (block %d)\n", + __func__, block_num); + return retval; + } + + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command for block %d\n", + __func__, block_num); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status (block %d)\n", + __func__, block_num); + return retval; + } + + block_ptr += fwu->block_size; + } + + dev_info(rmi4_data->pdev->dev.parent, + "updated %d/%d blocks\n", block_num, block_cnt); + + return 0; +} + +static int fwu_write_firmware(void) +{ + return fwu_write_blocks((unsigned char *)fwu->firmware_data, + fwu->fw_block_count, CMD_WRITE_FW_BLOCK); +} + +static int fwu_write_configuration(void) +{ + return fwu_write_blocks((unsigned char *)fwu->config_data, + fwu->config_block_count, CMD_WRITE_CONFIG_BLOCK); +} + +static int fwu_write_lockdown(void) +{ + return fwu_write_blocks((unsigned char *)fwu->lockdown_data, + fwu->lockdown_block_count, CMD_WRITE_LOCKDOWN_BLOCK); +} + +static int fwu_write_bootloader_id(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->blk_data_off, + fwu->bootloader_id, + sizeof(fwu->bootloader_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write bootloader ID\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_enter_flash_prog(void) +{ + int retval; + struct f01_device_status f01_device_status; + struct f01_device_control f01_device_control; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_write_bootloader_id(); + if (retval < 0) + return retval; + + retval = fwu_write_f34_command(CMD_ENABLE_FLASH_PROG); + if (retval < 0) + return retval; + + retval = fwu_wait_for_idle(ENABLE_WAIT_MS); + if (retval < 0) + return retval; + + if (!fwu->program_enabled) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Program enabled bit not set\n", + __func__); + return -EINVAL; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + return retval; + + retval = fwu_read_f01_device_status(&f01_device_status); + if (retval < 0) + return retval; + + if (!f01_device_status.flash_prog) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not in flash prog mode\n", + __func__); + return -EINVAL; + } + + retval = fwu_read_f34_queries(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + f01_device_control.data, + sizeof(f01_device_control.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F01 device control\n", + __func__); + return retval; + } + + f01_device_control.nosleep = true; + f01_device_control.sleep_mode = SLEEP_MODE_NORMAL; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + f01_device_control.data, + sizeof(f01_device_control.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write F01 device control\n", + __func__); + return retval; + } + + return retval; +} + +static int fwu_do_reflash(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Entered flash prog mode\n", + __func__); + + retval = fwu_write_bootloader_id(); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Bootloader ID written\n", + __func__); + + retval = fwu_write_f34_command(CMD_ERASE_ALL); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase all command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + if (fwu->firmware_data) { + retval = fwu_write_firmware(); + if (retval < 0) + return retval; + pr_notice("%s: Firmware programmed\n", __func__); + } + + if (fwu->config_data) { + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + pr_notice("%s: Configuration programmed\n", __func__); + } + + return retval; +} + +static int fwu_do_write_config(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Entered flash prog mode\n", + __func__); + + if (fwu->config_area == PERM_CONFIG_AREA) { + fwu->config_block_count = fwu->perm_config_block_count; + goto write_config; + } + + retval = fwu_write_bootloader_id(); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Bootloader ID written\n", + __func__); + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_CONFIG); + break; + case BL_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_BL_CONFIG); + fwu->config_block_count = fwu->bl_config_block_count; + break; + case DISP_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_DISP_CONFIG); + fwu->config_block_count = fwu->disp_config_block_count; + break; + } + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + +write_config: + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + pr_notice("%s: Config written\n", __func__); + + return retval; +} + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static int fwu_start_write_config(void) +{ + int retval; + unsigned short block_count; + struct image_header_data header; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + block_count = fwu->config_block_count; + break; + case PERM_CONFIG_AREA: + if (!fwu->has_perm_config) + return -EINVAL; + block_count = fwu->perm_config_block_count; + break; + case BL_CONFIG_AREA: + if (!fwu->has_bl_config) + return -EINVAL; + block_count = fwu->bl_config_block_count; + break; + case DISP_CONFIG_AREA: + if (!fwu->has_disp_config) + return -EINVAL; + block_count = fwu->disp_config_block_count; + break; + default: + return -EINVAL; + } + + if (fwu->ext_data_source) + fwu->config_data = fwu->ext_data_source; + else + return -EINVAL; + + fwu->config_size = fwu->block_size * block_count; + + /* Jump to the config area if given a packrat image */ + if ((fwu->config_area == UI_CONFIG_AREA) && + (fwu->config_size != fwu->image_size)) { + parse_header(&header, fwu->ext_data_source); + + if (header.config_size) { + fwu->config_data = fwu->ext_data_source + + FW_IMAGE_OFFSET + + header.firmware_size; + } else { + return -EINVAL; + } + } + + pr_notice("%s: Start of write config process\n", __func__); + + retval = fwu_do_write_config(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write config\n", + __func__); + } + + rmi4_data->reset_device(rmi4_data); + + pr_notice("%s: End of write config process\n", __func__); + + return retval; +} + +static int fwu_do_read_config(void) +{ + int retval; + unsigned char block_offset[] = {0, 0}; + unsigned short block_num; + unsigned short block_count; + unsigned short index = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + goto exit; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Entered flash prog mode\n", + __func__); + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + block_count = fwu->config_block_count; + break; + case PERM_CONFIG_AREA: + if (!fwu->has_perm_config) { + retval = -EINVAL; + goto exit; + } + block_count = fwu->perm_config_block_count; + break; + case BL_CONFIG_AREA: + if (!fwu->has_bl_config) { + retval = -EINVAL; + goto exit; + } + block_count = fwu->bl_config_block_count; + break; + case DISP_CONFIG_AREA: + if (!fwu->has_disp_config) { + retval = -EINVAL; + goto exit; + } + block_count = fwu->disp_config_block_count; + break; + default: + retval = -EINVAL; + goto exit; + } + + fwu->config_size = fwu->block_size * block_count; + + kfree(fwu->read_config_buf); + fwu->read_config_buf = kzalloc(fwu->config_size, GFP_KERNEL); + if (!fwu->read_config_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc memory for config buffer\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + block_offset[1] |= (fwu->config_area << 5); + + retval = synaptics_rmi4_reg_write(rmi4_data, + fwu->f34_fd.data_base_addr + BLOCK_NUMBER_OFFSET, + block_offset, + sizeof(block_offset)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write to block number registers\n", + __func__); + goto exit; + } + + for (block_num = 0; block_num < block_count; block_num++) { + retval = fwu_write_f34_command(CMD_READ_CONFIG_BLOCK); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write read config command\n", + __func__); + goto exit; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status\n", + __func__); + goto exit; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->blk_data_off, + &fwu->read_config_buf[index], + fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block data (block %d)\n", + __func__, block_num); + goto exit; + } + + index += fwu->block_size; + } + +exit: + rmi4_data->reset_device(rmi4_data); + + return retval; +} +#endif + +static int fwu_do_lockdown(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.query_base_addr + fwu->properties_off, + &fwu->flash_properties, + sizeof(fwu->flash_properties)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties\n", + __func__); + return retval; + } + + if ((fwu->flash_properties & UNLOCKED) == 0) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device already locked down\n", + __func__); + return retval; + } + + retval = fwu_write_lockdown(); + if (retval < 0) + return retval; + + pr_notice("%s: Lockdown programmed\n", __func__); + + return retval; +} + +static int fwu_start_reflash(void) +{ + int retval = 0; + enum flash_area flash_area; + struct image_header_data header; + struct f01_device_status f01_device_status; + const unsigned char *fw_image; + const struct firmware *fw_entry = NULL; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + pr_notice("%s: Start of reflash process\n", __func__); + + if (fwu->ext_data_source) { + fw_image = fwu->ext_data_source; + } else { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Requesting firmware image %s\n", + __func__, fwu->image_name); + + retval = request_firmware(&fw_entry, fwu->image_name, + rmi4_data->pdev->dev.parent); + if (retval != 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Firmware image %s not available\n", + __func__, fwu->image_name); + rmi4_data->stay_awake = false; + return retval; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Firmware image size = %zu\n", + __func__, fw_entry->size); + + fw_image = fw_entry->data; + } + + parse_header(&header, fw_image); + + if (fwu->bl_version != header.bootloader_version) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Bootloader version mismatch\n", + __func__); + retval = -EINVAL; + goto exit; + } + + retval = fwu_read_f01_device_status(&f01_device_status); + if (retval < 0) + goto exit; + + if (f01_device_status.flash_prog) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: In flash prog mode\n", + __func__); + fwu->in_flash_prog_mode = true; + } else { + fwu->in_flash_prog_mode = false; + } + + if (fwu->do_lockdown) { + switch (fwu->bl_version) { + case V5: + case V6: + fwu->lockdown_data = fw_image + LOCKDOWN_OFFSET; + fwu->lockdown_block_count = LOCKDOWN_BLOCK_COUNT; + retval = fwu_do_lockdown(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do lockdown\n", + __func__); + } + default: + break; + } + } + + if (header.firmware_size) + fwu->firmware_data = fw_image + FW_IMAGE_OFFSET; + if (header.config_size) { + fwu->config_data = fw_image + FW_IMAGE_OFFSET + + header.firmware_size; + } + + flash_area = fwu_go_nogo(&header); + switch (flash_area) { + case UI_FIRMWARE: + retval = fwu_do_reflash(); + break; + case CONFIG_AREA: + retval = fwu_do_write_config(); + break; + case NONE: + default: + goto exit; + } + + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do reflash\n", + __func__); + } + +exit: + rmi4_data->reset_device(rmi4_data); + + if (fw_entry) + release_firmware(fw_entry); + + pr_notice("%s: End of reflash process\n", __func__); + + rmi4_data->stay_awake = false; + + return retval; +} + +int synaptics_dsx_fw_updater(unsigned char *fw_data) +{ + int retval; + + if (!fwu) + return -ENODEV; + + if (!fwu->initialized) + return -ENODEV; + + fwu->rmi4_data->fw_updating = true; + if (fwu->rmi4_data->suspended == true) { + fwu->rmi4_data->fw_updating = false; + dev_err(fwu->rmi4_data->pdev->dev.parent, + "Cannot start fw upgrade: Device is in suspend\n"); + return -EBUSY; + } + + fwu->ext_data_source = fw_data; + fwu->config_area = UI_CONFIG_AREA; + + retval = fwu_start_reflash(); + + fwu->rmi4_data->fw_updating = false; + + return retval; +} +EXPORT_SYMBOL(synaptics_dsx_fw_updater); + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS +static ssize_t fwu_sysfs_show_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + ssize_t retval; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + + if (count < fwu->config_size) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not enough space (%zu bytes) in buffer\n", + __func__, count); + retval = -EINVAL; + goto show_image_exit; + } + + memcpy(buf, fwu->read_config_buf, fwu->config_size); + retval = fwu->config_size; +show_image_exit: + mutex_unlock(&dsx_fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_store_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + ssize_t retval; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + + if (count > (fwu->image_size - fwu->data_pos)) { + dev_err(fwu->rmi4_data->pdev->dev.parent, + "%s: Not enough space in buffer\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) { + dev_err(fwu->rmi4_data->pdev->dev.parent, + "%s: Need to set imagesize\n", + __func__); + retval = -EINVAL; + goto exit; + } + + memcpy((void *)(&fwu->ext_data_source[fwu->data_pos]), + (const void *)buf, + count); + + fwu->data_pos += count; + +exit: + mutex_unlock(&dsx_fwu_sysfs_mutex); + return count; +} + +static ssize_t fwu_sysfs_force_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + + if (sscanf(buf, "%u", &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (input != 1) { + retval = -EINVAL; + goto exit; + } + + if (LOCKDOWN) + fwu->do_lockdown = true; + + fwu->force_update = true; + retval = synaptics_dsx_fw_updater(fwu->ext_data_source); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do reflash\n", + __func__); + goto exit; + } + + retval = count; +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->force_update = FORCE_UPDATE; + fwu->do_lockdown = DO_LOCKDOWN; + fwu->data_pos = 0; + fwu->image_size = 0; + mutex_unlock(&dsx_fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_do_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + + if (sscanf(buf, "%u", &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (input & LOCKDOWN) { + fwu->do_lockdown = true; + input &= ~LOCKDOWN; + } + + if ((input != NORMAL) && (input != FORCE)) { + retval = -EINVAL; + goto exit; + } + + if (input == FORCE) + fwu->force_update = true; + + retval = synaptics_dsx_fw_updater(fwu->ext_data_source); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do reflash\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->force_update = FORCE_UPDATE; + fwu->do_lockdown = DO_LOCKDOWN; + fwu->data_pos = 0; + fwu->image_size = 0; + mutex_unlock(&dsx_fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_write_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + + if (sscanf(buf, "%u", &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (input != 1) { + retval = -EINVAL; + goto exit; + } + + retval = fwu_start_write_config(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write config\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->data_pos = 0; + fwu->image_size = 0; + mutex_unlock(&dsx_fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_read_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + retval = fwu_do_read_config(); + mutex_unlock(&dsx_fwu_sysfs_mutex); + + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read config\n", + __func__); + return retval; + } + + return count; +} + +static ssize_t fwu_sysfs_config_area_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long config_area; + + retval = sstrtoul(buf, 10, &config_area); + if (retval) + return retval; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + fwu->config_area = config_area; + mutex_unlock(&dsx_fwu_sysfs_mutex); + + return count; +} + +static ssize_t fwu_sysfs_image_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t retval; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + if (strnlen(fwu->rmi4_data->fw_name, SYNA_FW_NAME_MAX_LEN) > 0) + retval = snprintf(buf, PAGE_SIZE, "%s\n", + fwu->rmi4_data->fw_name); + else + retval = snprintf(buf, PAGE_SIZE, "No firmware name given\n"); + mutex_unlock(&dsx_fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_image_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t retval; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + retval = sscanf(buf, "%49s", fwu->image_name); + mutex_unlock(&dsx_fwu_sysfs_mutex); + + if (retval != 1) + return -EINVAL; + + return count; +} + +static ssize_t fwu_sysfs_image_size_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long size; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (!mutex_trylock(&dsx_fwu_sysfs_mutex)) + return -EBUSY; + + retval = sstrtoul(buf, 10, &size); + if (retval) + goto exit; + + fwu->image_size = size; + fwu->data_pos = 0; + + kfree(fwu->ext_data_source); + fwu->ext_data_source = kzalloc(fwu->image_size, GFP_KERNEL); + if (!fwu->ext_data_source) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for image data\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + retval = count; +exit: + mutex_unlock(&dsx_fwu_sysfs_mutex); + return retval; +} + +static ssize_t fwu_sysfs_block_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fwu->block_size); +} + +static ssize_t fwu_sysfs_firmware_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fwu->fw_block_count); +} + +static ssize_t fwu_sysfs_configuration_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fwu->config_block_count); +} + +static ssize_t fwu_sysfs_perm_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fwu->perm_config_block_count); +} + +static ssize_t fwu_sysfs_bl_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fwu->bl_config_block_count); +} + +static ssize_t fwu_sysfs_disp_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fwu->disp_config_block_count); +} + +static ssize_t fwu_sysfs_config_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + unsigned char config_id[4]; + int retval; + + /* device config id */ + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.ctrl_base_addr, + config_id, + sizeof(config_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device config ID\n", + __func__); + return retval; + } + + return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n", + config_id[0], config_id[1], config_id[2], config_id[3]); +} + +static ssize_t fwu_sysfs_package_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned char package_id[PACKAGE_ID_SIZE]; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + /* read device package id */ + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr + F01_PACKAGE_ID_OFFSET, + package_id, + sizeof(package_id)); + + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device package ID\n", + __func__); + return retval; + } + + return snprintf(buf, PAGE_SIZE, "%d rev %d\n", + (package_id[1] << 8) | package_id[0], + (package_id[3] << 8) | package_id[2]); +} +#endif + +static void synaptics_rmi4_fwu_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!fwu) + return; + + if (fwu->intr_mask & intr_mask) + fwu_read_f34_flash_status(); + + return; +} + +static int synaptics_rmi4_fwu_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char attr_count; + struct pdt_properties pdt_props; + + fwu = kzalloc(sizeof(*fwu), GFP_KERNEL); + if (!fwu) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fwu\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + fwu->image_name = rmi4_data->fw_name; + + fwu->rmi4_data = rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + PDT_PROPS, + pdt_props.data, + sizeof(pdt_props.data)); + if (retval < 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Failed to read PDT properties, assuming 0x00\n", + __func__); + } else if (pdt_props.has_bsr) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Reflash for LTS not currently supported\n", + __func__); + retval = -ENODEV; + goto exit_free_fwu; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + goto exit_free_fwu; + + fwu->productinfo1 = rmi4_data->rmi4_mod_info.product_info[0]; + fwu->productinfo2 = rmi4_data->rmi4_mod_info.product_info[1]; + memcpy(fwu->product_id, rmi4_data->rmi4_mod_info.product_id_string, + SYNAPTICS_RMI4_PRODUCT_ID_SIZE); + fwu->product_id[SYNAPTICS_RMI4_PRODUCT_ID_SIZE] = 0; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: F01 product info: 0x%04x 0x%04x\n", + __func__, fwu->productinfo1, fwu->productinfo2); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: F01 product ID: %s\n", + __func__, fwu->product_id); + + retval = fwu_read_f34_queries(); + if (retval < 0) + goto exit_free_fwu; + + fwu->force_update = FORCE_UPDATE; + fwu->do_lockdown = DO_LOCKDOWN; + fwu->initialized = true; + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + retval = sysfs_create_bin_file(&rmi4_data->input_dev->dev.kobj, + &dev_attr_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs bin file\n", + __func__); + goto exit_free_fwu; + } +#endif + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto exit_remove_attrs; + } + } + + return 0; + +exit_remove_attrs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + sysfs_remove_bin_file(&rmi4_data->input_dev->dev.kobj, &dev_attr_data); +#endif + +exit_free_fwu: + kfree(fwu); + fwu = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_fwu_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + + if (!fwu) + goto exit; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + +#ifdef CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE_EXTRA_SYSFS + sysfs_remove_bin_file(&rmi4_data->input_dev->dev.kobj, &dev_attr_data); +#endif + + kfree(fwu->read_config_buf); + kfree(fwu); + fwu = NULL; + +exit: + complete(&fwu_dsx_remove_complete); + + return; +} + +static struct synaptics_rmi4_exp_fn fwu_module = { + .fn_type = RMI_FW_UPDATER, + .init = synaptics_rmi4_fwu_init, + .remove = synaptics_rmi4_fwu_remove, + .reset = NULL, + .reinit = NULL, + .early_suspend = NULL, + .suspend = NULL, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_fwu_attn, +}; + +static int __init rmi4_fw_update_module_init(void) +{ + synaptics_rmi4_dsx_new_function(&fwu_module, true); + + return 0; +} + +static void __exit rmi4_fw_update_module_exit(void) +{ + synaptics_rmi4_dsx_new_function(&fwu_module, false); + + wait_for_completion(&fwu_dsx_remove_complete); + + return; +} + +module_init(rmi4_fw_update_module_init); +module_exit(rmi4_fw_update_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX FW Update Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_i2c.c b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_i2c.c new file mode 100644 index 000000000000..0b3fbaf9f462 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_i2c.c @@ -0,0 +1,530 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. + * + * Linux foundation chooses to take subject only to the GPLv2 license terms, + * and distributes only under these terms. + * + * Copyright (C) 2012 Synaptics Incorporated + * + * Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com> + * Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx_v2.h> +#include "synaptics_dsx_core.h" +#include <linux/of_gpio.h> +#include <linux/of_irq.h> +#if defined(CONFIG_SECURE_TOUCH) +#include <linux/pm_runtime.h> +#endif + +#define SYN_I2C_RETRY_TIMES 10 +#define RESET_DELAY 100 +#define DSX_COORDS_ARR_SIZE 4 + +static int synaptics_rmi4_i2c_set_page(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr) +{ + int retval; + unsigned char retry; + unsigned char buf[PAGE_SELECT_LEN]; + unsigned char page; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + page = ((addr >> 8) & MASK_8BIT); + if (page != rmi4_data->current_page) { + buf[0] = MASK_8BIT; + buf[1] = page; + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + retval = i2c_master_send(i2c, buf, PAGE_SELECT_LEN); + if (retval != PAGE_SELECT_LEN) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + } else { + rmi4_data->current_page = page; + break; + } + } + } else { + retval = PAGE_SELECT_LEN; + } + + return retval; +} + +static int synaptics_rmi4_i2c_read(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned char retry; + unsigned char buf; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[] = { + { + .addr = i2c->addr, + .flags = 0, + .len = 1, + .buf = &buf, + }, + { + .addr = i2c->addr, + .flags = I2C_M_RD, + .len = length, + .buf = data, + }, + }; + + buf = addr & MASK_8BIT; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(i2c->adapter, msg, 2) == 2) { + retval = length; + break; + } + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C read over retry limit\n", + __func__); + retval = -EIO; + } + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int synaptics_rmi4_i2c_write(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned char retry; + unsigned char buf[length + 1]; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[] = { + { + .addr = i2c->addr, + .flags = 0, + .len = length + 1, + .buf = buf, + } + }; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + buf[0] = addr & MASK_8BIT; + memcpy(&buf[1], &data[0], length); + + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(i2c->adapter, msg, 1) == 1) { + retval = length; + break; + } + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C write over retry limit\n", + __func__); + retval = -EIO; + } + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +#if defined(CONFIG_SECURE_TOUCH) +static int synaptics_rmi4_clk_prepare_enable( + struct synaptics_rmi4_data *rmi4_data) +{ + int ret; + ret = clk_prepare_enable(rmi4_data->iface_clk); + if (ret) { + dev_err(rmi4_data->pdev->dev.parent, + "error on clk_prepare_enable(iface_clk):%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(rmi4_data->core_clk); + if (ret) { + clk_disable_unprepare(rmi4_data->iface_clk); + dev_err(rmi4_data->pdev->dev.parent, + "error clk_prepare_enable(core_clk):%d\n", ret); + } + return ret; +} + +static void synaptics_rmi4_clk_disable_unprepare( + struct synaptics_rmi4_data *rmi4_data) +{ + clk_disable_unprepare(rmi4_data->core_clk); + clk_disable_unprepare(rmi4_data->iface_clk); +} + +static int synaptics_rmi4_i2c_get(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + retval = pm_runtime_get_sync(i2c->adapter->dev.parent); + if (retval >= 0) { + retval = synaptics_rmi4_clk_prepare_enable(rmi4_data); + if (retval) + pm_runtime_put_sync(i2c->adapter->dev.parent); + } + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static void synaptics_rmi4_i2c_put(struct synaptics_rmi4_data *rmi4_data) +{ + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + synaptics_rmi4_clk_disable_unprepare(rmi4_data); + pm_runtime_put_sync(i2c->adapter->dev.parent); + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); +} +#endif + +static struct synaptics_dsx_bus_access bus_access = { + .type = BUS_I2C, + .read = synaptics_rmi4_i2c_read, + .write = synaptics_rmi4_i2c_write, +#if defined(CONFIG_SECURE_TOUCH) + .get = synaptics_rmi4_i2c_get, + .put = synaptics_rmi4_i2c_put, +#endif +}; + +static struct synaptics_dsx_hw_interface hw_if; + +static struct platform_device *synaptics_dsx_i2c_device; + +static void synaptics_rmi4_i2c_dev_release(struct device *dev) +{ + kfree(synaptics_dsx_i2c_device); + + return; +} +#ifdef CONFIG_OF +int synaptics_dsx_get_dt_coords(struct device *dev, char *name, + struct synaptics_dsx_board_data *pdata, + struct device_node *node) +{ + u32 coords[DSX_COORDS_ARR_SIZE]; + struct property *prop; + struct device_node *np = (node == NULL) ? (dev->of_node) : (node); + int coords_size, rc; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + + coords_size = prop->length / sizeof(u32); + if (coords_size != DSX_COORDS_ARR_SIZE) { + dev_err(dev, "invalid %s\n", name); + return -EINVAL; + } + + rc = of_property_read_u32_array(np, name, coords, coords_size); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read %s\n", name); + return rc; + } + + if (strcmp(name, "synaptics,panel-coords") == 0) { + pdata->panel_minx = coords[0]; + pdata->panel_miny = coords[1]; + pdata->panel_maxx = coords[2]; + pdata->panel_maxy = coords[3]; + } else if (strcmp(name, "synaptics,display-coords") == 0) { + pdata->disp_minx = coords[0]; + pdata->disp_miny = coords[1]; + pdata->disp_maxx = coords[2]; + pdata->disp_maxy = coords[3]; + } else { + dev_err(dev, "unsupported property %s\n", name); + return -EINVAL; + } + + return 0; +} + +static int synaptics_dsx_parse_dt(struct device *dev, + struct synaptics_dsx_board_data *rmi4_pdata) +{ + struct device_node *np = dev->of_node; + struct property *prop; + u32 temp_val, num_buttons; + u32 button_map[MAX_NUMBER_OF_BUTTONS]; + int rc, i; + + rmi4_pdata->x_flip = of_property_read_bool(np, "synaptics,x-flip"); + rmi4_pdata->y_flip = of_property_read_bool(np, "synaptics,y-flip"); + + rmi4_pdata->disable_gpios = of_property_read_bool(np, + "synaptics,disable-gpios"); + + rmi4_pdata->bypass_packrat_id_check = of_property_read_bool(np, + "synaptics,bypass-packrat-id-check"); + + rmi4_pdata->resume_in_workqueue = of_property_read_bool(np, + "synaptics,resume-in-workqueue"); + + rmi4_pdata->reset_delay_ms = RESET_DELAY; + rc = of_property_read_u32(np, "synaptics,reset-delay-ms", &temp_val); + if (!rc) + rmi4_pdata->reset_delay_ms = temp_val; + else if (rc != -EINVAL) { + dev_err(dev, "Unable to read reset delay\n"); + return rc; + } + + rc = of_property_read_u32(np, "synaptics,config-id", + &rmi4_pdata->config_id); + if (rc && (rc != -EINVAL)) + dev_err(dev, "Unable to read config id from DT\n"); + + rmi4_pdata->fw_name = "PRXXX_fw.img"; + rc = of_property_read_string(np, "synaptics,fw-name", + &rmi4_pdata->fw_name); + if (rc && (rc != -EINVAL)) { + dev_err(dev, "Unable to read fw name\n"); + return rc; + } + + /* reset, irq gpio info */ + rmi4_pdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, &rmi4_pdata->reset_flags); + rmi4_pdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, &rmi4_pdata->irq_flags); + + rc = synaptics_dsx_get_dt_coords(dev, "synaptics,display-coords", + rmi4_pdata, NULL); + if (rc && (rc != -EINVAL)) + return rc; + + rc = synaptics_dsx_get_dt_coords(dev, "synaptics,panel-coords", + rmi4_pdata, NULL); + if (rc && (rc != -EINVAL)) + return rc; + + rmi4_pdata->detect_device = of_property_read_bool(np, + "synaptics,detect-device"); + + if (rmi4_pdata->detect_device) + return 0; + + prop = of_find_property(np, "synaptics,button-map", NULL); + if (prop) { + num_buttons = prop->length / sizeof(temp_val); + + rmi4_pdata->cap_button_map = devm_kzalloc(dev, + sizeof(*rmi4_pdata->cap_button_map), + GFP_KERNEL); + if (!rmi4_pdata->cap_button_map) + return -ENOMEM; + + rmi4_pdata->cap_button_map->map = devm_kzalloc(dev, + sizeof(*rmi4_pdata->cap_button_map->map) * + MAX_NUMBER_OF_BUTTONS, GFP_KERNEL); + if (!rmi4_pdata->cap_button_map->map) + return -ENOMEM; + + if (num_buttons <= MAX_NUMBER_OF_BUTTONS) { + rc = of_property_read_u32_array(np, + "synaptics,button-map", button_map, + num_buttons); + if (rc) { + dev_err(dev, "Unable to read key codes\n"); + return rc; + } + for (i = 0; i < num_buttons; i++) + rmi4_pdata->cap_button_map->map[i] = + button_map[i]; + rmi4_pdata->cap_button_map->nbuttons = + num_buttons; + } else { + return -EINVAL; + } + } + return 0; +} +#else +static inline int synaptics_dsx_parse_dt(struct device *dev, + struct synaptics_dsx_board_data *rmi4_pdata) +{ + return 0; +} +#endif + +static int synaptics_rmi4_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int retval; + struct synaptics_dsx_board_data *platform_data; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "%s: SMBus byte data commands not supported by host\n", + __func__); + return -EIO; + } + + if (client->dev.of_node) { + platform_data = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_board_data), + GFP_KERNEL); + if (!platform_data) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + retval = synaptics_dsx_parse_dt(&client->dev, platform_data); + if (retval) + return retval; + } else { + platform_data = client->dev.platform_data; + } + + if (!platform_data) { + dev_err(&client->dev, + "%s: No platform data found\n", + __func__); + return -EINVAL; + } + + synaptics_dsx_i2c_device = kzalloc( + sizeof(struct platform_device), + GFP_KERNEL); + if (!synaptics_dsx_i2c_device) { + dev_err(&client->dev, + "%s: Failed to allocate memory for synaptics_dsx_i2c_device\n", + __func__); + return -ENOMEM; + } + + hw_if.board_data = platform_data; + hw_if.bus_access = &bus_access; + + synaptics_dsx_i2c_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_i2c_device->id = 0; + synaptics_dsx_i2c_device->num_resources = 0; + synaptics_dsx_i2c_device->dev.parent = &client->dev; + synaptics_dsx_i2c_device->dev.platform_data = &hw_if; + synaptics_dsx_i2c_device->dev.release = synaptics_rmi4_i2c_dev_release; + + retval = platform_device_register(synaptics_dsx_i2c_device); + if (retval) { + dev_err(&client->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_i2c_remove(struct i2c_client *client) +{ + platform_device_unregister(synaptics_dsx_i2c_device); + + return 0; +} + +static const struct i2c_device_id synaptics_rmi4_id_table[] = { + {I2C_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table); + +#ifdef CONFIG_OF +static struct of_device_id dsx_match_table[] = { + { .compatible = "synaptics,dsx",}, + { }, +}; +#else +#define dsx_match_table NULL +#endif + +static struct i2c_driver synaptics_rmi4_i2c_driver = { + .driver = { + .name = I2C_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = dsx_match_table, + }, + .probe = synaptics_rmi4_i2c_probe, + .remove = synaptics_rmi4_i2c_remove, + .id_table = synaptics_rmi4_id_table, +}; + +int synaptics_rmi4_bus_init(void) +{ + return i2c_add_driver(&synaptics_rmi4_i2c_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_init); + +void synaptics_rmi4_bus_exit(void) +{ + i2c_del_driver(&synaptics_rmi4_i2c_driver); + + return; +} +EXPORT_SYMBOL(synaptics_rmi4_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX I2C Bus Support Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_spi.c b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_spi.c new file mode 100755 index 000000000000..dd797eef3be1 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_spi.c @@ -0,0 +1,335 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012 Synaptics Incorporated + * + * Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com> + * Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define SPI_READ 0x80 +#define SPI_WRITE 0x00 + +static int synaptics_rmi4_spi_set_page(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr) +{ + int retval; + unsigned int index; + unsigned int xfer_count = PAGE_SELECT_LEN + 1; + unsigned char txbuf[xfer_count]; + unsigned char page; + struct spi_message msg; + struct spi_transfer xfers[xfer_count]; + struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + page = ((addr >> 8) & ~MASK_7BIT); + if (page != rmi4_data->current_page) { + spi_message_init(&msg); + + txbuf[0] = SPI_WRITE; + txbuf[1] = MASK_8BIT; + txbuf[2] = page; + + for (index = 0; index < xfer_count; index++) { + memset(&xfers[index], 0, sizeof(struct spi_transfer)); + xfers[index].len = 1; + xfers[index].delay_usecs = bdata->byte_delay_us; + xfers[index].tx_buf = &txbuf[index]; + spi_message_add_tail(&xfers[index], &msg); + } + + if (bdata->block_delay_us) + xfers[index - 1].delay_usecs = bdata->block_delay_us; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + rmi4_data->current_page = page; + retval = PAGE_SELECT_LEN; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete SPI transfer, error = %d\n", + __func__, retval); + } + } else { + retval = PAGE_SELECT_LEN; + } + + return retval; +} + +static int synaptics_rmi4_spi_read(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned int index; + unsigned int xfer_count = length + ADDRESS_WORD_LEN; + unsigned char txbuf[ADDRESS_WORD_LEN]; + unsigned char *rxbuf = NULL; + struct spi_message msg; + struct spi_transfer *xfers = NULL; + struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + spi_message_init(&msg); + + xfers = kcalloc(xfer_count, sizeof(struct spi_transfer), GFP_KERNEL); + if (!xfers) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate memory for xfers\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + txbuf[0] = (addr >> 8) | SPI_READ; + txbuf[1] = addr & MASK_8BIT; + + rxbuf = kmalloc(length, GFP_KERNEL); + if (!rxbuf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate memory for rxbuf\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_spi_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + for (index = 0; index < xfer_count; index++) { + xfers[index].len = 1; + xfers[index].delay_usecs = bdata->byte_delay_us; + if (index < ADDRESS_WORD_LEN) + xfers[index].tx_buf = &txbuf[index]; + else + xfers[index].rx_buf = &rxbuf[index - ADDRESS_WORD_LEN]; + spi_message_add_tail(&xfers[index], &msg); + } + + if (bdata->block_delay_us) + xfers[index - 1].delay_usecs = bdata->block_delay_us; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + memcpy(data, rxbuf, length); + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete SPI transfer, error = %d\n", + __func__, retval); + } + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + +exit: + kfree(rxbuf); + kfree(xfers); + + return retval; +} + +static int synaptics_rmi4_spi_write(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned int index; + unsigned int xfer_count = length + ADDRESS_WORD_LEN; + unsigned char *txbuf = NULL; + struct spi_message msg; + struct spi_transfer *xfers = NULL; + struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + spi_message_init(&msg); + + xfers = kcalloc(xfer_count, sizeof(struct spi_transfer), GFP_KERNEL); + if (!xfers) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate memory for xfers\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + txbuf = kmalloc(xfer_count, GFP_KERNEL); + if (!txbuf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate memory for txbuf\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + txbuf[0] = (addr >> 8) & ~SPI_READ; + txbuf[1] = addr & MASK_8BIT; + memcpy(&txbuf[ADDRESS_WORD_LEN], data, length); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_spi_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + for (index = 0; index < xfer_count; index++) { + xfers[index].len = 1; + xfers[index].delay_usecs = bdata->byte_delay_us; + xfers[index].tx_buf = &txbuf[index]; + spi_message_add_tail(&xfers[index], &msg); + } + + if (bdata->block_delay_us) + xfers[index - 1].delay_usecs = bdata->block_delay_us; + + retval = spi_sync(spi, &msg); + if (retval == 0) { + retval = length; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete SPI transfer, error = %d\n", + __func__, retval); + } + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + +exit: + kfree(txbuf); + kfree(xfers); + + return retval; +} + +static struct synaptics_dsx_bus_access bus_access = { + .type = BUS_SPI, + .read = synaptics_rmi4_spi_read, + .write = synaptics_rmi4_spi_write, +}; + +static struct synaptics_dsx_hw_interface hw_if; + +static struct platform_device *synaptics_dsx_spi_device; + +static void synaptics_rmi4_spi_dev_release(struct device *dev) +{ + kfree(synaptics_dsx_spi_device); + + return; +} + +static int synaptics_rmi4_spi_probe(struct spi_device *spi) +{ + int retval; + + if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) { + dev_err(&spi->dev, + "%s: Full duplex not supported by host\n", + __func__); + return -EIO; + } + + synaptics_dsx_spi_device = kzalloc( + sizeof(struct platform_device), + GFP_KERNEL); + if (!synaptics_dsx_spi_device) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for synaptics_dsx_spi_device\n", + __func__); + return -ENOMEM; + } + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_3; + + retval = spi_setup(spi); + if (retval < 0) { + dev_err(&spi->dev, + "%s: Failed to perform SPI setup\n", + __func__); + return retval; + } + + hw_if.board_data = spi->dev.platform_data; + hw_if.bus_access = &bus_access; + + synaptics_dsx_spi_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_spi_device->id = 0; + synaptics_dsx_spi_device->num_resources = 0; + synaptics_dsx_spi_device->dev.parent = &spi->dev; + synaptics_dsx_spi_device->dev.platform_data = &hw_if; + synaptics_dsx_spi_device->dev.release = synaptics_rmi4_spi_dev_release; + + retval = platform_device_register(synaptics_dsx_spi_device); + if (retval) { + dev_err(&spi->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_spi_remove(struct spi_device *spi) +{ + platform_device_unregister(synaptics_dsx_spi_device); + + return 0; +} + +static struct spi_driver synaptics_rmi4_spi_driver = { + .driver = { + .name = SPI_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = synaptics_rmi4_spi_probe, + .remove = __devexit_p(synaptics_rmi4_spi_remove), +}; + + +int synaptics_rmi4_bus_init(void) +{ + return spi_register_driver(&synaptics_rmi4_spi_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_init); + +void synaptics_rmi4_bus_exit(void) +{ + spi_unregister_driver(&synaptics_rmi4_spi_driver); + + return; +} +EXPORT_SYMBOL(synaptics_rmi4_bus_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX SPI Bus Support Module"); +MODULE_LICENSE("GPL v2"); |
