diff options
15 files changed, 21046 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/Kconfig b/drivers/input/touchscreen/synaptics_dsx_2.6/Kconfig new file mode 100644 index 000000000000..b40f26e0a555 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/Kconfig @@ -0,0 +1,117 @@ +# +# Synaptics DSX touchscreen driver configuration +# +menuconfig TOUCHSCREEN_SYNAPTICS_DSX + bool "Synaptics DSX touchscreen" + default N + help + Say Y here if you have a Synaptics DSX touchscreen connected + to your system. + + If unsure, say N. + +if TOUCHSCREEN_SYNAPTICS_DSX + +choice + default TOUCHSCREEN_SYNAPTICS_DSX_I2C + prompt "Synaptics DSX bus interface" +config TOUCHSCREEN_SYNAPTICS_DSX_I2C + bool "RMI over I2C" + depends on I2C +config TOUCHSCREEN_SYNAPTICS_DSX_SPI + bool "RMI over SPI" + depends on SPI_MASTER +config TOUCHSCREEN_SYNAPTICS_DSX_RMI_HID_I2C + bool "HID over I2C" + depends on I2C +endchoice + +config TOUCHSCREEN_SYNAPTICS_DSX_CORE + tristate "Synaptics DSX core driver module" + depends on I2C || SPI_MASTER + help + Say Y here to enable basic touch reporting functionality. + + 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_RMI_DEV + tristate "Synaptics DSX RMI device module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE + help + Say Y here to enable support for direct RMI register access. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_rmi_dev. + +config TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE + tristate "Synaptics DSX firmware update module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE + help + Say Y here to enable support for doing 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. + +config TOUCHSCREEN_SYNAPTICS_DSX_TEST_REPORTING + tristate "Synaptics DSX test reporting module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE + help + Say Y here to enable support for retrieving production test reports. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_test_reporting. + +config TOUCHSCREEN_SYNAPTICS_DSX_PROXIMITY + tristate "Synaptics DSX proximity module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE + help + Say Y here to enable support for proximity functionality. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_proximity. + +config TOUCHSCREEN_SYNAPTICS_DSX_ACTIVE_PEN + tristate "Synaptics DSX active pen module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE + help + Say Y here to enable support for active pen functionality. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_active_pen. + +config TOUCHSCREEN_SYNAPTICS_DSX_GESTURE + tristate "Synaptics DSX user defined gesture module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE + help + Say Y here to enable support for user defined gesture functionality. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_gesture. + +config TOUCHSCREEN_SYNAPTICS_DSX_VIDEO + tristate "Synaptics DSX video module" + depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE + help + Say Y here to enable support for video communication functionality. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called synaptics_dsx_video. + +endif diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/Makefile b/drivers/input/touchscreen/synaptics_dsx_2.6/Makefile new file mode 100644 index 000000000000..515bc17e1563 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for the Synaptics DSX touchscreen driver. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_I2C) += synaptics_dsx_i2c.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_SPI) += synaptics_dsx_spi.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_RMI_HID_I2C) += synaptics_dsx_rmi_hid_i2c.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_CORE) += synaptics_dsx_core.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_RMI_DEV) += synaptics_dsx_rmi_dev.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE) += synaptics_dsx_fw_update.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_TEST_REPORTING) += synaptics_dsx_test_reporting.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_PROXIMITY) += synaptics_dsx_proximity.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_ACTIVE_PEN) += synaptics_dsx_active_pen.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_GESTURE) += synaptics_dsx_gesture.o +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_VIDEO) += synaptics_dsx_video.o diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_active_pen.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_active_pen.c new file mode 100644 index 000000000000..0e617084e911 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_active_pen.c @@ -0,0 +1,624 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define APEN_PHYS_NAME "synaptics_dsx/active_pen" + +#define ACTIVE_PEN_MAX_PRESSURE_16BIT 65535 +#define ACTIVE_PEN_MAX_PRESSURE_8BIT 255 + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + }; + unsigned char data[2]; + }; +}; + +struct apen_data_8b_pressure { + union { + struct { + unsigned char status_pen:1; + unsigned char status_invert:1; + unsigned char status_barrel:1; + unsigned char status_reserved:5; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; + unsigned char pressure_msb; + unsigned char battery_state; + unsigned char pen_id_0_7; + unsigned char pen_id_8_15; + unsigned char pen_id_16_23; + unsigned char pen_id_24_31; + } __packed; + unsigned char data[11]; + }; +}; + +struct apen_data { + union { + struct { + unsigned char status_pen:1; + unsigned char status_invert:1; + unsigned char status_barrel:1; + unsigned char status_reserved:5; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; + unsigned char pressure_lsb; + unsigned char pressure_msb; + unsigned char battery_state; + unsigned char pen_id_0_7; + unsigned char pen_id_8_15; + unsigned char pen_id_16_23; + unsigned char pen_id_24_31; + } __packed; + unsigned char data[12]; + }; +}; + +struct synaptics_rmi4_apen_handle { + bool apen_present; + unsigned char intr_mask; + unsigned char battery_state; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short apen_data_addr; + unsigned short max_pressure; + unsigned int pen_id; + struct input_dev *apen_dev; + struct apen_data *apen_data; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct synaptics_rmi4_apen_handle *apen; + +DECLARE_COMPLETION(apen_remove_complete); + +static void apen_lift(void) +{ + input_report_key(apen->apen_dev, BTN_TOUCH, 0); + input_report_key(apen->apen_dev, BTN_TOOL_PEN, 0); + input_report_key(apen->apen_dev, BTN_TOOL_RUBBER, 0); + input_sync(apen->apen_dev); + apen->apen_present = false; + + return; +} + +static void apen_report(void) +{ + int retval; + int x; + int y; + int pressure; + static int invert = -1; + struct apen_data_8b_pressure *apen_data_8b; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->apen_data_addr, + apen->apen_data->data, + sizeof(apen->apen_data->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read active pen data\n", + __func__); + return; + } + + if (apen->apen_data->status_pen == 0) { + if (apen->apen_present) + apen_lift(); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: No active pen data\n", + __func__); + + return; + } + + x = (apen->apen_data->x_msb << 8) | (apen->apen_data->x_lsb); + y = (apen->apen_data->y_msb << 8) | (apen->apen_data->y_lsb); + + if ((x == -1) && (y == -1)) { + if (apen->apen_present) + apen_lift(); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Active pen in range but no valid x & y\n", + __func__); + + return; + } + + if (!apen->apen_present) + invert = -1; + + if (invert != -1 && invert != apen->apen_data->status_invert) + apen_lift(); + + invert = apen->apen_data->status_invert; + + if (apen->max_pressure == ACTIVE_PEN_MAX_PRESSURE_16BIT) { + pressure = (apen->apen_data->pressure_msb << 8) | + apen->apen_data->pressure_lsb; + apen->battery_state = apen->apen_data->battery_state; + apen->pen_id = (apen->apen_data->pen_id_24_31 << 24) | + (apen->apen_data->pen_id_16_23 << 16) | + (apen->apen_data->pen_id_8_15 << 8) | + apen->apen_data->pen_id_0_7; + } else { + apen_data_8b = (struct apen_data_8b_pressure *)apen->apen_data; + pressure = apen_data_8b->pressure_msb; + apen->battery_state = apen_data_8b->battery_state; + apen->pen_id = (apen_data_8b->pen_id_24_31 << 24) | + (apen_data_8b->pen_id_16_23 << 16) | + (apen_data_8b->pen_id_8_15 << 8) | + apen_data_8b->pen_id_0_7; + } + + input_report_key(apen->apen_dev, BTN_TOUCH, pressure > 0 ? 1 : 0); + input_report_key(apen->apen_dev, + apen->apen_data->status_invert > 0 ? + BTN_TOOL_RUBBER : BTN_TOOL_PEN, 1); + input_report_key(apen->apen_dev, + BTN_STYLUS, apen->apen_data->status_barrel > 0 ? + 1 : 0); + input_report_abs(apen->apen_dev, ABS_X, x); + input_report_abs(apen->apen_dev, ABS_Y, y); + input_report_abs(apen->apen_dev, ABS_PRESSURE, pressure); + + input_sync(apen->apen_dev); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Active pen: status = %d, invert = %d, barrel = %d, x = %d, y = %d, pressure = %d\n", + __func__, + apen->apen_data->status_pen, + apen->apen_data->status_invert, + apen->apen_data->status_barrel, + x, y, pressure); + + apen->apen_present = true; + + return; +} + +static void apen_set_params(void) +{ + input_set_abs_params(apen->apen_dev, ABS_X, 0, + apen->rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(apen->apen_dev, ABS_Y, 0, + apen->rmi4_data->sensor_max_y, 0, 0); + input_set_abs_params(apen->apen_dev, ABS_PRESSURE, 0, + apen->max_pressure, 0, 0); + + return; +} + +static int apen_pressure(struct synaptics_rmi4_f12_query_8 *query_8) +{ + int retval; + unsigned char ii; + unsigned char data_reg_presence; + unsigned char size_of_query_9; + unsigned char *query_9; + unsigned char *data_desc; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + data_reg_presence = query_8->data[1]; + + size_of_query_9 = query_8->size_of_query9; + query_9 = kmalloc(size_of_query_9, GFP_KERNEL); + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->query_base_addr + 9, + query_9, + size_of_query_9); + if (retval < 0) + goto exit; + + data_desc = query_9; + + for (ii = 0; ii < 6; ii++) { + if (!(data_reg_presence & (1 << ii))) + continue; /* The data register is not present */ + data_desc++; /* Jump over the size entry */ + while (*data_desc & (1 << 7)) + data_desc++; + data_desc++; /* Go to the next descriptor */ + } + + data_desc++; /* Jump over the size entry */ + /* Check for the presence of subpackets 1 and 2 */ + if ((*data_desc & (3 << 1)) == (3 << 1)) + apen->max_pressure = ACTIVE_PEN_MAX_PRESSURE_16BIT; + else + apen->max_pressure = ACTIVE_PEN_MAX_PRESSURE_8BIT; + +exit: + kfree(query_9); + + return retval; +} + +static int apen_reg_init(void) +{ + int retval; + unsigned char data_offset; + unsigned char size_of_query8; + struct synaptics_rmi4_f12_query_8 query_8; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->query_base_addr + 7, + &size_of_query8, + sizeof(size_of_query8)); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + apen->query_base_addr + 8, + query_8.data, + sizeof(query_8.data)); + if (retval < 0) + return retval; + + if ((size_of_query8 >= 2) && (query_8.data6_is_present)) { + data_offset = query_8.data0_is_present + + query_8.data1_is_present + + query_8.data2_is_present + + query_8.data3_is_present + + query_8.data4_is_present + + query_8.data5_is_present; + apen->apen_data_addr = apen->data_base_addr + data_offset; + retval = apen_pressure(&query_8); + if (retval < 0) + return retval; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Active pen support unavailable\n", + __func__); + retval = -ENODEV; + } + + return retval; +} + +static int apen_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char page; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + struct synaptics_rmi4_fn_desc fd; + struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&fd, + sizeof(fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, fd.fn_number); + switch (fd.fn_number) { + case SYNAPTICS_RMI4_F12: + goto f12_found; + break; + } + } else { + break; + } + + intr_count += fd.intr_src_count; + } + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F12\n", + __func__); + return -EINVAL; + +f12_found: + apen->query_base_addr = fd.query_base_addr | (page << 8); + apen->control_base_addr = fd.ctrl_base_addr | (page << 8); + apen->data_base_addr = fd.data_base_addr | (page << 8); + apen->command_base_addr = fd.cmd_base_addr | (page << 8); + + retval = apen_reg_init(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize active pen registers\n", + __func__); + return retval; + } + + apen->intr_mask = 0; + intr_src = fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + apen->intr_mask |= 1 << ii; + } + + rmi4_data->intr_mask[0] |= apen->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &(rmi4_data->intr_mask[0]), + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static void synaptics_rmi4_apen_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!apen) + return; + + if (apen->intr_mask & intr_mask) + apen_report(); + + return; +} + +static int synaptics_rmi4_apen_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (apen) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + apen = kzalloc(sizeof(*apen), GFP_KERNEL); + if (!apen) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for apen\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + apen->apen_data = kzalloc(sizeof(*(apen->apen_data)), GFP_KERNEL); + if (!apen->apen_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for apen_data\n", + __func__); + retval = -ENOMEM; + goto exit_free_apen; + } + + apen->rmi4_data = rmi4_data; + + retval = apen_scan_pdt(); + if (retval < 0) + goto exit_free_apen_data; + + apen->apen_dev = input_allocate_device(); + if (apen->apen_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate active pen device\n", + __func__); + retval = -ENOMEM; + goto exit_free_apen_data; + } + + apen->apen_dev->name = ACTIVE_PEN_DRIVER_NAME; + apen->apen_dev->phys = APEN_PHYS_NAME; + apen->apen_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + apen->apen_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + apen->apen_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(apen->apen_dev, rmi4_data); + + set_bit(EV_KEY, apen->apen_dev->evbit); + set_bit(EV_ABS, apen->apen_dev->evbit); + set_bit(BTN_TOUCH, apen->apen_dev->keybit); + set_bit(BTN_TOOL_PEN, apen->apen_dev->keybit); + set_bit(BTN_TOOL_RUBBER, apen->apen_dev->keybit); + set_bit(BTN_STYLUS, apen->apen_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, apen->apen_dev->propbit); +#endif + + apen_set_params(); + + retval = input_register_device(apen->apen_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register active pen device\n", + __func__); + goto exit_free_input_device; + } + + return 0; + +exit_free_input_device: + input_free_device(apen->apen_dev); + +exit_free_apen_data: + kfree(apen->apen_data); + +exit_free_apen: + kfree(apen); + apen = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_apen_remove(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + goto exit; + + input_unregister_device(apen->apen_dev); + kfree(apen->apen_data); + kfree(apen); + apen = NULL; + +exit: + complete(&apen_remove_complete); + + return; +} + +static void synaptics_rmi4_apen_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) { + synaptics_rmi4_apen_init(rmi4_data); + return; + } + + apen_lift(); + + apen_scan_pdt(); + + return; +} + +static void synaptics_rmi4_apen_reinit(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + return; + + apen_lift(); + + return; +} + +static void synaptics_rmi4_apen_e_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + return; + + apen_lift(); + + return; +} + +static void synaptics_rmi4_apen_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!apen) + return; + + apen_lift(); + + return; +} + +static struct synaptics_rmi4_exp_fn active_pen_module = { + .fn_type = RMI_ACTIVE_PEN, + .init = synaptics_rmi4_apen_init, + .remove = synaptics_rmi4_apen_remove, + .reset = synaptics_rmi4_apen_reset, + .reinit = synaptics_rmi4_apen_reinit, + .early_suspend = synaptics_rmi4_apen_e_suspend, + .suspend = synaptics_rmi4_apen_suspend, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_apen_attn, +}; + +static int __init rmi4_active_pen_module_init(void) +{ + synaptics_rmi4_new_function(&active_pen_module, true); + + return 0; +} + +static void __exit rmi4_active_pen_module_exit(void) +{ + synaptics_rmi4_new_function(&active_pen_module, false); + + wait_for_completion(&apen_remove_complete); + + return; +} + +module_init(rmi4_active_pen_module_init); +module_exit(rmi4_active_pen_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Active Pen Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.c new file mode 100644 index 000000000000..158329925a32 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.c @@ -0,0 +1,4330 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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.h> +#include "synaptics_dsx_core.h" +#ifdef KERNEL_ABOVE_2_6_38 +#include <linux/input/mt.h> +#endif + +#define INPUT_PHYS_NAME "synaptics_dsx/touch_input" +#define STYLUS_PHYS_NAME "synaptics_dsx/stylus" + +#define VIRTUAL_KEY_MAP_FILE_NAME "virtualkeys." PLATFORM_DRIVER_NAME + +#ifdef KERNEL_ABOVE_2_6_38 +#define TYPE_B_PROTOCOL +#endif + +#define WAKEUP_GESTURE false + +#define NO_0D_WHILE_2D +#define REPORT_2D_Z +#define REPORT_2D_W +/* +#define REPORT_2D_PRESSURE +*/ + +#define F12_DATA_15_WORKAROUND + +#define IGNORE_FN_INIT_FAILURE +/* +#define FB_READY_RESET +#define FB_READY_WAIT_MS 100 +#define FB_READY_TIMEOUT_S 30 +*/ +#define RPT_TYPE (1 << 0) +#define RPT_X_LSB (1 << 1) +#define RPT_X_MSB (1 << 2) +#define RPT_Y_LSB (1 << 3) +#define RPT_Y_MSB (1 << 4) +#define RPT_Z (1 << 5) +#define RPT_WX (1 << 6) +#define RPT_WY (1 << 7) +#define RPT_DEFAULT (RPT_TYPE | RPT_X_LSB | RPT_X_MSB | RPT_Y_LSB | RPT_Y_MSB) + +#define REBUILD_WORK_DELAY_MS 500 /* ms */ + +#define EXP_FN_WORK_DELAY_MS 500 /* ms */ +#define MAX_F11_TOUCH_WIDTH 15 +#define MAX_F12_TOUCH_WIDTH 255 +#define MAX_F12_TOUCH_PRESSURE 255 + +#define CHECK_STATUS_TIMEOUT_MS 100 + +#define F01_STD_QUERY_LEN 21 +#define F01_BUID_ID_OFFSET 18 + +#define STATUS_NO_ERROR 0x00 +#define STATUS_RESET_OCCURRED 0x01 +#define STATUS_INVALID_CONFIG 0x02 +#define STATUS_DEVICE_FAILURE 0x03 +#define STATUS_CONFIG_CRC_FAILURE 0x04 +#define STATUS_FIRMWARE_CRC_FAILURE 0x05 +#define STATUS_CRC_IN_PROGRESS 0x06 + +#define NORMAL_OPERATION (0 << 0) +#define SENSOR_SLEEP (1 << 0) +#define NO_SLEEP_OFF (0 << 2) +#define NO_SLEEP_ON (1 << 2) +#define CONFIGURED (1 << 7) + +#define F11_CONTINUOUS_MODE 0x00 +#define F11_WAKEUP_GESTURE_MODE 0x04 +#define F12_CONTINUOUS_MODE 0x00 +#define F12_WAKEUP_GESTURE_MODE 0x02 +#define F12_UDG_DETECT 0x0f + +static int synaptics_rmi4_check_status(struct synaptics_rmi4_data *rmi4_data, + bool *was_in_bl_mode); +static int synaptics_rmi4_free_fingers(struct synaptics_rmi4_data *rmi4_data); +static int synaptics_rmi4_reinit_device(struct synaptics_rmi4_data *rmi4_data); +static int synaptics_rmi4_reset_device(struct synaptics_rmi4_data *rmi4_data, + bool rebuild); + +#ifdef CONFIG_FB +static int synaptics_rmi4_fb_notifier_cb(struct notifier_block *self, + unsigned long event, void *data); +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +#ifndef CONFIG_FB +#define USE_EARLYSUSPEND +#endif +#endif + +#ifdef USE_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_f01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_f01_buildid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_f01_flashprog_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_0dbutton_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_0dbutton_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_suspend_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_wake_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_wake_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t synaptics_rmi4_virtual_key_map_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); + +struct synaptics_rmi4_f01_device_status { + union { + struct { + unsigned char status_code:4; + unsigned char reserved:2; + unsigned char flash_prog:1; + unsigned char unconfigured:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_query_0_5 { + union { + struct { + /* query 0 */ + unsigned char f11_query0_b0__2:3; + unsigned char has_query_9:1; + unsigned char has_query_11:1; + unsigned char has_query_12:1; + unsigned char has_query_27:1; + unsigned char has_query_28:1; + + /* query 1 */ + unsigned char num_of_fingers:3; + unsigned char has_rel:1; + unsigned char has_abs:1; + unsigned char has_gestures:1; + unsigned char has_sensitibity_adjust:1; + unsigned char f11_query1_b7:1; + + /* query 2 */ + unsigned char num_of_x_electrodes; + + /* query 3 */ + unsigned char num_of_y_electrodes; + + /* query 4 */ + unsigned char max_electrodes:7; + unsigned char f11_query4_b7:1; + + /* query 5 */ + unsigned char abs_data_size:2; + unsigned char has_anchored_finger:1; + unsigned char has_adj_hyst:1; + unsigned char has_dribble:1; + unsigned char has_bending_correction:1; + unsigned char has_large_object_suppression:1; + unsigned char has_jitter_filter:1; + } __packed; + unsigned char data[6]; + }; +}; + +struct synaptics_rmi4_f11_query_7_8 { + union { + struct { + /* query 7 */ + unsigned char has_single_tap:1; + unsigned char has_tap_and_hold:1; + unsigned char has_double_tap:1; + unsigned char has_early_tap:1; + unsigned char has_flick:1; + unsigned char has_press:1; + unsigned char has_pinch:1; + unsigned char has_chiral_scroll:1; + + /* query 8 */ + unsigned char has_palm_detect:1; + unsigned char has_rotate:1; + unsigned char has_touch_shapes:1; + unsigned char has_scroll_zones:1; + unsigned char individual_scroll_zones:1; + unsigned char has_multi_finger_scroll:1; + unsigned char has_multi_finger_scroll_edge_motion:1; + unsigned char has_multi_finger_scroll_inertia:1; + } __packed; + unsigned char data[2]; + }; +}; + +struct synaptics_rmi4_f11_query_9 { + union { + struct { + unsigned char has_pen:1; + unsigned char has_proximity:1; + unsigned char has_large_object_sensitivity:1; + unsigned char has_suppress_on_large_object_detect:1; + unsigned char has_two_pen_thresholds:1; + unsigned char has_contact_geometry:1; + unsigned char has_pen_hover_discrimination:1; + unsigned char has_pen_hover_and_edge_filters:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_query_12 { + union { + struct { + unsigned char has_small_object_detection:1; + unsigned char has_small_object_detection_tuning:1; + unsigned char has_8bit_w:1; + unsigned char has_2d_adjustable_mapping:1; + unsigned char has_general_information_2:1; + unsigned char has_physical_properties:1; + unsigned char has_finger_limit:1; + unsigned char has_linear_cofficient_2:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_query_27 { + union { + struct { + unsigned char f11_query27_b0:1; + unsigned char has_pen_position_correction:1; + unsigned char has_pen_jitter_filter_coefficient:1; + unsigned char has_group_decomposition:1; + unsigned char has_wakeup_gesture:1; + unsigned char has_small_finger_correction:1; + unsigned char has_data_37:1; + unsigned char f11_query27_b7:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f11_ctrl_6_9 { + union { + struct { + unsigned char sensor_max_x_pos_7_0; + unsigned char sensor_max_x_pos_11_8:4; + unsigned char f11_ctrl7_b4__7:4; + unsigned char sensor_max_y_pos_7_0; + unsigned char sensor_max_y_pos_11_8:4; + unsigned char f11_ctrl9_b4__7:4; + } __packed; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f11_data_1_5 { + union { + struct { + unsigned char x_position_11_4; + unsigned char y_position_11_4; + unsigned char x_position_3_0:4; + unsigned char y_position_3_0:4; + unsigned char wx:4; + unsigned char wy:4; + unsigned char z; + } __packed; + unsigned char data[5]; + }; +}; + +struct synaptics_rmi4_f12_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + struct { + unsigned char ctrl24_is_present:1; + unsigned char ctrl25_is_present:1; + unsigned char ctrl26_is_present:1; + unsigned char ctrl27_is_present:1; + unsigned char ctrl28_is_present:1; + unsigned char ctrl29_is_present:1; + unsigned char ctrl30_is_present:1; + unsigned char ctrl31_is_present:1; + } __packed; + }; + 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; + struct { + unsigned char data16_is_present:1; + unsigned char data17_is_present:1; + unsigned char data18_is_present:1; + unsigned char data19_is_present:1; + unsigned char data20_is_present:1; + unsigned char data21_is_present:1; + unsigned char data22_is_present:1; + unsigned char data23_is_present:1; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_8 { + union { + struct { + unsigned char max_x_coord_lsb; + unsigned char max_x_coord_msb; + unsigned char max_y_coord_lsb; + unsigned char max_y_coord_msb; + unsigned char rx_pitch_lsb; + unsigned char rx_pitch_msb; + unsigned char tx_pitch_lsb; + unsigned char tx_pitch_msb; + unsigned char low_rx_clip; + unsigned char high_rx_clip; + unsigned char low_tx_clip; + unsigned char high_tx_clip; + unsigned char num_of_rx; + unsigned char num_of_tx; + }; + unsigned char data[14]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_23 { + union { + struct { + unsigned char finger_enable:1; + unsigned char active_stylus_enable:1; + unsigned char palm_enable:1; + unsigned char unclassified_object_enable:1; + unsigned char hovering_finger_enable:1; + unsigned char gloved_finger_enable:1; + unsigned char f12_ctr23_00_b6__7:2; + unsigned char max_reported_objects; + unsigned char f12_ctr23_02_b0:1; + unsigned char report_active_stylus_as_finger:1; + unsigned char report_palm_as_finger:1; + unsigned char report_unclassified_object_as_finger:1; + unsigned char report_hovering_finger_as_finger:1; + unsigned char report_gloved_finger_as_finger:1; + unsigned char report_narrow_object_swipe_as_finger:1; + unsigned char report_handedge_as_finger:1; + unsigned char cover_enable:1; + unsigned char stylus_enable:1; + unsigned char eraser_enable:1; + unsigned char small_object_enable:1; + unsigned char f12_ctr23_03_b4__7:4; + unsigned char report_cover_as_finger:1; + unsigned char report_stylus_as_finger:1; + unsigned char report_eraser_as_finger:1; + unsigned char report_small_object_as_finger:1; + unsigned char f12_ctr23_04_b4__7:4; + }; + unsigned char data[5]; + }; +}; + +struct synaptics_rmi4_f12_ctrl_31 { + union { + struct { + unsigned char max_x_coord_lsb; + unsigned char max_x_coord_msb; + unsigned char max_y_coord_lsb; + unsigned char max_y_coord_msb; + unsigned char rx_pitch_lsb; + unsigned char rx_pitch_msb; + unsigned char rx_clip_low; + unsigned char rx_clip_high; + unsigned char wedge_clip_low; + unsigned char wedge_clip_high; + unsigned char num_of_p; + unsigned char num_of_q; + }; + unsigned char data[12]; + }; +}; + +struct synaptics_rmi4_f12_finger_data { + unsigned char object_type_and_status; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; +#ifdef REPORT_2D_Z + unsigned char z; +#endif +#ifdef REPORT_2D_W + unsigned char wx; + unsigned char wy; +#endif +}; + +struct synaptics_rmi4_f1a_query { + union { + struct { + unsigned char max_button_count:3; + unsigned char f1a_query0_b3__4:2; + unsigned char has_query4:1; + unsigned char has_query3:1; + unsigned char has_query2:1; + unsigned char has_general_control:1; + unsigned char has_interrupt_enable:1; + unsigned char has_multibutton_select:1; + unsigned char has_tx_rx_map:1; + unsigned char has_perbutton_threshold:1; + unsigned char has_release_threshold:1; + unsigned char has_strongestbtn_hysteresis:1; + unsigned char has_filter_strength:1; + } __packed; + unsigned char data[2]; + }; +}; + +struct synaptics_rmi4_f1a_query_4 { + union { + struct { + unsigned char has_ctrl19:1; + unsigned char f1a_query4_b1__4:4; + unsigned char has_ctrl24:1; + unsigned char f1a_query4_b6__7:2; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f1a_control_0 { + union { + struct { + unsigned char multibutton_report:2; + unsigned char filter_mode:2; + unsigned char reserved:4; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f1a_control { + struct synaptics_rmi4_f1a_control_0 general_control; + unsigned char button_int_enable; + unsigned char multi_button; + unsigned char *txrx_map; + unsigned char *button_threshold; + unsigned char button_release_threshold; + unsigned char strongest_button_hysteresis; + unsigned char filter_strength; +}; + +struct synaptics_rmi4_f1a_handle { + int button_bitmask_size; + unsigned char max_count; + unsigned char valid_button_count; + unsigned char *button_data_buffer; + unsigned char *button_map; + struct synaptics_rmi4_f1a_query button_query; + struct synaptics_rmi4_f1a_control button_control; +}; + +struct synaptics_rmi4_exp_fhandler { + struct synaptics_rmi4_exp_fn *exp_fn; + bool insert; + bool remove; + struct list_head link; +}; + +struct synaptics_rmi4_exp_fn_data { + bool initialized; + bool queue_work; + struct mutex mutex; + struct list_head list; + struct delayed_work work; + struct workqueue_struct *workqueue; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct synaptics_rmi4_exp_fn_data exp_data; + +static struct synaptics_dsx_button_map *vir_button_map; + +static struct device_attribute attrs[] = { + __ATTR(reset, S_IWUGO, + synaptics_rmi4_show_error, + synaptics_rmi4_f01_reset_store), + __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_IWUGO), + synaptics_rmi4_0dbutton_show, + synaptics_rmi4_0dbutton_store), + __ATTR(suspend, S_IWUGO, + synaptics_rmi4_show_error, + synaptics_rmi4_suspend_store), + __ATTR(wake_gesture, (S_IRUGO | S_IWUGO), + synaptics_rmi4_wake_gesture_show, + synaptics_rmi4_wake_gesture_store), +}; + +static struct kobj_attribute virtual_key_map_attr = { + .attr = { + .name = VIRTUAL_KEY_MAP_FILE_NAME, + .mode = S_IRUGO, + }, + .show = synaptics_rmi4_virtual_key_map_show, +}; + +static ssize_t synaptics_rmi4_f01_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int reset; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (sscanf(buf, "%u", &reset) != 1) + return -EINVAL; + + if (reset != 1) + return -EINVAL; + + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command, error = %d\n", + __func__, retval); + return retval; + } + + return count; +} + +static ssize_t synaptics_rmi4_f01_productinfo_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "0x%02x 0x%02x\n", + (rmi4_data->rmi4_mod_info.product_info[0]), + (rmi4_data->rmi4_mod_info.product_info[1])); +} + +static ssize_t synaptics_rmi4_f01_buildid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->firmware_id); +} + +static ssize_t synaptics_rmi4_f01_flashprog_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + struct synaptics_rmi4_f01_device_status device_status; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + device_status.data, + sizeof(device_status.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device status, error = %d\n", + __func__, retval); + return retval; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", + device_status.flash_prog); +} + +static ssize_t synaptics_rmi4_0dbutton_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->button_0d_enabled); +} + +static ssize_t synaptics_rmi4_0dbutton_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + unsigned char ii; + unsigned char intr_enable; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (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; +} + +static ssize_t synaptics_rmi4_suspend_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 1) + synaptics_rmi4_suspend(dev); + else if (input == 0) + synaptics_rmi4_resume(dev); + else + return -EINVAL; + + return count; +} + +static ssize_t synaptics_rmi4_wake_gesture_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + rmi4_data->enable_wakeup_gesture); +} + +static ssize_t synaptics_rmi4_wake_gesture_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + input = input > 0 ? 1 : 0; + + if (rmi4_data->f11_wakeup_gesture || rmi4_data->f12_wakeup_gesture) + rmi4_data->enable_wakeup_gesture = input; + + return count; +} + +static ssize_t synaptics_rmi4_virtual_key_map_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ii; + int cnt; + int count = 0; + + for (ii = 0; ii < vir_button_map->nbuttons; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, "0x01:%d:%d:%d:%d:%d\n", + vir_button_map->map[ii * 5 + 0], + vir_button_map->map[ii * 5 + 1], + vir_button_map->map[ii * 5 + 2], + vir_button_map->map[ii * 5 + 3], + vir_button_map->map[ii * 5 + 4]); + buf += cnt; + count += cnt; + } + + return count; +} + +static int synaptics_rmi4_f11_abs_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; /* number of touch points */ + unsigned char reg_index; + unsigned char finger; + unsigned char fingers_supported; + unsigned char num_of_finger_status_regs; + unsigned char finger_shift; + unsigned char finger_status; + unsigned char finger_status_reg[3]; + unsigned char detected_gestures; + unsigned short data_addr; + unsigned short data_offset; + int x; + int y; + int wx; + int wy; + int temp; + struct synaptics_rmi4_f11_data_1_5 data; + struct synaptics_rmi4_f11_extra_data *extra_data; + + /* + * The number of finger status registers is determined by the + * maximum number of fingers supported - 2 bits per finger. So + * the number of finger status registers to read is: + * register_count = ceil(max_num_of_fingers / 4) + */ + fingers_supported = fhandler->num_of_data_points; + num_of_finger_status_regs = (fingers_supported + 3) / 4; + data_addr = fhandler->full_addr.data_base; + + extra_data = (struct synaptics_rmi4_f11_extra_data *)fhandler->extra; + + if (rmi4_data->suspend && rmi4_data->enable_wakeup_gesture) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data38_offset, + &detected_gestures, + sizeof(detected_gestures)); + if (retval < 0) + return 0; + + if (detected_gestures) { + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 1); + input_sync(rmi4_data->input_dev); + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 0); + input_sync(rmi4_data->input_dev); + rmi4_data->suspend = false; + } + + return 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr, + finger_status_reg, + num_of_finger_status_regs); + if (retval < 0) + return 0; + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + + for (finger = 0; finger < fingers_supported; finger++) { + reg_index = finger / 4; + finger_shift = (finger % 4) * 2; + finger_status = (finger_status_reg[reg_index] >> finger_shift) + & MASK_2BIT; + + /* + * Each 2-bit finger status field represents the following: + * 00 = finger not present + * 01 = finger present and data accurate + * 10 = finger present but data may be inaccurate + * 11 = reserved + */ +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, finger_status); +#endif + + if (finger_status) { + data_offset = data_addr + + num_of_finger_status_regs + + (finger * sizeof(data.data)); + retval = synaptics_rmi4_reg_read(rmi4_data, + data_offset, + data.data, + sizeof(data.data)); + if (retval < 0) { + touch_count = 0; + goto exit; + } + + x = (data.x_position_11_4 << 4) | data.x_position_3_0; + y = (data.y_position_11_4 << 4) | data.y_position_3_0; + wx = data.wx; + wy = data.wy; + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = x; + x = y; + y = temp; + temp = wx; + wx = wy; + wy = temp; + } + + if (rmi4_data->hw_if->board_data->x_flip) + x = rmi4_data->sensor_max_x - x; + if (rmi4_data->hw_if->board_data->y_flip) + y = rmi4_data->sensor_max_y - y; + + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 1); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_Y, y); +#ifdef REPORT_2D_W + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, max(wx, wy)); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, min(wx, wy)); +#endif +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d: status = 0x%02x, x = %d, y = %d, wx = %d, wy = %d\n", + __func__, finger, + finger_status, + x, y, wx, wy); + + touch_count++; + } + } + + if (touch_count == 0) { + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + } + + input_sync(rmi4_data->input_dev); + +exit: + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + return touch_count; +} + +static int synaptics_rmi4_f12_abs_report(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char touch_count = 0; /* number of touch points */ + unsigned char index; + unsigned char finger; + unsigned char fingers_to_process; + unsigned char finger_status; + unsigned char size_of_2d_data; + unsigned char gesture_type; + unsigned short data_addr; + int x; + int y; + int wx; + int wy; + int temp; +#ifdef REPORT_2D_PRESSURE + int pressure; +#endif + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_f12_finger_data *data; + struct synaptics_rmi4_f12_finger_data *finger_data; + static unsigned char finger_presence; + static unsigned char stylus_presence; +#ifdef F12_DATA_15_WORKAROUND + static unsigned char objects_already_present; +#endif + + fingers_to_process = fhandler->num_of_data_points; + data_addr = fhandler->full_addr.data_base; + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + size_of_2d_data = sizeof(struct synaptics_rmi4_f12_finger_data); + + if (rmi4_data->suspend && rmi4_data->enable_wakeup_gesture) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data4_offset, + rmi4_data->gesture_detection, + sizeof(rmi4_data->gesture_detection)); + if (retval < 0) + return 0; + + gesture_type = rmi4_data->gesture_detection[0]; + + if (gesture_type && gesture_type != F12_UDG_DETECT) { + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 1); + input_sync(rmi4_data->input_dev); + input_report_key(rmi4_data->input_dev, KEY_WAKEUP, 0); + input_sync(rmi4_data->input_dev); + rmi4_data->suspend = false; + } + + return 0; + } + + /* Determine the total number of fingers to process */ + if (extra_data->data15_size) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data15_offset, + extra_data->data15_data, + extra_data->data15_size); + if (retval < 0) + return 0; + + /* Start checking from the highest bit */ + index = extra_data->data15_size - 1; /* Highest byte */ + finger = (fingers_to_process - 1) % 8; /* Highest bit */ + do { + if (extra_data->data15_data[index] & (1 << finger)) + break; + + if (finger) { + finger--; + } else if (index > 0) { + index--; /* Move to the next lower byte */ + finger = 7; + } + + fingers_to_process--; + } while (fingers_to_process); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Number of fingers to process = %d\n", + __func__, fingers_to_process); + } + +#ifdef F12_DATA_15_WORKAROUND + fingers_to_process = max(fingers_to_process, objects_already_present); +#endif + + if (!fingers_to_process) { + synaptics_rmi4_free_fingers(rmi4_data); + finger_presence = 0; + stylus_presence = 0; + return 0; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data1_offset, + (unsigned char *)fhandler->data, + fingers_to_process * size_of_2d_data); + if (retval < 0) + return 0; + + data = (struct synaptics_rmi4_f12_finger_data *)fhandler->data; + +#ifdef REPORT_2D_PRESSURE + if (rmi4_data->report_pressure) { + retval = synaptics_rmi4_reg_read(rmi4_data, + data_addr + extra_data->data23_offset, + extra_data->data23_data, + fingers_to_process); + if (retval < 0) + return 0; + } +#endif + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + + for (finger = 0; finger < fingers_to_process; finger++) { + finger_data = data + finger; + finger_status = finger_data->object_type_and_status; + +#ifdef F12_DATA_15_WORKAROUND + objects_already_present = finger + 1; +#endif + + x = (finger_data->x_msb << 8) | (finger_data->x_lsb); + y = (finger_data->y_msb << 8) | (finger_data->y_lsb); +#ifdef REPORT_2D_W + wx = finger_data->wx; + wy = finger_data->wy; +#endif + + if (rmi4_data->hw_if->board_data->swap_axes) { + temp = x; + x = y; + y = temp; + temp = wx; + wx = wy; + wy = temp; + } + + if (rmi4_data->hw_if->board_data->x_flip) + x = rmi4_data->sensor_max_x - x; + if (rmi4_data->hw_if->board_data->y_flip) + y = rmi4_data->sensor_max_y - y; + + switch (finger_status) { + case F12_FINGER_STATUS: + case F12_GLOVED_FINGER_STATUS: + /* Stylus has priority over fingers */ + if (stylus_presence) + break; +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 1); +#endif + + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 1); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 1); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_X, x); + input_report_abs(rmi4_data->input_dev, + ABS_MT_POSITION_Y, y); +#ifdef REPORT_2D_W + if (rmi4_data->wedge_sensor) { + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, wx); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, wx); + } else { + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, + max(wx, wy)); + input_report_abs(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, + min(wx, wy)); + } +#endif +#ifdef REPORT_2D_PRESSURE + if (rmi4_data->report_pressure) { + pressure = extra_data->data23_data[finger]; + input_report_abs(rmi4_data->input_dev, + ABS_MT_PRESSURE, pressure); + } +#endif +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + input_sync(rmi4_data->input_dev); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d: status = 0x%02x, x = %d, y = %d, wx = %d, wy = %d\n", + __func__, finger, + finger_status, + x, y, wx, wy); + + finger_presence = 1; + touch_count++; + break; + case F12_PALM_STATUS: + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Finger %d: x = %d, y = %d, wx = %d, wy = %d\n", + __func__, finger, + x, y, wx, wy); + break; + case F12_STYLUS_STATUS: + case F12_ERASER_STATUS: + if (!rmi4_data->stylus_enable) + break; + /* Stylus has priority over fingers */ + if (finger_presence) { + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + synaptics_rmi4_free_fingers(rmi4_data); + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + finger_presence = 0; + } + if (stylus_presence) {/* Allow one stylus at a timee */ + if (finger + 1 != stylus_presence) + break; + } + input_report_key(rmi4_data->stylus_dev, + BTN_TOUCH, 1); + if (finger_status == F12_STYLUS_STATUS) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_PEN, 1); + } else { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_RUBBER, 1); + } + input_report_abs(rmi4_data->stylus_dev, + ABS_X, x); + input_report_abs(rmi4_data->stylus_dev, + ABS_Y, y); + input_sync(rmi4_data->stylus_dev); + + stylus_presence = finger + 1; + touch_count++; + break; + default: +#ifdef TYPE_B_PROTOCOL + input_mt_slot(rmi4_data->input_dev, finger); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 0); +#endif + break; + } + } + + if (touch_count == 0) { + finger_presence = 0; +#ifdef F12_DATA_15_WORKAROUND + objects_already_present = 0; +#endif + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + input_sync(rmi4_data->input_dev); + + if (rmi4_data->stylus_enable) { + stylus_presence = 0; + input_report_key(rmi4_data->stylus_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_PEN, 0); + if (rmi4_data->eraser_enable) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_RUBBER, 0); + } + input_sync(rmi4_data->stylus_dev); + } + } + + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + 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; + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + + for (button = 0; button < f1a->valid_button_count; button++) { + index = button / 8; + shift = button % 8; + status = ((data[index] >> shift) & MASK_1BIT); + + if (current_status[button] == status) + continue; + else + current_status[button] = status; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Button %d (code %d) ->%d\n", + __func__, button, + f1a->button_map[button], + status); +#ifdef NO_0D_WHILE_2D + if (rmi4_data->fingers_on_2d == false) { + if (status == 1) { + before_2d_status[button] = 1; + } else { + if (while_2d_status[button] == 1) { + while_2d_status[button] = 0; + continue; + } else { + before_2d_status[button] = 0; + } + } + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); + } else { + if (before_2d_status[button] == 1) { + before_2d_status[button] = 0; + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); + } else { + if (status == 1) + while_2d_status[button] = 1; + else + while_2d_status[button] = 0; + } + } +#else + touch_count++; + input_report_key(rmi4_data->input_dev, + f1a->button_map[button], + status); +#endif + } + + if (touch_count) + input_sync(rmi4_data->input_dev); + + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + return; +} + +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; +} + +static void synaptics_rmi4_sensor_report(struct synaptics_rmi4_data *rmi4_data, + bool report) +{ + int retval; + unsigned char data[MAX_INTR_REGISTERS + 1]; + unsigned char *intr = &data[1]; + bool was_in_bl_mode; + struct synaptics_rmi4_f01_device_status status; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + /* + * Get interrupt status information from F01 Data1 register to + * determine the source(s) that are flagging the interrupt. + */ + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + data, + rmi4_data->num_of_intr_regs + 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read interrupt status\n", + __func__); + return; + } + + status.data[0] = data[0]; + if (status.status_code == STATUS_CRC_IN_PROGRESS) { + retval = synaptics_rmi4_check_status(rmi4_data, + &was_in_bl_mode); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to check status\n", + __func__); + return; + } + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device status\n", + __func__); + return; + } + } + if (status.unconfigured && !status.flash_prog) { + pr_notice("%s: spontaneous reset detected\n", __func__); + retval = synaptics_rmi4_reinit_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to reinit device\n", + __func__); + } + } + + if (!report) + return; + + /* + * 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; +} + +static irqreturn_t synaptics_rmi4_irq(int irq, void *data) +{ + struct synaptics_rmi4_data *rmi4_data = data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (gpio_get_value(bdata->irq_gpio) != bdata->irq_on_state) + goto exit; + + synaptics_rmi4_sensor_report(rmi4_data, true); + +exit: + return IRQ_HANDLED; +} + +static int synaptics_rmi4_int_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval = 0; + unsigned char ii; + unsigned char zero = 0x00; + unsigned char *intr_mask; + unsigned short intr_addr; + + intr_mask = rmi4_data->intr_mask; + + for (ii = 0; ii < rmi4_data->num_of_intr_regs; ii++) { + if (intr_mask[ii] != 0x00) { + intr_addr = rmi4_data->f01_ctrl_base_addr + 1 + ii; + if (enable) { + retval = synaptics_rmi4_reg_write(rmi4_data, + intr_addr, + &(intr_mask[ii]), + sizeof(intr_mask[ii])); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_write(rmi4_data, + intr_addr, + &zero, + sizeof(zero)); + if (retval < 0) + return retval; + } + } + } + + return retval; +} + +static int synaptics_rmi4_irq_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable, bool attn_only) +{ + int retval = 0; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (attn_only) { + retval = synaptics_rmi4_int_enable(rmi4_data, enable); + return retval; + } + + if (enable) { + if (rmi4_data->irq_enabled) + return retval; + + retval = synaptics_rmi4_int_enable(rmi4_data, false); + if (retval < 0) + return retval; + + /* Process and clear interrupts */ + synaptics_rmi4_sensor_report(rmi4_data, false); + + 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; + } + + retval = synaptics_rmi4_int_enable(rmi4_data, true); + if (retval < 0) + 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 void synaptics_rmi4_set_intr_mask(struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + unsigned char ii; + unsigned char intr_offset; + + fhandler->intr_reg_num = (intr_count + 7) / 8; + if (fhandler->intr_reg_num != 0) + fhandler->intr_reg_num -= 1; + + /* Set an enable bit for each data source */ + intr_offset = intr_count % 8; + fhandler->intr_mask = 0; + for (ii = intr_offset; + ii < (fd->intr_src_count + intr_offset); + ii++) + fhandler->intr_mask |= 1 << ii; + + return; +} + +static int synaptics_rmi4_f01_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + fhandler->data = NULL; + fhandler->extra = NULL; + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + rmi4_data->f01_query_base_addr = fd->query_base_addr; + rmi4_data->f01_ctrl_base_addr = fd->ctrl_base_addr; + rmi4_data->f01_data_base_addr = fd->data_base_addr; + rmi4_data->f01_cmd_base_addr = fd->cmd_base_addr; + + return 0; +} + +static int synaptics_rmi4_f11_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval; + int temp; + unsigned char offset; + unsigned char fingers_supported; + struct synaptics_rmi4_f11_extra_data *extra_data; + struct synaptics_rmi4_f11_query_0_5 query_0_5; + struct synaptics_rmi4_f11_query_7_8 query_7_8; + struct synaptics_rmi4_f11_query_9 query_9; + struct synaptics_rmi4_f11_query_12 query_12; + struct synaptics_rmi4_f11_query_27 query_27; + struct synaptics_rmi4_f11_ctrl_6_9 control_6_9; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + fhandler->extra = kmalloc(sizeof(*extra_data), GFP_KERNEL); + if (!fhandler->extra) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fhandler->extra\n", + __func__); + return -ENOMEM; + } + extra_data = (struct synaptics_rmi4_f11_extra_data *)fhandler->extra; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base, + query_0_5.data, + sizeof(query_0_5.data)); + if (retval < 0) + return retval; + + /* Maximum number of fingers supported */ + if (query_0_5.num_of_fingers <= 4) + fhandler->num_of_data_points = query_0_5.num_of_fingers + 1; + else if (query_0_5.num_of_fingers == 5) + fhandler->num_of_data_points = 10; + + rmi4_data->num_of_fingers = fhandler->num_of_data_points; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + 6, + control_6_9.data, + sizeof(control_6_9.data)); + if (retval < 0) + return retval; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = control_6_9.sensor_max_x_pos_7_0 | + (control_6_9.sensor_max_x_pos_11_8 << 8); + rmi4_data->sensor_max_y = control_6_9.sensor_max_y_pos_7_0 | + (control_6_9.sensor_max_y_pos_11_8 << 8); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x max x = %d max y = %d\n", + __func__, fhandler->fn_number, + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + + rmi4_data->max_touch_width = MAX_F11_TOUCH_WIDTH; + + if (bdata->swap_axes) { + temp = rmi4_data->sensor_max_x; + rmi4_data->sensor_max_x = rmi4_data->sensor_max_y; + rmi4_data->sensor_max_y = temp; + } + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + fhandler->data = NULL; + + offset = sizeof(query_0_5.data); + + /* query 6 */ + if (query_0_5.has_rel) + offset += 1; + + /* queries 7 8 */ + if (query_0_5.has_gestures) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_7_8.data, + sizeof(query_7_8.data)); + if (retval < 0) + return retval; + + offset += sizeof(query_7_8.data); + } + + /* query 9 */ + if (query_0_5.has_query_9) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_9.data, + sizeof(query_9.data)); + if (retval < 0) + return retval; + + offset += sizeof(query_9.data); + } + + /* query 10 */ + if (query_0_5.has_gestures && query_7_8.has_touch_shapes) + offset += 1; + + /* query 11 */ + if (query_0_5.has_query_11) + offset += 1; + + /* query 12 */ + if (query_0_5.has_query_12) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_12.data, + sizeof(query_12.data)); + if (retval < 0) + return retval; + + offset += sizeof(query_12.data); + } + + /* query 13 */ + if (query_0_5.has_jitter_filter) + offset += 1; + + /* query 14 */ + if (query_0_5.has_query_12 && query_12.has_general_information_2) + offset += 1; + + /* queries 15 16 17 18 19 20 21 22 23 24 25 26*/ + if (query_0_5.has_query_12 && query_12.has_physical_properties) + offset += 12; + + /* query 27 */ + if (query_0_5.has_query_27) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_27.data, + sizeof(query_27.data)); + if (retval < 0) + return retval; + + rmi4_data->f11_wakeup_gesture = query_27.has_wakeup_gesture; + } + + if (!rmi4_data->f11_wakeup_gesture) + return retval; + + /* data 0 */ + fingers_supported = fhandler->num_of_data_points; + offset = (fingers_supported + 3) / 4; + + /* data 1 2 3 4 5 */ + offset += 5 * fingers_supported; + + /* data 6 7 */ + if (query_0_5.has_rel) + offset += 2 * fingers_supported; + + /* data 8 */ + if (query_0_5.has_gestures && query_7_8.data[0]) + offset += 1; + + /* data 9 */ + if (query_0_5.has_gestures && (query_7_8.data[0] || query_7_8.data[1])) + offset += 1; + + /* data 10 */ + if (query_0_5.has_gestures && + (query_7_8.has_pinch || query_7_8.has_flick)) + offset += 1; + + /* data 11 12 */ + if (query_0_5.has_gestures && + (query_7_8.has_flick || query_7_8.has_rotate)) + offset += 2; + + /* data 13 */ + if (query_0_5.has_gestures && query_7_8.has_touch_shapes) + offset += (fingers_supported + 3) / 4; + + /* data 14 15 */ + if (query_0_5.has_gestures && + (query_7_8.has_scroll_zones || + query_7_8.has_multi_finger_scroll || + query_7_8.has_chiral_scroll)) + offset += 2; + + /* data 16 17 */ + if (query_0_5.has_gestures && + (query_7_8.has_scroll_zones && + query_7_8.individual_scroll_zones)) + offset += 2; + + /* data 18 19 20 21 22 23 24 25 26 27 */ + if (query_0_5.has_query_9 && query_9.has_contact_geometry) + offset += 10 * fingers_supported; + + /* data 28 */ + if (query_0_5.has_bending_correction || + query_0_5.has_large_object_suppression) + offset += 1; + + /* data 29 30 31 */ + if (query_0_5.has_query_9 && query_9.has_pen_hover_discrimination) + offset += 3; + + /* data 32 */ + if (query_0_5.has_query_12 && + query_12.has_small_object_detection_tuning) + offset += 1; + + /* data 33 34 */ + if (query_0_5.has_query_27 && query_27.f11_query27_b0) + offset += 2; + + /* data 35 */ + if (query_0_5.has_query_12 && query_12.has_8bit_w) + offset += fingers_supported; + + /* data 36 */ + if (query_0_5.has_bending_correction) + offset += 1; + + /* data 37 */ + if (query_0_5.has_query_27 && query_27.has_data_37) + offset += 1; + + /* data 38 */ + if (query_0_5.has_query_27 && query_27.has_wakeup_gesture) + extra_data->data38_offset = offset; + + return retval; +} + +static int synaptics_rmi4_f12_set_enables(struct synaptics_rmi4_data *rmi4_data, + unsigned short ctrl28) +{ + int retval; + static unsigned short ctrl_28_address; + + if (ctrl28) + ctrl_28_address = ctrl28; + + retval = synaptics_rmi4_reg_write(rmi4_data, + ctrl_28_address, + &rmi4_data->report_enable, + sizeof(rmi4_data->report_enable)); + if (retval < 0) + return retval; + + return retval; +} + +static int synaptics_rmi4_f12_ctrl_sub(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_f12_query_5 *query_5, + unsigned char ctrlreg, unsigned char subpacket) +{ + int retval; + unsigned char cnt; + unsigned char regnum; + unsigned char bitnum; + unsigned char q5_index; + unsigned char q6_index; + unsigned char offset; + unsigned char max_ctrlreg; + unsigned char *query_6; + + max_ctrlreg = (sizeof(query_5->data) - 1) * 8 - 1; + + if (ctrlreg > max_ctrlreg) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Control register number (%d) over limit\n", + __func__, ctrlreg); + return -EINVAL; + } + + q5_index = ctrlreg / 8 + 1; + bitnum = ctrlreg % 8; + if ((query_5->data[q5_index] & (1 << bitnum)) == 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Control %d is not present\n", + __func__, ctrlreg); + return -EINVAL; + } + + query_6 = kmalloc(query_5->size_of_query6, GFP_KERNEL); + if (!query_6) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query 6\n", + __func__); + return -ENOMEM; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 6, + query_6, + query_5->size_of_query6); + if (retval < 0) + goto exit; + + q6_index = 0; + + for (regnum = 0; regnum < ctrlreg; regnum++) { + q5_index = regnum / 8 + 1; + bitnum = regnum % 8; + if ((query_5->data[q5_index] & (1 << bitnum)) == 0x00) + continue; + + if (query_6[q6_index] == 0x00) + q6_index += 3; + else + q6_index++; + + while (query_6[q6_index] & ~MASK_7BIT) + q6_index++; + + q6_index++; + } + + cnt = 0; + q6_index++; + offset = subpacket / 7; + bitnum = subpacket % 7; + + do { + if (cnt == offset) { + if (query_6[q6_index + cnt] & (1 << bitnum)) + retval = 1; + else + retval = 0; + goto exit; + } + cnt++; + } while (query_6[q6_index + cnt - 1] & ~MASK_7BIT); + + retval = 0; + +exit: + kfree(query_6); + + return retval; +} + +static int synaptics_rmi4_f12_init(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count) +{ + int retval = 0; + int temp; + unsigned char subpacket; + unsigned char ctrl_23_size; + unsigned char size_of_2d_data; + unsigned char size_of_query8; + unsigned char ctrl_8_offset; + unsigned char ctrl_20_offset; + unsigned char ctrl_23_offset; + unsigned char ctrl_28_offset; + unsigned char ctrl_31_offset; + unsigned char num_of_fingers; + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_f12_query_5 *query_5 = NULL; + struct synaptics_rmi4_f12_query_8 *query_8 = NULL; + struct synaptics_rmi4_f12_ctrl_8 *ctrl_8 = NULL; + struct synaptics_rmi4_f12_ctrl_23 *ctrl_23 = NULL; + struct synaptics_rmi4_f12_ctrl_31 *ctrl_31 = NULL; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + fhandler->fn_number = fd->fn_number; + fhandler->num_of_data_sources = fd->intr_src_count; + fhandler->extra = kmalloc(sizeof(*extra_data), GFP_KERNEL); + if (!fhandler->extra) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fhandler->extra\n", + __func__); + return -ENOMEM; + } + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + size_of_2d_data = sizeof(struct synaptics_rmi4_f12_finger_data); + + query_5 = kmalloc(sizeof(*query_5), GFP_KERNEL); + if (!query_5) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query_5\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + query_8 = kmalloc(sizeof(*query_8), GFP_KERNEL); + if (!query_8) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query_8\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + ctrl_8 = kmalloc(sizeof(*ctrl_8), GFP_KERNEL); + if (!ctrl_8) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_8\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + ctrl_23 = kmalloc(sizeof(*ctrl_23), GFP_KERNEL); + if (!ctrl_23) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_23\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + ctrl_31 = kmalloc(sizeof(*ctrl_31), GFP_KERNEL); + if (!ctrl_31) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_31\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 5, + query_5->data, + sizeof(query_5->data)); + if (retval < 0) + goto exit; + + ctrl_8_offset = query_5->ctrl0_is_present + + query_5->ctrl1_is_present + + query_5->ctrl2_is_present + + query_5->ctrl3_is_present + + query_5->ctrl4_is_present + + query_5->ctrl5_is_present + + query_5->ctrl6_is_present + + query_5->ctrl7_is_present; + + ctrl_20_offset = ctrl_8_offset + + query_5->ctrl8_is_present + + query_5->ctrl9_is_present + + query_5->ctrl10_is_present + + query_5->ctrl11_is_present + + query_5->ctrl12_is_present + + query_5->ctrl13_is_present + + query_5->ctrl14_is_present + + query_5->ctrl15_is_present + + query_5->ctrl16_is_present + + query_5->ctrl17_is_present + + query_5->ctrl18_is_present + + query_5->ctrl19_is_present; + + ctrl_23_offset = ctrl_20_offset + + query_5->ctrl20_is_present + + query_5->ctrl21_is_present + + query_5->ctrl22_is_present; + + ctrl_28_offset = ctrl_23_offset + + query_5->ctrl23_is_present + + query_5->ctrl24_is_present + + query_5->ctrl25_is_present + + query_5->ctrl26_is_present + + query_5->ctrl27_is_present; + + ctrl_31_offset = ctrl_28_offset + + query_5->ctrl28_is_present + + query_5->ctrl29_is_present + + query_5->ctrl30_is_present; + + ctrl_23_size = 2; + for (subpacket = 2; subpacket <= 4; subpacket++) { + retval = synaptics_rmi4_f12_ctrl_sub(rmi4_data, + fhandler, query_5, 23, subpacket); + if (retval == 1) + ctrl_23_size++; + else if (retval < 0) + goto exit; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_23_offset, + ctrl_23->data, + ctrl_23_size); + if (retval < 0) + goto exit; + + /* Maximum number of fingers supported */ + fhandler->num_of_data_points = min_t(unsigned char, + ctrl_23->max_reported_objects, + (unsigned char)F12_FINGERS_TO_SUPPORT); + + num_of_fingers = fhandler->num_of_data_points; + rmi4_data->num_of_fingers = num_of_fingers; + + rmi4_data->stylus_enable = ctrl_23->stylus_enable; + rmi4_data->eraser_enable = ctrl_23->eraser_enable; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 7, + &size_of_query8, + sizeof(size_of_query8)); + if (retval < 0) + goto exit; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + 8, + query_8->data, + size_of_query8); + if (retval < 0) + goto exit; + + /* Determine the presence of the Data0 register */ + extra_data->data1_offset = query_8->data0_is_present; + + if ((size_of_query8 >= 3) && (query_8->data15_is_present)) { + extra_data->data15_offset = query_8->data0_is_present + + query_8->data1_is_present + + query_8->data2_is_present + + query_8->data3_is_present + + query_8->data4_is_present + + query_8->data5_is_present + + query_8->data6_is_present + + query_8->data7_is_present + + query_8->data8_is_present + + query_8->data9_is_present + + query_8->data10_is_present + + query_8->data11_is_present + + query_8->data12_is_present + + query_8->data13_is_present + + query_8->data14_is_present; + extra_data->data15_size = (num_of_fingers + 7) / 8; + } else { + extra_data->data15_size = 0; + } + +#ifdef REPORT_2D_PRESSURE + if ((size_of_query8 >= 4) && (query_8->data23_is_present)) { + extra_data->data23_offset = query_8->data0_is_present + + query_8->data1_is_present + + query_8->data2_is_present + + query_8->data3_is_present + + query_8->data4_is_present + + query_8->data5_is_present + + query_8->data6_is_present + + query_8->data7_is_present + + query_8->data8_is_present + + query_8->data9_is_present + + query_8->data10_is_present + + query_8->data11_is_present + + query_8->data12_is_present + + query_8->data13_is_present + + query_8->data14_is_present + + query_8->data15_is_present + + query_8->data16_is_present + + query_8->data17_is_present + + query_8->data18_is_present + + query_8->data19_is_present + + query_8->data20_is_present + + query_8->data21_is_present + + query_8->data22_is_present; + extra_data->data23_size = num_of_fingers; + rmi4_data->report_pressure = true; + } else { + extra_data->data23_size = 0; + rmi4_data->report_pressure = false; + } +#endif + + rmi4_data->report_enable = RPT_DEFAULT; +#ifdef REPORT_2D_Z + rmi4_data->report_enable |= RPT_Z; +#endif +#ifdef REPORT_2D_W + rmi4_data->report_enable |= (RPT_WX | RPT_WY); +#endif + + retval = synaptics_rmi4_f12_set_enables(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_28_offset); + if (retval < 0) + goto exit; + + if (query_5->ctrl8_is_present) { + rmi4_data->wedge_sensor = false; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_8_offset, + ctrl_8->data, + sizeof(ctrl_8->data)); + if (retval < 0) + goto exit; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = + ((unsigned int)ctrl_8->max_x_coord_lsb << 0) | + ((unsigned int)ctrl_8->max_x_coord_msb << 8); + rmi4_data->sensor_max_y = + ((unsigned int)ctrl_8->max_y_coord_lsb << 0) | + ((unsigned int)ctrl_8->max_y_coord_msb << 8); + + rmi4_data->max_touch_width = MAX_F12_TOUCH_WIDTH; + } else { + rmi4_data->wedge_sensor = true; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + ctrl_31_offset, + ctrl_31->data, + sizeof(ctrl_31->data)); + if (retval < 0) + goto exit; + + /* Maximum x and y */ + rmi4_data->sensor_max_x = + ((unsigned int)ctrl_31->max_x_coord_lsb << 0) | + ((unsigned int)ctrl_31->max_x_coord_msb << 8); + rmi4_data->sensor_max_y = + ((unsigned int)ctrl_31->max_y_coord_lsb << 0) | + ((unsigned int)ctrl_31->max_y_coord_msb << 8); + + rmi4_data->max_touch_width = MAX_F12_TOUCH_WIDTH; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Function %02x max x = %d max y = %d\n", + __func__, fhandler->fn_number, + rmi4_data->sensor_max_x, + rmi4_data->sensor_max_y); + + if (bdata->swap_axes) { + temp = rmi4_data->sensor_max_x; + rmi4_data->sensor_max_x = rmi4_data->sensor_max_y; + rmi4_data->sensor_max_y = temp; + } + + rmi4_data->f12_wakeup_gesture = query_5->ctrl27_is_present; + if (rmi4_data->f12_wakeup_gesture) { + extra_data->ctrl20_offset = ctrl_20_offset; + extra_data->data4_offset = query_8->data0_is_present + + query_8->data1_is_present + + query_8->data2_is_present + + query_8->data3_is_present; + } + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + /* Allocate memory for finger data storage space */ + fhandler->data_size = num_of_fingers * size_of_2d_data; + fhandler->data = kmalloc(fhandler->data_size, GFP_KERNEL); + if (!fhandler->data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fhandler->data\n", + __func__); + retval = -ENOMEM; + goto exit; + } + +exit: + kfree(query_5); + kfree(query_8); + kfree(ctrl_8); + kfree(ctrl_23); + kfree(ctrl_31); + + return retval; +} + +static int synaptics_rmi4_f1a_alloc_mem(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + struct synaptics_rmi4_f1a_handle *f1a; + + f1a = kzalloc(sizeof(*f1a), GFP_KERNEL); + if (!f1a) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for function handle\n", + __func__); + return -ENOMEM; + } + + fhandler->data = (void *)f1a; + fhandler->extra = NULL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base, + f1a->button_query.data, + sizeof(f1a->button_query.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read query registers\n", + __func__); + return retval; + } + + f1a->max_count = f1a->button_query.max_button_count + 1; + + f1a->button_control.txrx_map = kzalloc(f1a->max_count * 2, GFP_KERNEL); + if (!f1a->button_control.txrx_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for tx rx mapping\n", + __func__); + return -ENOMEM; + } + + f1a->button_bitmask_size = (f1a->max_count + 7) / 8; + + f1a->button_data_buffer = kcalloc(f1a->button_bitmask_size, + sizeof(*(f1a->button_data_buffer)), GFP_KERNEL); + if (!f1a->button_data_buffer) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for data buffer\n", + __func__); + return -ENOMEM; + } + + f1a->button_map = kcalloc(f1a->max_count, + sizeof(*(f1a->button_map)), GFP_KERNEL); + if (!f1a->button_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for button map\n", + __func__); + return -ENOMEM; + } + + return 0; +} + +static int synaptics_rmi4_f1a_button_map(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler) +{ + int retval; + unsigned char ii; + unsigned char offset = 0; + struct synaptics_rmi4_f1a_query_4 query_4; + struct synaptics_rmi4_f1a_handle *f1a = fhandler->data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + offset = f1a->button_query.has_general_control + + f1a->button_query.has_interrupt_enable + + f1a->button_query.has_multibutton_select; + + if (f1a->button_query.has_tx_rx_map) { + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + offset, + f1a->button_control.txrx_map, + f1a->max_count * 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read tx rx mapping\n", + __func__); + return retval; + } + + rmi4_data->button_txrx_mapping = f1a->button_control.txrx_map; + } + + if (f1a->button_query.has_query4) { + offset = 2 + f1a->button_query.has_query2 + + f1a->button_query.has_query3; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.query_base + offset, + query_4.data, + sizeof(query_4.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read button features 4\n", + __func__); + return retval; + } + + if (query_4.has_ctrl24) + rmi4_data->external_afe_buttons = true; + else + rmi4_data->external_afe_buttons = false; + } + + if (!bdata->cap_button_map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: cap_button_map is NULL in board file\n", + __func__); + return -ENODEV; + } else if (!bdata->cap_button_map->map) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Button map is missing in board file\n", + __func__); + return -ENODEV; + } else { + if (bdata->cap_button_map->nbuttons != f1a->max_count) { + f1a->valid_button_count = min(f1a->max_count, + bdata->cap_button_map->nbuttons); + } else { + f1a->valid_button_count = f1a->max_count; + } + + for (ii = 0; ii < f1a->valid_button_count; ii++) + f1a->button_map[ii] = bdata->cap_button_map->map[ii]; + } + + 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; + + synaptics_rmi4_set_intr_mask(fhandler, fd, intr_count); + + retval = synaptics_rmi4_f1a_alloc_mem(rmi4_data, fhandler); + if (retval < 0) + goto error_exit; + + retval = synaptics_rmi4_f1a_button_map(rmi4_data, fhandler); + if (retval < 0) + goto error_exit; + + rmi4_data->button_0d_enabled = 1; + + return 0; + +error_exit: + synaptics_rmi4_f1a_kfree(fhandler); + + return retval; +} + +static void synaptics_rmi4_empty_fn_list(struct synaptics_rmi4_data *rmi4_data) +{ + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_fn *fhandler_temp; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry_safe(fhandler, + fhandler_temp, + &rmi->support_fn_list, + link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F1A) { + synaptics_rmi4_f1a_kfree(fhandler); + } else { + kfree(fhandler->extra); + kfree(fhandler->data); + } + list_del(&fhandler->link); + kfree(fhandler); + } + } + INIT_LIST_HEAD(&rmi->support_fn_list); + + 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; + struct synaptics_rmi4_f01_device_status status; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) + return retval; + + while (status.status_code == STATUS_CRC_IN_PROGRESS) { + if (timeout > 0) + msleep(20); + else + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_data_base_addr, + status.data, + sizeof(status.data)); + if (retval < 0) + return retval; + + timeout -= 20; + } + + if (timeout != CHECK_STATUS_TIMEOUT_MS) + *was_in_bl_mode = true; + + if (status.flash_prog == 1) { + rmi4_data->flash_prog_mode = true; + pr_notice("%s: In flash prog mode, status = 0x%02x\n", + __func__, + status.status_code); + } else { + rmi4_data->flash_prog_mode = false; + } + + return 0; +} + +static 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_query_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char page_number; + unsigned char intr_count; + unsigned char *f01_query; + unsigned short pdt_entry_addr; + bool f01found; + bool f35found; + bool was_in_bl_mode; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + +rescan_pdt: + f01found = false; + f35found = false; + was_in_bl_mode = false; + intr_count = 0; + INIT_LIST_HEAD(&rmi->support_fn_list); + + /* Scan the page description tables of the pages to service */ + for (page_number = 0; page_number < PAGES_TO_SERVICE; page_number++) { + for (pdt_entry_addr = PDT_START; pdt_entry_addr > PDT_END; + pdt_entry_addr -= PDT_ENTRY_SIZE) { + pdt_entry_addr |= (page_number << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + pdt_entry_addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + pdt_entry_addr &= ~(MASK_8BIT << 8); + + fhandler = NULL; + + if (rmi_fd.fn_number == 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Reached end of PDT\n", + __func__); + break; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: F%02x found (page %d)\n", + __func__, rmi_fd.fn_number, + page_number); + + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F01: + if (rmi_fd.intr_src_count == 0) + break; + + f01found = true; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f01_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_check_status(rmi4_data, + &was_in_bl_mode); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to check status\n", + __func__); + return retval; + } + + if (was_in_bl_mode) { + kfree(fhandler); + fhandler = NULL; + goto rescan_pdt; + } + + if (rmi4_data->flash_prog_mode) + goto flash_prog_mode; + + break; + case SYNAPTICS_RMI4_F11: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f11_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + break; + case SYNAPTICS_RMI4_F12: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f12_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) + return retval; + break; + case SYNAPTICS_RMI4_F1A: + if (rmi_fd.intr_src_count == 0) + break; + + retval = synaptics_rmi4_alloc_fh(&fhandler, + &rmi_fd, page_number); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc for F%d\n", + __func__, + rmi_fd.fn_number); + return retval; + } + + retval = synaptics_rmi4_f1a_init(rmi4_data, + fhandler, &rmi_fd, intr_count); + if (retval < 0) { +#ifdef IGNORE_FN_INIT_FAILURE + kfree(fhandler); + fhandler = NULL; +#else + return retval; +#endif + } + break; + case SYNAPTICS_RMI4_F35: + f35found = true; + break; + } + + /* Accumulate the interrupt count */ + intr_count += rmi_fd.intr_src_count; + + if (fhandler && rmi_fd.intr_src_count) { + list_add_tail(&fhandler->link, + &rmi->support_fn_list); + } + } + } + + if (!f01found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F01\n", + __func__); + if (!f35found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F35\n", + __func__); + return -EINVAL; + } else { + pr_notice("%s: In microbootloader mode\n", + __func__); + return 0; + } + } + +flash_prog_mode: + rmi4_data->num_of_intr_regs = (intr_count + 7) / 8; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Number of interrupt registers = %d\n", + __func__, rmi4_data->num_of_intr_regs); + + f01_query = kmalloc(F01_STD_QUERY_LEN, GFP_KERNEL); + if (!f01_query) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for f01_query\n", + __func__); + return -ENOMEM; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr, + f01_query, + F01_STD_QUERY_LEN); + if (retval < 0) { + kfree(f01_query); + return retval; + } + + /* RMI Version 4.0 currently supported */ + rmi->version_major = 4; + rmi->version_minor = 0; + + rmi->manufacturer_id = f01_query[0]; + rmi->product_props = f01_query[1]; + rmi->product_info[0] = f01_query[2]; + rmi->product_info[1] = f01_query[3]; + retval = secure_memcpy(rmi->product_id_string, + sizeof(rmi->product_id_string), + &f01_query[11], + F01_STD_QUERY_LEN - 11, + PRODUCT_ID_SIZE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy product ID string\n", + __func__); + } + + kfree(f01_query); + + if (rmi->manufacturer_id != 1) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Non-Synaptics device found, manufacturer ID = %d\n", + __func__, rmi->manufacturer_id); + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_query_base_addr + F01_BUID_ID_OFFSET, + rmi->build_id, + sizeof(rmi->build_id)); + if (retval < 0) + return retval; + + rmi4_data->firmware_id = (unsigned int)rmi->build_id[0] + + (unsigned int)rmi->build_id[1] * 0x100 + + (unsigned int)rmi->build_id[2] * 0x10000; + + memset(rmi4_data->intr_mask, 0x00, sizeof(rmi4_data->intr_mask)); + + /* + * Map out the interrupt bit masks for the interrupt sources + * from the registered function handlers. + */ + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + rmi4_data->intr_mask[fhandler->intr_reg_num] |= + fhandler->intr_mask; + } + } + } + + if (rmi4_data->f11_wakeup_gesture || rmi4_data->f12_wakeup_gesture) + rmi4_data->enable_wakeup_gesture = WAKEUP_GESTURE; + else + rmi4_data->enable_wakeup_gesture = false; + + synaptics_rmi4_set_configured(rmi4_data); + + return 0; +} + +static int synaptics_rmi4_gpio_setup(int gpio, bool config, int dir, int state) +{ + int retval = 0; + unsigned char buf[16]; + + if (config) { + snprintf(buf, PAGE_SIZE, "dsx_gpio_%u\n", gpio); + + retval = gpio_request(gpio, buf); + if (retval) { + pr_err("%s: Failed to get gpio %d (code: %d)", + __func__, gpio, retval); + return retval; + } + + if (dir == 0) + retval = gpio_direction_input(gpio); + else + retval = gpio_direction_output(gpio, state); + if (retval) { + pr_err("%s: Failed to set gpio %d direction", + __func__, gpio); + return retval; + } + } else { + gpio_free(gpio); + } + + return retval; +} + +static void synaptics_rmi4_set_params(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char ii; + struct synaptics_rmi4_f1a_handle *f1a; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_POSITION_X, 0, + rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_POSITION_Y, 0, + rmi4_data->sensor_max_y, 0, 0); +#ifdef REPORT_2D_W + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_TOUCH_MAJOR, 0, + rmi4_data->max_touch_width, 0, 0); + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_TOUCH_MINOR, 0, + rmi4_data->max_touch_width, 0, 0); +#endif + +#ifdef REPORT_2D_PRESSURE + if (rmi4_data->report_pressure) { + input_set_abs_params(rmi4_data->input_dev, + ABS_MT_PRESSURE, 0, + MAX_F12_TOUCH_PRESSURE, 0, 0); + } +#endif + +#ifdef TYPE_B_PROTOCOL +#ifdef KERNEL_ABOVE_3_6 + input_mt_init_slots(rmi4_data->input_dev, + rmi4_data->num_of_fingers, INPUT_MT_DIRECT); +#else + input_mt_init_slots(rmi4_data->input_dev, + rmi4_data->num_of_fingers); +#endif +#endif + + 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]); + } + } + + if (vir_button_map->nbuttons) { + for (ii = 0; ii < vir_button_map->nbuttons; ii++) { + set_bit(vir_button_map->map[ii * 5], + rmi4_data->input_dev->keybit); + input_set_capability(rmi4_data->input_dev, + EV_KEY, vir_button_map->map[ii * 5]); + } + } + + if (rmi4_data->f11_wakeup_gesture || rmi4_data->f12_wakeup_gesture) { + set_bit(KEY_WAKEUP, rmi4_data->input_dev->keybit); + input_set_capability(rmi4_data->input_dev, EV_KEY, KEY_WAKEUP); + } + + return; +} + +static int synaptics_rmi4_set_input_dev(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + rmi4_data->input_dev = input_allocate_device(); + if (rmi4_data->input_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate input device\n", + __func__); + retval = -ENOMEM; + goto err_input_device; + } + + retval = synaptics_rmi4_query_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to query device\n", + __func__); + goto err_query_device; + } + + rmi4_data->input_dev->name = PLATFORM_DRIVER_NAME; + rmi4_data->input_dev->phys = INPUT_PHYS_NAME; + rmi4_data->input_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + rmi4_data->input_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + rmi4_data->input_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(rmi4_data->input_dev, rmi4_data); + + set_bit(EV_SYN, rmi4_data->input_dev->evbit); + set_bit(EV_KEY, rmi4_data->input_dev->evbit); + set_bit(EV_ABS, rmi4_data->input_dev->evbit); + set_bit(BTN_TOUCH, rmi4_data->input_dev->keybit); + set_bit(BTN_TOOL_FINGER, rmi4_data->input_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, rmi4_data->input_dev->propbit); +#endif + + if (bdata->max_y_for_2d >= 0) + rmi4_data->sensor_max_y = bdata->max_y_for_2d; + + synaptics_rmi4_set_params(rmi4_data); + + retval = input_register_device(rmi4_data->input_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register input device\n", + __func__); + goto err_register_input; + } + + if (!rmi4_data->stylus_enable) + return 0; + + rmi4_data->stylus_dev = input_allocate_device(); + if (rmi4_data->stylus_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate stylus device\n", + __func__); + retval = -ENOMEM; + goto err_stylus_device; + } + + rmi4_data->stylus_dev->name = STYLUS_DRIVER_NAME; + rmi4_data->stylus_dev->phys = STYLUS_PHYS_NAME; + rmi4_data->stylus_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + rmi4_data->stylus_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + rmi4_data->stylus_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(rmi4_data->stylus_dev, rmi4_data); + + set_bit(EV_KEY, rmi4_data->stylus_dev->evbit); + set_bit(EV_ABS, rmi4_data->stylus_dev->evbit); + set_bit(BTN_TOUCH, rmi4_data->stylus_dev->keybit); + set_bit(BTN_TOOL_PEN, rmi4_data->stylus_dev->keybit); + if (rmi4_data->eraser_enable) + set_bit(BTN_TOOL_RUBBER, rmi4_data->stylus_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, rmi4_data->stylus_dev->propbit); +#endif + + input_set_abs_params(rmi4_data->stylus_dev, ABS_X, 0, + rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(rmi4_data->stylus_dev, ABS_Y, 0, + rmi4_data->sensor_max_y, 0, 0); + + retval = input_register_device(rmi4_data->stylus_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register stylus device\n", + __func__); + goto err_register_stylus; + } + + return 0; + +err_register_stylus: + rmi4_data->stylus_dev = NULL; + +err_stylus_device: + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + +err_register_input: +err_query_device: + synaptics_rmi4_empty_fn_list(rmi4_data); + input_free_device(rmi4_data->input_dev); + +err_input_device: + return retval; +} + +static int synaptics_rmi4_set_gpio(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + retval = synaptics_rmi4_gpio_setup( + bdata->irq_gpio, + true, 0, 0); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure attention GPIO\n", + __func__); + goto err_gpio_irq; + } + + if (bdata->power_gpio >= 0) { + retval = synaptics_rmi4_gpio_setup( + bdata->power_gpio, + true, 1, !bdata->power_on_state); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure power GPIO\n", + __func__); + goto err_gpio_power; + } + } + + if (bdata->reset_gpio >= 0) { + retval = synaptics_rmi4_gpio_setup( + bdata->reset_gpio, + true, 1, !bdata->reset_on_state); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to configure reset GPIO\n", + __func__); + goto err_gpio_reset; + } + } + + if (bdata->power_gpio >= 0) { + gpio_set_value(bdata->power_gpio, bdata->power_on_state); + msleep(bdata->power_delay_ms); + } + + if (bdata->reset_gpio >= 0) { + gpio_set_value(bdata->reset_gpio, bdata->reset_on_state); + msleep(bdata->reset_active_ms); + gpio_set_value(bdata->reset_gpio, !bdata->reset_on_state); + msleep(bdata->reset_delay_ms); + } + + return 0; + +err_gpio_reset: + if (bdata->power_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->power_gpio, false, 0, 0); + +err_gpio_power: + synaptics_rmi4_gpio_setup(bdata->irq_gpio, false, 0, 0); + +err_gpio_irq: + return retval; +} + +static int synaptics_rmi4_get_reg(struct synaptics_rmi4_data *rmi4_data, + bool get) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (!get) { + retval = 0; + goto regulator_put; + } + + if ((bdata->pwr_reg_name != NULL) && (*bdata->pwr_reg_name != 0)) { + rmi4_data->pwr_reg = regulator_get(rmi4_data->pdev->dev.parent, + bdata->pwr_reg_name); + if (IS_ERR(rmi4_data->pwr_reg)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get power regulator\n", + __func__); + retval = PTR_ERR(rmi4_data->pwr_reg); + goto regulator_put; + } + } + + if ((bdata->bus_reg_name != NULL) && (*bdata->bus_reg_name != 0)) { + rmi4_data->bus_reg = regulator_get(rmi4_data->pdev->dev.parent, + bdata->bus_reg_name); + if (IS_ERR(rmi4_data->bus_reg)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to get bus pullup regulator\n", + __func__); + retval = PTR_ERR(rmi4_data->bus_reg); + goto regulator_put; + } + } + + return 0; + +regulator_put: + if (rmi4_data->pwr_reg) { + regulator_put(rmi4_data->pwr_reg); + rmi4_data->pwr_reg = NULL; + } + + if (rmi4_data->bus_reg) { + regulator_put(rmi4_data->bus_reg); + rmi4_data->bus_reg = NULL; + } + + return retval; +} + +static int synaptics_rmi4_enable_reg(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (!enable) { + retval = 0; + goto disable_pwr_reg; + } + + if (rmi4_data->bus_reg) { + retval = regulator_enable(rmi4_data->bus_reg); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable bus pullup regulator\n", + __func__); + goto exit; + } + } + + if (rmi4_data->pwr_reg) { + retval = regulator_enable(rmi4_data->pwr_reg); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable power regulator\n", + __func__); + goto disable_bus_reg; + } + msleep(bdata->power_delay_ms); + } + + return 0; + +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 synaptics_rmi4_free_fingers(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char ii; + + mutex_lock(&(rmi4_data->rmi4_report_mutex)); + +#ifdef TYPE_B_PROTOCOL + for (ii = 0; ii < rmi4_data->num_of_fingers; ii++) { + input_mt_slot(rmi4_data->input_dev, ii); + input_mt_report_slot_state(rmi4_data->input_dev, + MT_TOOL_FINGER, 0); + } +#endif + input_report_key(rmi4_data->input_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->input_dev, + BTN_TOOL_FINGER, 0); +#ifndef TYPE_B_PROTOCOL + input_mt_sync(rmi4_data->input_dev); +#endif + input_sync(rmi4_data->input_dev); + + if (rmi4_data->stylus_enable) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOUCH, 0); + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_PEN, 0); + if (rmi4_data->eraser_enable) { + input_report_key(rmi4_data->stylus_dev, + BTN_TOOL_RUBBER, 0); + } + input_sync(rmi4_data->stylus_dev); + } + + mutex_unlock(&(rmi4_data->rmi4_report_mutex)); + + rmi4_data->fingers_on_2d = false; + + return 0; +} + +static int synaptics_rmi4_sw_reset(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char command = 0x01; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_cmd_base_addr, + &command, + sizeof(command)); + if (retval < 0) + return retval; + + msleep(rmi4_data->hw_if->board_data->reset_delay_ms); + + if (rmi4_data->hw_if->ui_hw_init) { + retval = rmi4_data->hw_if->ui_hw_init(rmi4_data); + if (retval < 0) + return retval; + } + + return 0; +} + +static void synaptics_rmi4_rebuild_work(struct work_struct *work) +{ + int retval; + unsigned char attr_count; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct synaptics_rmi4_data *rmi4_data = + container_of(delayed_work, struct synaptics_rmi4_data, + rb_work); + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + mutex_lock(&exp_data.mutex); + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->remove != NULL) + exp_fhandler->exp_fn->remove(rmi4_data); + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + synaptics_rmi4_free_fingers(rmi4_data); + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + if (rmi4_data->stylus_enable) { + input_unregister_device(rmi4_data->stylus_dev); + rmi4_data->stylus_dev = NULL; + } + + retval = synaptics_rmi4_sw_reset(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + goto exit; + } + + retval = synaptics_rmi4_set_input_dev(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set up input device\n", + __func__); + goto exit; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + goto exit; + } + } + + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->init != NULL) + exp_fhandler->exp_fn->init(rmi4_data); + } + + retval = 0; + +exit: + synaptics_rmi4_irq_enable(rmi4_data, true, false); + + mutex_unlock(&exp_data.mutex); + + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + + return; +} + +static int synaptics_rmi4_reinit_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + synaptics_rmi4_free_fingers(rmi4_data); + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F12) { + synaptics_rmi4_f12_set_enables(rmi4_data, 0); + break; + } + } + } + + retval = synaptics_rmi4_int_enable(rmi4_data, true); + if (retval < 0) + goto exit; + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->reinit != NULL) + exp_fhandler->exp_fn->reinit(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + synaptics_rmi4_set_configured(rmi4_data); + + retval = 0; + +exit: + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + return retval; +} + +static int synaptics_rmi4_reset_device(struct synaptics_rmi4_data *rmi4_data, + bool rebuild) +{ + int retval; + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + + if (rebuild) { + queue_delayed_work(rmi4_data->rb_workqueue, + &rmi4_data->rb_work, + msecs_to_jiffies(REBUILD_WORK_DELAY_MS)); + return 0; + } + + mutex_lock(&(rmi4_data->rmi4_reset_mutex)); + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + + retval = synaptics_rmi4_sw_reset(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + goto exit; + } + + synaptics_rmi4_free_fingers(rmi4_data); + + synaptics_rmi4_empty_fn_list(rmi4_data); + + retval = synaptics_rmi4_query_device(rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to query device\n", + __func__); + goto exit; + } + + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->reset != NULL) + exp_fhandler->exp_fn->reset(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + retval = 0; + +exit: + synaptics_rmi4_irq_enable(rmi4_data, true, false); + + mutex_unlock(&(rmi4_data->rmi4_reset_mutex)); + + return retval; +} + +#ifdef FB_READY_RESET +static void synaptics_rmi4_reset_work(struct work_struct *work) +{ + int retval; + unsigned int timeout; + struct synaptics_rmi4_data *rmi4_data = + container_of(work, struct synaptics_rmi4_data, + reset_work); + + timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS + 1; + + while (!rmi4_data->fb_ready) { + msleep(FB_READY_WAIT_MS); + timeout--; + if (timeout == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for FB ready\n", + __func__); + return; + } + } + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + } + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + return; +} +#endif + +static void synaptics_rmi4_sleep_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + unsigned char device_ctrl; + unsigned char no_sleep_setting = rmi4_data->no_sleep_setting; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device control\n", + __func__); + return; + } + + device_ctrl = device_ctrl & ~MASK_3BIT; + if (enable) + device_ctrl = device_ctrl | NO_SLEEP_OFF | SENSOR_SLEEP; + else + device_ctrl = device_ctrl | no_sleep_setting | NORMAL_OPERATION; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write device control\n", + __func__); + return; + } + + rmi4_data->sensor_sleep = enable; + + return; +} + +static void synaptics_rmi4_exp_fn_work(struct work_struct *work) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_exp_fhandler *exp_fhandler_temp; + struct synaptics_rmi4_data *rmi4_data = exp_data.rmi4_data; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + mutex_lock(&rmi4_data->rmi4_reset_mutex); + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry_safe(exp_fhandler, + exp_fhandler_temp, + &exp_data.list, + link) { + if ((exp_fhandler->exp_fn->init != NULL) && + exp_fhandler->insert) { + exp_fhandler->exp_fn->init(rmi4_data); + exp_fhandler->insert = false; + } else if ((exp_fhandler->exp_fn->remove != NULL) && + exp_fhandler->remove) { + exp_fhandler->exp_fn->remove(rmi4_data); + list_del(&exp_fhandler->link); + kfree(exp_fhandler); + } + } + } + mutex_unlock(&exp_data.mutex); + mutex_unlock(&rmi4_data->rmi4_reset_mutex); + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + return; +} + +void synaptics_rmi4_new_function(struct synaptics_rmi4_exp_fn *exp_fn, + bool insert) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + + if (!exp_data.initialized) { + mutex_init(&exp_data.mutex); + INIT_LIST_HEAD(&exp_data.list); + exp_data.initialized = true; + } + + mutex_lock(&exp_data.mutex); + if (insert) { + exp_fhandler = kzalloc(sizeof(*exp_fhandler), GFP_KERNEL); + if (!exp_fhandler) { + pr_err("%s: Failed to alloc mem for expansion function\n", + __func__); + goto exit; + } + exp_fhandler->exp_fn = exp_fn; + exp_fhandler->insert = true; + exp_fhandler->remove = false; + list_add_tail(&exp_fhandler->link, &exp_data.list); + } else if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) { + if (exp_fhandler->exp_fn->fn_type == exp_fn->fn_type) { + exp_fhandler->insert = false; + exp_fhandler->remove = true; + goto exit; + } + } + } + +exit: + mutex_unlock(&exp_data.mutex); + + if (exp_data.queue_work) { + queue_delayed_work(exp_data.workqueue, + &exp_data.work, + msecs_to_jiffies(EXP_FN_WORK_DELAY_MS)); + } + + return; +} +EXPORT_SYMBOL(synaptics_rmi4_new_function); + +static int synaptics_rmi4_probe(struct platform_device *pdev) +{ + int retval; + 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; + + hw_if = pdev->dev.platform_data; + if (!hw_if) { + dev_err(&pdev->dev, + "%s: No hardware interface found\n", + __func__); + return -EINVAL; + } + + bdata = hw_if->board_data; + if (!bdata) { + dev_err(&pdev->dev, + "%s: No board data found\n", + __func__); + return -EINVAL; + } + + rmi4_data = kzalloc(sizeof(*rmi4_data), GFP_KERNEL); + if (!rmi4_data) { + dev_err(&pdev->dev, + "%s: Failed to alloc mem for rmi4_data\n", + __func__); + return -ENOMEM; + } + + rmi4_data->pdev = pdev; + rmi4_data->current_page = MASK_8BIT; + rmi4_data->hw_if = hw_if; + rmi4_data->suspend = false; + rmi4_data->irq_enabled = false; + rmi4_data->fingers_on_2d = false; + + rmi4_data->reset_device = synaptics_rmi4_reset_device; + rmi4_data->irq_enable = synaptics_rmi4_irq_enable; + rmi4_data->sleep_enable = synaptics_rmi4_sleep_enable; + rmi4_data->report_touch = synaptics_rmi4_report_touch; + + mutex_init(&(rmi4_data->rmi4_reset_mutex)); + mutex_init(&(rmi4_data->rmi4_report_mutex)); + mutex_init(&(rmi4_data->rmi4_io_ctrl_mutex)); + mutex_init(&(rmi4_data->rmi4_exp_init_mutex)); + + platform_set_drvdata(pdev, rmi4_data); + + vir_button_map = bdata->vir_button_map; + + retval = synaptics_rmi4_get_reg(rmi4_data, true); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to get regulators\n", + __func__); + goto err_get_reg; + } + + retval = synaptics_rmi4_enable_reg(rmi4_data, true); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to enable regulators\n", + __func__); + goto err_enable_reg; + } + + retval = synaptics_rmi4_set_gpio(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to set up GPIO's\n", + __func__); + goto err_set_gpio; + } + + if (hw_if->ui_hw_init) { + retval = hw_if->ui_hw_init(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to initialize hardware interface\n", + __func__); + goto err_ui_hw_init; + } + } + + retval = synaptics_rmi4_set_input_dev(rmi4_data); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to set up input device\n", + __func__); + goto err_set_input_dev; + } + +#ifdef CONFIG_FB + rmi4_data->fb_notifier.notifier_call = synaptics_rmi4_fb_notifier_cb; + retval = fb_register_client(&rmi4_data->fb_notifier); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to register fb notifier client\n", + __func__); + } +#endif + +#ifdef USE_EARLYSUSPEND + rmi4_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + rmi4_data->early_suspend.suspend = synaptics_rmi4_early_suspend; + rmi4_data->early_suspend.resume = synaptics_rmi4_late_resume; + register_early_suspend(&rmi4_data->early_suspend); +#endif + + if (!exp_data.initialized) { + mutex_init(&exp_data.mutex); + INIT_LIST_HEAD(&exp_data.list); + exp_data.initialized = true; + } + + rmi4_data->irq = gpio_to_irq(bdata->irq_gpio); + + retval = synaptics_rmi4_irq_enable(rmi4_data, true, false); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to enable attention interrupt\n", + __func__); + goto err_enable_irq; + } + + if (vir_button_map->nbuttons) { + rmi4_data->board_prop_dir = kobject_create_and_add( + "board_properties", NULL); + if (!rmi4_data->board_prop_dir) { + dev_err(&pdev->dev, + "%s: Failed to create board_properties directory\n", + __func__); + goto err_virtual_buttons; + } else { + retval = sysfs_create_file(rmi4_data->board_prop_dir, + &virtual_key_map_attr.attr); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to create virtual key map file\n", + __func__); + goto err_virtual_buttons; + } + } + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(&pdev->dev, + "%s: Failed to create sysfs attributes\n", + __func__); + goto err_sysfs; + } + } + + rmi4_data->rb_workqueue = + create_singlethread_workqueue("dsx_rebuild_workqueue"); + INIT_DELAYED_WORK(&rmi4_data->rb_work, synaptics_rmi4_rebuild_work); + + exp_data.workqueue = create_singlethread_workqueue("dsx_exp_workqueue"); + INIT_DELAYED_WORK(&exp_data.work, synaptics_rmi4_exp_fn_work); + exp_data.rmi4_data = rmi4_data; + exp_data.queue_work = true; + queue_delayed_work(exp_data.workqueue, + &exp_data.work, + 0); + +#ifdef FB_READY_RESET + rmi4_data->reset_workqueue = + create_singlethread_workqueue("dsx_reset_workqueue"); + INIT_WORK(&rmi4_data->reset_work, synaptics_rmi4_reset_work); + queue_work(rmi4_data->reset_workqueue, &rmi4_data->reset_work); +#endif + + 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_virtual_buttons: + if (rmi4_data->board_prop_dir) { + sysfs_remove_file(rmi4_data->board_prop_dir, + &virtual_key_map_attr.attr); + kobject_put(rmi4_data->board_prop_dir); + } + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + +err_enable_irq: +#ifdef CONFIG_FB + fb_unregister_client(&rmi4_data->fb_notifier); +#endif + +#ifdef USE_EARLYSUSPEND + unregister_early_suspend(&rmi4_data->early_suspend); +#endif + + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + if (rmi4_data->stylus_enable) { + input_unregister_device(rmi4_data->stylus_dev); + rmi4_data->stylus_dev = NULL; + } + +err_set_input_dev: + synaptics_rmi4_gpio_setup(bdata->irq_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->reset_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->power_gpio, false, 0, 0); + +err_ui_hw_init: +err_set_gpio: + synaptics_rmi4_enable_reg(rmi4_data, false); + +err_enable_reg: + synaptics_rmi4_get_reg(rmi4_data, false); + +err_get_reg: + kfree(rmi4_data); + + return retval; +} + +static int synaptics_rmi4_remove(struct platform_device *pdev) +{ + unsigned char attr_count; + struct synaptics_rmi4_data *rmi4_data = platform_get_drvdata(pdev); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + +#ifdef FB_READY_RESET + cancel_work_sync(&rmi4_data->reset_work); + flush_workqueue(rmi4_data->reset_workqueue); + destroy_workqueue(rmi4_data->reset_workqueue); +#endif + + cancel_delayed_work_sync(&exp_data.work); + flush_workqueue(exp_data.workqueue); + destroy_workqueue(exp_data.workqueue); + + cancel_delayed_work_sync(&rmi4_data->rb_work); + flush_workqueue(rmi4_data->rb_workqueue); + destroy_workqueue(rmi4_data->rb_workqueue); + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + if (rmi4_data->board_prop_dir) { + sysfs_remove_file(rmi4_data->board_prop_dir, + &virtual_key_map_attr.attr); + kobject_put(rmi4_data->board_prop_dir); + } + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + +#ifdef CONFIG_FB + fb_unregister_client(&rmi4_data->fb_notifier); +#endif + +#ifdef USE_EARLYSUSPEND + unregister_early_suspend(&rmi4_data->early_suspend); +#endif + + synaptics_rmi4_empty_fn_list(rmi4_data); + input_unregister_device(rmi4_data->input_dev); + rmi4_data->input_dev = NULL; + if (rmi4_data->stylus_enable) { + input_unregister_device(rmi4_data->stylus_dev); + rmi4_data->stylus_dev = NULL; + } + + synaptics_rmi4_gpio_setup(bdata->irq_gpio, false, 0, 0); + + if (bdata->reset_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->reset_gpio, false, 0, 0); + + if (bdata->power_gpio >= 0) + synaptics_rmi4_gpio_setup(bdata->power_gpio, false, 0, 0); + + synaptics_rmi4_enable_reg(rmi4_data, false); + synaptics_rmi4_get_reg(rmi4_data, false); + + kfree(rmi4_data); + + return 0; +} + +static void synaptics_rmi4_f11_wg(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + unsigned char reporting_control; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F11) + break; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base, + &reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return; + } + + reporting_control = (reporting_control & ~MASK_3BIT); + if (enable) + reporting_control |= F11_WAKEUP_GESTURE_MODE; + else + reporting_control |= F11_CONTINUOUS_MODE; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fhandler->full_addr.ctrl_base, + &reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return; + } + + return; +} + +static void synaptics_rmi4_f12_wg(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval; + unsigned char offset; + unsigned char reporting_control[3]; + struct synaptics_rmi4_f12_extra_data *extra_data; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + + rmi = &(rmi4_data->rmi4_mod_info); + + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->fn_number == SYNAPTICS_RMI4_F12) + break; + } + + extra_data = (struct synaptics_rmi4_f12_extra_data *)fhandler->extra; + offset = extra_data->ctrl20_offset; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fhandler->full_addr.ctrl_base + offset, + reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return; + } + + if (enable) + reporting_control[2] = F12_WAKEUP_GESTURE_MODE; + else + reporting_control[2] = F12_CONTINUOUS_MODE; + + retval = synaptics_rmi4_reg_write(rmi4_data, + fhandler->full_addr.ctrl_base + offset, + reporting_control, + sizeof(reporting_control)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change reporting mode\n", + __func__); + return; + } + + return; +} + +static void synaptics_rmi4_wakeup_gesture(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + if (rmi4_data->f11_wakeup_gesture) + synaptics_rmi4_f11_wg(rmi4_data, enable); + else if (rmi4_data->f12_wakeup_gesture) + synaptics_rmi4_f12_wg(rmi4_data, enable); + + return; +} + +#ifdef CONFIG_FB +static int synaptics_rmi4_fb_notifier_cb(struct notifier_block *self, + unsigned long event, void *data) +{ + int *transition; + struct fb_event *evdata = data; + struct synaptics_rmi4_data *rmi4_data = + container_of(self, struct synaptics_rmi4_data, + fb_notifier); + + if (evdata && evdata->data && rmi4_data) { + if (event == FB_EVENT_BLANK) { + transition = evdata->data; + if (*transition == FB_BLANK_POWERDOWN) { + synaptics_rmi4_suspend(&rmi4_data->pdev->dev); + rmi4_data->fb_ready = false; + } else if (*transition == FB_BLANK_UNBLANK) { + synaptics_rmi4_resume(&rmi4_data->pdev->dev); + rmi4_data->fb_ready = true; + } + } + } + + return 0; +} +#endif + +#ifdef USE_EARLYSUSPEND +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) + return; + + if (rmi4_data->enable_wakeup_gesture) { + synaptics_rmi4_wakeup_gesture(rmi4_data, true); + enable_irq_wake(rmi4_data->irq); + goto exit; + } + + synaptics_rmi4_irq_enable(rmi4_data, false, false); + synaptics_rmi4_sleep_enable(rmi4_data, true); + synaptics_rmi4_free_fingers(rmi4_data); + +exit: + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->early_suspend != NULL) + exp_fhandler->exp_fn->early_suspend(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = true; + + return; +} + +static void synaptics_rmi4_late_resume(struct early_suspend *h) +{ +#ifdef FB_READY_RESET + int retval; +#endif + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = + container_of(h, struct synaptics_rmi4_data, + early_suspend); + + if (rmi4_data->stay_awake) + return; + + if (rmi4_data->enable_wakeup_gesture) { + synaptics_rmi4_wakeup_gesture(rmi4_data, false); + disable_irq_wake(rmi4_data->irq); + goto exit; + } + + rmi4_data->current_page = MASK_8BIT; + + if (rmi4_data->suspend) { + synaptics_rmi4_sleep_enable(rmi4_data, false); + synaptics_rmi4_irq_enable(rmi4_data, true, false); + } + +exit: +#ifdef FB_READY_RESET + if (rmi4_data->suspend) { + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + } + } +#endif + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->late_resume != NULL) + exp_fhandler->exp_fn->late_resume(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = false; + + return; +} +#endif + +static int synaptics_rmi4_suspend(struct device *dev) +{ + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (rmi4_data->stay_awake) + return 0; + + if (rmi4_data->enable_wakeup_gesture) { + synaptics_rmi4_wakeup_gesture(rmi4_data, true); + enable_irq_wake(rmi4_data->irq); + goto exit; + } + + if (!rmi4_data->suspend) { + synaptics_rmi4_irq_enable(rmi4_data, false, false); + synaptics_rmi4_sleep_enable(rmi4_data, true); + synaptics_rmi4_free_fingers(rmi4_data); + } + +exit: + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->suspend != NULL) + exp_fhandler->exp_fn->suspend(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = true; + + return 0; +} + +static int synaptics_rmi4_resume(struct device *dev) +{ +#ifdef FB_READY_RESET + int retval; +#endif + struct synaptics_rmi4_exp_fhandler *exp_fhandler; + struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); + + if (rmi4_data->stay_awake) + return 0; + + if (rmi4_data->enable_wakeup_gesture) { + synaptics_rmi4_wakeup_gesture(rmi4_data, false); + disable_irq_wake(rmi4_data->irq); + goto exit; + } + + rmi4_data->current_page = MASK_8BIT; + + synaptics_rmi4_sleep_enable(rmi4_data, false); + synaptics_rmi4_irq_enable(rmi4_data, true, false); + +exit: +#ifdef FB_READY_RESET + retval = synaptics_rmi4_reset_device(rmi4_data, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + } +#endif + mutex_lock(&exp_data.mutex); + if (!list_empty(&exp_data.list)) { + list_for_each_entry(exp_fhandler, &exp_data.list, link) + if (exp_fhandler->exp_fn->resume != NULL) + exp_fhandler->exp_fn->resume(rmi4_data); + } + mutex_unlock(&exp_data.mutex); + + rmi4_data->suspend = false; + + return 0; +} + +#ifdef CONFIG_PM +static const struct dev_pm_ops synaptics_rmi4_dev_pm_ops = { +#ifndef CONFIG_FB + .suspend = synaptics_rmi4_suspend, + .resume = synaptics_rmi4_resume, +#endif +}; +#endif + +static struct platform_driver synaptics_rmi4_driver = { + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &synaptics_rmi4_dev_pm_ops, +#endif + }, + .probe = synaptics_rmi4_probe, + .remove = synaptics_rmi4_remove, +}; + +static int __init synaptics_rmi4_init(void) +{ + int retval; + + retval = synaptics_rmi4_bus_init(); + if (retval) + return retval; + + return platform_driver_register(&synaptics_rmi4_driver); +} + +static void __exit synaptics_rmi4_exit(void) +{ + platform_driver_unregister(&synaptics_rmi4_driver); + + synaptics_rmi4_bus_exit(); + + return; +} + +module_init(synaptics_rmi4_init); +module_exit(synaptics_rmi4_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Touch Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.h b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.h new file mode 100644 index 000000000000..1b4079cfda6a --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.h @@ -0,0 +1,477 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_DSX_RMI4_H_ +#define _SYNAPTICS_DSX_RMI4_H_ + +#define SYNAPTICS_DS4 (1 << 0) +#define SYNAPTICS_DS5 (1 << 1) +#define SYNAPTICS_DSX_DRIVER_PRODUCT (SYNAPTICS_DS4 | SYNAPTICS_DS5) +#define SYNAPTICS_DSX_DRIVER_VERSION 0x2061 + +#include <linux/version.h> +#ifdef CONFIG_FB +#include <linux/notifier.h> +#include <linux/fb.h> +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38)) +#define KERNEL_ABOVE_2_6_38 +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) +#define KERNEL_ABOVE_3_6 +#endif + +#ifdef KERNEL_ABOVE_2_6_38 +#define sstrtoul(...) kstrtoul(__VA_ARGS__) +#else +#define sstrtoul(...) strict_strtoul(__VA_ARGS__) +#endif + +#define PDT_PROPS (0X00EF) +#define PDT_START (0x00E9) +#define PDT_END (0x00D0) +#define PDT_ENTRY_SIZE (0x0006) +#define PAGES_TO_SERVICE (10) +#define PAGE_SELECT_LEN (2) +#define ADDRESS_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_F35 (0x35) +#define SYNAPTICS_RMI4_F38 (0x38) +#define SYNAPTICS_RMI4_F51 (0x51) +#define SYNAPTICS_RMI4_F54 (0x54) +#define SYNAPTICS_RMI4_F55 (0x55) +#define SYNAPTICS_RMI4_FDB (0xDB) + +#define PRODUCT_INFO_SIZE 2 +#define PRODUCT_ID_SIZE 10 +#define BUILD_ID_SIZE 3 + +#define F12_FINGERS_TO_SUPPORT 10 +#define F12_NO_OBJECT_STATUS 0x00 +#define F12_FINGER_STATUS 0x01 +#define F12_ACTIVE_STYLUS_STATUS 0x02 +#define F12_PALM_STATUS 0x03 +#define F12_HOVERING_FINGER_STATUS 0x05 +#define F12_GLOVED_FINGER_STATUS 0x06 +#define F12_NARROW_OBJECT_STATUS 0x07 +#define F12_HAND_EDGE_STATUS 0x08 +#define F12_COVER_STATUS 0x0A +#define F12_STYLUS_STATUS 0x0B +#define F12_ERASER_STATUS 0x0C +#define F12_SMALL_OBJECT_STATUS 0x0D + +#define F12_GESTURE_DETECTION_LEN 5 + +#define MAX_NUMBER_OF_BUTTONS 4 +#define MAX_INTR_REGISTERS 4 + +#define MASK_16BIT 0xFFFF +#define MASK_8BIT 0xFF +#define MASK_7BIT 0x7F +#define MASK_6BIT 0x3F +#define MASK_5BIT 0x1F +#define MASK_4BIT 0x0F +#define MASK_3BIT 0x07 +#define MASK_2BIT 0x03 +#define MASK_1BIT 0x01 + +enum exp_fn { + RMI_DEV = 0, + RMI_FW_UPDATER, + RMI_TEST_REPORTING, + RMI_PROXIMITY, + RMI_ACTIVE_PEN, + RMI_GESTURE, + RMI_VIDEO, + RMI_DEBUG, + RMI_LAST, +}; + +/* + * struct synaptics_rmi4_fn_desc - function descriptor fields in PDT entry + * @query_base_addr: base address for query registers + * @cmd_base_addr: base address for command registers + * @ctrl_base_addr: base address for control registers + * @data_base_addr: base address for data registers + * @intr_src_count: number of interrupt sources + * @fn_version: version of function + * @fn_number: function number + */ +struct synaptics_rmi4_fn_desc { + union { + struct { + unsigned char query_base_addr; + unsigned char cmd_base_addr; + unsigned char ctrl_base_addr; + unsigned char data_base_addr; + unsigned char intr_src_count:3; + unsigned char reserved_1:2; + unsigned char fn_version:2; + unsigned char reserved_2:1; + unsigned char fn_number; + } __packed; + unsigned char data[6]; + }; +}; + +/* + * synaptics_rmi4_fn_full_addr - full 16-bit base addresses + * @query_base: 16-bit base address for query registers + * @cmd_base: 16-bit base address for command registers + * @ctrl_base: 16-bit base address for control registers + * @data_base: 16-bit base address for data registers + */ +struct synaptics_rmi4_fn_full_addr { + unsigned short query_base; + unsigned short cmd_base; + unsigned short ctrl_base; + unsigned short data_base; +}; + +/* + * struct synaptics_rmi4_f11_extra_data - extra data of F$11 + * @data38_offset: offset to F11_2D_DATA38 register + */ +struct synaptics_rmi4_f11_extra_data { + unsigned char data38_offset; +}; + +/* + * struct synaptics_rmi4_f12_extra_data - extra data of F$12 + * @data1_offset: offset to F12_2D_DATA01 register + * @data4_offset: offset to F12_2D_DATA04 register + * @data15_offset: offset to F12_2D_DATA15 register + * @data15_size: size of F12_2D_DATA15 register + * @data15_data: buffer for reading F12_2D_DATA15 register + * @data23_offset: offset to F12_2D_DATA23 register + * @data23_size: size of F12_2D_DATA23 register + * @data23_data: buffer for reading F12_2D_DATA23 register + * @ctrl20_offset: offset to F12_2D_CTRL20 register + */ +struct synaptics_rmi4_f12_extra_data { + unsigned char data1_offset; + unsigned char data4_offset; + unsigned char data15_offset; + unsigned char data15_size; + unsigned char data15_data[(F12_FINGERS_TO_SUPPORT + 7) / 8]; + unsigned char data23_offset; + unsigned char data23_size; + unsigned char data23_data[F12_FINGERS_TO_SUPPORT]; + unsigned char ctrl20_offset; +}; + +/* + * struct synaptics_rmi4_fn - RMI function handler + * @fn_number: function number + * @num_of_data_sources: number of data sources + * @num_of_data_points: maximum number of fingers supported + * @intr_reg_num: index to associated interrupt register + * @intr_mask: interrupt mask + * @full_addr: full 16-bit base addresses of function registers + * @link: linked list for function handlers + * @data_size: size of private data + * @data: pointer to private data + * @extra: pointer to extra data + */ +struct synaptics_rmi4_fn { + unsigned char fn_number; + unsigned char num_of_data_sources; + unsigned char num_of_data_points; + unsigned char intr_reg_num; + unsigned char intr_mask; + struct synaptics_rmi4_fn_full_addr full_addr; + struct list_head link; + int data_size; + void *data; + void *extra; +}; + +/* + * struct synaptics_rmi4_device_info - device information + * @version_major: RMI protocol major version number + * @version_minor: RMI protocol minor version number + * @manufacturer_id: manufacturer ID + * @product_props: product properties + * @product_info: product information + * @product_id_string: product ID + * @build_id: firmware build ID + * @support_fn_list: linked list for function handlers + */ +struct synaptics_rmi4_device_info { + unsigned int version_major; + unsigned int version_minor; + unsigned char manufacturer_id; + unsigned char product_props; + unsigned char product_info[PRODUCT_INFO_SIZE]; + unsigned char product_id_string[PRODUCT_ID_SIZE + 1]; + unsigned char build_id[BUILD_ID_SIZE]; + struct list_head support_fn_list; +}; + +/* + * struct synaptics_rmi4_data - RMI4 device instance data + * @pdev: pointer to platform device + * @input_dev: pointer to associated input device + * @stylus_dev: pointer to associated stylus device + * @hw_if: pointer to hardware interface data + * @rmi4_mod_info: device information + * @board_prop_dir: /sys/board_properties directory for virtual key map file + * @pwr_reg: pointer to regulator for power control + * @bus_reg: pointer to regulator for bus pullup control + * @rmi4_reset_mutex: mutex for software reset + * @rmi4_report_mutex: mutex for input event reporting + * @rmi4_io_ctrl_mutex: mutex for communication interface I/O + * @rmi4_exp_init_mutex: mutex for expansion function module initialization + * @rb_work: work for rebuilding input device + * @rb_workqueue: workqueue for rebuilding input device + * @fb_notifier: framebuffer notifier client + * @reset_work: work for issuing reset after display framebuffer ready + * @reset_workqueue: workqueue for issuing reset after display framebuffer ready + * @early_suspend: early suspend power management + * @current_page: current RMI page for register access + * @button_0d_enabled: switch for enabling 0d button support + * @num_of_tx: number of Tx channels for 2D touch + * @num_of_rx: number of Rx channels for 2D touch + * @num_of_fingers: maximum number of fingers for 2D touch + * @max_touch_width: maximum touch width + * @report_enable: input data to report for F$12 + * @no_sleep_setting: default setting of NoSleep in F01_RMI_CTRL00 register + * @gesture_detection: detected gesture type and properties + * @intr_mask: interrupt enable mask + * @button_txrx_mapping: Tx Rx mapping of 0D buttons + * @num_of_intr_regs: number of interrupt registers + * @f01_query_base_addr: query base address for f$01 + * @f01_cmd_base_addr: command base address for f$01 + * @f01_ctrl_base_addr: control base address for f$01 + * @f01_data_base_addr: data base address for f$01 + * @firmware_id: firmware build ID + * @irq: attention interrupt + * @sensor_max_x: maximum x coordinate for 2D touch + * @sensor_max_y: maximum y coordinate for 2D touch + * @flash_prog_mode: flag to indicate flash programming mode status + * @irq_enabled: flag to indicate attention interrupt enable status + * @fingers_on_2d: flag to indicate presence of fingers in 2D area + * @suspend: flag to indicate whether in suspend state + * @sensor_sleep: flag to indicate sleep state of sensor + * @stay_awake: flag to indicate whether to stay awake during suspend + * @fb_ready: flag to indicate whether display framebuffer in ready state + * @f11_wakeup_gesture: flag to indicate support for wakeup gestures in F$11 + * @f12_wakeup_gesture: flag to indicate support for wakeup gestures in F$12 + * @enable_wakeup_gesture: flag to indicate usage of wakeup gestures + * @wedge_sensor: flag to indicate use of wedge sensor + * @report_pressure: flag to indicate reporting of pressure data + * @stylus_enable: flag to indicate reporting of stylus data + * @eraser_enable: flag to indicate reporting of eraser data + * @external_afe_buttons: flag to indicate presence of external AFE buttons + * @reset_device: pointer to device reset function + * @irq_enable: pointer to interrupt enable function + * @sleep_enable: pointer to sleep enable function + * @report_touch: pointer to touch reporting function + */ +struct synaptics_rmi4_data { + struct platform_device *pdev; + struct input_dev *input_dev; + struct input_dev *stylus_dev; + const struct synaptics_dsx_hw_interface *hw_if; + struct synaptics_rmi4_device_info rmi4_mod_info; + struct kobject *board_prop_dir; + struct regulator *pwr_reg; + struct regulator *bus_reg; + struct mutex rmi4_reset_mutex; + struct mutex rmi4_report_mutex; + struct mutex rmi4_io_ctrl_mutex; + struct mutex rmi4_exp_init_mutex; + struct delayed_work rb_work; + struct workqueue_struct *rb_workqueue; +#ifdef CONFIG_FB + struct notifier_block fb_notifier; + struct work_struct reset_work; + struct workqueue_struct *reset_workqueue; +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif + unsigned char current_page; + unsigned char button_0d_enabled; + unsigned char num_of_tx; + unsigned char num_of_rx; + unsigned char num_of_fingers; + unsigned char max_touch_width; + unsigned char report_enable; + unsigned char no_sleep_setting; + unsigned char gesture_detection[F12_GESTURE_DETECTION_LEN]; + unsigned char intr_mask[MAX_INTR_REGISTERS]; + unsigned char *button_txrx_mapping; + unsigned short num_of_intr_regs; + unsigned short f01_query_base_addr; + unsigned short f01_cmd_base_addr; + unsigned short f01_ctrl_base_addr; + unsigned short f01_data_base_addr; + unsigned int firmware_id; + int irq; + int sensor_max_x; + int sensor_max_y; + bool flash_prog_mode; + bool irq_enabled; + bool fingers_on_2d; + bool suspend; + bool sensor_sleep; + bool stay_awake; + bool fb_ready; + bool f11_wakeup_gesture; + bool f12_wakeup_gesture; + bool enable_wakeup_gesture; + bool wedge_sensor; + bool report_pressure; + bool stylus_enable; + bool eraser_enable; + bool external_afe_buttons; + int (*reset_device)(struct synaptics_rmi4_data *rmi4_data, + bool rebuild); + int (*irq_enable)(struct synaptics_rmi4_data *rmi4_data, bool enable, + bool attn_only); + void (*sleep_enable)(struct synaptics_rmi4_data *rmi4_data, + bool enable); + void (*report_touch)(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn *fhandler); +}; + +struct synaptics_dsx_bus_access { + unsigned char type; + int (*read)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr, + unsigned char *data, unsigned short length); + int (*write)(struct synaptics_rmi4_data *rmi4_data, unsigned short addr, + unsigned char *data, unsigned short length); +}; + +struct synaptics_dsx_hw_interface { + struct synaptics_dsx_board_data *board_data; + const struct synaptics_dsx_bus_access *bus_access; + int (*bl_hw_init)(struct synaptics_rmi4_data *rmi4_data); + int (*ui_hw_init)(struct synaptics_rmi4_data *rmi4_data); +}; + +struct synaptics_rmi4_exp_fn { + enum exp_fn fn_type; + int (*init)(struct synaptics_rmi4_data *rmi4_data); + void (*remove)(struct synaptics_rmi4_data *rmi4_data); + void (*reset)(struct synaptics_rmi4_data *rmi4_data); + void (*reinit)(struct synaptics_rmi4_data *rmi4_data); + void (*early_suspend)(struct synaptics_rmi4_data *rmi4_data); + void (*suspend)(struct synaptics_rmi4_data *rmi4_data); + void (*resume)(struct synaptics_rmi4_data *rmi4_data); + void (*late_resume)(struct synaptics_rmi4_data *rmi4_data); + void (*attn)(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask); +}; + +int synaptics_rmi4_bus_init(void); + +void synaptics_rmi4_bus_exit(void); + +void synaptics_rmi4_new_function(struct synaptics_rmi4_exp_fn *exp_fn_module, + bool insert); + +int synaptics_fw_updater(const unsigned char *fw_data); + +static inline int synaptics_rmi4_reg_read( + struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, + unsigned char *data, + unsigned 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); +} + +static inline ssize_t synaptics_rmi4_show_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + dev_warn(dev, "%s Attempted to read from write-only attribute %s\n", + __func__, attr->attr.name); + return -EPERM; +} + +static inline ssize_t synaptics_rmi4_store_error(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + dev_warn(dev, "%s Attempted to write to read-only attribute %s\n", + __func__, attr->attr.name); + return -EPERM; +} + +static inline int secure_memcpy(unsigned char *dest, unsigned int dest_size, + const unsigned char *src, unsigned int src_size, + unsigned int count) +{ + if (dest == NULL || src == NULL) + return -EINVAL; + + if (count > dest_size || count > src_size) + return -EINVAL; + + memcpy((void *)dest, (const void *)src, count); + + return 0; +} + +static inline void batohs(unsigned short *dest, unsigned char *src) +{ + *dest = src[1] * 0x100 + src[0]; +} + +static inline void hstoba(unsigned char *dest, unsigned short src) +{ + dest[0] = src % 0x100; + dest[1] = src / 0x100; +} + +#endif diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_fw_update.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_fw_update.c new file mode 100644 index 000000000000..5dd0ec4e53cc --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_fw_update.c @@ -0,0 +1,4438 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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.h> +#include "synaptics_dsx_core.h" + +#define FW_IMAGE_NAME "synaptics/startup_fw_update.img" +/* +#define DO_STARTUP_FW_UPDATE +*/ +/* +#ifdef DO_STARTUP_FW_UPDATE +#ifdef CONFIG_FB +#define WAIT_FOR_FB_READY +#define FB_READY_WAIT_MS 100 +#define FB_READY_TIMEOUT_S 30 +#endif +#endif +*/ +#define FORCE_UPDATE false +#define DO_LOCKDOWN false + +#define MAX_IMAGE_NAME_LEN 256 +#define MAX_FIRMWARE_ID_LEN 10 + +#define IMAGE_HEADER_VERSION_05 0x05 +#define IMAGE_HEADER_VERSION_06 0x06 +#define IMAGE_HEADER_VERSION_10 0x10 + +#define IMAGE_AREA_OFFSET 0x100 +#define LOCKDOWN_SIZE 0x50 + +#define V5V6_BOOTLOADER_ID_OFFSET 0 +#define V5V6_CONFIG_ID_SIZE 4 + +#define V5_PROPERTIES_OFFSET 2 +#define V5_BLOCK_SIZE_OFFSET 3 +#define V5_BLOCK_COUNT_OFFSET 5 +#define V5_BLOCK_NUMBER_OFFSET 0 +#define V5_BLOCK_DATA_OFFSET 2 + +#define V6_PROPERTIES_OFFSET 1 +#define V6_BLOCK_SIZE_OFFSET 2 +#define V6_BLOCK_COUNT_OFFSET 3 +#define V6_PROPERTIES_2_OFFSET 4 +#define V6_GUEST_CODE_BLOCK_COUNT_OFFSET 5 +#define V6_BLOCK_NUMBER_OFFSET 0 +#define V6_BLOCK_DATA_OFFSET 1 +#define V6_FLASH_COMMAND_OFFSET 2 +#define V6_FLASH_STATUS_OFFSET 3 + +#define V7_CONFIG_ID_SIZE 32 + +#define V7_FLASH_STATUS_OFFSET 0 +#define V7_PARTITION_ID_OFFSET 1 +#define V7_BLOCK_NUMBER_OFFSET 2 +#define V7_TRANSFER_LENGTH_OFFSET 3 +#define V7_COMMAND_OFFSET 4 +#define V7_PAYLOAD_OFFSET 5 + +#define V7_PARTITION_SUPPORT_BYTES 4 + +#define F35_ERROR_CODE_OFFSET 0 +#define F35_CHUNK_NUM_LSB_OFFSET 0 +#define F35_CHUNK_NUM_MSB_OFFSET 1 +#define F35_CHUNK_DATA_OFFSET 2 +#define F35_CHUNK_COMMAND_OFFSET 18 + +#define F35_CHUNK_SIZE 16 +#define F35_ERASE_ALL_WAIT_MS 3000 +#define F35_RESET_WAIT_MS 250 + +#define SLEEP_MODE_NORMAL (0x00) +#define SLEEP_MODE_SENSOR_SLEEP (0x01) +#define SLEEP_MODE_RESERVED0 (0x02) +#define SLEEP_MODE_RESERVED1 (0x03) + +#define ENABLE_WAIT_MS (1 * 1000) +#define WRITE_WAIT_MS (3 * 1000) +#define ERASE_WAIT_MS (5 * 1000) + +#define MIN_SLEEP_TIME_US 50 +#define MAX_SLEEP_TIME_US 100 + +#define INT_DISABLE_WAIT_MS 20 +#define ENTER_FLASH_PROG_WAIT_MS 20 + +static int fwu_do_reflash(void); + +static int fwu_recovery_check_status(void); + +static ssize_t fwu_sysfs_show_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t fwu_sysfs_store_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t fwu_sysfs_do_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_do_reflash_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_write_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_read_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_config_area_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_image_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_image_size_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t fwu_sysfs_block_size_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_firmware_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_configuration_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_disp_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_perm_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_bl_config_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_guest_code_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t fwu_sysfs_write_guest_code_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +enum f34_version { + F34_V0 = 0, + F34_V1, + F34_V2, +}; + +enum bl_version { + BL_V5 = 5, + BL_V6 = 6, + BL_V7 = 7, + BL_V8 = 8, +}; + +enum flash_area { + NONE = 0, + UI_FIRMWARE, + UI_CONFIG, +}; + +enum update_mode { + NORMAL = 1, + FORCE = 2, + LOCKDOWN = 8, +}; + +enum config_area { + UI_CONFIG_AREA = 0, + PM_CONFIG_AREA, + BL_CONFIG_AREA, + DP_CONFIG_AREA, + FLASH_CONFIG_AREA, +}; + +enum v7_status { + SUCCESS = 0x00, + DEVICE_NOT_IN_BOOTLOADER_MODE, + INVALID_PARTITION, + INVALID_COMMAND, + INVALID_BLOCK_OFFSET, + INVALID_TRANSFER, + NOT_ERASED, + FLASH_PROGRAMMING_KEY_INCORRECT, + BAD_PARTITION_TABLE, + CHECKSUM_FAILED, + FLASH_HARDWARE_FAILURE = 0x1f, +}; + +enum v7_partition_id { + BOOTLOADER_PARTITION = 0x01, + DEVICE_CONFIG_PARTITION, + FLASH_CONFIG_PARTITION, + MANUFACTURING_BLOCK_PARTITION, + GUEST_SERIALIZATION_PARTITION, + GLOBAL_PARAMETERS_PARTITION, + CORE_CODE_PARTITION, + CORE_CONFIG_PARTITION, + GUEST_CODE_PARTITION, + DISPLAY_CONFIG_PARTITION, +}; + +enum v7_flash_command { + CMD_V7_IDLE = 0x00, + CMD_V7_ENTER_BL, + CMD_V7_READ, + CMD_V7_WRITE, + CMD_V7_ERASE, + CMD_V7_ERASE_AP, + CMD_V7_SENSOR_ID, +}; + +enum v5v6_flash_command { + CMD_V5V6_IDLE = 0x0, + CMD_V5V6_WRITE_FW = 0x2, + CMD_V5V6_ERASE_ALL = 0x3, + CMD_V5V6_WRITE_LOCKDOWN = 0x4, + CMD_V5V6_READ_CONFIG = 0x5, + CMD_V5V6_WRITE_CONFIG = 0x6, + CMD_V5V6_ERASE_UI_CONFIG = 0x7, + CMD_V5V6_ERASE_BL_CONFIG = 0x9, + CMD_V5V6_ERASE_DISP_CONFIG = 0xa, + CMD_V5V6_ERASE_GUEST_CODE = 0xb, + CMD_V5V6_WRITE_GUEST_CODE = 0xc, + CMD_V5V6_ENABLE_FLASH_PROG = 0xf, +}; + +enum flash_command { + CMD_IDLE = 0, + CMD_WRITE_FW, + CMD_WRITE_CONFIG, + CMD_WRITE_LOCKDOWN, + CMD_WRITE_GUEST_CODE, + CMD_READ_CONFIG, + CMD_ERASE_ALL, + CMD_ERASE_UI_FIRMWARE, + CMD_ERASE_UI_CONFIG, + CMD_ERASE_BL_CONFIG, + CMD_ERASE_DISP_CONFIG, + CMD_ERASE_FLASH_CONFIG, + CMD_ERASE_GUEST_CODE, + CMD_ENABLE_FLASH_PROG, +}; + +enum f35_flash_command { + CMD_F35_IDLE = 0x0, + CMD_F35_RESERVED = 0x1, + CMD_F35_WRITE_CHUNK = 0x2, + CMD_F35_ERASE_ALL = 0x3, + CMD_F35_RESET = 0x10, +}; + +enum container_id { + TOP_LEVEL_CONTAINER = 0, + UI_CONTAINER, + UI_CONFIG_CONTAINER, + BL_CONTAINER, + BL_IMAGE_CONTAINER, + BL_CONFIG_CONTAINER, + BL_LOCKDOWN_INFO_CONTAINER, + PERMANENT_CONFIG_CONTAINER, + GUEST_CODE_CONTAINER, + BL_PROTOCOL_DESCRIPTOR_CONTAINER, + UI_PROTOCOL_DESCRIPTOR_CONTAINER, + RMI_SELF_DISCOVERY_CONTAINER, + RMI_PAGE_CONTENT_CONTAINER, + GENERAL_INFORMATION_CONTAINER, + DEVICE_CONFIG_CONTAINER, + FLASH_CONFIG_CONTAINER, + GUEST_SERIALIZATION_CONTAINER, + GLOBAL_PARAMETERS_CONTAINER, + CORE_CODE_CONTAINER, + CORE_CONFIG_CONTAINER, + DISPLAY_CONFIG_CONTAINER, +}; + +struct pdt_properties { + union { + struct { + unsigned char reserved_1:6; + unsigned char has_bsr:1; + unsigned char reserved_2:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct partition_table { + unsigned char partition_id:5; + unsigned char byte_0_reserved:3; + unsigned char byte_1_reserved; + unsigned char partition_length_7_0; + unsigned char partition_length_15_8; + unsigned char start_physical_address_7_0; + unsigned char start_physical_address_15_8; + unsigned char partition_properties_7_0; + unsigned char partition_properties_15_8; +} __packed; + +struct f01_device_control { + union { + struct { + unsigned char sleep_mode:2; + unsigned char nosleep:1; + unsigned char reserved:2; + unsigned char charger_connected:1; + unsigned char report_rate:1; + unsigned char configured:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v7_query_0 { + union { + struct { + unsigned char subpacket_1_size:3; + unsigned char has_config_id:1; + unsigned char f34_query0_b4:1; + unsigned char has_thqa:1; + unsigned char f34_query0_b6__7:2; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v7_query_1_7 { + union { + struct { + /* query 1 */ + unsigned char bl_minor_revision; + unsigned char bl_major_revision; + + /* query 2 */ + unsigned char bl_fw_id_7_0; + unsigned char bl_fw_id_15_8; + unsigned char bl_fw_id_23_16; + unsigned char bl_fw_id_31_24; + + /* query 3 */ + unsigned char minimum_write_size; + unsigned char block_size_7_0; + unsigned char block_size_15_8; + unsigned char flash_page_size_7_0; + unsigned char flash_page_size_15_8; + + /* query 4 */ + unsigned char adjustable_partition_area_size_7_0; + unsigned char adjustable_partition_area_size_15_8; + + /* query 5 */ + unsigned char flash_config_length_7_0; + unsigned char flash_config_length_15_8; + + /* query 6 */ + unsigned char payload_length_7_0; + unsigned char payload_length_15_8; + + /* query 7 */ + unsigned char f34_query7_b0:1; + unsigned char has_bootloader:1; + unsigned char has_device_config:1; + unsigned char has_flash_config:1; + unsigned char has_manufacturing_block:1; + unsigned char has_guest_serialization:1; + unsigned char has_global_parameters:1; + unsigned char has_core_code:1; + unsigned char has_core_config:1; + unsigned char has_guest_code:1; + unsigned char has_display_config:1; + unsigned char f34_query7_b11__15:5; + unsigned char f34_query7_b16__23; + unsigned char f34_query7_b24__31; + } __packed; + unsigned char data[21]; + }; +}; + +struct f34_v7_data0 { + union { + struct { + unsigned char operation_status:5; + unsigned char device_cfg_status:2; + unsigned char bl_mode:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v7_data_1_5 { + union { + struct { + unsigned char partition_id:5; + unsigned char f34_data1_b5__7:3; + unsigned char block_offset_7_0; + unsigned char block_offset_15_8; + unsigned char transfer_length_7_0; + unsigned char transfer_length_15_8; + unsigned char command; + unsigned char payload_0; + unsigned char payload_1; + } __packed; + unsigned char data[8]; + }; +}; + +struct f34_v5v6_flash_properties { + union { + struct { + unsigned char reg_map:1; + unsigned char unlocked:1; + unsigned char has_config_id:1; + unsigned char has_pm_config:1; + unsigned char has_bl_config:1; + unsigned char has_disp_config:1; + unsigned char has_ctrl1:1; + unsigned char has_query4:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f34_v5v6_flash_properties_2 { + union { + struct { + unsigned char has_guest_code:1; + unsigned char reserved:7; + } __packed; + unsigned char data[1]; + }; +}; + +struct register_offset { + unsigned char properties; + unsigned char properties_2; + unsigned char block_size; + unsigned char block_count; + unsigned char gc_block_count; + unsigned char flash_status; + unsigned char partition_id; + unsigned char block_number; + unsigned char transfer_length; + unsigned char flash_cmd; + unsigned char payload; +}; + +struct block_count { + unsigned short ui_firmware; + unsigned short ui_config; + unsigned short dp_config; + unsigned short pm_config; + unsigned short fl_config; + unsigned short bl_image; + unsigned short bl_config; + unsigned short lockdown; + unsigned short guest_code; + unsigned short total_count; +}; + +struct physical_address { + unsigned short ui_firmware; + unsigned short ui_config; + unsigned short dp_config; + unsigned short fl_config; + unsigned short guest_code; +}; + +struct container_descriptor { + unsigned char content_checksum[4]; + unsigned char container_id[2]; + unsigned char minor_version; + unsigned char major_version; + unsigned char reserved_08; + unsigned char reserved_09; + unsigned char reserved_0a; + unsigned char reserved_0b; + unsigned char container_option_flags[4]; + unsigned char content_options_length[4]; + unsigned char content_options_address[4]; + unsigned char content_length[4]; + unsigned char content_address[4]; +}; + +struct image_header_10 { + unsigned char checksum[4]; + unsigned char reserved_04; + unsigned char reserved_05; + unsigned char minor_header_version; + unsigned char major_header_version; + unsigned char reserved_08; + unsigned char reserved_09; + unsigned char reserved_0a; + unsigned char reserved_0b; + unsigned char top_level_container_start_addr[4]; +}; + +struct image_header_05_06 { + /* 0x00 - 0x0f */ + unsigned char checksum[4]; + unsigned char reserved_04; + unsigned char reserved_05; + unsigned char options_firmware_id:1; + unsigned char options_bootloader:1; + unsigned char options_guest_code:1; + unsigned char options_tddi:1; + unsigned char options_reserved:4; + unsigned char header_version; + unsigned char firmware_size[4]; + unsigned char config_size[4]; + /* 0x10 - 0x1f */ + unsigned char product_id[PRODUCT_ID_SIZE]; + unsigned char package_id[2]; + unsigned char package_id_revision[2]; + unsigned char product_info[PRODUCT_INFO_SIZE]; + /* 0x20 - 0x2f */ + unsigned char bootloader_addr[4]; + unsigned char bootloader_size[4]; + unsigned char ui_addr[4]; + unsigned char ui_size[4]; + /* 0x30 - 0x3f */ + unsigned char ds_id[16]; + /* 0x40 - 0x4f */ + union { + struct { + unsigned char cstmr_product_id[PRODUCT_ID_SIZE]; + unsigned char reserved_4a_4f[6]; + }; + struct { + unsigned char dsp_cfg_addr[4]; + unsigned char dsp_cfg_size[4]; + unsigned char reserved_48_4f[8]; + }; + }; + /* 0x50 - 0x53 */ + unsigned char firmware_id[4]; +}; + +struct block_data { + unsigned int size; + const unsigned char *data; +}; + +struct image_metadata { + bool contains_firmware_id; + bool contains_bootloader; + bool contains_guest_code; + bool contains_disp_config; + bool contains_perm_config; + bool contains_flash_config; + unsigned int firmware_id; + unsigned int checksum; + unsigned int bootloader_size; + unsigned int disp_config_offset; + unsigned char bl_version; + unsigned char product_id[PRODUCT_ID_SIZE + 1]; + unsigned char cstmr_product_id[PRODUCT_ID_SIZE + 1]; + struct block_data bootloader; + struct block_data ui_firmware; + struct block_data ui_config; + struct block_data dp_config; + struct block_data pm_config; + struct block_data fl_config; + struct block_data bl_image; + struct block_data bl_config; + struct block_data lockdown; + struct block_data guest_code; + struct block_count blkcount; + struct physical_address phyaddr; +}; + +struct synaptics_rmi4_fwu_handle { + enum bl_version bl_version; + bool initialized; + bool in_bl_mode; + bool in_ub_mode; + bool force_update; + bool do_lockdown; + bool has_guest_code; + bool new_partition_table; + unsigned int data_pos; + unsigned char *ext_data_source; + unsigned char *read_config_buf; + unsigned char intr_mask; + unsigned char command; + unsigned char bootloader_id[2]; + unsigned char config_id[32]; + unsigned char flash_status; + unsigned char partitions; + unsigned short block_size; + unsigned short config_size; + unsigned short config_area; + unsigned short config_block_count; + unsigned short flash_config_length; + unsigned short payload_length; + unsigned short partition_table_bytes; + unsigned short read_config_buf_size; + const unsigned char *config_data; + const unsigned char *image; + unsigned char *image_name; + unsigned int image_size; + struct image_metadata img; + struct register_offset off; + struct block_count blkcount; + struct physical_address phyaddr; + struct f34_v5v6_flash_properties flash_properties; + struct synaptics_rmi4_fn_desc f34_fd; + struct synaptics_rmi4_fn_desc f35_fd; + struct synaptics_rmi4_data *rmi4_data; + struct workqueue_struct *fwu_workqueue; + struct work_struct fwu_work; +}; + +static struct bin_attribute dev_attr_data = { + .attr = { + .name = "data", + .mode = (S_IRUGO | S_IWUGO), + }, + .size = 0, + .read = fwu_sysfs_show_image, + .write = fwu_sysfs_store_image, +}; + +static struct device_attribute attrs[] = { + __ATTR(dorecovery, S_IWUGO, + synaptics_rmi4_show_error, + fwu_sysfs_do_recovery_store), + __ATTR(doreflash, S_IWUGO, + synaptics_rmi4_show_error, + fwu_sysfs_do_reflash_store), + __ATTR(writeconfig, S_IWUGO, + synaptics_rmi4_show_error, + fwu_sysfs_write_config_store), + __ATTR(readconfig, S_IWUGO, + synaptics_rmi4_show_error, + fwu_sysfs_read_config_store), + __ATTR(configarea, S_IWUGO, + synaptics_rmi4_show_error, + fwu_sysfs_config_area_store), + __ATTR(imagename, S_IWUGO, + synaptics_rmi4_show_error, + fwu_sysfs_image_name_store), + __ATTR(imagesize, S_IWUGO, + synaptics_rmi4_show_error, + 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(dispconfigblockcount, S_IRUGO, + fwu_sysfs_disp_config_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(guestcodeblockcount, S_IRUGO, + fwu_sysfs_guest_code_block_count_show, + synaptics_rmi4_store_error), + __ATTR(writeguestcode, S_IWUGO, + synaptics_rmi4_show_error, + fwu_sysfs_write_guest_code_store), +}; + +static struct synaptics_rmi4_fwu_handle *fwu; + +DECLARE_COMPLETION(fwu_remove_complete); + +static unsigned int le_to_uint(const unsigned char *ptr) +{ + return (unsigned int)ptr[0] + + (unsigned int)ptr[1] * 0x100 + + (unsigned int)ptr[2] * 0x10000 + + (unsigned int)ptr[3] * 0x1000000; +} + +static int fwu_allocate_read_config_buf(unsigned int count) +{ + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (count > fwu->read_config_buf_size) { + kfree(fwu->read_config_buf); + fwu->read_config_buf = kzalloc(count, GFP_KERNEL); + if (!fwu->read_config_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fwu->read_config_buf\n", + __func__); + fwu->read_config_buf_size = 0; + return -ENOMEM; + } + fwu->read_config_buf_size = count; + } + + return 0; +} + +static void fwu_compare_partition_tables(void) +{ + if (fwu->phyaddr.ui_firmware != fwu->img.phyaddr.ui_firmware) { + fwu->new_partition_table = true; + return; + } + + if (fwu->phyaddr.ui_config != fwu->img.phyaddr.ui_config) { + fwu->new_partition_table = true; + return; + } + + if (fwu->flash_properties.has_disp_config) { + if (fwu->phyaddr.dp_config != fwu->img.phyaddr.dp_config) { + fwu->new_partition_table = true; + return; + } + } + + if (fwu->has_guest_code) { + if (fwu->phyaddr.guest_code != fwu->img.phyaddr.guest_code) { + fwu->new_partition_table = true; + return; + } + } + + fwu->new_partition_table = false; + + return; +} + +static void fwu_parse_partition_table(const unsigned char *partition_table, + struct block_count *blkcount, struct physical_address *phyaddr) +{ + unsigned char ii; + unsigned char index; + unsigned char offset; + unsigned short partition_length; + unsigned short physical_address; + struct partition_table *ptable; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + for (ii = 0; ii < fwu->partitions; ii++) { + index = ii * 8 + 2; + ptable = (struct partition_table *)&partition_table[index]; + partition_length = ptable->partition_length_15_8 << 8 | + ptable->partition_length_7_0; + physical_address = ptable->start_physical_address_15_8 << 8 | + ptable->start_physical_address_7_0; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Partition entry %d:\n", + __func__, ii); + for (offset = 0; offset < 8; offset++) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: 0x%02x\n", + __func__, + partition_table[index + offset]); + } + switch (ptable->partition_id) { + case CORE_CODE_PARTITION: + blkcount->ui_firmware = partition_length; + phyaddr->ui_firmware = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Core code block count: %d\n", + __func__, blkcount->ui_firmware); + blkcount->total_count += partition_length; + break; + case CORE_CONFIG_PARTITION: + blkcount->ui_config = partition_length; + phyaddr->ui_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Core config block count: %d\n", + __func__, blkcount->ui_config); + blkcount->total_count += partition_length; + break; + case BOOTLOADER_PARTITION: + blkcount->bl_image = partition_length; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Core config block count: %d\n", + __func__, blkcount->ui_config); + blkcount->total_count += partition_length; + break; + case DISPLAY_CONFIG_PARTITION: + blkcount->dp_config = partition_length; + phyaddr->dp_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Display config block count: %d\n", + __func__, blkcount->dp_config); + blkcount->total_count += partition_length; + break; + case FLASH_CONFIG_PARTITION: + blkcount->fl_config = partition_length; + phyaddr->fl_config = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Flash config block count: %d\n", + __func__, blkcount->fl_config); + blkcount->total_count += partition_length; + break; + case GUEST_CODE_PARTITION: + blkcount->guest_code = partition_length; + phyaddr->guest_code = physical_address; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Guest code block count: %d\n", + __func__, blkcount->guest_code); + blkcount->total_count += partition_length; + break; + case GUEST_SERIALIZATION_PARTITION: + blkcount->pm_config = partition_length; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Guest serialization block count: %d\n", + __func__, blkcount->pm_config); + blkcount->total_count += partition_length; + break; + case GLOBAL_PARAMETERS_PARTITION: + blkcount->bl_config = partition_length; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Global parameters block count: %d\n", + __func__, blkcount->bl_config); + blkcount->total_count += partition_length; + break; + case DEVICE_CONFIG_PARTITION: + blkcount->lockdown = partition_length; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Device config block count: %d\n", + __func__, blkcount->lockdown); + blkcount->total_count += partition_length; + break; + }; + } + + return; +} + +static void fwu_parse_image_header_10_bl_container(const unsigned char *image) +{ + unsigned char ii; + unsigned char num_of_containers; + unsigned int addr; + unsigned int container_id; + unsigned int length; + const unsigned char *content; + struct container_descriptor *descriptor; + + num_of_containers = (fwu->img.bootloader.size - 4) / 4; + + for (ii = 1; ii <= num_of_containers; ii++) { + addr = le_to_uint(fwu->img.bootloader.data + (ii * 4)); + descriptor = (struct container_descriptor *)(image + addr); + container_id = descriptor->container_id[0] | + descriptor->container_id[1] << 8; + content = image + le_to_uint(descriptor->content_address); + length = le_to_uint(descriptor->content_length); + switch (container_id) { + case BL_IMAGE_CONTAINER: + fwu->img.bl_image.data = content; + fwu->img.bl_image.size = length; + break; + case BL_CONFIG_CONTAINER: + case GLOBAL_PARAMETERS_CONTAINER: + fwu->img.bl_config.data = content; + fwu->img.bl_config.size = length; + break; + case BL_LOCKDOWN_INFO_CONTAINER: + case DEVICE_CONFIG_CONTAINER: + fwu->img.lockdown.data = content; + fwu->img.lockdown.size = length; + break; + default: + break; + }; + } + + return; +} + +static void fwu_parse_image_header_10(void) +{ + unsigned char ii; + unsigned char num_of_containers; + unsigned int addr; + unsigned int offset; + unsigned int container_id; + unsigned int length; + const unsigned char *image; + const unsigned char *content; + struct container_descriptor *descriptor; + struct image_header_10 *header; + + image = fwu->image; + header = (struct image_header_10 *)image; + + fwu->img.checksum = le_to_uint(header->checksum); + + /* address of top level container */ + offset = le_to_uint(header->top_level_container_start_addr); + descriptor = (struct container_descriptor *)(image + offset); + + /* address of top level container content */ + offset = le_to_uint(descriptor->content_address); + num_of_containers = le_to_uint(descriptor->content_length) / 4; + + for (ii = 0; ii < num_of_containers; ii++) { + addr = le_to_uint(image + offset); + offset += 4; + descriptor = (struct container_descriptor *)(image + addr); + container_id = descriptor->container_id[0] | + descriptor->container_id[1] << 8; + content = image + le_to_uint(descriptor->content_address); + length = le_to_uint(descriptor->content_length); + switch (container_id) { + case UI_CONTAINER: + case CORE_CODE_CONTAINER: + fwu->img.ui_firmware.data = content; + fwu->img.ui_firmware.size = length; + break; + case UI_CONFIG_CONTAINER: + case CORE_CONFIG_CONTAINER: + fwu->img.ui_config.data = content; + fwu->img.ui_config.size = length; + break; + case BL_CONTAINER: + fwu->img.bl_version = *content; + fwu->img.bootloader.data = content; + fwu->img.bootloader.size = length; + fwu_parse_image_header_10_bl_container(image); + break; + case GUEST_CODE_CONTAINER: + fwu->img.contains_guest_code = true; + fwu->img.guest_code.data = content; + fwu->img.guest_code.size = length; + break; + case DISPLAY_CONFIG_CONTAINER: + fwu->img.contains_disp_config = true; + fwu->img.dp_config.data = content; + fwu->img.dp_config.size = length; + break; + case PERMANENT_CONFIG_CONTAINER: + case GUEST_SERIALIZATION_CONTAINER: + fwu->img.contains_perm_config = true; + fwu->img.pm_config.data = content; + fwu->img.pm_config.size = length; + break; + case FLASH_CONFIG_CONTAINER: + fwu->img.contains_flash_config = true; + fwu->img.fl_config.data = content; + fwu->img.fl_config.size = length; + break; + case GENERAL_INFORMATION_CONTAINER: + fwu->img.contains_firmware_id = true; + fwu->img.firmware_id = le_to_uint(content + 4); + break; + default: + break; + } + } + + return; +} + +static void fwu_parse_image_header_05_06(void) +{ + int retval; + const unsigned char *image; + struct image_header_05_06 *header; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + image = fwu->image; + header = (struct image_header_05_06 *)image; + + fwu->img.checksum = le_to_uint(header->checksum); + + fwu->img.bl_version = header->header_version; + + fwu->img.contains_bootloader = header->options_bootloader; + if (fwu->img.contains_bootloader) + fwu->img.bootloader_size = le_to_uint(header->bootloader_size); + + fwu->img.ui_firmware.size = le_to_uint(header->firmware_size); + if (fwu->img.ui_firmware.size) { + fwu->img.ui_firmware.data = image + IMAGE_AREA_OFFSET; + if (fwu->img.contains_bootloader) + fwu->img.ui_firmware.data += fwu->img.bootloader_size; + } + + if ((fwu->img.bl_version == BL_V6) && header->options_tddi) + fwu->img.ui_firmware.data = image + IMAGE_AREA_OFFSET; + + fwu->img.ui_config.size = le_to_uint(header->config_size); + if (fwu->img.ui_config.size) { + fwu->img.ui_config.data = fwu->img.ui_firmware.data + + fwu->img.ui_firmware.size; + } + + if ((fwu->img.bl_version == BL_V5 && fwu->img.contains_bootloader) || + (fwu->img.bl_version == BL_V6 && header->options_tddi)) + fwu->img.contains_disp_config = true; + else + fwu->img.contains_disp_config = false; + + if (fwu->img.contains_disp_config) { + fwu->img.disp_config_offset = le_to_uint(header->dsp_cfg_addr); + fwu->img.dp_config.size = le_to_uint(header->dsp_cfg_size); + fwu->img.dp_config.data = image + fwu->img.disp_config_offset; + } else { + retval = secure_memcpy(fwu->img.cstmr_product_id, + sizeof(fwu->img.cstmr_product_id), + header->cstmr_product_id, + sizeof(header->cstmr_product_id), + PRODUCT_ID_SIZE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy custom product ID string\n", + __func__); + } + fwu->img.cstmr_product_id[PRODUCT_ID_SIZE] = 0; + } + + fwu->img.contains_firmware_id = header->options_firmware_id; + if (fwu->img.contains_firmware_id) + fwu->img.firmware_id = le_to_uint(header->firmware_id); + + retval = secure_memcpy(fwu->img.product_id, + sizeof(fwu->img.product_id), + header->product_id, + sizeof(header->product_id), + PRODUCT_ID_SIZE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy product ID string\n", + __func__); + } + fwu->img.product_id[PRODUCT_ID_SIZE] = 0; + + fwu->img.lockdown.size = LOCKDOWN_SIZE; + fwu->img.lockdown.data = image + IMAGE_AREA_OFFSET - LOCKDOWN_SIZE; + + return; +} + +static int fwu_parse_image_info(void) +{ + struct image_header_10 *header; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + header = (struct image_header_10 *)fwu->image; + + memset(&fwu->img, 0x00, sizeof(fwu->img)); + + switch (header->major_header_version) { + case IMAGE_HEADER_VERSION_10: + fwu_parse_image_header_10(); + break; + case IMAGE_HEADER_VERSION_05: + case IMAGE_HEADER_VERSION_06: + fwu_parse_image_header_05_06(); + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Unsupported image file format (0x%02x)\n", + __func__, header->major_header_version); + return -EINVAL; + } + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) { + if (!fwu->img.contains_flash_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No flash config found in firmware image\n", + __func__); + return -EINVAL; + } + + fwu_parse_partition_table(fwu->img.fl_config.data, + &fwu->img.blkcount, &fwu->img.phyaddr); + + fwu_compare_partition_tables(); + } else { + fwu->new_partition_table = false; + } + + return 0; +} + +static int fwu_read_flash_status(void) +{ + int retval; + unsigned char status; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->off.flash_status, + &status, + sizeof(status)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash status\n", + __func__); + return retval; + } + + fwu->in_bl_mode = status >> 7; + + if (fwu->bl_version == BL_V5) + fwu->flash_status = (status >> 4) & MASK_3BIT; + else if (fwu->bl_version == BL_V6) + fwu->flash_status = status & MASK_3BIT; + else if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + fwu->flash_status = status & MASK_5BIT; + + if (fwu->flash_status != 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash status = %d, command = 0x%02x\n", + __func__, fwu->flash_status, fwu->command); + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->off.flash_cmd, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash command\n", + __func__); + return retval; + } + + if (fwu->bl_version == BL_V5) + fwu->command = command & MASK_4BIT; + else if (fwu->bl_version == BL_V6) + fwu->command = command & MASK_6BIT; + else if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + fwu->command = command; + + return 0; +} + +static int fwu_wait_for_idle(int timeout_ms, bool poll) +{ + int count = 0; + int timeout_count = ((timeout_ms * 1000) / MAX_SLEEP_TIME_US) + 1; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + do { + usleep_range(MIN_SLEEP_TIME_US, MAX_SLEEP_TIME_US); + + count++; + if (poll || (count == timeout_count)) + fwu_read_flash_status(); + + if ((fwu->command == CMD_IDLE) && (fwu->flash_status == 0x00)) + return 0; + } while (count < timeout_count); + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for idle status\n", + __func__); + + return -ETIMEDOUT; +} + +static int fwu_write_f34_v7_command_single_transaction(unsigned char cmd) +{ + int retval; + unsigned char base; + struct f34_v7_data_1_5 data_1_5; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + memset(data_1_5.data, 0x00, sizeof(data_1_5.data)); + + switch (cmd) { + case CMD_ERASE_ALL: + data_1_5.partition_id = CORE_CODE_PARTITION; + data_1_5.command = CMD_V7_ERASE_AP; + break; + case CMD_ERASE_UI_FIRMWARE: + data_1_5.partition_id = CORE_CODE_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_BL_CONFIG: + data_1_5.partition_id = GLOBAL_PARAMETERS_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_UI_CONFIG: + data_1_5.partition_id = CORE_CONFIG_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_DISP_CONFIG: + data_1_5.partition_id = DISPLAY_CONFIG_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_FLASH_CONFIG: + data_1_5.partition_id = FLASH_CONFIG_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ERASE_GUEST_CODE: + data_1_5.partition_id = GUEST_CODE_PARTITION; + data_1_5.command = CMD_V7_ERASE; + break; + case CMD_ENABLE_FLASH_PROG: + data_1_5.partition_id = BOOTLOADER_PARTITION; + data_1_5.command = CMD_V7_ENTER_BL; + break; + }; + + data_1_5.payload_0 = fwu->bootloader_id[0]; + data_1_5.payload_1 = fwu->bootloader_id[1]; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.partition_id, + data_1_5.data, + sizeof(data_1_5.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write single transaction command\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_write_f34_v7_command(unsigned char cmd) +{ + int retval; + unsigned char base; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + switch (cmd) { + case CMD_WRITE_FW: + case CMD_WRITE_CONFIG: + case CMD_WRITE_LOCKDOWN: + case CMD_WRITE_GUEST_CODE: + command = CMD_V7_WRITE; + break; + case CMD_READ_CONFIG: + command = CMD_V7_READ; + break; + case CMD_ERASE_ALL: + command = CMD_V7_ERASE_AP; + break; + case CMD_ERASE_UI_FIRMWARE: + case CMD_ERASE_BL_CONFIG: + case CMD_ERASE_UI_CONFIG: + case CMD_ERASE_DISP_CONFIG: + case CMD_ERASE_FLASH_CONFIG: + case CMD_ERASE_GUEST_CODE: + command = CMD_V7_ERASE; + break; + case CMD_ENABLE_FLASH_PROG: + command = CMD_V7_ENTER_BL; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid command 0x%02x\n", + __func__, cmd); + return -EINVAL; + }; + + fwu->command = command; + + switch (cmd) { + case CMD_ERASE_ALL: + case CMD_ERASE_UI_FIRMWARE: + case CMD_ERASE_BL_CONFIG: + case CMD_ERASE_UI_CONFIG: + case CMD_ERASE_DISP_CONFIG: + case CMD_ERASE_FLASH_CONFIG: + case CMD_ERASE_GUEST_CODE: + case CMD_ENABLE_FLASH_PROG: + retval = fwu_write_f34_v7_command_single_transaction(cmd); + if (retval < 0) + return retval; + else + return 0; + default: + break; + }; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.flash_cmd, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write flash command\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_write_f34_v5v6_command(unsigned char cmd) +{ + int retval; + unsigned char base; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + switch (cmd) { + case CMD_IDLE: + command = CMD_V5V6_IDLE; + break; + case CMD_WRITE_FW: + command = CMD_V5V6_WRITE_FW; + break; + case CMD_WRITE_CONFIG: + command = CMD_V5V6_WRITE_CONFIG; + break; + case CMD_WRITE_LOCKDOWN: + command = CMD_V5V6_WRITE_LOCKDOWN; + break; + case CMD_WRITE_GUEST_CODE: + command = CMD_V5V6_WRITE_GUEST_CODE; + break; + case CMD_READ_CONFIG: + command = CMD_V5V6_READ_CONFIG; + break; + case CMD_ERASE_ALL: + command = CMD_V5V6_ERASE_ALL; + break; + case CMD_ERASE_UI_CONFIG: + command = CMD_V5V6_ERASE_UI_CONFIG; + break; + case CMD_ERASE_DISP_CONFIG: + command = CMD_V5V6_ERASE_DISP_CONFIG; + break; + case CMD_ERASE_GUEST_CODE: + command = CMD_V5V6_ERASE_GUEST_CODE; + break; + case CMD_ENABLE_FLASH_PROG: + command = CMD_V5V6_ENABLE_FLASH_PROG; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid command 0x%02x\n", + __func__, cmd); + return -EINVAL; + } + + switch (cmd) { + case CMD_ERASE_ALL: + case CMD_ERASE_UI_CONFIG: + case CMD_ERASE_DISP_CONFIG: + case CMD_ERASE_GUEST_CODE: + case CMD_ENABLE_FLASH_PROG: + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.payload, + fwu->bootloader_id, + sizeof(fwu->bootloader_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write bootloader ID\n", + __func__); + return retval; + } + break; + default: + break; + }; + + fwu->command = command; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.flash_cmd, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command 0x%02x\n", + __func__, command); + return retval; + } + + return 0; +} + +static int fwu_write_f34_command(unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_write_f34_v7_command(cmd); + else + retval = fwu_write_f34_v5v6_command(cmd); + + return retval; +} + +static int fwu_write_f34_v7_partition_id(unsigned char cmd) +{ + int retval; + unsigned char base; + unsigned char partition; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + switch (cmd) { + case CMD_WRITE_FW: + partition = CORE_CODE_PARTITION; + break; + case CMD_WRITE_CONFIG: + case CMD_READ_CONFIG: + if (fwu->config_area == UI_CONFIG_AREA) + partition = CORE_CONFIG_PARTITION; + else if (fwu->config_area == DP_CONFIG_AREA) + partition = DISPLAY_CONFIG_PARTITION; + else if (fwu->config_area == PM_CONFIG_AREA) + partition = GUEST_SERIALIZATION_PARTITION; + else if (fwu->config_area == BL_CONFIG_AREA) + partition = GLOBAL_PARAMETERS_PARTITION; + else if (fwu->config_area == FLASH_CONFIG_AREA) + partition = FLASH_CONFIG_PARTITION; + break; + case CMD_WRITE_LOCKDOWN: + partition = DEVICE_CONFIG_PARTITION; + break; + case CMD_WRITE_GUEST_CODE: + partition = GUEST_CODE_PARTITION; + break; + case CMD_ERASE_ALL: + partition = CORE_CODE_PARTITION; + break; + case CMD_ERASE_BL_CONFIG: + partition = GLOBAL_PARAMETERS_PARTITION; + break; + case CMD_ERASE_UI_CONFIG: + partition = CORE_CONFIG_PARTITION; + break; + case CMD_ERASE_DISP_CONFIG: + partition = DISPLAY_CONFIG_PARTITION; + break; + case CMD_ERASE_FLASH_CONFIG: + partition = FLASH_CONFIG_PARTITION; + break; + case CMD_ERASE_GUEST_CODE: + partition = GUEST_CODE_PARTITION; + break; + case CMD_ENABLE_FLASH_PROG: + partition = BOOTLOADER_PARTITION; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid command 0x%02x\n", + __func__, cmd); + return -EINVAL; + }; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.partition_id, + &partition, + sizeof(partition)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write partition ID\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_write_f34_partition_id(unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_write_f34_v7_partition_id(cmd); + else + retval = 0; + + return retval; +} + +static int fwu_read_f34_v7_partition_table(unsigned char *partition_table) +{ + int retval; + unsigned char base; + unsigned char length[2]; + unsigned short block_number = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + fwu->config_area = FLASH_CONFIG_AREA; + + retval = fwu_write_f34_partition_id(CMD_READ_CONFIG); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.block_number, + (unsigned char *)&block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + length[0] = (unsigned char)(fwu->flash_config_length & MASK_8BIT); + length[1] = (unsigned char)(fwu->flash_config_length >> 8); + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.transfer_length, + length, + sizeof(length)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write transfer length\n", + __func__); + return retval; + } + + retval = fwu_write_f34_command(CMD_READ_CONFIG); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command\n", + __func__); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, true); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status\n", + __func__); + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.payload, + partition_table, + fwu->partition_table_bytes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block data\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_read_f34_v7_queries(void) +{ + int retval; + unsigned char ii; + unsigned char base; + unsigned char index; + unsigned char offset; + unsigned char *ptable; + struct f34_v7_query_0 query_0; + struct f34_v7_query_1_7 query_1_7; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.query_base_addr; + + retval = synaptics_rmi4_reg_read(rmi4_data, + base, + query_0.data, + sizeof(query_0.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read query 0\n", + __func__); + return retval; + } + + offset = query_0.subpacket_1_size + 1; + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + offset, + query_1_7.data, + sizeof(query_1_7.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read queries 1 to 7\n", + __func__); + return retval; + } + + fwu->bootloader_id[0] = query_1_7.bl_minor_revision; + fwu->bootloader_id[1] = query_1_7.bl_major_revision; + + if (fwu->bootloader_id[1] == BL_V8) + fwu->bl_version = BL_V8; + + fwu->block_size = query_1_7.block_size_15_8 << 8 | + query_1_7.block_size_7_0; + + fwu->flash_config_length = query_1_7.flash_config_length_15_8 << 8 | + query_1_7.flash_config_length_7_0; + + fwu->payload_length = query_1_7.payload_length_15_8 << 8 | + query_1_7.payload_length_7_0; + + fwu->off.flash_status = V7_FLASH_STATUS_OFFSET; + fwu->off.partition_id = V7_PARTITION_ID_OFFSET; + fwu->off.block_number = V7_BLOCK_NUMBER_OFFSET; + fwu->off.transfer_length = V7_TRANSFER_LENGTH_OFFSET; + fwu->off.flash_cmd = V7_COMMAND_OFFSET; + fwu->off.payload = V7_PAYLOAD_OFFSET; + + index = sizeof(query_1_7.data) - V7_PARTITION_SUPPORT_BYTES; + + fwu->partitions = 0; + for (offset = 0; offset < V7_PARTITION_SUPPORT_BYTES; offset++) { + for (ii = 0; ii < 8; ii++) { + if (query_1_7.data[index + offset] & (1 << ii)) + fwu->partitions++; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Supported partitions: 0x%02x\n", + __func__, query_1_7.data[index + offset]); + } + + fwu->partition_table_bytes = fwu->partitions * 8 + 2; + + ptable = kzalloc(fwu->partition_table_bytes, GFP_KERNEL); + if (!ptable) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for partition table\n", + __func__); + return -ENOMEM; + } + + retval = fwu_read_f34_v7_partition_table(ptable); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read partition table\n", + __func__); + kfree(ptable); + return retval; + } + + fwu_parse_partition_table(ptable, &fwu->blkcount, &fwu->phyaddr); + + if (fwu->blkcount.dp_config) + fwu->flash_properties.has_disp_config = 1; + else + fwu->flash_properties.has_disp_config = 0; + + if (fwu->blkcount.pm_config) + fwu->flash_properties.has_pm_config = 1; + else + fwu->flash_properties.has_pm_config = 0; + + if (fwu->blkcount.bl_config) + fwu->flash_properties.has_bl_config = 1; + else + fwu->flash_properties.has_bl_config = 0; + + if (fwu->blkcount.guest_code) + fwu->has_guest_code = 1; + else + fwu->has_guest_code = 0; + + kfree(ptable); + + return 0; +} + +static int fwu_read_f34_v5v6_queries(void) +{ + int retval; + unsigned char count; + unsigned char base; + unsigned char buf[10]; + struct f34_v5v6_flash_properties_2 properties_2; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.query_base_addr; + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + V5V6_BOOTLOADER_ID_OFFSET, + fwu->bootloader_id, + sizeof(fwu->bootloader_id)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read bootloader ID\n", + __func__); + return retval; + } + + if (fwu->bl_version == BL_V5) { + fwu->off.properties = V5_PROPERTIES_OFFSET; + fwu->off.block_size = V5_BLOCK_SIZE_OFFSET; + fwu->off.block_count = V5_BLOCK_COUNT_OFFSET; + fwu->off.block_number = V5_BLOCK_NUMBER_OFFSET; + fwu->off.payload = V5_BLOCK_DATA_OFFSET; + } else if (fwu->bl_version == BL_V6) { + fwu->off.properties = V6_PROPERTIES_OFFSET; + fwu->off.properties_2 = V6_PROPERTIES_2_OFFSET; + fwu->off.block_size = V6_BLOCK_SIZE_OFFSET; + fwu->off.block_count = V6_BLOCK_COUNT_OFFSET; + fwu->off.gc_block_count = V6_GUEST_CODE_BLOCK_COUNT_OFFSET; + fwu->off.block_number = V6_BLOCK_NUMBER_OFFSET; + fwu->off.payload = V6_BLOCK_DATA_OFFSET; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.block_size, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block size info\n", + __func__); + return retval; + } + + batohs(&fwu->block_size, &(buf[0])); + + if (fwu->bl_version == BL_V5) { + fwu->off.flash_cmd = fwu->off.payload + fwu->block_size; + fwu->off.flash_status = fwu->off.flash_cmd; + } else if (fwu->bl_version == BL_V6) { + fwu->off.flash_cmd = V6_FLASH_COMMAND_OFFSET; + fwu->off.flash_status = V6_FLASH_STATUS_OFFSET; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.properties, + fwu->flash_properties.data, + sizeof(fwu->flash_properties.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties\n", + __func__); + return retval; + } + + count = 4; + + if (fwu->flash_properties.has_pm_config) + count += 2; + + if (fwu->flash_properties.has_bl_config) + count += 2; + + if (fwu->flash_properties.has_disp_config) + count += 2; + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.block_count, + buf, + count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block count info\n", + __func__); + return retval; + } + + batohs(&fwu->blkcount.ui_firmware, &(buf[0])); + batohs(&fwu->blkcount.ui_config, &(buf[2])); + + count = 4; + + if (fwu->flash_properties.has_pm_config) { + batohs(&fwu->blkcount.pm_config, &(buf[count])); + count += 2; + } + + if (fwu->flash_properties.has_bl_config) { + batohs(&fwu->blkcount.bl_config, &(buf[count])); + count += 2; + } + + if (fwu->flash_properties.has_disp_config) + batohs(&fwu->blkcount.dp_config, &(buf[count])); + + fwu->has_guest_code = false; + + if (fwu->flash_properties.has_query4) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.properties_2, + properties_2.data, + sizeof(properties_2.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties 2\n", + __func__); + return retval; + } + + if (properties_2.has_guest_code) { + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.gc_block_count, + buf, + 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read guest code block count\n", + __func__); + return retval; + } + + batohs(&fwu->blkcount.guest_code, &(buf[0])); + fwu->has_guest_code = true; + } + } + + return 0; +} + +static int fwu_read_f34_queries(void) +{ + int retval; + + memset(&fwu->blkcount, 0x00, sizeof(fwu->blkcount)); + memset(&fwu->phyaddr, 0x00, sizeof(fwu->phyaddr)); + + if (fwu->bl_version == BL_V7) + retval = fwu_read_f34_v7_queries(); + else + retval = fwu_read_f34_v5v6_queries(); + + return retval; +} + +static int fwu_write_f34_v7_blocks(unsigned char *block_ptr, + unsigned short block_cnt, unsigned char command) +{ + int retval; + unsigned char base; + unsigned char length[2]; + unsigned short transfer; + unsigned short max_transfer; + unsigned short remaining = block_cnt; + unsigned short block_number = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + retval = fwu_write_f34_partition_id(command); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.block_number, + (unsigned char *)&block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + if (fwu->payload_length > (PAGE_SIZE / fwu->block_size)) + max_transfer = PAGE_SIZE / fwu->block_size; + else + max_transfer = fwu->payload_length; + + do { + if (remaining / max_transfer) + transfer = max_transfer; + else + transfer = remaining; + + length[0] = (unsigned char)(transfer & MASK_8BIT); + length[1] = (unsigned char)(transfer >> 8); + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.transfer_length, + length, + sizeof(length)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write transfer length (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.payload, + block_ptr, + transfer * fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block data (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + block_ptr += (transfer * fwu->block_size); + remaining -= transfer; + } while (remaining); + + return 0; +} + +static int fwu_write_f34_v5v6_blocks(unsigned char *block_ptr, + unsigned short block_cnt, unsigned char command) +{ + int retval; + unsigned char base; + unsigned char block_number[] = {0, 0}; + unsigned short blk; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + block_number[1] |= (fwu->config_area << 5); + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.block_number, + block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + for (blk = 0; blk < block_cnt; blk++) { + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.payload, + block_ptr, + fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block data (block %d)\n", + __func__, blk); + return retval; + } + + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command for block %d\n", + __func__, blk); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status (block %d)\n", + __func__, blk); + return retval; + } + + block_ptr += fwu->block_size; + } + + return 0; +} + +static int fwu_write_f34_blocks(unsigned char *block_ptr, + unsigned short block_cnt, unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_write_f34_v7_blocks(block_ptr, block_cnt, cmd); + else + retval = fwu_write_f34_v5v6_blocks(block_ptr, block_cnt, cmd); + + return retval; +} + +static int fwu_read_f34_v7_blocks(unsigned short block_cnt, + unsigned char command) +{ + int retval; + unsigned char base; + unsigned char length[2]; + unsigned short transfer; + unsigned short max_transfer; + unsigned short remaining = block_cnt; + unsigned short block_number = 0; + unsigned short index = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + retval = fwu_write_f34_partition_id(command); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.block_number, + (unsigned char *)&block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + if (fwu->payload_length > (PAGE_SIZE / fwu->block_size)) + max_transfer = PAGE_SIZE / fwu->block_size; + else + max_transfer = fwu->payload_length; + + do { + if (remaining / max_transfer) + transfer = max_transfer; + else + transfer = remaining; + + length[0] = (unsigned char)(transfer & MASK_8BIT); + length[1] = (unsigned char)(transfer >> 8); + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.transfer_length, + length, + sizeof(length)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write transfer length (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.payload, + &fwu->read_config_buf[index], + transfer * fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block data (%d blocks remaining)\n", + __func__, remaining); + return retval; + } + + index += (transfer * fwu->block_size); + remaining -= transfer; + } while (remaining); + + return 0; +} + +static int fwu_read_f34_v5v6_blocks(unsigned short block_cnt, + unsigned char command) +{ + int retval; + unsigned char base; + unsigned char block_number[] = {0, 0}; + unsigned short blk; + unsigned short index = 0; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f34_fd.data_base_addr; + + block_number[1] |= (fwu->config_area << 5); + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + fwu->off.block_number, + block_number, + sizeof(block_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write block number\n", + __func__); + return retval; + } + + for (blk = 0; blk < block_cnt; blk++) { + retval = fwu_write_f34_command(command); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write read config command\n", + __func__); + return retval; + } + + retval = fwu_wait_for_idle(WRITE_WAIT_MS, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to wait for idle status\n", + __func__); + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + fwu->off.payload, + &fwu->read_config_buf[index], + fwu->block_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read block data (block %d)\n", + __func__, blk); + return retval; + } + + index += fwu->block_size; + } + + return 0; +} + +static int fwu_read_f34_blocks(unsigned short block_cnt, unsigned char cmd) +{ + int retval; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + retval = fwu_read_f34_v7_blocks(block_cnt, cmd); + else + retval = fwu_read_f34_v5v6_blocks(block_cnt, cmd); + + return retval; +} + +static int fwu_get_image_firmware_id(unsigned int *fw_id) +{ + int retval; + unsigned char index = 0; + char *strptr; + char *firmware_id; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->img.contains_firmware_id) { + *fw_id = fwu->img.firmware_id; + } else { + strptr = strnstr(fwu->image_name, "PR", MAX_IMAGE_NAME_LEN); + if (!strptr) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No valid PR number (PRxxxxxxx) found in image file name (%s)\n", + __func__, fwu->image_name); + return -EINVAL; + } + + strptr += 2; + firmware_id = kzalloc(MAX_FIRMWARE_ID_LEN, GFP_KERNEL); + if (!firmware_id) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for firmware_id\n", + __func__); + return -ENOMEM; + } + while (strptr[index] >= '0' && strptr[index] <= '9') { + firmware_id[index] = strptr[index]; + index++; + } + + retval = sstrtoul(firmware_id, 10, (unsigned long *)fw_id); + kfree(firmware_id); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to obtain image firmware ID\n", + __func__); + return -EINVAL; + } + } + + return 0; +} + +static int fwu_get_device_config_id(void) +{ + int retval; + unsigned char config_id_size; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + config_id_size = V7_CONFIG_ID_SIZE; + else + config_id_size = V5V6_CONFIG_ID_SIZE; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.ctrl_base_addr, + fwu->config_id, + config_id_size); + if (retval < 0) + return retval; + + return 0; +} + +static enum flash_area fwu_go_nogo(void) +{ + int retval; + enum flash_area flash_area = NONE; + unsigned char ii; + unsigned char config_id_size; + unsigned int device_fw_id; + unsigned int image_fw_id; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->force_update) { + flash_area = UI_FIRMWARE; + goto exit; + } + + /* Update both UI and config if device is in bootloader mode */ + if (fwu->in_bl_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 */ + retval = fwu_get_image_firmware_id(&image_fw_id); + if (retval < 0) { + flash_area = NONE; + goto exit; + } + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image firmware ID = %d\n", + __func__, image_fw_id); + + if (image_fw_id > device_fw_id) { + flash_area = UI_FIRMWARE; + goto exit; + } else if (image_fw_id < device_fw_id) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Image firmware ID older than device firmware ID\n", + __func__); + flash_area = NONE; + goto exit; + } + + /* Get device config ID */ + retval = fwu_get_device_config_id(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device config ID\n", + __func__); + flash_area = NONE; + goto exit; + } + + if (fwu->bl_version == BL_V7 || fwu->bl_version == BL_V8) + config_id_size = V7_CONFIG_ID_SIZE; + else + config_id_size = V5V6_CONFIG_ID_SIZE; + + for (ii = 0; ii < config_id_size; ii++) { + if (fwu->img.ui_config.data[ii] > fwu->config_id[ii]) { + flash_area = UI_CONFIG; + goto exit; + } else if (fwu->img.ui_config.data[ii] < fwu->config_id[ii]) { + flash_area = NONE; + goto exit; + } + } + + flash_area = NONE; + +exit: + if (flash_area == NONE) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: No need to do reflash\n", + __func__); + } else { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Updating %s\n", + __func__, + flash_area == UI_FIRMWARE ? + "UI firmware and config" : + "UI config only"); + } + + return flash_area; +} + +static int fwu_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + bool f01found = false; + bool f34found = false; + bool f35found = false; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + fwu->in_ub_mode = false; + + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + if (rmi_fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, rmi_fd.fn_number); + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F01: + f01found = true; + + rmi4_data->f01_query_base_addr = + rmi_fd.query_base_addr; + rmi4_data->f01_ctrl_base_addr = + rmi_fd.ctrl_base_addr; + rmi4_data->f01_data_base_addr = + rmi_fd.data_base_addr; + rmi4_data->f01_cmd_base_addr = + rmi_fd.cmd_base_addr; + break; + case SYNAPTICS_RMI4_F34: + f34found = true; + fwu->f34_fd.query_base_addr = + rmi_fd.query_base_addr; + fwu->f34_fd.ctrl_base_addr = + rmi_fd.ctrl_base_addr; + fwu->f34_fd.data_base_addr = + rmi_fd.data_base_addr; + + switch (rmi_fd.fn_version) { + case F34_V0: + fwu->bl_version = BL_V5; + break; + case F34_V1: + fwu->bl_version = BL_V6; + break; + case F34_V2: + fwu->bl_version = BL_V7; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Unrecognized F34 version\n", + __func__); + return -EINVAL; + } + + fwu->intr_mask = 0; + intr_src = rmi_fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + fwu->intr_mask |= 1 << ii; + } + break; + case SYNAPTICS_RMI4_F35: + f35found = true; + fwu->f35_fd.query_base_addr = + rmi_fd.query_base_addr; + fwu->f35_fd.ctrl_base_addr = + rmi_fd.ctrl_base_addr; + fwu->f35_fd.data_base_addr = + rmi_fd.data_base_addr; + break; + } + } else { + break; + } + + intr_count += rmi_fd.intr_src_count; + } + + if (!f01found || !f34found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find both F01 and F34\n", + __func__); + if (!f35found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F35\n", + __func__); + return -EINVAL; + } else { + fwu->in_ub_mode = true; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + fwu_recovery_check_status(); + return 0; + } + } + + rmi4_data->intr_mask[0] |= fwu->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &(rmi4_data->intr_mask[0]), + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_enter_flash_prog(void) +{ + int retval; + struct f01_device_control f01_device_control; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_read_flash_status(); + if (retval < 0) + return retval; + + if (fwu->in_bl_mode) + return 0; + + retval = rmi4_data->irq_enable(rmi4_data, false, true); + if (retval < 0) + return retval; + + msleep(INT_DISABLE_WAIT_MS); + + retval = fwu_write_f34_command(CMD_ENABLE_FLASH_PROG); + if (retval < 0) + return retval; + + retval = fwu_wait_for_idle(ENABLE_WAIT_MS, false); + if (retval < 0) + return retval; + + if (!fwu->in_bl_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: BL mode not entered\n", + __func__); + return -EINVAL; + } + + if (rmi4_data->hw_if->bl_hw_init) { + retval = rmi4_data->hw_if->bl_hw_init(rmi4_data); + if (retval < 0) + return retval; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + return retval; + + retval = fwu_read_f34_queries(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + f01_device_control.data, + sizeof(f01_device_control.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read F01 device control\n", + __func__); + return retval; + } + + f01_device_control.nosleep = true; + f01_device_control.sleep_mode = SLEEP_MODE_NORMAL; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + f01_device_control.data, + sizeof(f01_device_control.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write F01 device control\n", + __func__); + return retval; + } + + msleep(ENTER_FLASH_PROG_WAIT_MS); + + return retval; +} + +static int fwu_check_ui_firmware_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.ui_firmware.size / fwu->block_size; + + if (block_count != fwu->blkcount.ui_firmware) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: UI firmware size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_ui_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.ui_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.ui_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: UI configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_dp_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.dp_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.dp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Display configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_pm_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.pm_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.pm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Permanent configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_bl_configuration_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.bl_config.size / fwu->block_size; + + if (block_count != fwu->blkcount.bl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Bootloader configuration size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_check_guest_code_size(void) +{ + unsigned short block_count; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + block_count = fwu->img.guest_code.size / fwu->block_size; + if (block_count != fwu->blkcount.guest_code) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Guest code size mismatch\n", + __func__); + return -EINVAL; + } + + return 0; +} + +static int fwu_write_firmware(void) +{ + unsigned short firmware_block_count; + + firmware_block_count = fwu->img.ui_firmware.size / fwu->block_size; + + return fwu_write_f34_blocks((unsigned char *)fwu->img.ui_firmware.data, + firmware_block_count, CMD_WRITE_FW); +} + +static int fwu_erase_configuration(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_UI_CONFIG); + if (retval < 0) + return retval; + break; + case DP_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_DISP_CONFIG); + if (retval < 0) + return retval; + break; + case BL_CONFIG_AREA: + retval = fwu_write_f34_command(CMD_ERASE_BL_CONFIG); + if (retval < 0) + return retval; + break; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + return retval; +} + +static int fwu_erase_guest_code(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_write_f34_command(CMD_ERASE_GUEST_CODE); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + return 0; +} + +static int fwu_erase_all(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (fwu->bl_version == BL_V7) { + retval = fwu_write_f34_command(CMD_ERASE_UI_FIRMWARE); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + fwu->config_area = UI_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + } else { + retval = fwu_write_f34_command(CMD_ERASE_ALL); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase all command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (!(fwu->bl_version == BL_V8 && + fwu->flash_status == BAD_PARTITION_TABLE)) { + if (retval < 0) + return retval; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + if (fwu->bl_version == BL_V8) + return 0; + } + + if (fwu->flash_properties.has_disp_config && + fwu->img.contains_disp_config) { + fwu->config_area = DP_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + } + + if (fwu->has_guest_code && fwu->img.contains_guest_code) { + retval = fwu_erase_guest_code(); + if (retval < 0) + return retval; + } + + return 0; +} + +static int fwu_write_configuration(void) +{ + return fwu_write_f34_blocks((unsigned char *)fwu->config_data, + fwu->config_block_count, CMD_WRITE_CONFIG); +} + +static int fwu_write_ui_configuration(void) +{ + fwu->config_area = UI_CONFIG_AREA; + fwu->config_data = fwu->img.ui_config.data; + fwu->config_size = fwu->img.ui_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + return fwu_write_configuration(); +} + +static int fwu_write_dp_configuration(void) +{ + fwu->config_area = DP_CONFIG_AREA; + fwu->config_data = fwu->img.dp_config.data; + fwu->config_size = fwu->img.dp_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + return fwu_write_configuration(); +} + +static int fwu_write_pm_configuration(void) +{ + fwu->config_area = PM_CONFIG_AREA; + fwu->config_data = fwu->img.pm_config.data; + fwu->config_size = fwu->img.pm_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + return fwu_write_configuration(); +} + +static int fwu_write_flash_configuration(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + fwu->config_area = FLASH_CONFIG_AREA; + fwu->config_data = fwu->img.fl_config.data; + fwu->config_size = fwu->img.fl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + if (fwu->config_block_count != fwu->blkcount.fl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash configuration size mismatch\n", + __func__); + return -EINVAL; + } + + retval = fwu_write_f34_command(CMD_ERASE_FLASH_CONFIG); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Erase flash configuration command written\n", + __func__); + + retval = fwu_wait_for_idle(ERASE_WAIT_MS, false); + if (retval < 0) + return retval; + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Idle status detected\n", + __func__); + + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + rmi4_data->reset_device(rmi4_data, false); + + return 0; +} + +static int fwu_write_guest_code(void) +{ + int retval; + unsigned short guest_code_block_count; + + guest_code_block_count = fwu->img.guest_code.size / fwu->block_size; + + retval = fwu_write_f34_blocks((unsigned char *)fwu->img.guest_code.data, + guest_code_block_count, CMD_WRITE_GUEST_CODE); + if (retval < 0) + return retval; + + return 0; +} + +static int fwu_write_lockdown(void) +{ + unsigned short lockdown_block_count; + + lockdown_block_count = fwu->img.lockdown.size / fwu->block_size; + + return fwu_write_f34_blocks((unsigned char *)fwu->img.lockdown.data, + lockdown_block_count, CMD_WRITE_LOCKDOWN); +} + +static int fwu_write_partition_table_v8(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + fwu->config_area = FLASH_CONFIG_AREA; + fwu->config_data = fwu->img.fl_config.data; + fwu->config_size = fwu->img.fl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + if (fwu->config_block_count != fwu->blkcount.fl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash configuration size mismatch\n", + __func__); + return -EINVAL; + } + + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + rmi4_data->reset_device(rmi4_data, false); + + return 0; +} + +static int fwu_write_partition_table_v7(void) +{ + int retval; + unsigned short block_count; + + block_count = fwu->blkcount.bl_config; + fwu->config_area = BL_CONFIG_AREA; + fwu->config_size = fwu->block_size * block_count; + + retval = fwu_allocate_read_config_buf(fwu->config_size); + if (retval < 0) + return retval; + + retval = fwu_read_f34_blocks(block_count, CMD_READ_CONFIG); + if (retval < 0) + return retval; + + retval = fwu_erase_configuration(); + if (retval < 0) + return retval; + + retval = fwu_write_flash_configuration(); + if (retval < 0) + return retval; + + fwu->config_area = BL_CONFIG_AREA; + fwu->config_data = fwu->read_config_buf; + fwu->config_size = fwu->img.bl_config.size; + fwu->config_block_count = fwu->config_size / fwu->block_size; + + retval = fwu_write_configuration(); + if (retval < 0) + return retval; + + return 0; +} + +static int fwu_do_reflash(void) +{ + int retval; + + if (!fwu->new_partition_table) { + retval = fwu_check_ui_firmware_size(); + if (retval < 0) + return retval; + + retval = fwu_check_ui_configuration_size(); + if (retval < 0) + return retval; + + if (fwu->flash_properties.has_disp_config && + fwu->img.contains_disp_config) { + retval = fwu_check_dp_configuration_size(); + if (retval < 0) + return retval; + } + + if (fwu->has_guest_code && fwu->img.contains_guest_code) { + retval = fwu_check_guest_code_size(); + if (retval < 0) + return retval; + } + } else if (fwu->bl_version == BL_V7) { + retval = fwu_check_bl_configuration_size(); + if (retval < 0) + return retval; + } + + retval = fwu_erase_all(); + if (retval < 0) + return retval; + + if (fwu->bl_version == BL_V7 && fwu->new_partition_table) { + retval = fwu_write_partition_table_v7(); + if (retval < 0) + return retval; + pr_notice("%s: Partition table programmed\n", __func__); + } else if (fwu->bl_version == BL_V8) { + retval = fwu_write_partition_table_v8(); + if (retval < 0) + return retval; + pr_notice("%s: Partition table programmed\n", __func__); + } + + retval = fwu_write_firmware(); + if (retval < 0) + return retval; + pr_notice("%s: Firmware programmed\n", __func__); + + fwu->config_area = UI_CONFIG_AREA; + retval = fwu_write_ui_configuration(); + if (retval < 0) + return retval; + pr_notice("%s: Configuration programmed\n", __func__); + + if (fwu->flash_properties.has_disp_config && + fwu->img.contains_disp_config) { + retval = fwu_write_dp_configuration(); + if (retval < 0) + return retval; + pr_notice("%s: Display configuration programmed\n", __func__); + } + + if (fwu->has_guest_code && fwu->img.contains_guest_code) { + retval = fwu_write_guest_code(); + if (retval < 0) + return retval; + pr_notice("%s: Guest code programmed\n", __func__); + } + + return retval; +} + +static int fwu_do_read_config(void) +{ + int retval; + unsigned short block_count; + unsigned short config_area; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + block_count = fwu->blkcount.ui_config; + break; + case DP_CONFIG_AREA: + if (!fwu->flash_properties.has_disp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Display configuration not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.dp_config; + break; + case PM_CONFIG_AREA: + if (!fwu->flash_properties.has_pm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Permanent configuration not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.pm_config; + break; + case BL_CONFIG_AREA: + if (!fwu->flash_properties.has_bl_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Bootloader configuration not supported\n", + __func__); + return -EINVAL; + } + block_count = fwu->blkcount.bl_config; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid config area\n", + __func__); + return -EINVAL; + } + + if (block_count == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid block count\n", + __func__); + return -EINVAL; + } + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + config_area = fwu->config_area; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + goto exit; + + fwu->config_area = config_area; + + fwu->config_size = fwu->block_size * block_count; + + retval = fwu_allocate_read_config_buf(fwu->config_size); + if (retval < 0) + goto exit; + + retval = fwu_read_f34_blocks(block_count, CMD_READ_CONFIG); + +exit: + rmi4_data->reset_device(rmi4_data, false); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + return retval; +} + +static int fwu_do_lockdown_v7(void) +{ + int retval; + struct f34_v7_data0 status; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.data_base_addr + fwu->off.flash_status, + status.data, + sizeof(status.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash status\n", + __func__); + return retval; + } + + if (status.device_cfg_status == 2) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device already locked down\n", + __func__); + return 0; + } + + retval = fwu_write_lockdown(); + if (retval < 0) + return retval; + + pr_notice("%s: Lockdown programmed\n", __func__); + + return retval; +} + +static int fwu_do_lockdown_v5v6(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_reg_read(rmi4_data, + fwu->f34_fd.query_base_addr + fwu->off.properties, + fwu->flash_properties.data, + sizeof(fwu->flash_properties.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read flash properties\n", + __func__); + return retval; + } + + if (fwu->flash_properties.unlocked == 0) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device already locked down\n", + __func__); + return 0; + } + + retval = fwu_write_lockdown(); + if (retval < 0) + return retval; + + pr_notice("%s: Lockdown programmed\n", __func__); + + return retval; +} + +static int fwu_start_write_guest_code(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_parse_image_info(); + if (retval < 0) + return -EINVAL; + + if (!fwu->has_guest_code) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Guest code not supported\n", + __func__); + return -EINVAL; + } + + if (!fwu->img.contains_guest_code) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No guest code in firmware image\n", + __func__); + return -EINVAL; + } + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of write guest code process\n", __func__); + + retval = fwu_enter_flash_prog(); + if (retval < 0) + goto exit; + + retval = fwu_check_guest_code_size(); + if (retval < 0) + goto exit; + + retval = fwu_erase_guest_code(); + if (retval < 0) + goto exit; + + retval = fwu_write_guest_code(); + if (retval < 0) + goto exit; + + pr_notice("%s: Guest code programmed\n", __func__); + +exit: + rmi4_data->reset_device(rmi4_data, false); + + pr_notice("%s: End of write guest code process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} + +static int fwu_start_write_config(void) +{ + int retval; + unsigned short config_area; + unsigned int device_fw_id; + unsigned int image_fw_id; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = fwu_parse_image_info(); + if (retval < 0) + return -EINVAL; + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + device_fw_id = rmi4_data->firmware_id; + retval = fwu_get_image_firmware_id(&image_fw_id); + if (retval < 0) + return retval; + if (device_fw_id != image_fw_id) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Device and image firmware IDs don't match\n", + __func__); + return -EINVAL; + } + retval = fwu_check_ui_configuration_size(); + if (retval < 0) + return retval; + break; + case DP_CONFIG_AREA: + if (!fwu->flash_properties.has_disp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Display configuration not supported\n", + __func__); + return -EINVAL; + } + if (!fwu->img.contains_disp_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No display configuration in firmware image\n", + __func__); + return -EINVAL; + } + retval = fwu_check_dp_configuration_size(); + if (retval < 0) + return retval; + break; + case PM_CONFIG_AREA: + if (!fwu->flash_properties.has_pm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Permanent configuration not supported\n", + __func__); + return -EINVAL; + } + if (!fwu->img.contains_perm_config) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: No permanent configuration in firmware image\n", + __func__); + return -EINVAL; + } + retval = fwu_check_pm_configuration_size(); + if (retval < 0) + return retval; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Configuration not supported\n", + __func__); + return -EINVAL; + } + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of write config process\n", __func__); + + config_area = fwu->config_area; + + retval = fwu_enter_flash_prog(); + if (retval < 0) + goto exit; + + fwu->config_area = config_area; + + if (fwu->config_area != PM_CONFIG_AREA) { + retval = fwu_erase_configuration(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to erase config\n", + __func__); + goto exit; + } + } + + switch (fwu->config_area) { + case UI_CONFIG_AREA: + retval = fwu_write_ui_configuration(); + if (retval < 0) + goto exit; + break; + case DP_CONFIG_AREA: + retval = fwu_write_dp_configuration(); + if (retval < 0) + goto exit; + break; + case PM_CONFIG_AREA: + retval = fwu_write_pm_configuration(); + if (retval < 0) + goto exit; + break; + } + + pr_notice("%s: Config written\n", __func__); + +exit: + switch (fwu->config_area) { + case UI_CONFIG_AREA: + rmi4_data->reset_device(rmi4_data, true); + break; + case DP_CONFIG_AREA: + case PM_CONFIG_AREA: + rmi4_data->reset_device(rmi4_data, false); + break; + } + + pr_notice("%s: End of write config process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} + +static int fwu_start_reflash(void) +{ + int retval = 0; + enum flash_area flash_area; + const struct firmware *fw_entry = NULL; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of reflash process\n", __func__); + + if (fwu->image == NULL) { + retval = secure_memcpy(fwu->image_name, MAX_IMAGE_NAME_LEN, + FW_IMAGE_NAME, sizeof(FW_IMAGE_NAME), + sizeof(FW_IMAGE_NAME)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy image file name\n", + __func__); + goto exit; + } + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Requesting firmware image %s\n", + __func__, fwu->image_name); + + retval = request_firmware(&fw_entry, fwu->image_name, + rmi4_data->pdev->dev.parent); + if (retval != 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Firmware image %s not available\n", + __func__, fwu->image_name); + retval = -EINVAL; + goto exit; + } + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Firmware image size = %d\n", + __func__, (unsigned int)fw_entry->size); + + fwu->image = fw_entry->data; + } + + retval = fwu_parse_image_info(); + if (retval < 0) + goto exit; + + if (fwu->blkcount.total_count != fwu->img.blkcount.total_count) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Flash size mismatch\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (fwu->bl_version != fwu->img.bl_version) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Bootloader version mismatch\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->force_update && fwu->new_partition_table) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Partition table mismatch\n", + __func__); + retval = -EINVAL; + goto exit; + } + + retval = fwu_read_flash_status(); + if (retval < 0) + goto exit; + + if (fwu->in_bl_mode) { + dev_info(rmi4_data->pdev->dev.parent, + "%s: Device in bootloader mode\n", + __func__); + } + + flash_area = fwu_go_nogo(); + + if (flash_area != NONE) { + retval = fwu_enter_flash_prog(); + if (retval < 0) { + rmi4_data->reset_device(rmi4_data, false); + goto exit; + } + } + + switch (flash_area) { + case UI_FIRMWARE: + retval = fwu_do_reflash(); + rmi4_data->reset_device(rmi4_data, true); + break; + case UI_CONFIG: + retval = fwu_check_ui_configuration_size(); + if (retval < 0) + break; + fwu->config_area = UI_CONFIG_AREA; + retval = fwu_erase_configuration(); + if (retval < 0) + break; + retval = fwu_write_ui_configuration(); + rmi4_data->reset_device(rmi4_data, true); + break; + case NONE: + default: + rmi4_data->reset_device(rmi4_data, false); + break; + } + + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do reflash\n", + __func__); + goto exit; + } + + if (fwu->do_lockdown && (fwu->img.lockdown.data != NULL)) { + switch (fwu->bl_version) { + case BL_V5: + case BL_V6: + retval = fwu_do_lockdown_v5v6(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do lockdown\n", + __func__); + } + rmi4_data->reset_device(rmi4_data, false); + break; + case BL_V7: + case BL_V8: + retval = fwu_do_lockdown_v7(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do lockdown\n", + __func__); + } + rmi4_data->reset_device(rmi4_data, false); + break; + default: + break; + } + } + +exit: + if (fw_entry) + release_firmware(fw_entry); + + pr_notice("%s: End of reflash process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} + +static int fwu_recovery_check_status(void) +{ + int retval; + unsigned char base; + unsigned char status; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f35_fd.data_base_addr; + + retval = synaptics_rmi4_reg_read(rmi4_data, + base + F35_ERROR_CODE_OFFSET, + &status, + 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read status\n", + __func__); + return retval; + } + + status = status & MASK_7BIT; + + if (status != 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Recovery mode status = %d\n", + __func__, status); + return -EINVAL; + } + + return 0; +} + +static int fwu_recovery_erase_all(void) +{ + int retval; + unsigned char base; + unsigned char command = CMD_F35_ERASE_ALL; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f35_fd.ctrl_base_addr; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + F35_CHUNK_COMMAND_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue erase all command\n", + __func__); + return retval; + } + + msleep(F35_ERASE_ALL_WAIT_MS); + + retval = fwu_recovery_check_status(); + if (retval < 0) + return retval; + + return 0; +} + +static int fwu_recovery_write_chunk(void) +{ + int retval; + unsigned char base; + unsigned char chunk_number[] = {0, 0}; + unsigned char chunk_spare; + unsigned char chunk_size; + unsigned char buf[F35_CHUNK_SIZE + 1]; + unsigned short chunk; + unsigned short chunk_total; + unsigned short bytes_written = 0; + unsigned char *chunk_ptr = (unsigned char *)fwu->image; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f35_fd.ctrl_base_addr; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + F35_CHUNK_NUM_LSB_OFFSET, + chunk_number, + sizeof(chunk_number)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk number\n", + __func__); + return retval; + } + + buf[sizeof(buf) - 1] = CMD_F35_WRITE_CHUNK; + + chunk_total = fwu->image_size / F35_CHUNK_SIZE; + chunk_spare = fwu->image_size % F35_CHUNK_SIZE; + if (chunk_spare) + chunk_total++; + + for (chunk = 0; chunk < chunk_total; chunk++) { + if (chunk_spare && chunk == chunk_total - 1) + chunk_size = chunk_spare; + else + chunk_size = F35_CHUNK_SIZE; + + memset(buf, 0x00, F35_CHUNK_SIZE); + secure_memcpy(buf, sizeof(buf), chunk_ptr, + fwu->image_size - bytes_written, + chunk_size); + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + F35_CHUNK_DATA_OFFSET, + buf, + sizeof(buf)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk data (chunk %d)\n", + __func__, chunk); + return retval; + } + chunk_ptr += chunk_size; + bytes_written += chunk_size; + } + + retval = fwu_recovery_check_status(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk data\n", + __func__); + return retval; + } + + return 0; +} + +static int fwu_recovery_reset(void) +{ + int retval; + unsigned char base; + unsigned char command = CMD_F35_RESET; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + base = fwu->f35_fd.ctrl_base_addr; + + retval = synaptics_rmi4_reg_write(rmi4_data, + base + F35_CHUNK_COMMAND_OFFSET, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to issue reset command\n", + __func__); + return retval; + } + + msleep(F35_RESET_WAIT_MS); + + return 0; +} + +static int fwu_start_recovery(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + mutex_lock(&rmi4_data->rmi4_exp_init_mutex); + + pr_notice("%s: Start of recovery process\n", __func__); + + retval = rmi4_data->irq_enable(rmi4_data, false, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable interrupt\n", + __func__); + goto exit; + } + + retval = fwu_recovery_erase_all(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do erase all in recovery mode\n", + __func__); + goto exit; + } + + pr_notice("%s: External flash erased\n", __func__); + + retval = fwu_recovery_write_chunk(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write chunk data in recovery mode\n", + __func__); + goto exit; + } + + pr_notice("%s: Chunk data programmed\n", __func__); + + retval = fwu_recovery_reset(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to reset device in recovery mode\n", + __func__); + goto exit; + } + + pr_notice("%s: Recovery mode reset issued\n", __func__); + + rmi4_data->reset_device(rmi4_data, true); + + retval = 0; + +exit: + pr_notice("%s: End of recovery process\n", __func__); + + mutex_unlock(&rmi4_data->rmi4_exp_init_mutex); + + rmi4_data->stay_awake = false; + + return retval; +} + +int synaptics_fw_updater(const unsigned char *fw_data) +{ + int retval; + + if (!fwu) + return -ENODEV; + + if (!fwu->initialized) + return -ENODEV; + + if (fwu->in_ub_mode) + return -ENODEV; + + fwu->image = fw_data; + + retval = fwu_start_reflash(); + + fwu->image = NULL; + + return retval; +} +EXPORT_SYMBOL(synaptics_fw_updater); + +#ifdef DO_STARTUP_FW_UPDATE +static void fwu_startup_fw_update_work(struct work_struct *work) +{ + static unsigned char do_once = 1; +#ifdef WAIT_FOR_FB_READY + unsigned int timeout; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; +#endif + + if (!do_once) + return; + do_once = 0; + +#ifdef WAIT_FOR_FB_READY + timeout = FB_READY_TIMEOUT_S * 1000 / FB_READY_WAIT_MS + 1; + + while (!rmi4_data->fb_ready) { + msleep(FB_READY_WAIT_MS); + timeout--; + if (timeout == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for FB ready\n", + __func__); + return; + } + } +#endif + + synaptics_fw_updater(NULL); + + return; +} +#endif + +static ssize_t fwu_sysfs_show_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (count < fwu->config_size) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not enough space (%d bytes) in buffer\n", + __func__, (unsigned int)count); + return -EINVAL; + } + + retval = secure_memcpy(buf, count, fwu->read_config_buf, + fwu->read_config_buf_size, fwu->config_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy config data\n", + __func__); + return retval; + } + + return fwu->config_size; +} + +static ssize_t fwu_sysfs_store_image(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = secure_memcpy(&fwu->ext_data_source[fwu->data_pos], + fwu->image_size - fwu->data_pos, buf, count, count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy image data\n", + __func__); + return retval; + } + + fwu->data_pos += count; + + return count; +} + +static ssize_t fwu_sysfs_do_recovery_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (!fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not in microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) + return -EINVAL; + else + fwu->image = fwu->ext_data_source; + + retval = fwu_start_recovery(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do recovery\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + 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 (sscanf(buf, "%u", &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) + return -EINVAL; + else + fwu->image = fwu->ext_data_source; + + if (input & LOCKDOWN) { + fwu->do_lockdown = true; + input &= ~LOCKDOWN; + } + + if ((input != NORMAL) && (input != FORCE)) { + retval = -EINVAL; + goto exit; + } + + if (input == FORCE) + fwu->force_update = true; + + retval = synaptics_fw_updater(fwu->image); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do reflash\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + fwu->force_update = FORCE_UPDATE; + fwu->do_lockdown = DO_LOCKDOWN; + 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 (sscanf(buf, "%u", &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (input != 1) { + retval = -EINVAL; + goto exit; + } + + if (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) + return -EINVAL; + else + fwu->image = fwu->ext_data_source; + + retval = fwu_start_write_config(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write config\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + 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 (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + return -EINVAL; + } + + retval = fwu_do_read_config(); + 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; + + fwu->config_area = config_area; + + return count; +} + +static ssize_t fwu_sysfs_image_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + retval = secure_memcpy(fwu->image_name, MAX_IMAGE_NAME_LEN, + buf, count, count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy image file name\n", + __func__); + return retval; + } + + 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; + + retval = sstrtoul(buf, 10, &size); + if (retval) + return retval; + + 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__); + return -ENOMEM; + } + + return count; +} + +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->blkcount.ui_firmware); +} + +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->blkcount.ui_config); +} + +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->blkcount.dp_config); +} + +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->blkcount.pm_config); +} + +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->blkcount.bl_config); +} + +static ssize_t fwu_sysfs_guest_code_block_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", fwu->blkcount.guest_code); +} + +static ssize_t fwu_sysfs_write_guest_code_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = fwu->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) { + retval = -EINVAL; + goto exit; + } + + if (input != 1) { + retval = -EINVAL; + goto exit; + } + + if (fwu->in_ub_mode) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: In microbootloader mode\n", + __func__); + retval = -EINVAL; + goto exit; + } + + if (!fwu->ext_data_source) + return -EINVAL; + else + fwu->image = fwu->ext_data_source; + + retval = fwu_start_write_guest_code(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write guest code\n", + __func__); + goto exit; + } + + retval = count; + +exit: + kfree(fwu->ext_data_source); + fwu->ext_data_source = NULL; + fwu->image = NULL; + return retval; +} + +static void synaptics_rmi4_fwu_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!fwu) + return; + + if (fwu->intr_mask & intr_mask) + fwu_read_flash_status(); + + return; +} + +static int synaptics_rmi4_fwu_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char attr_count; + struct pdt_properties pdt_props; + + if (fwu) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + fwu = kzalloc(sizeof(*fwu), GFP_KERNEL); + if (!fwu) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for fwu\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + fwu->image_name = kzalloc(MAX_IMAGE_NAME_LEN, GFP_KERNEL); + if (!fwu->image_name) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for image name\n", + __func__); + retval = -ENOMEM; + goto exit_free_fwu; + } + + fwu->rmi4_data = rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + PDT_PROPS, + pdt_props.data, + sizeof(pdt_props.data)); + if (retval < 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Failed to read PDT properties, assuming 0x00\n", + __func__); + } else if (pdt_props.has_bsr) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Reflash for LTS not currently supported\n", + __func__); + retval = -ENODEV; + goto exit_free_mem; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + goto exit_free_mem; + + if (!fwu->in_ub_mode) { + retval = fwu_read_f34_queries(); + if (retval < 0) + goto exit_free_mem; + + retval = fwu_get_device_config_id(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read device config ID\n", + __func__); + goto exit_free_mem; + } + } + + fwu->force_update = FORCE_UPDATE; + fwu->do_lockdown = DO_LOCKDOWN; + fwu->initialized = true; + + retval = sysfs_create_bin_file(&rmi4_data->input_dev->dev.kobj, + &dev_attr_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs bin file\n", + __func__); + goto exit_free_mem; + } + + 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; + } + } + +#ifdef DO_STARTUP_FW_UPDATE + fwu->fwu_workqueue = create_singlethread_workqueue("fwu_workqueue"); + INIT_WORK(&fwu->fwu_work, fwu_startup_fw_update_work); + queue_work(fwu->fwu_workqueue, + &fwu->fwu_work); +#endif + + 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); + } + + sysfs_remove_bin_file(&rmi4_data->input_dev->dev.kobj, &dev_attr_data); + +exit_free_mem: + kfree(fwu->image_name); + +exit_free_fwu: + kfree(fwu); + fwu = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_fwu_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + + if (!fwu) + goto exit; + +#ifdef DO_STARTUP_FW_UPDATE + cancel_work_sync(&fwu->fwu_work); + flush_workqueue(fwu->fwu_workqueue); + destroy_workqueue(fwu->fwu_workqueue); +#endif + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + sysfs_remove_bin_file(&rmi4_data->input_dev->dev.kobj, &dev_attr_data); + + kfree(fwu->read_config_buf); + kfree(fwu->image_name); + kfree(fwu); + fwu = NULL; + +exit: + complete(&fwu_remove_complete); + + return; +} + +static void synaptics_rmi4_fwu_reset(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (!fwu) { + synaptics_rmi4_fwu_init(rmi4_data); + return; + } + + retval = fwu_scan_pdt(); + if (retval < 0) + return; + + if (!fwu->in_ub_mode) + fwu_read_f34_queries(); + + return; +} + +static struct synaptics_rmi4_exp_fn fwu_module = { + .fn_type = RMI_FW_UPDATER, + .init = synaptics_rmi4_fwu_init, + .remove = synaptics_rmi4_fwu_remove, + .reset = synaptics_rmi4_fwu_reset, + .reinit = NULL, + .early_suspend = NULL, + .suspend = NULL, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_fwu_attn, +}; + +static int __init rmi4_fw_update_module_init(void) +{ + synaptics_rmi4_new_function(&fwu_module, true); + + return 0; +} + +static void __exit rmi4_fw_update_module_exit(void) +{ + synaptics_rmi4_new_function(&fwu_module, false); + + wait_for_completion(&fwu_remove_complete); + + 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_2.6/synaptics_dsx_gesture.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_gesture.c new file mode 100644 index 000000000000..7630a60737cd --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_gesture.c @@ -0,0 +1,2308 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define GESTURE_PHYS_NAME "synaptics_dsx/gesture" + +#define TUNING_SYSFS_DIR_NAME "tuning" + +#define STORE_GESTURES +#ifdef STORE_GESTURES +#define GESTURES_TO_STORE 10 +#endif + +#define CTRL23_FINGER_REPORT_ENABLE_BIT 0 +#define CTRL27_UDG_ENABLE_BIT 4 +#define WAKEUP_GESTURE_MODE 0x02 + +static ssize_t udg_sysfs_engine_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_detection_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_detection_score_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_detection_index_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_registration_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_registration_begin_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_registration_status_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_size_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_max_index_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_detection_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_index_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_template_valid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_valid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_template_clear_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_trace_size_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t udg_sysfs_template_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t udg_sysfs_trace_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t udg_sysfs_template_displacement_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_template_displacement_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_rotation_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_rotation_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_scale_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_scale_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_threshold_factor_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_threshold_factor_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_match_metric_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_match_metric_threshold_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t udg_sysfs_max_inter_stroke_time_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t udg_sysfs_max_inter_stroke_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static int udg_read_tuning_params(void); + +static int udg_write_tuning_params(void); + +static int udg_detection_enable(bool enable); + +static int udg_engine_enable(bool enable); + +static int udg_set_index(unsigned char index); + +#ifdef STORE_GESTURES +static int udg_read_valid_data(void); +static int udg_write_valid_data(void); +static int udg_read_template_data(unsigned char index); +static int udg_write_template_data(void); +#endif + +enum gesture_type { + DETECTION = 0x0f, + REGISTRATION = 0x10, +}; + +struct udg_tuning { + union { + struct { + unsigned char maximum_number_of_templates; + unsigned char template_size; + unsigned char template_disp_lsb; + unsigned char template_disp_msb; + unsigned char rotation_inv_lsb; + unsigned char rotation_inv_msb; + unsigned char scale_inv_lsb; + unsigned char scale_inv_msb; + unsigned char thres_factor_lsb; + unsigned char thres_factor_msb; + unsigned char metric_thres_lsb; + unsigned char metric_thres_msb; + unsigned char inter_stroke_lsb; + unsigned char inter_stroke_msb; + } __packed; + unsigned char data[14]; + }; +}; + +struct udg_addr { + unsigned short data_4; + unsigned short ctrl_18; + unsigned short ctrl_20; + unsigned short ctrl_23; + unsigned short ctrl_27; + unsigned short ctrl_41; + unsigned short trace_x; + unsigned short trace_y; + unsigned short trace_segment; + unsigned short template_helper; + unsigned short template_data; + unsigned short template_flags; +}; + +struct synaptics_rmi4_f12_query_0 { + union { + struct { + struct { + unsigned char has_register_descriptors:1; + unsigned char has_closed_cover:1; + unsigned char has_fast_glove_detect:1; + unsigned char has_dribble:1; + unsigned char has_4p4_jitter_filter_strength:1; + unsigned char f12_query0_s0_b5__7:3; + } __packed; + struct { + unsigned char max_num_templates:4; + unsigned char f12_query0_s1_b4__7:4; + unsigned char template_size_lsb; + unsigned char template_size_msb; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f12_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + struct { + unsigned char ctrl24_is_present:1; + unsigned char ctrl25_is_present:1; + unsigned char ctrl26_is_present:1; + unsigned char ctrl27_is_present:1; + unsigned char ctrl28_is_present:1; + unsigned char ctrl29_is_present:1; + unsigned char ctrl30_is_present:1; + unsigned char ctrl31_is_present:1; + } __packed; + struct { + unsigned char ctrl32_is_present:1; + unsigned char ctrl33_is_present:1; + unsigned char ctrl34_is_present:1; + unsigned char ctrl35_is_present:1; + unsigned char ctrl36_is_present:1; + unsigned char ctrl37_is_present:1; + unsigned char ctrl38_is_present:1; + unsigned char ctrl39_is_present:1; + } __packed; + struct { + unsigned char ctrl40_is_present:1; + unsigned char ctrl41_is_present:1; + unsigned char ctrl42_is_present:1; + unsigned char ctrl43_is_present:1; + unsigned char ctrl44_is_present:1; + unsigned char ctrl45_is_present:1; + unsigned char ctrl46_is_present:1; + unsigned char ctrl47_is_present:1; + } __packed; + }; + unsigned char data[7]; + }; +}; + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + struct { + unsigned char data8_is_present:1; + unsigned char data9_is_present:1; + unsigned char data10_is_present:1; + unsigned char data11_is_present:1; + unsigned char data12_is_present:1; + unsigned char data13_is_present:1; + unsigned char data14_is_present:1; + unsigned char data15_is_present:1; + } __packed; + struct { + unsigned char data16_is_present:1; + unsigned char data17_is_present:1; + unsigned char data18_is_present:1; + unsigned char data19_is_present:1; + unsigned char data20_is_present:1; + unsigned char data21_is_present:1; + unsigned char data22_is_present:1; + unsigned char data23_is_present:1; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f12_control_41 { + union { + struct { + unsigned char enable_registration:1; + unsigned char template_index:4; + unsigned char begin:1; + unsigned char f12_ctrl41_b6__7:2; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_udg_handle { + atomic_t attn_event; + unsigned char intr_mask; + unsigned char report_flags; + unsigned char object_type_enable1; + unsigned char object_type_enable2; + unsigned char trace_size; + unsigned char template_index; + unsigned char max_num_templates; + unsigned char detection_score; + unsigned char detection_index; + unsigned char detection_status; + unsigned char registration_status; + unsigned char *ctrl_buf; + unsigned char *trace_data_buf; + unsigned char *template_data_buf; +#ifdef STORE_GESTURES + unsigned char gestures_to_store; + unsigned char *storage_buf; + unsigned char valid_buf[2]; +#endif + unsigned short trace_data_buf_size; + unsigned short template_size; + unsigned short template_data_size; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short ctrl_18_sub10_off; + unsigned short ctrl_20_sub1_off; + unsigned short ctrl_23_sub3_off; + unsigned short ctrl_27_sub5_off; + struct input_dev *udg_dev; + struct kobject *tuning_dir; + struct udg_addr addr; + struct udg_tuning tuning; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct device_attribute attrs[] = { + __ATTR(engine_enable, S_IWUGO, + synaptics_rmi4_show_error, + udg_sysfs_engine_enable_store), + __ATTR(detection_enable, S_IWUGO, + synaptics_rmi4_show_error, + udg_sysfs_detection_enable_store), + __ATTR(detection_score, S_IRUGO, + udg_sysfs_detection_score_show, + synaptics_rmi4_store_error), + __ATTR(detection_index, S_IRUGO, + udg_sysfs_detection_index_show, + synaptics_rmi4_store_error), + __ATTR(registration_enable, S_IWUGO, + synaptics_rmi4_show_error, + udg_sysfs_registration_enable_store), + __ATTR(registration_begin, S_IWUGO, + synaptics_rmi4_show_error, + udg_sysfs_registration_begin_store), + __ATTR(registration_status, S_IRUGO, + udg_sysfs_registration_status_show, + synaptics_rmi4_store_error), + __ATTR(template_size, S_IRUGO, + udg_sysfs_template_size_show, + synaptics_rmi4_store_error), + __ATTR(template_max_index, S_IRUGO, + udg_sysfs_template_max_index_show, + synaptics_rmi4_store_error), + __ATTR(template_detection, S_IRUGO, + udg_sysfs_template_detection_show, + synaptics_rmi4_store_error), + __ATTR(template_index, S_IWUGO, + synaptics_rmi4_show_error, + udg_sysfs_template_index_store), + __ATTR(template_valid, (S_IRUGO | S_IWUGO), + udg_sysfs_template_valid_show, + udg_sysfs_template_valid_store), + __ATTR(template_clear, S_IWUGO, + synaptics_rmi4_show_error, + udg_sysfs_template_clear_store), + __ATTR(trace_size, S_IRUGO, + udg_sysfs_trace_size_show, + synaptics_rmi4_store_error), +}; + +static struct bin_attribute template_data = { + .attr = { + .name = "template_data", + .mode = (S_IRUGO | S_IWUGO), + }, + .size = 0, + .read = udg_sysfs_template_data_show, + .write = udg_sysfs_template_data_store, +}; + +static struct bin_attribute trace_data = { + .attr = { + .name = "trace_data", + .mode = S_IRUGO, + }, + .size = 0, + .read = udg_sysfs_trace_data_show, + .write = NULL, +}; + +static struct device_attribute params[] = { + __ATTR(template_displacement, (S_IRUGO | S_IWUGO), + udg_sysfs_template_displacement_show, + udg_sysfs_template_displacement_store), + __ATTR(rotation_invariance, (S_IRUGO | S_IWUGO), + udg_sysfs_rotation_invariance_show, + udg_sysfs_rotation_invariance_store), + __ATTR(scale_invariance, (S_IRUGO | S_IWUGO), + udg_sysfs_scale_invariance_show, + udg_sysfs_scale_invariance_store), + __ATTR(threshold_factor, (S_IRUGO | S_IWUGO), + udg_sysfs_threshold_factor_show, + udg_sysfs_threshold_factor_store), + __ATTR(match_metric_threshold, (S_IRUGO | S_IWUGO), + udg_sysfs_match_metric_threshold_show, + udg_sysfs_match_metric_threshold_store), + __ATTR(max_inter_stroke_time, (S_IRUGO | S_IWUGO), + udg_sysfs_max_inter_stroke_time_show, + udg_sysfs_max_inter_stroke_time_store), +}; + +static struct synaptics_rmi4_udg_handle *udg; + +static unsigned char ctrl_18_sub_size[] = {10, 10, 10, 2, 3, 4, 3, 3, 1, 1}; +static unsigned char ctrl_20_sub_size[] = {2}; +static unsigned char ctrl_23_sub_size[] = {1, 1, 1}; +static unsigned char ctrl_27_sub_size[] = {1, 5, 2, 1, 7}; + +DECLARE_COMPLETION(udg_remove_complete); + +static ssize_t udg_sysfs_engine_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool enable; + unsigned int input; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 1) + enable = true; + else if (input == 0) + enable = false; + else + return -EINVAL; + + retval = udg_engine_enable(enable); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_detection_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool enable; + unsigned int input; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 1) + enable = true; + else if (input == 0) + enable = false; + else + return -EINVAL; + + udg->detection_status = 0; + + retval = udg_detection_enable(enable); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_detection_score_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->detection_score); +} + +static ssize_t udg_sysfs_detection_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->detection_index); +} + +static ssize_t udg_sysfs_registration_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool enable; + unsigned int input; + struct synaptics_rmi4_f12_control_41 control_41; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 1) + enable = true; + else if (input == 0) + enable = false; + else + return -EINVAL; + + if (enable) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[0] = 0; + udg->ctrl_buf[0] |= (1 << CTRL23_FINGER_REPORT_ENABLE_BIT); + if (udg->ctrl_23_sub3_off) + udg->ctrl_buf[udg->ctrl_23_sub3_off] = 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[0] = udg->object_type_enable1; + if (udg->ctrl_23_sub3_off) { + udg->ctrl_buf[udg->ctrl_23_sub3_off] = + udg->object_type_enable2; + } + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + control_41.enable_registration = enable ? 1 : 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_registration_begin_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + bool begin; + unsigned int input; + struct synaptics_rmi4_f12_control_41 control_41; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input == 1) + begin = true; + else if (input == 0) + begin = false; + else + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + control_41.begin = begin ? 1 : 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_registration_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%02x\n", udg->registration_status); +} + +static ssize_t udg_sysfs_template_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->template_size); +} + +static ssize_t udg_sysfs_template_max_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->max_num_templates - 1); +} + +static ssize_t udg_sysfs_template_detection_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + int attn_event; + unsigned char detection_status; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + attn_event = atomic_read(&udg->attn_event); + atomic_set(&udg->attn_event, 0); + + if (attn_event == 0) + return snprintf(buf, PAGE_SIZE, "0\n"); + + if (udg->detection_status == 0) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.data_4, + rmi4_data->gesture_detection, + sizeof(rmi4_data->gesture_detection)); + if (retval < 0) + return retval; + + udg->detection_status = rmi4_data->gesture_detection[0]; + } + + detection_status = udg->detection_status; + udg->detection_status = 0; + + switch (detection_status) { + case DETECTION: + udg->detection_score = rmi4_data->gesture_detection[1]; + udg->detection_index = rmi4_data->gesture_detection[4]; + udg->trace_size = rmi4_data->gesture_detection[3]; + break; + case REGISTRATION: + udg->registration_status = rmi4_data->gesture_detection[1]; + udg->trace_size = rmi4_data->gesture_detection[3]; + break; + default: + return snprintf(buf, PAGE_SIZE, "0\n"); + } + + return snprintf(buf, PAGE_SIZE, "0x%02x\n", detection_status); +} + +static ssize_t udg_sysfs_template_index_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long index; + + retval = sstrtoul(buf, 10, &index); + if (retval) + return retval; + + retval = udg_set_index((unsigned char)index); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_template_valid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned char valid; + unsigned char offset; + unsigned char byte_num; + unsigned char template_flags[2]; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + byte_num = udg->template_index / 8; + offset = udg->template_index % 8; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_flags, + template_flags, + sizeof(template_flags)); + if (retval < 0) + return retval; + + valid = (template_flags[byte_num] & (1 << offset)) >> offset; + + return snprintf(buf, PAGE_SIZE, "%u\n", valid); +} + +static ssize_t udg_sysfs_template_valid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long valid; + unsigned char offset; + unsigned char byte_num; + unsigned char template_flags[2]; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = sstrtoul(buf, 10, &valid); + if (retval) + return retval; + + if (valid > 0) + valid = 1; + + byte_num = udg->template_index / 8; + offset = udg->template_index % 8; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_flags, + template_flags, + sizeof(template_flags)); + if (retval < 0) + return retval; + + if (valid) + template_flags[byte_num] |= (1 << offset); + else + template_flags[byte_num] &= ~(1 << offset); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_flags, + template_flags, + sizeof(template_flags)); + if (retval < 0) + return retval; + +#ifdef STORE_GESTURES + udg_read_valid_data(); +#endif + + return count; +} + +static ssize_t udg_sysfs_template_clear_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + const char cmd[] = {'0', 0}; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + memset(udg->template_data_buf, 0x00, udg->template_data_size); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_data, + udg->template_data_buf, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to clear template data\n", + __func__); + return retval; + } + + retval = udg_sysfs_template_valid_store(dev, attr, cmd, 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to clear valid bit\n", + __func__); + return retval; + } + +#ifdef STORE_GESTURES + udg_read_template_data(udg->template_index); + udg_read_valid_data(); +#endif + + return count; +} + +static ssize_t udg_sysfs_trace_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", udg->trace_size); +} + +static ssize_t udg_sysfs_trace_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned short index = 0; + unsigned short trace_data_size; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + trace_data_size = udg->trace_size * 5; + + if (trace_data_size == 0) + return -EINVAL; + + if (count < trace_data_size) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not enough space (%d bytes) in buffer\n", + __func__, (unsigned int)count); + return -EINVAL; + } + + if (udg->trace_data_buf_size < trace_data_size) { + if (udg->trace_data_buf_size) + kfree(udg->trace_data_buf); + udg->trace_data_buf = kzalloc(trace_data_size, GFP_KERNEL); + if (!udg->trace_data_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for trace data buffer\n", + __func__); + udg->trace_data_buf_size = 0; + return -ENOMEM; + } + udg->trace_data_buf_size = trace_data_size; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.trace_x, + &udg->trace_data_buf[index], + udg->trace_size * 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read trace X data\n", + __func__); + return retval; + } else { + index += udg->trace_size * 2; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.trace_y, + &udg->trace_data_buf[index], + udg->trace_size * 2); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read trace Y data\n", + __func__); + return retval; + } else { + index += udg->trace_size * 2; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.trace_segment, + &udg->trace_data_buf[index], + udg->trace_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read trace segment data\n", + __func__); + return retval; + } + + retval = secure_memcpy(buf, count, udg->trace_data_buf, + udg->trace_data_buf_size, trace_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy trace data\n", + __func__); + return retval; + } + + return trace_data_size; +} + +static ssize_t udg_sysfs_template_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (count < udg->template_data_size) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Not enough space (%d bytes) in buffer\n", + __func__, (unsigned int)count); + return -EINVAL; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_data, + udg->template_data_buf, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read template data\n", + __func__); + return retval; + } + + retval = secure_memcpy(buf, count, udg->template_data_buf, + udg->template_data_size, udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy template data\n", + __func__); + return retval; + } + +#ifdef STORE_GESTURES + udg_read_template_data(udg->template_index); + udg_read_valid_data(); +#endif + + return udg->template_data_size; +} + +static ssize_t udg_sysfs_template_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = secure_memcpy(udg->template_data_buf, udg->template_data_size, + buf, count, count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy template data\n", + __func__); + return retval; + } + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_data, + udg->template_data_buf, + count); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write template data\n", + __func__); + return retval; + } + +#ifdef STORE_GESTURES + udg_read_template_data(udg->template_index); + udg_read_valid_data(); +#endif + + return count; +} + +static ssize_t udg_sysfs_template_displacement_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short template_displacement; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + template_displacement = + ((unsigned short)udg->tuning.template_disp_lsb << 0) | + ((unsigned short)udg->tuning.template_disp_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", template_displacement); +} + +static ssize_t udg_sysfs_template_displacement_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.template_disp_lsb = (unsigned char)(input >> 0); + udg->tuning.template_disp_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_rotation_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short rotation_invariance; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + rotation_invariance = + ((unsigned short)udg->tuning.rotation_inv_lsb << 0) | + ((unsigned short)udg->tuning.rotation_inv_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", rotation_invariance); +} + +static ssize_t udg_sysfs_rotation_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.rotation_inv_lsb = (unsigned char)(input >> 0); + udg->tuning.rotation_inv_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_scale_invariance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short scale_invariance; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + scale_invariance = + ((unsigned short)udg->tuning.scale_inv_lsb << 0) | + ((unsigned short)udg->tuning.scale_inv_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", scale_invariance); +} + +static ssize_t udg_sysfs_scale_invariance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.scale_inv_lsb = (unsigned char)(input >> 0); + udg->tuning.scale_inv_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_threshold_factor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short threshold_factor; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + threshold_factor = + ((unsigned short)udg->tuning.thres_factor_lsb << 0) | + ((unsigned short)udg->tuning.thres_factor_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", threshold_factor); +} + +static ssize_t udg_sysfs_threshold_factor_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.thres_factor_lsb = (unsigned char)(input >> 0); + udg->tuning.thres_factor_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_match_metric_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short match_metric_threshold; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + match_metric_threshold = + ((unsigned short)udg->tuning.metric_thres_lsb << 0) | + ((unsigned short)udg->tuning.metric_thres_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", match_metric_threshold); +} + +static ssize_t udg_sysfs_match_metric_threshold_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.metric_thres_lsb = (unsigned char)(input >> 0); + udg->tuning.metric_thres_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t udg_sysfs_max_inter_stroke_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned short max_inter_stroke_time; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + max_inter_stroke_time = + ((unsigned short)udg->tuning.inter_stroke_lsb << 0) | + ((unsigned short)udg->tuning.inter_stroke_msb << 8); + + return snprintf(buf, PAGE_SIZE, "%u\n", max_inter_stroke_time); +} + +static ssize_t udg_sysfs_max_inter_stroke_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long input; + + retval = sstrtoul(buf, 10, &input); + if (retval) + return retval; + + retval = udg_read_tuning_params(); + if (retval < 0) + return retval; + + udg->tuning.inter_stroke_lsb = (unsigned char)(input >> 0); + udg->tuning.inter_stroke_msb = (unsigned char)(input >> 8); + + retval = udg_write_tuning_params(); + if (retval < 0) + return retval; + + return count; +} + +static int udg_ctrl_subpacket(unsigned char ctrlreg, + unsigned char subpacket, + struct synaptics_rmi4_f12_query_5 *query_5) +{ + int retval; + unsigned char cnt; + unsigned char regnum; + unsigned char bitnum; + unsigned char q5_index; + unsigned char q6_index; + unsigned char offset; + unsigned char max_ctrlreg; + unsigned char *query_6; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + max_ctrlreg = (sizeof(query_5->data) - 1) * 8 - 1; + + if (ctrlreg > max_ctrlreg) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Control register number (%d) over limit\n", + __func__, ctrlreg); + return -EINVAL; + } + + q5_index = ctrlreg / 8 + 1; + bitnum = ctrlreg % 8; + if ((query_5->data[q5_index] & (1 << bitnum)) == 0x00) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Control %d is not present\n", + __func__, ctrlreg); + return -EINVAL; + } + + query_6 = kmalloc(query_5->size_of_query6, GFP_KERNEL); + if (!query_6) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for query 6\n", + __func__); + return -ENOMEM; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 6, + query_6, + query_5->size_of_query6); + if (retval < 0) + goto exit; + + q6_index = 0; + + for (regnum = 0; regnum < ctrlreg; regnum++) { + q5_index = regnum / 8 + 1; + bitnum = regnum % 8; + if ((query_5->data[q5_index] & (1 << bitnum)) == 0x00) + continue; + + if (query_6[q6_index] == 0x00) + q6_index += 3; + else + q6_index++; + + while (query_6[q6_index] & ~MASK_7BIT) + q6_index++; + + q6_index++; + } + + cnt = 0; + q6_index++; + offset = subpacket / 7; + bitnum = subpacket % 7; + + do { + if (cnt == offset) { + if (query_6[q6_index + cnt] & (1 << bitnum)) + retval = 1; + else + retval = 0; + goto exit; + } + cnt++; + } while (query_6[q6_index + cnt - 1] & ~MASK_7BIT); + + retval = 0; + +exit: + kfree(query_6); + + return retval; +} + +static int udg_read_tuning_params(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_18, + udg->ctrl_buf, + udg->ctrl_18_sub10_off + sizeof(struct udg_tuning)); + if (retval < 0) + return retval; + + secure_memcpy(udg->tuning.data, + sizeof(udg->tuning.data), + (unsigned char *)&udg->ctrl_buf[udg->ctrl_18_sub10_off], + sizeof(struct udg_tuning), + sizeof(struct udg_tuning)); + + return 0; +} + +static int udg_write_tuning_params(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + secure_memcpy((unsigned char *)&udg->ctrl_buf[udg->ctrl_18_sub10_off], + sizeof(struct udg_tuning), + udg->tuning.data, + sizeof(udg->tuning.data), + sizeof(struct udg_tuning)); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_18, + udg->ctrl_buf, + udg->ctrl_18_sub10_off + sizeof(struct udg_tuning)); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_detection_enable(bool enable) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_20, + udg->ctrl_buf, + udg->ctrl_20_sub1_off + 1); + if (retval < 0) + return retval; + + if (enable) + udg->ctrl_buf[udg->ctrl_20_sub1_off] = WAKEUP_GESTURE_MODE; + else + udg->ctrl_buf[udg->ctrl_20_sub1_off] = udg->report_flags; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_20, + udg->ctrl_buf, + udg->ctrl_20_sub1_off + 1); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_engine_enable(bool enable) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (enable) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[udg->ctrl_27_sub5_off] |= + (1 << CTRL27_UDG_ENABLE_BIT); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + + udg->ctrl_buf[udg->ctrl_27_sub5_off] &= + ~(1 << CTRL27_UDG_ENABLE_BIT); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_27, + udg->ctrl_buf, + udg->ctrl_27_sub5_off + 1); + if (retval < 0) + return retval; + } + + return 0; +} + +static void udg_report(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + atomic_set(&udg->attn_event, 1); + + if (rmi4_data->suspend) { + if (rmi4_data->gesture_detection[0] == 0) { + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.data_4, + rmi4_data->gesture_detection, + sizeof(rmi4_data->gesture_detection)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read gesture detection\n", + __func__); + return; + } + } + + udg->detection_status = rmi4_data->gesture_detection[0]; + rmi4_data->gesture_detection[0] = 0; + + if (udg->detection_status == DETECTION) { + input_report_key(udg->udg_dev, KEY_WAKEUP, 1); + input_sync(udg->udg_dev); + input_report_key(udg->udg_dev, KEY_WAKEUP, 0); + input_sync(udg->udg_dev); + rmi4_data->suspend = false; + } + } + + return; +} + +static int udg_set_index(unsigned char index) +{ + int retval; + struct synaptics_rmi4_f12_control_41 control_41; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + if (index >= udg->max_num_templates) + return -EINVAL; + + udg->template_index = index; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + control_41.template_index = udg->template_index; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.ctrl_41, + control_41.data, + sizeof(control_41.data)); + if (retval < 0) + return retval; + + return 0; +} + +#ifdef STORE_GESTURES +static int udg_read_valid_data(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_flags, + udg->valid_buf, + sizeof(udg->valid_buf)); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_write_valid_data(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_flags, + udg->valid_buf, + sizeof(udg->valid_buf)); + if (retval < 0) + return retval; + + return 0; +} + +static int udg_read_template_data(unsigned char index) +{ + int retval; + unsigned char *storage; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + udg_set_index(index); + storage = &(udg->storage_buf[index * udg->template_data_size]); + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.template_data, + storage, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read template data\n", + __func__); + return retval; + } + + return 0; +} + +static int udg_write_template_data(void) +{ + int retval; + unsigned char ii; + unsigned char *storage; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + for (ii = 0; ii < udg->gestures_to_store; ii++) { + udg_set_index(ii); + storage = &(udg->storage_buf[ii * udg->template_data_size]); + + retval = synaptics_rmi4_reg_write(rmi4_data, + udg->addr.template_data, + storage, + udg->template_data_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write template data\n", + __func__); + return retval; + } + } + + return 0; +} +#endif + +static int udg_reg_init(void) +{ + int retval; + unsigned char ii; + unsigned char data_offset; + unsigned char size_of_query; + unsigned char ctrl_18_offset; + unsigned char ctrl_20_offset; + unsigned char ctrl_23_offset; + unsigned char ctrl_27_offset; + unsigned char ctrl_41_offset; + struct synaptics_rmi4_f12_query_0 query_0; + struct synaptics_rmi4_f12_query_5 query_5; + struct synaptics_rmi4_f12_query_8 query_8; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 7, + &size_of_query, + sizeof(size_of_query)); + if (retval < 0) + return retval; + + if (size_of_query < 4) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: User defined gesture support unavailable (missing data registers)\n", + __func__); + retval = -ENODEV; + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 8, + query_8.data, + sizeof(query_8.data)); + if (retval < 0) + return retval; + + if ((query_8.data16_is_present) && + (query_8.data17_is_present) && + (query_8.data18_is_present) && + (query_8.data19_is_present) && + (query_8.data20_is_present) && + (query_8.data21_is_present)) { + data_offset = query_8.data0_is_present + + query_8.data1_is_present + + query_8.data2_is_present + + query_8.data3_is_present; + udg->addr.data_4 = udg->data_base_addr + data_offset; + data_offset = data_offset + + query_8.data4_is_present + + query_8.data5_is_present + + query_8.data6_is_present + + query_8.data7_is_present + + query_8.data8_is_present + + query_8.data9_is_present + + query_8.data10_is_present + + query_8.data11_is_present + + query_8.data12_is_present + + query_8.data13_is_present + + query_8.data14_is_present + + query_8.data15_is_present; + udg->addr.trace_x = udg->data_base_addr + data_offset; + udg->addr.trace_y = udg->addr.trace_x + 1; + udg->addr.trace_segment = udg->addr.trace_y + 1; + udg->addr.template_helper = udg->addr.trace_segment + 1; + udg->addr.template_data = udg->addr.template_helper + 1; + udg->addr.template_flags = udg->addr.template_data + 1; + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: User defined gesture support unavailable (missing data registers)\n", + __func__); + retval = -ENODEV; + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 4, + &size_of_query, + sizeof(size_of_query)); + if (retval < 0) + return retval; + + if (size_of_query < 7) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: User defined gesture support unavailable (missing control registers)\n", + __func__); + retval = -ENODEV; + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 5, + query_5.data, + sizeof(query_5.data)); + if (retval < 0) + return retval; + + ctrl_18_offset = query_5.ctrl0_is_present + + query_5.ctrl1_is_present + + query_5.ctrl2_is_present + + query_5.ctrl3_is_present + + query_5.ctrl4_is_present + + query_5.ctrl5_is_present + + query_5.ctrl6_is_present + + query_5.ctrl7_is_present + + query_5.ctrl8_is_present + + query_5.ctrl9_is_present + + query_5.ctrl10_is_present + + query_5.ctrl11_is_present + + query_5.ctrl12_is_present + + query_5.ctrl13_is_present + + query_5.ctrl14_is_present + + query_5.ctrl15_is_present + + query_5.ctrl16_is_present + + query_5.ctrl17_is_present; + + ctrl_20_offset = ctrl_18_offset + + query_5.ctrl18_is_present + + query_5.ctrl19_is_present; + + ctrl_23_offset = ctrl_20_offset + + query_5.ctrl20_is_present + + query_5.ctrl21_is_present + + query_5.ctrl22_is_present; + + ctrl_27_offset = ctrl_23_offset+ + query_5.ctrl23_is_present + + query_5.ctrl24_is_present + + query_5.ctrl25_is_present + + query_5.ctrl26_is_present; + + ctrl_41_offset = ctrl_27_offset+ + query_5.ctrl27_is_present + + query_5.ctrl28_is_present + + query_5.ctrl29_is_present + + query_5.ctrl30_is_present + + query_5.ctrl31_is_present + + query_5.ctrl32_is_present + + query_5.ctrl33_is_present + + query_5.ctrl34_is_present + + query_5.ctrl35_is_present + + query_5.ctrl36_is_present + + query_5.ctrl37_is_present + + query_5.ctrl38_is_present + + query_5.ctrl39_is_present + + query_5.ctrl40_is_present; + + udg->addr.ctrl_18 = udg->control_base_addr + ctrl_18_offset; + udg->addr.ctrl_20 = udg->control_base_addr + ctrl_20_offset; + udg->addr.ctrl_23 = udg->control_base_addr + ctrl_23_offset; + udg->addr.ctrl_27 = udg->control_base_addr + ctrl_27_offset; + udg->addr.ctrl_41 = udg->control_base_addr + ctrl_41_offset; + + udg->ctrl_18_sub10_off = 0; + for (ii = 0; ii < 10; ii++) { + retval = udg_ctrl_subpacket(18, ii, &query_5); + if (retval == 1) + udg->ctrl_18_sub10_off += ctrl_18_sub_size[ii]; + else if (retval < 0) + return retval; + } + + udg->ctrl_20_sub1_off = 0; + for (ii = 0; ii < 1; ii++) { + retval = udg_ctrl_subpacket(20, ii, &query_5); + if (retval == 1) + udg->ctrl_20_sub1_off += ctrl_20_sub_size[ii]; + else if (retval < 0) + return retval; + } + + udg->ctrl_23_sub3_off = 0; + for (ii = 0; ii < 3; ii++) { + retval = udg_ctrl_subpacket(23, ii, &query_5); + if (retval == 1) + udg->ctrl_23_sub3_off += ctrl_23_sub_size[ii]; + else if (retval < 0) + return retval; + } + + retval = udg_ctrl_subpacket(23, 3, &query_5); + if (retval == 0) + udg->ctrl_23_sub3_off = 0; + else if (retval < 0) + return retval; + + udg->ctrl_27_sub5_off = 0; + for (ii = 0; ii < 5; ii++) { + retval = udg_ctrl_subpacket(27, ii, &query_5); + if (retval == 1) + udg->ctrl_27_sub5_off += ctrl_27_sub_size[ii]; + else if (retval < 0) + return retval; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->query_base_addr + 0, + query_0.data, + sizeof(query_0.data)); + if (retval < 0) + return retval; + + udg->max_num_templates = query_0.max_num_templates; + udg->template_size = + ((unsigned short)query_0.template_size_lsb << 0) | + ((unsigned short)query_0.template_size_msb << 8); + udg->template_data_size = udg->template_size * 4 * 2 + 4 + 1; + +#ifdef STORE_GESTURES + udg->gestures_to_store = udg->max_num_templates; + if (GESTURES_TO_STORE < udg->gestures_to_store) + udg->gestures_to_store = GESTURES_TO_STORE; +#endif + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_20, + udg->ctrl_buf, + udg->ctrl_20_sub1_off + 1); + if (retval < 0) + return retval; + + udg->report_flags = udg->ctrl_buf[udg->ctrl_20_sub1_off]; + + retval = synaptics_rmi4_reg_read(rmi4_data, + udg->addr.ctrl_23, + udg->ctrl_buf, + udg->ctrl_23_sub3_off + 1); + if (retval < 0) + return retval; + + udg->object_type_enable1 = udg->ctrl_buf[0]; + if (udg->ctrl_23_sub3_off) + udg->object_type_enable2 = udg->ctrl_buf[udg->ctrl_23_sub3_off]; + + return retval; +} + +static int udg_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char page; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + struct synaptics_rmi4_fn_desc fd; + struct synaptics_rmi4_data *rmi4_data = udg->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&fd, + sizeof(fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, fd.fn_number); + switch (fd.fn_number) { + case SYNAPTICS_RMI4_F12: + goto f12_found; + break; + } + } else { + break; + } + + intr_count += fd.intr_src_count; + } + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F12\n", + __func__); + return -EINVAL; + +f12_found: + udg->query_base_addr = fd.query_base_addr | (page << 8); + udg->control_base_addr = fd.ctrl_base_addr | (page << 8); + udg->data_base_addr = fd.data_base_addr | (page << 8); + udg->command_base_addr = fd.cmd_base_addr | (page << 8); + + retval = udg_reg_init(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize user defined gesture registers\n", + __func__); + return retval; + } + + udg->intr_mask = 0; + intr_src = fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + udg->intr_mask |= 1 << ii; + } + + rmi4_data->intr_mask[0] |= udg->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &rmi4_data->intr_mask[0], + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static void synaptics_rmi4_udg_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!udg) + return; + + if (udg->intr_mask & intr_mask) + udg_report(); + + return; +} + +static int synaptics_rmi4_udg_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char size; + unsigned char attr_count; + unsigned char param_count; + + if (udg) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + udg = kzalloc(sizeof(*udg), GFP_KERNEL); + if (!udg) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for udg\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + size = 0; + for (ii = 0; ii < sizeof(ctrl_18_sub_size); ii++) + size += ctrl_18_sub_size[ii]; + size += sizeof(struct udg_tuning); + udg->ctrl_buf = kzalloc(size, GFP_KERNEL); + if (!udg->ctrl_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for ctrl_buf\n", + __func__); + retval = -ENOMEM; + goto exit_free_udg; + } + + udg->rmi4_data = rmi4_data; + + retval = udg_scan_pdt(); + if (retval < 0) + goto exit_free_ctrl_buf; + + udg->template_data_buf = kzalloc(udg->template_data_size, GFP_KERNEL); + if (!udg->template_data_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for template_data_buf\n", + __func__); + retval = -ENOMEM; + goto exit_free_ctrl_buf; + } + +#ifdef STORE_GESTURES + udg->storage_buf = kzalloc( + udg->template_data_size * udg->gestures_to_store, + GFP_KERNEL); + if (!udg->storage_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for storage_buf\n", + __func__); + kfree(udg->template_data_buf); + retval = -ENOMEM; + goto exit_free_ctrl_buf; + } +#endif + + udg->udg_dev = input_allocate_device(); + if (udg->udg_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate gesture device\n", + __func__); + retval = -ENOMEM; + goto exit_free_template_data_buf; + } + + udg->udg_dev->name = GESTURE_DRIVER_NAME; + udg->udg_dev->phys = GESTURE_PHYS_NAME; + udg->udg_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + udg->udg_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + udg->udg_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(udg->udg_dev, rmi4_data); + + set_bit(EV_KEY, udg->udg_dev->evbit); + set_bit(KEY_WAKEUP, udg->udg_dev->keybit); + input_set_capability(udg->udg_dev, EV_KEY, KEY_WAKEUP); + + retval = input_register_device(udg->udg_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register gesture device\n", + __func__); + input_free_device(udg->udg_dev); + goto exit_free_template_data_buf; + } + + udg->tuning_dir = kobject_create_and_add(TUNING_SYSFS_DIR_NAME, + &udg->udg_dev->dev.kobj); + if (!udg->tuning_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create tuning sysfs directory\n", + __func__); + goto exit_unregister_input_device; + } + + retval = sysfs_create_bin_file(&udg->udg_dev->dev.kobj, &template_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create template data bin file\n", + __func__); + goto exit_remove_sysfs_directory; + } + + retval = sysfs_create_bin_file(&udg->udg_dev->dev.kobj, &trace_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create trace data bin file\n", + __func__); + goto exit_remove_bin_file; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&udg->udg_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto exit_remove_attrs; + } + } + + for (param_count = 0; param_count < ARRAY_SIZE(params); param_count++) { + retval = sysfs_create_file(udg->tuning_dir, + ¶ms[param_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create tuning parameters\n", + __func__); + retval = -ENODEV; + goto exit_remove_params; + } + } + + retval = udg_engine_enable(true); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to enable gesture engine\n", + __func__); + goto exit_remove_params; + } + + return 0; + +exit_remove_params: + for (param_count--; param_count >= 0; param_count--) { + sysfs_remove_file(udg->tuning_dir, + ¶ms[param_count].attr); + } + +exit_remove_attrs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&udg->udg_dev->dev.kobj, + &attrs[attr_count].attr); + } + + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &trace_data); + +exit_remove_bin_file: + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &template_data); + +exit_remove_sysfs_directory: + kobject_put(udg->tuning_dir); + +exit_unregister_input_device: + input_unregister_device(udg->udg_dev); + +exit_free_template_data_buf: +#ifdef STORE_GESTURES + kfree(udg->storage_buf); +#endif + kfree(udg->template_data_buf); + +exit_free_ctrl_buf: + kfree(udg->ctrl_buf); + +exit_free_udg: + kfree(udg); + udg = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_udg_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char count; + + if (!udg) + goto exit; + + for (count = 0; count < ARRAY_SIZE(params); count++) { + sysfs_remove_file(udg->tuning_dir, + ¶ms[count].attr); + } + + for (count = 0; count < ARRAY_SIZE(attrs); count++) { + sysfs_remove_file(&udg->udg_dev->dev.kobj, + &attrs[count].attr); + } + + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &trace_data); + sysfs_remove_bin_file(&udg->udg_dev->dev.kobj, &template_data); + kobject_put(udg->tuning_dir); + + input_unregister_device(udg->udg_dev); +#ifdef STORE_GESTURES + kfree(udg->storage_buf); +#endif + kfree(udg->template_data_buf); + kfree(udg->trace_data_buf); + kfree(udg->ctrl_buf); + kfree(udg); + udg = NULL; + +exit: + complete(&udg_remove_complete); + + return; +} + +static void synaptics_rmi4_udg_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) { + synaptics_rmi4_udg_init(rmi4_data); + return; + } + + udg_scan_pdt(); + udg_engine_enable(true); +#ifdef STORE_GESTURES + udg_write_template_data(); + udg_write_valid_data(); +#endif + + return; +} + +static void synaptics_rmi4_udg_reinit(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + udg_engine_enable(true); +#ifdef STORE_GESTURES + udg_write_template_data(); + udg_write_valid_data(); +#endif + + return; +} + +static void synaptics_rmi4_udg_e_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + rmi4_data->sleep_enable(rmi4_data, false); + rmi4_data->irq_enable(rmi4_data, true, false); + enable_irq_wake(rmi4_data->irq); + + udg_engine_enable(true); + udg_detection_enable(true); + + return; +} + +static void synaptics_rmi4_udg_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + rmi4_data->sleep_enable(rmi4_data, false); + rmi4_data->irq_enable(rmi4_data, true, false); + enable_irq_wake(rmi4_data->irq); + + udg_engine_enable(true); + udg_detection_enable(true); + + return; +} + +static void synaptics_rmi4_udg_resume(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + disable_irq_wake(rmi4_data->irq); + udg_detection_enable(false); + + return; +} + +static void synaptics_rmi4_udg_l_resume(struct synaptics_rmi4_data *rmi4_data) +{ + if (!udg) + return; + + disable_irq_wake(rmi4_data->irq); + udg_detection_enable(false); + + return; +} + +static struct synaptics_rmi4_exp_fn gesture_module = { + .fn_type = RMI_GESTURE, + .init = synaptics_rmi4_udg_init, + .remove = synaptics_rmi4_udg_remove, + .reset = synaptics_rmi4_udg_reset, + .reinit = synaptics_rmi4_udg_reinit, + .early_suspend = synaptics_rmi4_udg_e_suspend, + .suspend = synaptics_rmi4_udg_suspend, + .resume = synaptics_rmi4_udg_resume, + .late_resume = synaptics_rmi4_udg_l_resume, + .attn = synaptics_rmi4_udg_attn, +}; + +static int __init rmi4_gesture_module_init(void) +{ + synaptics_rmi4_new_function(&gesture_module, true); + + return 0; +} + +static void __exit rmi4_gesture_module_exit(void) +{ + synaptics_rmi4_new_function(&gesture_module, false); + + wait_for_completion(&udg_remove_complete); + + return; +} + +module_init(rmi4_gesture_module_init); +module_exit(rmi4_gesture_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX User Defined Gesture Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_i2c.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_i2c.c new file mode 100644 index 000000000000..3be3bcaab29a --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_i2c.c @@ -0,0 +1,646 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define SYN_I2C_RETRY_TIMES 10 + +/* +#define I2C_BURST_LIMIT 255 +*/ +/* +#define XFER_MSGS_LIMIT 8 +*/ + +static unsigned char *wr_buf; + +static struct synaptics_dsx_hw_interface hw_if; + +static struct platform_device *synaptics_dsx_i2c_device; + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata) +{ + int retval; + u32 value; + const char *name; + struct property *prop; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + + retval = of_property_read_u32(np, "synaptics,irq-on-state", + &value); + if (retval < 0) + bdata->irq_on_state = 0; + else + bdata->irq_on_state = value; + + retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name); + if (retval < 0) + bdata->pwr_reg_name = NULL; + else + bdata->pwr_reg_name = name; + + retval = of_property_read_string(np, "synaptics,bus-reg-name", &name); + if (retval < 0) + bdata->bus_reg_name = NULL; + else + bdata->bus_reg_name = name; + + prop = of_find_property(np, "synaptics,power-gpio", NULL); + if (prop && prop->length) { + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n", + __func__); + return retval; + } else { + bdata->power_on_state = value; + } + } else { + bdata->power_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n", + __func__); + return retval; + } else { + bdata->power_delay_ms = value; + } + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n", + __func__); + return retval; + } else { + bdata->reset_on_state = value; + } + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n", + __func__); + return retval; + } else { + bdata->reset_active_ms = value; + } + } else { + bdata->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n", + __func__); + return retval; + } else { + bdata->reset_delay_ms = value; + } + } else { + bdata->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,max-y-for-2d", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,max-y-for-2d", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n", + __func__); + return retval; + } else { + bdata->max_y_for_2d = value; + } + } else { + bdata->max_y_for_2d = -1; + } + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,x-flip", NULL); + bdata->x_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,y-flip", NULL); + bdata->y_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,ub-i2c-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ub-i2c-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n", + __func__); + return retval; + } else { + bdata->ub_i2c_addr = (unsigned short)value; + } + } else { + bdata->ub_i2c_addr = -1; + } + + prop = of_find_property(np, "synaptics,cap-button-codes", NULL); + if (prop && prop->length) { + bdata->cap_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->cap_button_map->map) + return -ENOMEM; + bdata->cap_button_map->nbuttons = prop->length / sizeof(u32); + retval = of_property_read_u32_array(np, + "synaptics,cap-button-codes", + bdata->cap_button_map->map, + bdata->cap_button_map->nbuttons); + if (retval < 0) { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + } else { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + + prop = of_find_property(np, "synaptics,vir-button-codes", NULL); + if (prop && prop->length) { + bdata->vir_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->vir_button_map->map) + return -ENOMEM; + bdata->vir_button_map->nbuttons = prop->length / sizeof(u32); + bdata->vir_button_map->nbuttons /= 5; + retval = of_property_read_u32_array(np, + "synaptics,vir-button-codes", + bdata->vir_button_map->map, + bdata->vir_button_map->nbuttons * 5); + if (retval < 0) { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + } else { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + + return 0; +} +#endif + +static int synaptics_rmi4_i2c_alloc_buf(struct synaptics_rmi4_data *rmi4_data, + unsigned int count) +{ + static unsigned int buf_size; + + if (count > buf_size) { + if (buf_size) + kfree(wr_buf); + wr_buf = kzalloc(count, GFP_KERNEL); + if (!wr_buf) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for buffer\n", + __func__); + buf_size = 0; + return -ENOMEM; + } + buf_size = count; + } + + return 0; +} + +static void synaptics_rmi4_i2c_check_addr(struct synaptics_rmi4_data *rmi4_data, + struct i2c_client *i2c) +{ + if (hw_if.board_data->ub_i2c_addr == -1) + return; + + if (hw_if.board_data->i2c_addr == i2c->addr) + hw_if.board_data->i2c_addr = hw_if.board_data->ub_i2c_addr; + else + hw_if.board_data->i2c_addr = i2c->addr; + + return; +} + +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); + struct i2c_msg msg[1]; + + msg[0].addr = hw_if.board_data->i2c_addr; + msg[0].flags = 0; + msg[0].len = PAGE_SELECT_LEN; + msg[0].buf = buf; + + page = ((addr >> 8) & MASK_8BIT); + buf[0] = MASK_8BIT; + buf[1] = page; + + if (page != rmi4_data->current_page) { + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(i2c->adapter, msg, 1) == 1) { + rmi4_data->current_page = page; + retval = PAGE_SELECT_LEN; + break; + } + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + + if (retry == SYN_I2C_RETRY_TIMES / 2) { + synaptics_rmi4_i2c_check_addr(rmi4_data, i2c); + msg[0].addr = hw_if.board_data->i2c_addr; + } + } + } else { + retval = PAGE_SELECT_LEN; + } + + return retval; +} + +static int synaptics_rmi4_i2c_read(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned char retry; + unsigned char buf; +#ifdef I2C_BURST_LIMIT + unsigned char ii; + unsigned char rd_msgs = ((length - 1) / I2C_BURST_LIMIT) + 1; +#else + unsigned char rd_msgs = 1; +#endif + unsigned char index = 0; + unsigned char xfer_msgs; + unsigned char remaining_msgs; + unsigned short i2c_addr; + unsigned short data_offset = 0; + unsigned short remaining_length = length; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_adapter *adap = i2c->adapter; + struct i2c_msg msg[rd_msgs + 1]; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + msg[0].addr = hw_if.board_data->i2c_addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &buf; + +#ifdef I2C_BURST_LIMIT + for (ii = 0; ii < (rd_msgs - 1); ii++) { + msg[ii + 1].addr = hw_if.board_data->i2c_addr; + msg[ii + 1].flags = I2C_M_RD; + msg[ii + 1].len = I2C_BURST_LIMIT; + msg[ii + 1].buf = &data[data_offset]; + data_offset += I2C_BURST_LIMIT; + remaining_length -= I2C_BURST_LIMIT; + } +#endif + + msg[rd_msgs].addr = hw_if.board_data->i2c_addr; + msg[rd_msgs].flags = I2C_M_RD; + msg[rd_msgs].len = remaining_length; + msg[rd_msgs].buf = &data[data_offset]; + + buf = addr & MASK_8BIT; + + remaining_msgs = rd_msgs + 1; + + while (remaining_msgs) { +#ifdef XFER_MSGS_LIMIT + if (remaining_msgs > XFER_MSGS_LIMIT) + xfer_msgs = XFER_MSGS_LIMIT; + else + xfer_msgs = remaining_msgs; +#else + xfer_msgs = remaining_msgs; +#endif + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + retval = i2c_transfer(adap, &msg[index], xfer_msgs); + if (retval == xfer_msgs) + break; + + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + + if (retry == SYN_I2C_RETRY_TIMES / 2) { + synaptics_rmi4_i2c_check_addr(rmi4_data, i2c); + i2c_addr = hw_if.board_data->i2c_addr; + msg[0].addr = i2c_addr; +#ifdef I2C_BURST_LIMIT + for (ii = 0; ii < (rd_msgs - 1); ii++) + msg[ii + 1].addr = i2c_addr; +#endif + msg[rd_msgs].addr = i2c_addr; + } + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C read over retry limit\n", + __func__); + retval = -EIO; + goto exit; + } + + remaining_msgs -= xfer_msgs; + index += xfer_msgs; + } + + retval = length; + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int synaptics_rmi4_i2c_write(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned char retry; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[1]; + + retval = synaptics_rmi4_i2c_alloc_buf(rmi4_data, length + 1); + if (retval < 0) + return retval; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + retval = -EIO; + goto exit; + } + + msg[0].addr = hw_if.board_data->i2c_addr; + msg[0].flags = 0; + msg[0].len = length + 1; + msg[0].buf = wr_buf; + + wr_buf[0] = addr & MASK_8BIT; + retval = secure_memcpy(&wr_buf[1], length, &data[0], length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + goto exit; + } + + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(i2c->adapter, msg, 1) == 1) { + retval = length; + break; + } + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + + if (retry == SYN_I2C_RETRY_TIMES / 2) { + synaptics_rmi4_i2c_check_addr(rmi4_data, i2c); + msg[0].addr = hw_if.board_data->i2c_addr; + } + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: I2C write over retry limit\n", + __func__); + retval = -EIO; + } + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static struct synaptics_dsx_bus_access bus_access = { + .type = BUS_I2C, + .read = synaptics_rmi4_i2c_read, + .write = synaptics_rmi4_i2c_write, +}; + +static void synaptics_rmi4_i2c_dev_release(struct device *dev) +{ + kfree(synaptics_dsx_i2c_device); + + return; +} + +static int synaptics_rmi4_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int retval; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "%s: SMBus byte data commands not supported by host\n", + __func__); + return -EIO; + } + + synaptics_dsx_i2c_device = kzalloc( + sizeof(struct platform_device), + GFP_KERNEL); + if (!synaptics_dsx_i2c_device) { + dev_err(&client->dev, + "%s: Failed to allocate memory for synaptics_dsx_i2c_device\n", + __func__); + return -ENOMEM; + } + +#ifdef CONFIG_OF + if (client->dev.of_node) { + hw_if.board_data = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_board_data), + GFP_KERNEL); + if (!hw_if.board_data) { + dev_err(&client->dev, + "%s: Failed to allocate memory for board data\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->cap_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->cap_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for 0D button map\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->vir_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->vir_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for virtual button map\n", + __func__); + return -ENOMEM; + } + parse_dt(&client->dev, hw_if.board_data); + } +#else + hw_if.board_data = client->dev.platform_data; +#endif + + hw_if.bus_access = &bus_access; + hw_if.board_data->i2c_addr = client->addr; + + synaptics_dsx_i2c_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_i2c_device->id = 0; + synaptics_dsx_i2c_device->num_resources = 0; + synaptics_dsx_i2c_device->dev.parent = &client->dev; + synaptics_dsx_i2c_device->dev.platform_data = &hw_if; + synaptics_dsx_i2c_device->dev.release = synaptics_rmi4_i2c_dev_release; + + retval = platform_device_register(synaptics_dsx_i2c_device); + if (retval) { + dev_err(&client->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_i2c_remove(struct i2c_client *client) +{ + platform_device_unregister(synaptics_dsx_i2c_device); + + return 0; +} + +static const struct i2c_device_id synaptics_rmi4_id_table[] = { + {I2C_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table); + +#ifdef CONFIG_OF +static struct of_device_id synaptics_rmi4_of_match_table[] = { + { + .compatible = "synaptics,dsx-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table); +#else +#define synaptics_rmi4_of_match_table NULL +#endif + +static struct i2c_driver synaptics_rmi4_i2c_driver = { + .driver = { + .name = I2C_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = synaptics_rmi4_of_match_table, + }, + .probe = synaptics_rmi4_i2c_probe, + .remove = synaptics_rmi4_i2c_remove, + .id_table = synaptics_rmi4_id_table, +}; + +int synaptics_rmi4_bus_init(void) +{ + return i2c_add_driver(&synaptics_rmi4_i2c_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_init); + +void synaptics_rmi4_bus_exit(void) +{ + kfree(wr_buf); + + i2c_del_driver(&synaptics_rmi4_i2c_driver); + + 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_2.6/synaptics_dsx_proximity.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_proximity.c new file mode 100644 index 000000000000..2507c7743d9a --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_proximity.c @@ -0,0 +1,692 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define PROX_PHYS_NAME "synaptics_dsx/proximity" + +#define HOVER_Z_MAX (255) + +#define HOVERING_FINGER_EN (1 << 4) + +static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static struct device_attribute attrs[] = { + __ATTR(hover_finger_en, (S_IRUGO | S_IWUGO), + synaptics_rmi4_hover_finger_en_show, + synaptics_rmi4_hover_finger_en_store), +}; + +struct synaptics_rmi4_f12_query_5 { + union { + struct { + unsigned char size_of_query6; + struct { + unsigned char ctrl0_is_present:1; + unsigned char ctrl1_is_present:1; + unsigned char ctrl2_is_present:1; + unsigned char ctrl3_is_present:1; + unsigned char ctrl4_is_present:1; + unsigned char ctrl5_is_present:1; + unsigned char ctrl6_is_present:1; + unsigned char ctrl7_is_present:1; + } __packed; + struct { + unsigned char ctrl8_is_present:1; + unsigned char ctrl9_is_present:1; + unsigned char ctrl10_is_present:1; + unsigned char ctrl11_is_present:1; + unsigned char ctrl12_is_present:1; + unsigned char ctrl13_is_present:1; + unsigned char ctrl14_is_present:1; + unsigned char ctrl15_is_present:1; + } __packed; + struct { + unsigned char ctrl16_is_present:1; + unsigned char ctrl17_is_present:1; + unsigned char ctrl18_is_present:1; + unsigned char ctrl19_is_present:1; + unsigned char ctrl20_is_present:1; + unsigned char ctrl21_is_present:1; + unsigned char ctrl22_is_present:1; + unsigned char ctrl23_is_present:1; + } __packed; + }; + unsigned char data[4]; + }; +}; + +struct synaptics_rmi4_f12_query_8 { + union { + struct { + unsigned char size_of_query9; + struct { + unsigned char data0_is_present:1; + unsigned char data1_is_present:1; + unsigned char data2_is_present:1; + unsigned char data3_is_present:1; + unsigned char data4_is_present:1; + unsigned char data5_is_present:1; + unsigned char data6_is_present:1; + unsigned char data7_is_present:1; + } __packed; + }; + unsigned char data[2]; + }; +}; + +struct prox_finger_data { + union { + struct { + unsigned char object_type_and_status; + unsigned char x_lsb; + unsigned char x_msb; + unsigned char y_lsb; + unsigned char y_msb; + unsigned char z; + } __packed; + unsigned char proximity_data[6]; + }; +}; + +struct synaptics_rmi4_prox_handle { + bool hover_finger_present; + bool hover_finger_en; + unsigned char intr_mask; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short hover_finger_en_addr; + unsigned short hover_finger_data_addr; + struct input_dev *prox_dev; + struct prox_finger_data *finger_data; + struct synaptics_rmi4_data *rmi4_data; +}; + +static struct synaptics_rmi4_prox_handle *prox; + +DECLARE_COMPLETION(prox_remove_complete); + +static void prox_hover_finger_lift(void) +{ + input_report_key(prox->prox_dev, BTN_TOUCH, 0); + input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 0); + input_sync(prox->prox_dev); + prox->hover_finger_present = false; + + return; +} + +static void prox_hover_finger_report(void) +{ + int retval; + int x; + int y; + int z; + struct prox_finger_data *data; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + data = prox->finger_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->hover_finger_data_addr, + data->proximity_data, + sizeof(data->proximity_data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read hovering finger data\n", + __func__); + return; + } + + if (data->object_type_and_status != F12_HOVERING_FINGER_STATUS) { + if (prox->hover_finger_present) + prox_hover_finger_lift(); + + return; + } + + x = (data->x_msb << 8) | (data->x_lsb); + y = (data->y_msb << 8) | (data->y_lsb); + z = HOVER_Z_MAX - data->z; + + input_report_key(prox->prox_dev, BTN_TOUCH, 0); + input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 1); + input_report_abs(prox->prox_dev, ABS_X, x); + input_report_abs(prox->prox_dev, ABS_Y, y); + input_report_abs(prox->prox_dev, ABS_DISTANCE, z); + + input_sync(prox->prox_dev); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: x = %d y = %d z = %d\n", + __func__, x, y, z); + + prox->hover_finger_present = true; + + return; +} + +static int prox_set_hover_finger_en(void) +{ + int retval; + unsigned char object_report_enable; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->hover_finger_en_addr, + &object_report_enable, + sizeof(object_report_enable)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read from object report enable register\n", + __func__); + return retval; + } + + if (prox->hover_finger_en) + object_report_enable |= HOVERING_FINGER_EN; + else + object_report_enable &= ~HOVERING_FINGER_EN; + + retval = synaptics_rmi4_reg_write(rmi4_data, + prox->hover_finger_en_addr, + &object_report_enable, + sizeof(object_report_enable)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write to object report enable register\n", + __func__); + return retval; + } + + return 0; +} + +static void prox_set_params(void) +{ + input_set_abs_params(prox->prox_dev, ABS_X, 0, + prox->rmi4_data->sensor_max_x, 0, 0); + input_set_abs_params(prox->prox_dev, ABS_Y, 0, + prox->rmi4_data->sensor_max_y, 0, 0); + input_set_abs_params(prox->prox_dev, ABS_DISTANCE, 0, + HOVER_Z_MAX, 0, 0); + + return; +} + +static int prox_reg_init(void) +{ + int retval; + unsigned char ctrl_23_offset; + unsigned char data_1_offset; + struct synaptics_rmi4_f12_query_5 query_5; + struct synaptics_rmi4_f12_query_8 query_8; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->query_base_addr + 5, + query_5.data, + sizeof(query_5.data)); + if (retval < 0) + return retval; + + ctrl_23_offset = query_5.ctrl0_is_present + + query_5.ctrl1_is_present + + query_5.ctrl2_is_present + + query_5.ctrl3_is_present + + query_5.ctrl4_is_present + + query_5.ctrl5_is_present + + query_5.ctrl6_is_present + + query_5.ctrl7_is_present + + query_5.ctrl8_is_present + + query_5.ctrl9_is_present + + query_5.ctrl10_is_present + + query_5.ctrl11_is_present + + query_5.ctrl12_is_present + + query_5.ctrl13_is_present + + query_5.ctrl14_is_present + + query_5.ctrl15_is_present + + query_5.ctrl16_is_present + + query_5.ctrl17_is_present + + query_5.ctrl18_is_present + + query_5.ctrl19_is_present + + query_5.ctrl20_is_present + + query_5.ctrl21_is_present + + query_5.ctrl22_is_present; + + prox->hover_finger_en_addr = prox->control_base_addr + ctrl_23_offset; + + retval = synaptics_rmi4_reg_read(rmi4_data, + prox->query_base_addr + 8, + query_8.data, + sizeof(query_8.data)); + if (retval < 0) + return retval; + + data_1_offset = query_8.data0_is_present; + prox->hover_finger_data_addr = prox->data_base_addr + data_1_offset; + + return retval; +} + +static int prox_scan_pdt(void) +{ + int retval; + unsigned char ii; + unsigned char page; + unsigned char intr_count = 0; + unsigned char intr_off; + unsigned char intr_src; + unsigned short addr; + struct synaptics_rmi4_fn_desc fd; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&fd, + sizeof(fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (fd.fn_number) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Found F%02x\n", + __func__, fd.fn_number); + switch (fd.fn_number) { + case SYNAPTICS_RMI4_F12: + goto f12_found; + break; + } + } else { + break; + } + + intr_count += fd.intr_src_count; + } + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F12\n", + __func__); + return -EINVAL; + +f12_found: + prox->query_base_addr = fd.query_base_addr | (page << 8); + prox->control_base_addr = fd.ctrl_base_addr | (page << 8); + prox->data_base_addr = fd.data_base_addr | (page << 8); + prox->command_base_addr = fd.cmd_base_addr | (page << 8); + + retval = prox_reg_init(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize proximity registers\n", + __func__); + return retval; + } + + prox->intr_mask = 0; + intr_src = fd.intr_src_count; + intr_off = intr_count % 8; + for (ii = intr_off; + ii < (intr_src + intr_off); + ii++) { + prox->intr_mask |= 1 << ii; + } + + rmi4_data->intr_mask[0] |= prox->intr_mask; + + addr = rmi4_data->f01_ctrl_base_addr + 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + addr, + &(rmi4_data->intr_mask[0]), + sizeof(rmi4_data->intr_mask[0])); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set interrupt enable bit\n", + __func__); + return retval; + } + + return 0; +} + +static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (!prox) + return -ENODEV; + + return snprintf(buf, PAGE_SIZE, "%u\n", + prox->hover_finger_en); +} + +static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data; + + if (!prox) + return -ENODEV; + + if (sscanf(buf, "%x", &input) != 1) + return -EINVAL; + + if (input == 1) + prox->hover_finger_en = true; + else if (input == 0) + prox->hover_finger_en = false; + else + return -EINVAL; + + retval = prox_set_hover_finger_en(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to change hovering finger enable setting\n", + __func__); + return retval; + } + + return count; +} + +int synaptics_rmi4_prox_hover_finger_en(bool enable) +{ + int retval; + + if (!prox) + return -ENODEV; + + prox->hover_finger_en = enable; + + retval = prox_set_hover_finger_en(); + if (retval < 0) + return retval; + + return 0; +} +EXPORT_SYMBOL(synaptics_rmi4_prox_hover_finger_en); + +static void synaptics_rmi4_prox_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!prox) + return; + + if (prox->intr_mask & intr_mask) + prox_hover_finger_report(); + + return; +} + +static int synaptics_rmi4_prox_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char attr_count; + + if (prox) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + prox = kzalloc(sizeof(*prox), GFP_KERNEL); + if (!prox) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for prox\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + prox->finger_data = kzalloc(sizeof(*(prox->finger_data)), GFP_KERNEL); + if (!prox->finger_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for finger_data\n", + __func__); + retval = -ENOMEM; + goto exit_free_prox; + } + + prox->rmi4_data = rmi4_data; + + retval = prox_scan_pdt(); + if (retval < 0) + goto exit_free_finger_data; + + prox->hover_finger_en = true; + + retval = prox_set_hover_finger_en(); + if (retval < 0) + return retval; + + prox->prox_dev = input_allocate_device(); + if (prox->prox_dev == NULL) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate proximity device\n", + __func__); + retval = -ENOMEM; + goto exit_free_finger_data; + } + + prox->prox_dev->name = PROXIMITY_DRIVER_NAME; + prox->prox_dev->phys = PROX_PHYS_NAME; + prox->prox_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT; + prox->prox_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION; + prox->prox_dev->dev.parent = rmi4_data->pdev->dev.parent; + input_set_drvdata(prox->prox_dev, rmi4_data); + + set_bit(EV_KEY, prox->prox_dev->evbit); + set_bit(EV_ABS, prox->prox_dev->evbit); + set_bit(BTN_TOUCH, prox->prox_dev->keybit); + set_bit(BTN_TOOL_FINGER, prox->prox_dev->keybit); +#ifdef INPUT_PROP_DIRECT + set_bit(INPUT_PROP_DIRECT, prox->prox_dev->propbit); +#endif + + prox_set_params(); + + retval = input_register_device(prox->prox_dev); + if (retval) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to register proximity device\n", + __func__); + goto exit_free_input_device; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + goto exit_free_sysfs; + } + } + + return 0; + +exit_free_sysfs: + for (attr_count--; attr_count >= 0; attr_count--) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + input_unregister_device(prox->prox_dev); + prox->prox_dev = NULL; + +exit_free_input_device: + if (prox->prox_dev) + input_free_device(prox->prox_dev); + +exit_free_finger_data: + kfree(prox->finger_data); + +exit_free_prox: + kfree(prox); + prox = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_prox_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + + if (!prox) + goto exit; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + sysfs_remove_file(&rmi4_data->input_dev->dev.kobj, + &attrs[attr_count].attr); + } + + input_unregister_device(prox->prox_dev); + kfree(prox->finger_data); + kfree(prox); + prox = NULL; + +exit: + complete(&prox_remove_complete); + + return; +} + +static void synaptics_rmi4_prox_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) { + synaptics_rmi4_prox_init(rmi4_data); + return; + } + + prox_hover_finger_lift(); + + prox_scan_pdt(); + + prox_set_hover_finger_en(); + + return; +} + +static void synaptics_rmi4_prox_reinit(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) + return; + + prox_hover_finger_lift(); + + prox_set_hover_finger_en(); + + return; +} + +static void synaptics_rmi4_prox_e_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) + return; + + prox_hover_finger_lift(); + + return; +} + +static void synaptics_rmi4_prox_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + if (!prox) + return; + + prox_hover_finger_lift(); + + return; +} + +static struct synaptics_rmi4_exp_fn proximity_module = { + .fn_type = RMI_PROXIMITY, + .init = synaptics_rmi4_prox_init, + .remove = synaptics_rmi4_prox_remove, + .reset = synaptics_rmi4_prox_reset, + .reinit = synaptics_rmi4_prox_reinit, + .early_suspend = synaptics_rmi4_prox_e_suspend, + .suspend = synaptics_rmi4_prox_suspend, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_prox_attn, +}; + +static int __init rmi4_proximity_module_init(void) +{ + synaptics_rmi4_new_function(&proximity_module, true); + + return 0; +} + +static void __exit rmi4_proximity_module_exit(void) +{ + synaptics_rmi4_new_function(&proximity_module, false); + + wait_for_completion(&prox_remove_complete); + + return; +} + +module_init(rmi4_proximity_module_init); +module_exit(rmi4_proximity_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Proximity Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_dev.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_dev.c new file mode 100644 index 000000000000..9d19bda545f7 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_dev.c @@ -0,0 +1,1058 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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/signal.h> +#include <linux/sched.h> +#include <linux/gpio.h> +#include <linux/uaccess.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define CHAR_DEVICE_NAME "rmi" +#define DEVICE_CLASS_NAME "rmidev" +#define SYSFS_FOLDER_NAME "rmidev" +#define DEV_NUMBER 1 +#define REG_ADDR_LIMIT 0xFFFF + +static ssize_t rmidev_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t rmidev_sysfs_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static ssize_t rmidev_sysfs_open_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_release_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_attn_state_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_pid_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_term_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_intr_mask_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_intr_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t rmidev_sysfs_concurrent_show(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t rmidev_sysfs_concurrent_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +struct rmidev_handle { + dev_t dev_no; + pid_t pid; + unsigned char intr_mask; + unsigned char *tmpbuf; + unsigned int tmpbuf_size; + struct device dev; + struct synaptics_rmi4_data *rmi4_data; + struct kobject *sysfs_dir; + struct siginfo interrupt_signal; + struct siginfo terminate_signal; + struct task_struct *task; + void *data; + bool irq_enabled; + bool concurrent; +}; + +struct rmidev_data { + int ref_count; + struct cdev main_dev; + struct class *device_class; + struct mutex file_mutex; + struct rmidev_handle *rmi_dev; +}; + +static struct bin_attribute attr_data = { + .attr = { + .name = "data", + .mode = (S_IRUGO | S_IWUGO), + }, + .size = 0, + .read = rmidev_sysfs_data_show, + .write = rmidev_sysfs_data_store, +}; + +static struct device_attribute attrs[] = { + __ATTR(open, S_IWUGO, + synaptics_rmi4_show_error, + rmidev_sysfs_open_store), + __ATTR(release, S_IWUGO, + synaptics_rmi4_show_error, + rmidev_sysfs_release_store), + __ATTR(attn_state, S_IRUGO, + rmidev_sysfs_attn_state_show, + synaptics_rmi4_store_error), + __ATTR(pid, S_IRUGO | S_IWUGO, + rmidev_sysfs_pid_show, + rmidev_sysfs_pid_store), + __ATTR(term, S_IWUGO, + synaptics_rmi4_show_error, + rmidev_sysfs_term_store), + __ATTR(intr_mask, S_IRUGO | S_IWUGO, + rmidev_sysfs_intr_mask_show, + rmidev_sysfs_intr_mask_store), + __ATTR(concurrent, S_IRUGO | S_IWUGO, + rmidev_sysfs_concurrent_show, + rmidev_sysfs_concurrent_store), +}; + +static int rmidev_major_num; + +static struct class *rmidev_device_class; + +static struct rmidev_handle *rmidev; + +DECLARE_COMPLETION(rmidev_remove_complete); + +static irqreturn_t rmidev_sysfs_irq(int irq, void *data) +{ + struct synaptics_rmi4_data *rmi4_data = data; + + sysfs_notify(&rmi4_data->input_dev->dev.kobj, + SYSFS_FOLDER_NAME, "attn_state"); + + return IRQ_HANDLED; +} + +static int rmidev_sysfs_irq_enable(struct synaptics_rmi4_data *rmi4_data, + bool enable) +{ + int retval = 0; + unsigned char intr_status[MAX_INTR_REGISTERS]; + unsigned long irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT; + + if (enable) { + if (rmidev->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, + rmidev_sysfs_irq, irq_flags, + PLATFORM_DRIVER_NAME, rmi4_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create irq thread\n", + __func__); + return retval; + } + + rmidev->irq_enabled = true; + } else { + if (rmidev->irq_enabled) { + disable_irq(rmi4_data->irq); + free_irq(rmi4_data->irq, rmi4_data); + rmidev->irq_enabled = false; + } + } + + return retval; +} + +static ssize_t rmidev_sysfs_data_show(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned char intr_status = 0; + unsigned int length = (unsigned int)count; + unsigned short address = (unsigned short)pos; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (length > (REG_ADDR_LIMIT - address)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Out of register map limit\n", + __func__); + return -EINVAL; + } + + if (length) { + retval = synaptics_rmi4_reg_read(rmi4_data, + address, + (unsigned char *)buf, + length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read data\n", + __func__); + return retval; + } + } else { + return -EINVAL; + } + + if (!rmidev->concurrent) + goto exit; + + if (address != rmi4_data->f01_data_base_addr) + goto exit; + + if (length <= 1) + goto exit; + + intr_status = buf[1]; + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + if (fhandler->intr_mask & intr_status) { + rmi4_data->report_touch(rmi4_data, + fhandler); + } + } + } + } + +exit: + return length; +} + +static ssize_t rmidev_sysfs_data_store(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int length = (unsigned int)count; + unsigned short address = (unsigned short)pos; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (length > (REG_ADDR_LIMIT - address)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Out of register map limit\n", + __func__); + return -EINVAL; + } + + if (length) { + retval = synaptics_rmi4_reg_write(rmi4_data, + address, + (unsigned char *)buf, + length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write data\n", + __func__); + return retval; + } + } else { + return -EINVAL; + } + + return length; +} + +static ssize_t rmidev_sysfs_open_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + rmi4_data->irq_enable(rmi4_data, false, false); + rmidev_sysfs_irq_enable(rmi4_data, true); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Attention interrupt disabled\n", + __func__); + + return count; +} + +static ssize_t rmidev_sysfs_release_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + rmidev_sysfs_irq_enable(rmi4_data, false); + rmi4_data->irq_enable(rmi4_data, true, false); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Attention interrupt enabled\n", + __func__); + + rmi4_data->reset_device(rmi4_data, false); + + rmi4_data->stay_awake = false; + + return count; +} + +static ssize_t rmidev_sysfs_attn_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int attn_state; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + attn_state = gpio_get_value(bdata->irq_gpio); + + return snprintf(buf, PAGE_SIZE, "%u\n", attn_state); +} + +static ssize_t rmidev_sysfs_pid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", rmidev->pid); +} + +static ssize_t rmidev_sysfs_pid_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + rmidev->pid = input; + + if (rmidev->pid) { + rmidev->task = pid_task(find_vpid(rmidev->pid), PIDTYPE_PID); + if (!rmidev->task) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to locate PID of data logging tool\n", + __func__); + return -EINVAL; + } + } + + return count; +} + +static ssize_t rmidev_sysfs_term_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + if (input != 1) + return -EINVAL; + + if (rmidev->pid) + send_sig_info(SIGTERM, &rmidev->terminate_signal, rmidev->task); + + return count; +} + +static ssize_t rmidev_sysfs_intr_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "0x%02x\n", rmidev->intr_mask); +} + +static ssize_t rmidev_sysfs_intr_mask_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + rmidev->intr_mask = (unsigned char)input; + + return count; +} + +static ssize_t rmidev_sysfs_concurrent_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", rmidev->concurrent); +} + +static ssize_t rmidev_sysfs_concurrent_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (sscanf(buf, "%u", &input) != 1) + return -EINVAL; + + rmidev->concurrent = input > 0 ? true : false; + + return count; +} + +static int rmidev_allocate_buffer(int count) +{ + if (count + 1 > rmidev->tmpbuf_size) { + if (rmidev->tmpbuf_size) + kfree(rmidev->tmpbuf); + rmidev->tmpbuf = kzalloc(count + 1, GFP_KERNEL); + if (!rmidev->tmpbuf) { + dev_err(rmidev->rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for buffer\n", + __func__); + rmidev->tmpbuf_size = 0; + return -ENOMEM; + } + rmidev->tmpbuf_size = count + 1; + } + + return 0; +} + +/* + * rmidev_llseek - set register address to access for RMI device + * + * @filp: pointer to file structure + * @off: + * if whence == SEEK_SET, + * off: 16-bit RMI register address + * if whence == SEEK_CUR, + * off: offset from current position + * if whence == SEEK_END, + * off: offset from end position (0xFFFF) + * @whence: SEEK_SET, SEEK_CUR, or SEEK_END + */ +static loff_t rmidev_llseek(struct file *filp, loff_t off, int whence) +{ + loff_t newpos; + struct rmidev_data *dev_data = filp->private_data; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (IS_ERR(dev_data)) { + pr_err("%s: Pointer of char device data is invalid", __func__); + return -EBADF; + } + + mutex_lock(&(dev_data->file_mutex)); + + switch (whence) { + case SEEK_SET: + newpos = off; + break; + case SEEK_CUR: + newpos = filp->f_pos + off; + break; + case SEEK_END: + newpos = REG_ADDR_LIMIT + off; + break; + default: + newpos = -EINVAL; + goto clean_up; + } + + if (newpos < 0 || newpos > REG_ADDR_LIMIT) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: New position 0x%04x is invalid\n", + __func__, (unsigned int)newpos); + newpos = -EINVAL; + goto clean_up; + } + + filp->f_pos = newpos; + +clean_up: + mutex_unlock(&(dev_data->file_mutex)); + + return newpos; +} + +/* + * rmidev_read: read register data from RMI device + * + * @filp: pointer to file structure + * @buf: pointer to user space buffer + * @count: number of bytes to read + * @f_pos: starting RMI register address + */ +static ssize_t rmidev_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + ssize_t retval; + unsigned char intr_status = 0; + unsigned short address; + struct rmidev_data *dev_data = filp->private_data; + struct synaptics_rmi4_fn *fhandler; + struct synaptics_rmi4_device_info *rmi; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + rmi = &(rmi4_data->rmi4_mod_info); + + if (IS_ERR(dev_data)) { + pr_err("%s: Pointer of char device data is invalid", __func__); + return -EBADF; + } + + if (count == 0) + return 0; + + if (count > (REG_ADDR_LIMIT - *f_pos)) + count = REG_ADDR_LIMIT - *f_pos; + + address = (unsigned short)(*f_pos); + + rmidev_allocate_buffer(count); + + mutex_lock(&(dev_data->file_mutex)); + + retval = synaptics_rmi4_reg_read(rmidev->rmi4_data, + *f_pos, + rmidev->tmpbuf, + count); + if (retval < 0) + goto clean_up; + + if (copy_to_user(buf, rmidev->tmpbuf, count)) + retval = -EFAULT; + else + *f_pos += retval; + + if (!rmidev->concurrent) + goto clean_up; + + if (address != rmi4_data->f01_data_base_addr) + goto clean_up; + + if (count <= 1) + goto clean_up; + + intr_status = rmidev->tmpbuf[1]; + + if (!list_empty(&rmi->support_fn_list)) { + list_for_each_entry(fhandler, &rmi->support_fn_list, link) { + if (fhandler->num_of_data_sources) { + if (fhandler->intr_mask & intr_status) { + rmi4_data->report_touch(rmi4_data, + fhandler); + } + } + } + } + +clean_up: + mutex_unlock(&(dev_data->file_mutex)); + + return retval; +} + +/* + * rmidev_write: write register data to RMI device + * + * @filp: pointer to file structure + * @buf: pointer to user space buffer + * @count: number of bytes to write + * @f_pos: starting RMI register address + */ +static ssize_t rmidev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + ssize_t retval; + struct rmidev_data *dev_data = filp->private_data; + + if (IS_ERR(dev_data)) { + pr_err("%s: Pointer of char device data is invalid", __func__); + return -EBADF; + } + + if (count == 0) + return 0; + + if (count > (REG_ADDR_LIMIT - *f_pos)) + count = REG_ADDR_LIMIT - *f_pos; + + rmidev_allocate_buffer(count); + + if (copy_from_user(rmidev->tmpbuf, buf, count)) + return -EFAULT; + + mutex_lock(&(dev_data->file_mutex)); + + retval = synaptics_rmi4_reg_write(rmidev->rmi4_data, + *f_pos, + rmidev->tmpbuf, + count); + if (retval >= 0) + *f_pos += retval; + + mutex_unlock(&(dev_data->file_mutex)); + + return retval; +} + +static int rmidev_open(struct inode *inp, struct file *filp) +{ + int retval = 0; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + struct rmidev_data *dev_data = + container_of(inp->i_cdev, struct rmidev_data, main_dev); + + if (!dev_data) + return -EACCES; + + if (rmi4_data->sensor_sleep) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Sensor sleeping\n", + __func__); + return -ENODEV; + } + + rmi4_data->stay_awake = true; + + filp->private_data = dev_data; + + mutex_lock(&(dev_data->file_mutex)); + + rmi4_data->irq_enable(rmi4_data, false, false); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Attention interrupt disabled\n", + __func__); + + if (dev_data->ref_count < 1) + dev_data->ref_count++; + else + retval = -EACCES; + + mutex_unlock(&(dev_data->file_mutex)); + + return retval; +} + +static int rmidev_release(struct inode *inp, struct file *filp) +{ + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + struct rmidev_data *dev_data = + container_of(inp->i_cdev, struct rmidev_data, main_dev); + + if (!dev_data) + return -EACCES; + + mutex_lock(&(dev_data->file_mutex)); + + dev_data->ref_count--; + if (dev_data->ref_count < 0) + dev_data->ref_count = 0; + + rmi4_data->irq_enable(rmi4_data, true, false); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Attention interrupt enabled\n", + __func__); + + mutex_unlock(&(dev_data->file_mutex)); + + rmi4_data->reset_device(rmi4_data, false); + + rmi4_data->stay_awake = false; + + return 0; +} + +static const struct file_operations rmidev_fops = { + .owner = THIS_MODULE, + .llseek = rmidev_llseek, + .read = rmidev_read, + .write = rmidev_write, + .open = rmidev_open, + .release = rmidev_release, +}; + +static void rmidev_device_cleanup(struct rmidev_data *dev_data) +{ + dev_t devno; + struct synaptics_rmi4_data *rmi4_data = rmidev->rmi4_data; + + if (dev_data) { + devno = dev_data->main_dev.dev; + + if (dev_data->device_class) + device_destroy(dev_data->device_class, devno); + + cdev_del(&dev_data->main_dev); + + unregister_chrdev_region(devno, 1); + + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: rmidev device removed\n", + __func__); + } + + return; +} + +static char *rmi_char_devnode(struct device *dev, umode_t *mode) +{ + if (!mode) + return NULL; + + *mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + return kasprintf(GFP_KERNEL, "rmi/%s", dev_name(dev)); +} + +static int rmidev_create_device_class(void) +{ + if (rmidev_device_class != NULL) + return 0; + + rmidev_device_class = class_create(THIS_MODULE, DEVICE_CLASS_NAME); + + if (IS_ERR(rmidev_device_class)) { + pr_err("%s: Failed to create /dev/%s\n", + __func__, CHAR_DEVICE_NAME); + return -ENODEV; + } + + rmidev_device_class->devnode = rmi_char_devnode; + + return 0; +} + +static void rmidev_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!rmidev) + return; + + if (rmidev->pid && (rmidev->intr_mask & intr_mask)) + send_sig_info(SIGIO, &rmidev->interrupt_signal, rmidev->task); + + return; +} + +static int rmidev_init_device(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + dev_t dev_no; + unsigned char attr_count; + struct rmidev_data *dev_data; + struct device *device_ptr; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (rmidev) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + rmidev = kzalloc(sizeof(*rmidev), GFP_KERNEL); + if (!rmidev) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for rmidev\n", + __func__); + retval = -ENOMEM; + goto err_rmidev; + } + + rmidev->rmi4_data = rmi4_data; + + memset(&rmidev->interrupt_signal, 0, sizeof(rmidev->interrupt_signal)); + rmidev->interrupt_signal.si_signo = SIGIO; + rmidev->interrupt_signal.si_code = SI_USER; + + memset(&rmidev->terminate_signal, 0, sizeof(rmidev->terminate_signal)); + rmidev->terminate_signal.si_signo = SIGTERM; + rmidev->terminate_signal.si_code = SI_USER; + + retval = rmidev_create_device_class(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create device class\n", + __func__); + goto err_device_class; + } + + if (rmidev_major_num) { + dev_no = MKDEV(rmidev_major_num, DEV_NUMBER); + retval = register_chrdev_region(dev_no, 1, CHAR_DEVICE_NAME); + } else { + retval = alloc_chrdev_region(&dev_no, 0, 1, CHAR_DEVICE_NAME); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to allocate char device region\n", + __func__); + goto err_device_region; + } + + rmidev_major_num = MAJOR(dev_no); + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Major number of rmidev = %d\n", + __func__, rmidev_major_num); + } + + dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); + if (!dev_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for dev_data\n", + __func__); + retval = -ENOMEM; + goto err_dev_data; + } + + mutex_init(&dev_data->file_mutex); + dev_data->rmi_dev = rmidev; + rmidev->data = dev_data; + + cdev_init(&dev_data->main_dev, &rmidev_fops); + + retval = cdev_add(&dev_data->main_dev, dev_no, 1); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to add rmi char device\n", + __func__); + goto err_char_device; + } + + dev_set_name(&rmidev->dev, "rmidev%d", MINOR(dev_no)); + dev_data->device_class = rmidev_device_class; + + device_ptr = device_create(dev_data->device_class, NULL, dev_no, + NULL, CHAR_DEVICE_NAME"%d", MINOR(dev_no)); + if (IS_ERR(device_ptr)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create rmi char device\n", + __func__); + retval = -ENODEV; + goto err_char_device; + } + + retval = gpio_export(bdata->irq_gpio, false); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to export attention gpio\n", + __func__); + } else { + retval = gpio_export_link(&(rmi4_data->input_dev->dev), + "attn", bdata->irq_gpio); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s Failed to create gpio symlink\n", + __func__); + } else { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Exported attention gpio %d\n", + __func__, bdata->irq_gpio); + } + } + + rmidev->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME, + &rmi4_data->input_dev->dev.kobj); + if (!rmidev->sysfs_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs directory\n", + __func__); + retval = -ENODEV; + goto err_sysfs_dir; + } + + retval = sysfs_create_bin_file(rmidev->sysfs_dir, + &attr_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs bin file\n", + __func__); + goto err_sysfs_bin; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(rmidev->sysfs_dir, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto err_sysfs_attrs; + } + } + + return 0; + +err_sysfs_attrs: + for (attr_count--; attr_count >= 0; attr_count--) + sysfs_remove_file(rmidev->sysfs_dir, &attrs[attr_count].attr); + + sysfs_remove_bin_file(rmidev->sysfs_dir, &attr_data); + +err_sysfs_bin: + kobject_put(rmidev->sysfs_dir); + +err_sysfs_dir: +err_char_device: + rmidev_device_cleanup(dev_data); + kfree(dev_data); + +err_dev_data: + unregister_chrdev_region(dev_no, 1); + +err_device_region: + if (rmidev_device_class != NULL) { + class_destroy(rmidev_device_class); + rmidev_device_class = NULL; + } + +err_device_class: + kfree(rmidev); + rmidev = NULL; + +err_rmidev: + return retval; +} + +static void rmidev_remove_device(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + struct rmidev_data *dev_data; + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + if (!rmidev) + goto exit; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) + sysfs_remove_file(rmidev->sysfs_dir, &attrs[attr_count].attr); + + sysfs_remove_bin_file(rmidev->sysfs_dir, &attr_data); + + kobject_put(rmidev->sysfs_dir); + + gpio_unexport(bdata->irq_gpio); + + dev_data = rmidev->data; + if (dev_data) { + rmidev_device_cleanup(dev_data); + kfree(dev_data); + } + + unregister_chrdev_region(rmidev->dev_no, 1); + + if (rmidev_device_class != NULL) { + class_destroy(rmidev_device_class); + rmidev_device_class = NULL; + } + + kfree(rmidev->tmpbuf); + + kfree(rmidev); + rmidev = NULL; + +exit: + complete(&rmidev_remove_complete); + + return; +} + +static struct synaptics_rmi4_exp_fn rmidev_module = { + .fn_type = RMI_DEV, + .init = rmidev_init_device, + .remove = rmidev_remove_device, + .reset = NULL, + .reinit = NULL, + .early_suspend = NULL, + .suspend = NULL, + .resume = NULL, + .late_resume = NULL, + .attn = rmidev_attn, +}; + +static int __init rmidev_module_init(void) +{ + synaptics_rmi4_new_function(&rmidev_module, true); + + return 0; +} + +static void __exit rmidev_module_exit(void) +{ + synaptics_rmi4_new_function(&rmidev_module, false); + + wait_for_completion(&rmidev_remove_complete); + + return; +} + +module_init(rmidev_module_init); +module_exit(rmidev_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX RMI Dev Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_hid_i2c.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_hid_i2c.c new file mode 100644 index 000000000000..df6e357a8ec3 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_hid_i2c.c @@ -0,0 +1,1006 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/types.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define SYN_I2C_RETRY_TIMES 10 + +#define REPORT_ID_GET_BLOB 0x07 +#define REPORT_ID_WRITE 0x09 +#define REPORT_ID_READ_ADDRESS 0x0a +#define REPORT_ID_READ_DATA 0x0b +#define REPORT_ID_SET_RMI_MODE 0x0f + +#define PREFIX_USAGE_PAGE_1BYTE 0x05 +#define PREFIX_USAGE_PAGE_2BYTES 0x06 +#define PREFIX_USAGE 0x09 +#define PREFIX_REPORT_ID 0x85 +#define PREFIX_REPORT_COUNT_1BYTE 0x95 +#define PREFIX_REPORT_COUNT_2BYTES 0x96 + +#define USAGE_GET_BLOB 0xc5 +#define USAGE_WRITE 0x02 +#define USAGE_READ_ADDRESS 0x03 +#define USAGE_READ_DATA 0x04 +#define USAGE_SET_MODE 0x06 + +#define FEATURE_REPORT_TYPE 0x03 + +#define VENDOR_DEFINED_PAGE 0xff00 + +#define BLOB_REPORT_SIZE 256 + +#define RESET_COMMAND 0x01 +#define GET_REPORT_COMMAND 0x02 +#define SET_REPORT_COMMAND 0x03 +#define SET_POWER_COMMAND 0x08 + +#define FINGER_MODE 0x00 +#define RMI_MODE 0x02 + +struct hid_report_info { + unsigned char get_blob_id; + unsigned char write_id; + unsigned char read_addr_id; + unsigned char read_data_id; + unsigned char set_mode_id; + unsigned int blob_size; +}; + +static struct hid_report_info hid_report; + +struct hid_device_descriptor { + unsigned short device_descriptor_length; + unsigned short format_version; + unsigned short report_descriptor_length; + unsigned short report_descriptor_index; + unsigned short input_register_index; + unsigned short input_report_max_length; + unsigned short output_register_index; + unsigned short output_report_max_length; + unsigned short command_register_index; + unsigned short data_register_index; + unsigned short vendor_id; + unsigned short product_id; + unsigned short version_id; + unsigned int reserved; +}; + +static struct hid_device_descriptor hid_dd; + +struct i2c_rw_buffer { + unsigned char *read; + unsigned char *write; + unsigned short read_size; + unsigned short write_size; +}; + +static struct i2c_rw_buffer buffer; + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata) +{ + int retval; + u32 value; + const char *name; + struct property *prop; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + + retval = of_property_read_u32(np, "synaptics,irq-on-state", + &value); + if (retval < 0) + bdata->irq_on_state = 0; + else + bdata->irq_on_state = value; + + retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name); + if (retval < 0) + bdata->pwr_reg_name = NULL; + else + bdata->pwr_reg_name = name; + + retval = of_property_read_string(np, "synaptics,bus-reg-name", &name); + if (retval < 0) + bdata->bus_reg_name = NULL; + else + bdata->bus_reg_name = name; + + prop = of_find_property(np, "synaptics,power-gpio", NULL); + if (prop && prop->length) { + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n", + __func__); + return retval; + } else { + bdata->power_on_state = value; + } + } else { + bdata->power_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n", + __func__); + return retval; + } else { + bdata->power_delay_ms = value; + } + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n", + __func__); + return retval; + } else { + bdata->reset_on_state = value; + } + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n", + __func__); + return retval; + } else { + bdata->reset_active_ms = value; + } + } else { + bdata->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n", + __func__); + return retval; + } else { + bdata->reset_delay_ms = value; + } + } else { + bdata->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,dev-dscrptr-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,dev-dscrptr-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,dev-dscrptr-addr property\n", + __func__); + return retval; + } else { + bdata->device_descriptor_addr = (unsigned short)value; + } + } else { + bdata->device_descriptor_addr = 0; + } + + prop = of_find_property(np, "synaptics,max-y-for-2d", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,max-y-for-2d", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n", + __func__); + return retval; + } else { + bdata->max_y_for_2d = value; + } + } else { + bdata->max_y_for_2d = -1; + } + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,x-flip", NULL); + bdata->x_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,y-flip", NULL); + bdata->y_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,ub-i2c-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ub-i2c-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n", + __func__); + return retval; + } else { + bdata->ub_i2c_addr = (unsigned short)value; + } + } else { + bdata->ub_i2c_addr = -1; + } + + prop = of_find_property(np, "synaptics,cap-button-codes", NULL); + if (prop && prop->length) { + bdata->cap_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->cap_button_map->map) + return -ENOMEM; + bdata->cap_button_map->nbuttons = prop->length / sizeof(u32); + retval = of_property_read_u32_array(np, + "synaptics,cap-button-codes", + bdata->cap_button_map->map, + bdata->cap_button_map->nbuttons); + if (retval < 0) { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + } else { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + + prop = of_find_property(np, "synaptics,vir-button-codes", NULL); + if (prop && prop->length) { + bdata->vir_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->vir_button_map->map) + return -ENOMEM; + bdata->vir_button_map->nbuttons = prop->length / sizeof(u32); + bdata->vir_button_map->nbuttons /= 5; + retval = of_property_read_u32_array(np, + "synaptics,vir-button-codes", + bdata->vir_button_map->map, + bdata->vir_button_map->nbuttons * 5); + if (retval < 0) { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + } else { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + + return 0; +} +#endif + +static int do_i2c_transfer(struct i2c_client *client, struct i2c_msg *msg) +{ + unsigned char retry; + + for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) { + if (i2c_transfer(client->adapter, msg, 1) == 1) + break; + dev_err(&client->dev, + "%s: I2C retry %d\n", + __func__, retry + 1); + msleep(20); + } + + if (retry == SYN_I2C_RETRY_TIMES) { + dev_err(&client->dev, + "%s: I2C transfer over retry limit\n", + __func__); + return -EIO; + } + + return 0; +} + +static int check_buffer(unsigned char **buffer, unsigned short *buffer_size, + unsigned short length) +{ + if (*buffer_size < length) { + if (*buffer_size) + kfree(*buffer); + *buffer = kzalloc(length, GFP_KERNEL); + if (!(*buffer)) + return -ENOMEM; + *buffer_size = length; + } + + return 0; +} + +static int generic_read(struct i2c_client *client, unsigned short length) +{ + int retval; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + } + }; + + check_buffer(&buffer.read, &buffer.read_size, length); + msg[0].buf = buffer.read; + + retval = do_i2c_transfer(client, msg); + + return retval; +} + +static int generic_write(struct i2c_client *client, unsigned short length) +{ + int retval; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = length, + .buf = buffer.write, + } + }; + + retval = do_i2c_transfer(client, msg); + + return retval; +} + +static void traverse_report_descriptor(unsigned int *index) +{ + unsigned char size; + unsigned char *buf = buffer.read; + + size = buf[*index] & MASK_2BIT; + switch (size) { + case 0: /* 0 bytes */ + *index += 1; + break; + case 1: /* 1 byte */ + *index += 2; + break; + case 2: /* 2 bytes */ + *index += 3; + break; + case 3: /* 4 bytes */ + *index += 5; + break; + default: + break; + } + + return; +} + +static void find_blob_size(unsigned int index) +{ + unsigned int ii = index; + unsigned char *buf = buffer.read; + + while (ii < hid_dd.report_descriptor_length) { + if (buf[ii] == PREFIX_REPORT_COUNT_1BYTE) { + hid_report.blob_size = buf[ii + 1]; + return; + } else if (buf[ii] == PREFIX_REPORT_COUNT_2BYTES) { + hid_report.blob_size = buf[ii + 1] | (buf[ii + 2] << 8); + return; + } + traverse_report_descriptor(&ii); + } + + return; +} + +static void find_reports(unsigned int index) +{ + unsigned int ii = index; + unsigned char *buf = buffer.read; + static unsigned int report_id_index; + static unsigned char report_id; + static unsigned short usage_page; + + if (buf[ii] == PREFIX_REPORT_ID) { + report_id = buf[ii + 1]; + report_id_index = ii; + return; + } + + if (buf[ii] == PREFIX_USAGE_PAGE_1BYTE) { + usage_page = buf[ii + 1]; + return; + } else if (buf[ii] == PREFIX_USAGE_PAGE_2BYTES) { + usage_page = buf[ii + 1] | (buf[ii + 2] << 8); + return; + } + + if ((usage_page == VENDOR_DEFINED_PAGE) && (buf[ii] == PREFIX_USAGE)) { + switch (buf[ii + 1]) { + case USAGE_GET_BLOB: + hid_report.get_blob_id = report_id; + find_blob_size(report_id_index); + break; + case USAGE_WRITE: + hid_report.write_id = report_id; + break; + case USAGE_READ_ADDRESS: + hid_report.read_addr_id = report_id; + break; + case USAGE_READ_DATA: + hid_report.read_data_id = report_id; + break; + case USAGE_SET_MODE: + hid_report.set_mode_id = report_id; + break; + default: + break; + } + } + + return; +} + +static int parse_report_descriptor(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned int ii = 0; + unsigned char *buf; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + buffer.write[0] = hid_dd.report_descriptor_index & MASK_8BIT; + buffer.write[1] = hid_dd.report_descriptor_index >> 8; + retval = generic_write(i2c, 2); + if (retval < 0) + return retval; + retval = generic_read(i2c, hid_dd.report_descriptor_length); + if (retval < 0) + return retval; + + buf = buffer.read; + + hid_report.get_blob_id = REPORT_ID_GET_BLOB; + hid_report.write_id = REPORT_ID_WRITE; + hid_report.read_addr_id = REPORT_ID_READ_ADDRESS; + hid_report.read_data_id = REPORT_ID_READ_DATA; + hid_report.set_mode_id = REPORT_ID_SET_RMI_MODE; + hid_report.blob_size = BLOB_REPORT_SIZE; + + while (ii < hid_dd.report_descriptor_length) { + find_reports(ii); + traverse_report_descriptor(&ii); + } + + return 0; +} + +static int switch_to_rmi(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, 11); + + /* set rmi mode */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = (FEATURE_REPORT_TYPE << 4) | hid_report.set_mode_id; + buffer.write[3] = SET_REPORT_COMMAND; + buffer.write[4] = hid_report.set_mode_id; + buffer.write[5] = hid_dd.data_register_index & MASK_8BIT; + buffer.write[6] = hid_dd.data_register_index >> 8; + buffer.write[7] = 0x04; + buffer.write[8] = 0x00; + buffer.write[9] = hid_report.set_mode_id; + buffer.write[10] = RMI_MODE; + + retval = generic_write(i2c, 11); + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int check_report_mode(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned short report_size; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, 7); + + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = (FEATURE_REPORT_TYPE << 4) | hid_report.set_mode_id; + buffer.write[3] = GET_REPORT_COMMAND; + buffer.write[4] = hid_report.set_mode_id; + buffer.write[5] = hid_dd.data_register_index & MASK_8BIT; + buffer.write[6] = hid_dd.data_register_index >> 8; + + retval = generic_write(i2c, 7); + if (retval < 0) + goto exit; + + retval = generic_read(i2c, 2); + if (retval < 0) + goto exit; + + report_size = (buffer.read[1] << 8) | buffer.read[0]; + + retval = generic_write(i2c, 7); + if (retval < 0) + goto exit; + + retval = generic_read(i2c, report_size); + if (retval < 0) + goto exit; + + retval = buffer.read[3]; + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Report mode = %d\n", + __func__, retval); + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + return retval; +} + +static int hid_i2c_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + const struct synaptics_dsx_board_data *bdata = + rmi4_data->hw_if->board_data; + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, 6); + + /* read device descriptor */ + buffer.write[0] = bdata->device_descriptor_addr & MASK_8BIT; + buffer.write[1] = bdata->device_descriptor_addr >> 8; + retval = generic_write(i2c, 2); + if (retval < 0) + goto exit; + retval = generic_read(i2c, sizeof(hid_dd)); + if (retval < 0) + goto exit; + retval = secure_memcpy((unsigned char *)&hid_dd, + sizeof(struct hid_device_descriptor), + buffer.read, + buffer.read_size, + sizeof(hid_dd)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy device descriptor data\n", + __func__); + goto exit; + } + + retval = parse_report_descriptor(rmi4_data); + if (retval < 0) + goto exit; + + /* set power */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = 0x00; + buffer.write[3] = SET_POWER_COMMAND; + retval = generic_write(i2c, 4); + if (retval < 0) + goto exit; + + /* reset */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = 0x00; + buffer.write[3] = RESET_COMMAND; + retval = generic_write(i2c, 4); + if (retval < 0) + goto exit; + + while (gpio_get_value(bdata->irq_gpio)) + msleep(20); + + retval = generic_read(i2c, hid_dd.input_report_max_length); + if (retval < 0) + goto exit; + + /* get blob */ + buffer.write[0] = hid_dd.command_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.command_register_index >> 8; + buffer.write[2] = (FEATURE_REPORT_TYPE << 4) | hid_report.get_blob_id; + buffer.write[3] = 0x02; + buffer.write[4] = hid_dd.data_register_index & MASK_8BIT; + buffer.write[5] = hid_dd.data_register_index >> 8; + + retval = generic_write(i2c, 6); + if (retval < 0) + goto exit; + + msleep(20); + + retval = generic_read(i2c, hid_report.blob_size + 3); + if (retval < 0) + goto exit; + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to initialize HID/I2C interface\n", + __func__); + return retval; + } + + retval = switch_to_rmi(rmi4_data); + + return retval; +} + +static int synaptics_rmi4_i2c_read(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned char retry; + unsigned char recover = 1; + unsigned short report_length; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[] = { + { + .addr = i2c->addr, + .flags = 0, + .len = hid_dd.output_report_max_length + 2, + }, + { + .addr = i2c->addr, + .flags = I2C_M_RD, + .len = length + 4, + }, + }; + +recover: + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, + hid_dd.output_report_max_length + 2); + msg[0].buf = buffer.write; + buffer.write[0] = hid_dd.output_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.output_register_index >> 8; + buffer.write[2] = hid_dd.output_report_max_length & MASK_8BIT; + buffer.write[3] = hid_dd.output_report_max_length >> 8; + buffer.write[4] = hid_report.read_addr_id; + buffer.write[5] = 0x00; + buffer.write[6] = addr & MASK_8BIT; + buffer.write[7] = addr >> 8; + buffer.write[8] = length & MASK_8BIT; + buffer.write[9] = length >> 8; + + check_buffer(&buffer.read, &buffer.read_size, length + 4); + msg[1].buf = buffer.read; + + retval = do_i2c_transfer(i2c, &msg[0]); + if (retval != 0) + goto exit; + + retry = 0; + do { + retval = do_i2c_transfer(i2c, &msg[1]); + if (retval == 0) + retval = length; + else + goto exit; + + report_length = (buffer.read[1] << 8) | buffer.read[0]; + if (report_length == hid_dd.input_report_max_length) { + retval = secure_memcpy(&data[0], length, + &buffer.read[4], buffer.read_size - 4, + length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + } else { + retval = length; + } + goto exit; + } + + msleep(20); + retry++; + } while (retry < SYN_I2C_RETRY_TIMES); + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to receive read report\n", + __func__); + retval = -EIO; + +exit: + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + if ((retval != length) && (recover == 1)) { + recover = 0; + if (check_report_mode(rmi4_data) != RMI_MODE) { + retval = hid_i2c_init(rmi4_data); + if (retval == 0) + goto recover; + } + } + + return retval; +} + +static int synaptics_rmi4_i2c_write(struct synaptics_rmi4_data *rmi4_data, + unsigned short addr, unsigned char *data, unsigned short length) +{ + int retval; + unsigned char recover = 1; + unsigned char msg_length; + struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent); + struct i2c_msg msg[] = { + { + .addr = i2c->addr, + .flags = 0, + } + }; + + if ((length + 10) < (hid_dd.output_report_max_length + 2)) + msg_length = hid_dd.output_report_max_length + 2; + else + msg_length = length + 10; + +recover: + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + check_buffer(&buffer.write, &buffer.write_size, msg_length); + msg[0].len = msg_length; + msg[0].buf = buffer.write; + buffer.write[0] = hid_dd.output_register_index & MASK_8BIT; + buffer.write[1] = hid_dd.output_register_index >> 8; + buffer.write[2] = hid_dd.output_report_max_length & MASK_8BIT; + buffer.write[3] = hid_dd.output_report_max_length >> 8; + buffer.write[4] = hid_report.write_id; + buffer.write[5] = 0x00; + buffer.write[6] = addr & MASK_8BIT; + buffer.write[7] = addr >> 8; + buffer.write[8] = length & MASK_8BIT; + buffer.write[9] = length >> 8; + retval = secure_memcpy(&buffer.write[10], buffer.write_size - 10, + &data[0], length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + } else { + retval = do_i2c_transfer(i2c, msg); + if (retval == 0) + retval = length; + } + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + + if ((retval != length) && (recover == 1)) { + recover = 0; + if (check_report_mode(rmi4_data) != RMI_MODE) { + retval = hid_i2c_init(rmi4_data); + if (retval == 0) + goto recover; + } + } + + return retval; +} + +static struct synaptics_dsx_bus_access bus_access = { + .type = BUS_I2C, + .read = synaptics_rmi4_i2c_read, + .write = synaptics_rmi4_i2c_write, +}; + +static struct synaptics_dsx_hw_interface hw_if; + +static struct platform_device *synaptics_dsx_i2c_device; + +static void synaptics_rmi4_i2c_dev_release(struct device *dev) +{ + kfree(synaptics_dsx_i2c_device); + + return; +} + +static int synaptics_rmi4_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int retval; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "%s: SMBus byte data commands not supported by host\n", + __func__); + return -EIO; + } + + synaptics_dsx_i2c_device = kzalloc( + sizeof(struct platform_device), + GFP_KERNEL); + if (!synaptics_dsx_i2c_device) { + dev_err(&client->dev, + "%s: Failed to allocate memory for synaptics_dsx_i2c_device\n", + __func__); + return -ENOMEM; + } + +#ifdef CONFIG_OF + if (client->dev.of_node) { + hw_if.board_data = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_board_data), + GFP_KERNEL); + if (!hw_if.board_data) { + dev_err(&client->dev, + "%s: Failed to allocate memory for board data\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->cap_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->cap_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for 0D button map\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->vir_button_map = devm_kzalloc(&client->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->vir_button_map) { + dev_err(&client->dev, + "%s: Failed to allocate memory for virtual button map\n", + __func__); + return -ENOMEM; + } + parse_dt(&client->dev, hw_if.board_data); + } +#else + hw_if.board_data = client->dev.platform_data; +#endif + + hw_if.bus_access = &bus_access; + hw_if.bl_hw_init = switch_to_rmi; + hw_if.ui_hw_init = hid_i2c_init; + + synaptics_dsx_i2c_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_i2c_device->id = 0; + synaptics_dsx_i2c_device->num_resources = 0; + synaptics_dsx_i2c_device->dev.parent = &client->dev; + synaptics_dsx_i2c_device->dev.platform_data = &hw_if; + synaptics_dsx_i2c_device->dev.release = synaptics_rmi4_i2c_dev_release; + + retval = platform_device_register(synaptics_dsx_i2c_device); + if (retval) { + dev_err(&client->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_i2c_remove(struct i2c_client *client) +{ + if (buffer.read_size) + kfree(buffer.read); + + if (buffer.write_size) + kfree(buffer.write); + + platform_device_unregister(synaptics_dsx_i2c_device); + + return 0; +} + +static const struct i2c_device_id synaptics_rmi4_id_table[] = { + {I2C_DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table); + +#ifdef CONFIG_OF +static struct of_device_id synaptics_rmi4_of_match_table[] = { + { + .compatible = "synaptics,dsx-rmi-hid-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table); +#else +#define synaptics_rmi4_of_match_table NULL +#endif + +static struct i2c_driver synaptics_rmi4_i2c_driver = { + .driver = { + .name = I2C_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = synaptics_rmi4_of_match_table, + }, + .probe = synaptics_rmi4_i2c_probe, + .remove = synaptics_rmi4_i2c_remove, + .id_table = synaptics_rmi4_id_table, +}; + +int synaptics_rmi4_bus_init(void) +{ + return i2c_add_driver(&synaptics_rmi4_i2c_driver); +} +EXPORT_SYMBOL(synaptics_rmi4_bus_init); + +void synaptics_rmi4_bus_exit(void) +{ + i2c_del_driver(&synaptics_rmi4_i2c_driver); + + 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_2.6/synaptics_dsx_spi.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_spi.c new file mode 100644 index 000000000000..d5d9c3cbf633 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_spi.c @@ -0,0 +1,634 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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/of_gpio.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 + +#ifdef CONFIG_OF +static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata) +{ + int retval; + u32 value; + const char *name; + struct property *prop; + struct device_node *np = dev->of_node; + + bdata->irq_gpio = of_get_named_gpio_flags(np, + "synaptics,irq-gpio", 0, + (enum of_gpio_flags *)&bdata->irq_flags); + + retval = of_property_read_u32(np, "synaptics,irq-on-state", + &value); + if (retval < 0) + bdata->irq_on_state = 0; + else + bdata->irq_on_state = value; + + retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name); + if (retval < 0) + bdata->pwr_reg_name = NULL; + else + bdata->pwr_reg_name = name; + + retval = of_property_read_string(np, "synaptics,bus-reg-name", &name); + if (retval < 0) + bdata->bus_reg_name = NULL; + else + bdata->bus_reg_name = name; + + prop = of_find_property(np, "synaptics,power-gpio", NULL); + if (prop && prop->length) { + bdata->power_gpio = of_get_named_gpio_flags(np, + "synaptics,power-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,power-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n", + __func__); + return retval; + } else { + bdata->power_on_state = value; + } + } else { + bdata->power_gpio = -1; + } + + prop = of_find_property(np, "synaptics,power-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,power-delay-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n", + __func__); + return retval; + } else { + bdata->power_delay_ms = value; + } + } else { + bdata->power_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,reset-gpio", NULL); + if (prop && prop->length) { + bdata->reset_gpio = of_get_named_gpio_flags(np, + "synaptics,reset-gpio", 0, NULL); + retval = of_property_read_u32(np, "synaptics,reset-on-state", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n", + __func__); + return retval; + } else { + bdata->reset_on_state = value; + } + retval = of_property_read_u32(np, "synaptics,reset-active-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n", + __func__); + return retval; + } else { + bdata->reset_active_ms = value; + } + } else { + bdata->reset_gpio = -1; + } + + prop = of_find_property(np, "synaptics,reset-delay-ms", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,reset-delay-ms", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n", + __func__); + return retval; + } else { + bdata->reset_delay_ms = value; + } + } else { + bdata->reset_delay_ms = 0; + } + + prop = of_find_property(np, "synaptics,byte-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,byte-delay-us", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,byte-delay-us property\n", + __func__); + return retval; + } else { + bdata->byte_delay_us = value; + } + } else { + bdata->byte_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,block-delay-us", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,block-delay-us", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,block-delay-us property\n", + __func__); + return retval; + } else { + bdata->block_delay_us = value; + } + } else { + bdata->block_delay_us = 0; + } + + prop = of_find_property(np, "synaptics,max-y-for-2d", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,max-y-for-2d", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n", + __func__); + return retval; + } else { + bdata->max_y_for_2d = value; + } + } else { + bdata->max_y_for_2d = -1; + } + + prop = of_find_property(np, "synaptics,swap-axes", NULL); + bdata->swap_axes = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,x-flip", NULL); + bdata->x_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,y-flip", NULL); + bdata->y_flip = prop > 0 ? true : false; + + prop = of_find_property(np, "synaptics,ub-i2c-addr", NULL); + if (prop && prop->length) { + retval = of_property_read_u32(np, "synaptics,ub-i2c-addr", + &value); + if (retval < 0) { + dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n", + __func__); + return retval; + } else { + bdata->ub_i2c_addr = (unsigned short)value; + } + } else { + bdata->ub_i2c_addr = -1; + } + + prop = of_find_property(np, "synaptics,cap-button-codes", NULL); + if (prop && prop->length) { + bdata->cap_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->cap_button_map->map) + return -ENOMEM; + bdata->cap_button_map->nbuttons = prop->length / sizeof(u32); + retval = of_property_read_u32_array(np, + "synaptics,cap-button-codes", + bdata->cap_button_map->map, + bdata->cap_button_map->nbuttons); + if (retval < 0) { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + } else { + bdata->cap_button_map->nbuttons = 0; + bdata->cap_button_map->map = NULL; + } + + prop = of_find_property(np, "synaptics,vir-button-codes", NULL); + if (prop && prop->length) { + bdata->vir_button_map->map = devm_kzalloc(dev, + prop->length, + GFP_KERNEL); + if (!bdata->vir_button_map->map) + return -ENOMEM; + bdata->vir_button_map->nbuttons = prop->length / sizeof(u32); + bdata->vir_button_map->nbuttons /= 5; + retval = of_property_read_u32_array(np, + "synaptics,vir-button-codes", + bdata->vir_button_map->map, + bdata->vir_button_map->nbuttons * 5); + if (retval < 0) { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + } else { + bdata->vir_button_map->nbuttons = 0; + bdata->vir_button_map->map = NULL; + } + + return 0; +} +#endif + +static int synaptics_rmi4_spi_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) { + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + 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 = secure_memcpy(data, length, rxbuf, length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + } else { + retval = length; + } + } else { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete SPI transfer, error = %d\n", + __func__, retval); + } + + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + +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; + retval = secure_memcpy(&txbuf[ADDRESS_WORD_LEN], + xfer_count - ADDRESS_WORD_LEN, data, length, length); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy data\n", + __func__); + goto exit; + } + + mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex); + + retval = synaptics_rmi4_spi_set_page(rmi4_data, addr); + if (retval != PAGE_SELECT_LEN) { + mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex); + 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; + } + +#ifdef CONFIG_OF + if (spi->dev.of_node) { + hw_if.board_data = devm_kzalloc(&spi->dev, + sizeof(struct synaptics_dsx_board_data), + GFP_KERNEL); + if (!hw_if.board_data) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for board data\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->cap_button_map = devm_kzalloc(&spi->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->cap_button_map) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for 0D button map\n", + __func__); + return -ENOMEM; + } + hw_if.board_data->vir_button_map = devm_kzalloc(&spi->dev, + sizeof(struct synaptics_dsx_button_map), + GFP_KERNEL); + if (!hw_if.board_data->vir_button_map) { + dev_err(&spi->dev, + "%s: Failed to allocate memory for virtual button map\n", + __func__); + return -ENOMEM; + } + parse_dt(&spi->dev, hw_if.board_data); + } +#else + hw_if.board_data = spi->dev.platform_data; +#endif + + hw_if.bus_access = &bus_access; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_3; + + retval = spi_setup(spi); + if (retval < 0) { + dev_err(&spi->dev, + "%s: Failed to perform SPI setup\n", + __func__); + return retval; + } + + synaptics_dsx_spi_device->name = PLATFORM_DRIVER_NAME; + synaptics_dsx_spi_device->id = 0; + synaptics_dsx_spi_device->num_resources = 0; + synaptics_dsx_spi_device->dev.parent = &spi->dev; + synaptics_dsx_spi_device->dev.platform_data = &hw_if; + synaptics_dsx_spi_device->dev.release = synaptics_rmi4_spi_dev_release; + + retval = platform_device_register(synaptics_dsx_spi_device); + if (retval) { + dev_err(&spi->dev, + "%s: Failed to register platform device\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int synaptics_rmi4_spi_remove(struct spi_device *spi) +{ + platform_device_unregister(synaptics_dsx_spi_device); + + return 0; +} + +#ifdef CONFIG_OF +static struct of_device_id synaptics_rmi4_of_match_table[] = { + { + .compatible = "synaptics,dsx-spi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table); +#else +#define synaptics_rmi4_of_match_table NULL +#endif + +static struct spi_driver synaptics_rmi4_spi_driver = { + .driver = { + .name = SPI_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = synaptics_rmi4_of_match_table, + }, + .probe = synaptics_rmi4_spi_probe, + .remove = synaptics_rmi4_spi_remove, +}; + + +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"); diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_test_reporting.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_test_reporting.c new file mode 100644 index 000000000000..6ed7bd62a6a4 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_test_reporting.c @@ -0,0 +1,4172 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <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/ctype.h> +#include <linux/hrtimer.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define SYSFS_FOLDER_NAME "f54" + +#define GET_REPORT_TIMEOUT_S 3 +#define CALIBRATION_TIMEOUT_S 10 +#define COMMAND_TIMEOUT_100MS 20 + +#define NO_SLEEP_OFF (0 << 2) +#define NO_SLEEP_ON (1 << 2) + +#define STATUS_IDLE 0 +#define STATUS_BUSY 1 +#define STATUS_ERROR 2 + +#define REPORT_INDEX_OFFSET 1 +#define REPORT_DATA_OFFSET 3 + +#define SENSOR_RX_MAPPING_OFFSET 1 +#define SENSOR_TX_MAPPING_OFFSET 2 + +#define COMMAND_GET_REPORT 1 +#define COMMAND_FORCE_CAL 2 +#define COMMAND_FORCE_UPDATE 4 + +#define CONTROL_NO_AUTO_CAL 1 + +#define CONTROL_0_SIZE 1 +#define CONTROL_1_SIZE 1 +#define CONTROL_2_SIZE 2 +#define CONTROL_3_SIZE 1 +#define CONTROL_4_6_SIZE 3 +#define CONTROL_7_SIZE 1 +#define CONTROL_8_9_SIZE 3 +#define CONTROL_10_SIZE 1 +#define CONTROL_11_SIZE 2 +#define CONTROL_12_13_SIZE 2 +#define CONTROL_14_SIZE 1 +#define CONTROL_15_SIZE 1 +#define CONTROL_16_SIZE 1 +#define CONTROL_17_SIZE 1 +#define CONTROL_18_SIZE 1 +#define CONTROL_19_SIZE 1 +#define CONTROL_20_SIZE 1 +#define CONTROL_21_SIZE 2 +#define CONTROL_22_26_SIZE 7 +#define CONTROL_27_SIZE 1 +#define CONTROL_28_SIZE 2 +#define CONTROL_29_SIZE 1 +#define CONTROL_30_SIZE 1 +#define CONTROL_31_SIZE 1 +#define CONTROL_32_35_SIZE 8 +#define CONTROL_36_SIZE 1 +#define CONTROL_37_SIZE 1 +#define CONTROL_38_SIZE 1 +#define CONTROL_39_SIZE 1 +#define CONTROL_40_SIZE 1 +#define CONTROL_41_SIZE 1 +#define CONTROL_42_SIZE 2 +#define CONTROL_43_54_SIZE 13 +#define CONTROL_55_56_SIZE 2 +#define CONTROL_57_SIZE 1 +#define CONTROL_58_SIZE 1 +#define CONTROL_59_SIZE 2 +#define CONTROL_60_62_SIZE 3 +#define CONTROL_63_SIZE 1 +#define CONTROL_64_67_SIZE 4 +#define CONTROL_68_73_SIZE 8 +#define CONTROL_74_SIZE 2 +#define CONTROL_75_SIZE 1 +#define CONTROL_76_SIZE 1 +#define CONTROL_77_78_SIZE 2 +#define CONTROL_79_83_SIZE 5 +#define CONTROL_84_85_SIZE 2 +#define CONTROL_86_SIZE 1 +#define CONTROL_87_SIZE 1 +#define CONTROL_88_SIZE 1 +#define CONTROL_89_SIZE 1 +#define CONTROL_90_SIZE 1 +#define CONTROL_91_SIZE 1 +#define CONTROL_92_SIZE 1 +#define CONTROL_93_SIZE 1 +#define CONTROL_94_SIZE 1 +#define CONTROL_95_SIZE 1 +#define CONTROL_96_SIZE 1 +#define CONTROL_97_SIZE 1 +#define CONTROL_98_SIZE 1 +#define CONTROL_99_SIZE 1 +#define CONTROL_100_SIZE 1 +#define CONTROL_101_SIZE 1 +#define CONTROL_102_SIZE 1 +#define CONTROL_103_SIZE 1 +#define CONTROL_104_SIZE 1 +#define CONTROL_105_SIZE 1 +#define CONTROL_106_SIZE 1 +#define CONTROL_107_SIZE 1 +#define CONTROL_108_SIZE 1 +#define CONTROL_109_SIZE 1 +#define CONTROL_110_SIZE 1 +#define CONTROL_111_SIZE 1 +#define CONTROL_112_SIZE 1 +#define CONTROL_113_SIZE 1 +#define CONTROL_114_SIZE 1 +#define CONTROL_115_SIZE 1 +#define CONTROL_116_SIZE 1 +#define CONTROL_117_SIZE 1 +#define CONTROL_118_SIZE 1 +#define CONTROL_119_SIZE 1 +#define CONTROL_120_SIZE 1 +#define CONTROL_121_SIZE 1 +#define CONTROL_122_SIZE 1 +#define CONTROL_123_SIZE 1 +#define CONTROL_124_SIZE 1 +#define CONTROL_125_SIZE 1 +#define CONTROL_126_SIZE 1 +#define CONTROL_127_SIZE 1 +#define CONTROL_128_SIZE 1 +#define CONTROL_129_SIZE 1 +#define CONTROL_130_SIZE 1 +#define CONTROL_131_SIZE 1 +#define CONTROL_132_SIZE 1 +#define CONTROL_133_SIZE 1 +#define CONTROL_134_SIZE 1 +#define CONTROL_135_SIZE 1 +#define CONTROL_136_SIZE 1 +#define CONTROL_137_SIZE 1 +#define CONTROL_138_SIZE 1 +#define CONTROL_139_SIZE 1 +#define CONTROL_140_SIZE 1 +#define CONTROL_141_SIZE 1 +#define CONTROL_142_SIZE 1 +#define CONTROL_143_SIZE 1 +#define CONTROL_144_SIZE 1 +#define CONTROL_145_SIZE 1 +#define CONTROL_146_SIZE 1 +#define CONTROL_147_SIZE 1 +#define CONTROL_148_SIZE 1 +#define CONTROL_149_SIZE 1 +#define CONTROL_163_SIZE 1 +#define CONTROL_165_SIZE 1 +#define CONTROL_167_SIZE 1 +#define CONTROL_176_SIZE 1 +#define CONTROL_179_SIZE 1 +#define CONTROL_188_SIZE 1 + +#define HIGH_RESISTANCE_DATA_SIZE 6 +#define FULL_RAW_CAP_MIN_MAX_DATA_SIZE 4 +#define TRX_OPEN_SHORT_DATA_SIZE 7 + +#define concat(a, b) a##b + +#define attrify(propname) (&dev_attr_##propname.attr) + +#define show_prototype(propname)\ +static ssize_t concat(test_sysfs, _##propname##_show)(\ + struct device *dev,\ + struct device_attribute *attr,\ + char *buf);\ +\ +static struct device_attribute dev_attr_##propname =\ + __ATTR(propname, S_IRUGO,\ + concat(test_sysfs, _##propname##_show),\ + synaptics_rmi4_store_error); + +#define store_prototype(propname)\ +static ssize_t concat(test_sysfs, _##propname##_store)(\ + struct device *dev,\ + struct device_attribute *attr,\ + const char *buf, size_t count);\ +\ +static struct device_attribute dev_attr_##propname =\ + __ATTR(propname, S_IWUGO,\ + synaptics_rmi4_show_error,\ + concat(test_sysfs, _##propname##_store)); + +#define show_store_prototype(propname)\ +static ssize_t concat(test_sysfs, _##propname##_show)(\ + struct device *dev,\ + struct device_attribute *attr,\ + char *buf);\ +\ +static ssize_t concat(test_sysfs, _##propname##_store)(\ + struct device *dev,\ + struct device_attribute *attr,\ + const char *buf, size_t count);\ +\ +static struct device_attribute dev_attr_##propname =\ + __ATTR(propname, (S_IRUGO | S_IWUGO),\ + concat(test_sysfs, _##propname##_show),\ + concat(test_sysfs, _##propname##_store)); + +#define disable_cbc(ctrl_num)\ +do {\ + retval = synaptics_rmi4_reg_read(rmi4_data,\ + f54->control.ctrl_num->address,\ + f54->control.ctrl_num->data,\ + sizeof(f54->control.ctrl_num->data));\ + if (retval < 0) {\ + dev_err(rmi4_data->pdev->dev.parent,\ + "%s: Failed to disable CBC (" #ctrl_num ")\n",\ + __func__);\ + return retval;\ + } \ + f54->control.ctrl_num->cbc_tx_carrier_selection = 0;\ + retval = synaptics_rmi4_reg_write(rmi4_data,\ + f54->control.ctrl_num->address,\ + f54->control.ctrl_num->data,\ + sizeof(f54->control.ctrl_num->data));\ + if (retval < 0) {\ + dev_err(rmi4_data->pdev->dev.parent,\ + "%s: Failed to disable CBC (" #ctrl_num ")\n",\ + __func__);\ + return retval;\ + } \ +} while (0) + +enum f54_report_types { + F54_8BIT_IMAGE = 1, + F54_16BIT_IMAGE = 2, + F54_RAW_16BIT_IMAGE = 3, + F54_HIGH_RESISTANCE = 4, + F54_TX_TO_TX_SHORTS = 5, + F54_RX_TO_RX_SHORTS_1 = 7, + F54_TRUE_BASELINE = 9, + F54_FULL_RAW_CAP_MIN_MAX = 13, + F54_RX_OPENS_1 = 14, + F54_TX_OPENS = 15, + F54_TX_TO_GND_SHORTS = 16, + F54_RX_TO_RX_SHORTS_2 = 17, + F54_RX_OPENS_2 = 18, + F54_FULL_RAW_CAP = 19, + F54_FULL_RAW_CAP_NO_RX_COUPLING = 20, + F54_SENSOR_SPEED = 22, + F54_ADC_RANGE = 23, + F54_TRX_OPENS = 24, + F54_TRX_TO_GND_SHORTS = 25, + F54_TRX_SHORTS = 26, + F54_ABS_RAW_CAP = 38, + F54_ABS_DELTA_CAP = 40, + F54_ABS_HYBRID_DELTA_CAP = 59, + F54_ABS_HYBRID_RAW_CAP = 63, + F54_AMP_FULL_RAW_CAP = 78, + F54_AMP_RAW_ADC = 83, + INVALID_REPORT_TYPE = -1, +}; + +enum f54_afe_cal { + F54_AFE_CAL, + F54_AFE_IS_CAL, +}; + +struct f54_query { + union { + struct { + /* query 0 */ + unsigned char num_of_rx_electrodes; + + /* query 1 */ + unsigned char num_of_tx_electrodes; + + /* query 2 */ + unsigned char f54_query2_b0__1:2; + unsigned char has_baseline:1; + unsigned char has_image8:1; + unsigned char f54_query2_b4__5:2; + unsigned char has_image16:1; + unsigned char f54_query2_b7:1; + + /* queries 3.0 and 3.1 */ + unsigned short clock_rate; + + /* query 4 */ + unsigned char touch_controller_family; + + /* query 5 */ + unsigned char has_pixel_touch_threshold_adjustment:1; + unsigned char f54_query5_b1__7:7; + + /* query 6 */ + unsigned char has_sensor_assignment:1; + unsigned char has_interference_metric:1; + unsigned char has_sense_frequency_control:1; + unsigned char has_firmware_noise_mitigation:1; + unsigned char has_ctrl11:1; + unsigned char has_two_byte_report_rate:1; + unsigned char has_one_byte_report_rate:1; + unsigned char has_relaxation_control:1; + + /* query 7 */ + unsigned char curve_compensation_mode:2; + unsigned char f54_query7_b2__7:6; + + /* query 8 */ + unsigned char f54_query8_b0:1; + unsigned char has_iir_filter:1; + unsigned char has_cmn_removal:1; + unsigned char has_cmn_maximum:1; + unsigned char has_touch_hysteresis:1; + unsigned char has_edge_compensation:1; + unsigned char has_per_frequency_noise_control:1; + unsigned char has_enhanced_stretch:1; + + /* query 9 */ + unsigned char has_force_fast_relaxation:1; + unsigned char has_multi_metric_state_machine:1; + unsigned char has_signal_clarity:1; + unsigned char has_variance_metric:1; + unsigned char has_0d_relaxation_control:1; + unsigned char has_0d_acquisition_control:1; + unsigned char has_status:1; + unsigned char has_slew_metric:1; + + /* query 10 */ + unsigned char has_h_blank:1; + unsigned char has_v_blank:1; + unsigned char has_long_h_blank:1; + unsigned char has_startup_fast_relaxation:1; + unsigned char has_esd_control:1; + unsigned char has_noise_mitigation2:1; + unsigned char has_noise_state:1; + unsigned char has_energy_ratio_relaxation:1; + + /* query 11 */ + unsigned char has_excessive_noise_reporting:1; + unsigned char has_slew_option:1; + unsigned char has_two_overhead_bursts:1; + unsigned char has_query13:1; + unsigned char has_one_overhead_burst:1; + unsigned char f54_query11_b5:1; + unsigned char has_ctrl88:1; + unsigned char has_query15:1; + + /* query 12 */ + unsigned char number_of_sensing_frequencies:4; + unsigned char f54_query12_b4__7:4; + } __packed; + unsigned char data[14]; + }; +}; + +struct f54_query_13 { + union { + struct { + unsigned char has_ctrl86:1; + unsigned char has_ctrl87:1; + unsigned char has_ctrl87_sub0:1; + unsigned char has_ctrl87_sub1:1; + unsigned char has_ctrl87_sub2:1; + unsigned char has_cidim:1; + unsigned char has_noise_mitigation_enhancement:1; + unsigned char has_rail_im:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_15 { + union { + struct { + unsigned char has_ctrl90:1; + unsigned char has_transmit_strength:1; + unsigned char has_ctrl87_sub3:1; + unsigned char has_query16:1; + unsigned char has_query20:1; + unsigned char has_query21:1; + unsigned char has_query22:1; + unsigned char has_query25:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_16 { + union { + struct { + unsigned char has_query17:1; + unsigned char has_data17:1; + unsigned char has_ctrl92:1; + unsigned char has_ctrl93:1; + unsigned char has_ctrl94_query18:1; + unsigned char has_ctrl95_query19:1; + unsigned char has_ctrl99:1; + unsigned char has_ctrl100:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_21 { + union { + struct { + unsigned char has_abs_rx:1; + unsigned char has_abs_tx:1; + unsigned char has_ctrl91:1; + unsigned char has_ctrl96:1; + unsigned char has_ctrl97:1; + unsigned char has_ctrl98:1; + unsigned char has_data19:1; + unsigned char has_query24_data18:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_22 { + union { + struct { + unsigned char has_packed_image:1; + unsigned char has_ctrl101:1; + unsigned char has_dynamic_sense_display_ratio:1; + unsigned char has_query23:1; + unsigned char has_ctrl103_query26:1; + unsigned char has_ctrl104:1; + unsigned char has_ctrl105:1; + unsigned char has_query28:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_23 { + union { + struct { + unsigned char has_ctrl102:1; + unsigned char has_ctrl102_sub1:1; + unsigned char has_ctrl102_sub2:1; + unsigned char has_ctrl102_sub4:1; + unsigned char has_ctrl102_sub5:1; + unsigned char has_ctrl102_sub9:1; + unsigned char has_ctrl102_sub10:1; + unsigned char has_ctrl102_sub11:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_25 { + union { + struct { + unsigned char has_ctrl106:1; + unsigned char has_ctrl102_sub12:1; + unsigned char has_ctrl107:1; + unsigned char has_ctrl108:1; + unsigned char has_ctrl109:1; + unsigned char has_data20:1; + unsigned char f54_query25_b6:1; + unsigned char has_query27:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_27 { + union { + struct { + unsigned char has_ctrl110:1; + unsigned char has_data21:1; + unsigned char has_ctrl111:1; + unsigned char has_ctrl112:1; + unsigned char has_ctrl113:1; + unsigned char has_data22:1; + unsigned char has_ctrl114:1; + unsigned char has_query29:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_29 { + union { + struct { + unsigned char has_ctrl115:1; + unsigned char has_ground_ring_options:1; + unsigned char has_lost_bursts_tuning:1; + unsigned char has_aux_exvcom2_select:1; + unsigned char has_ctrl116:1; + unsigned char has_data23:1; + unsigned char has_ctrl117:1; + unsigned char has_query30:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_30 { + union { + struct { + unsigned char has_ctrl118:1; + unsigned char has_ctrl119:1; + unsigned char has_ctrl120:1; + unsigned char has_ctrl121:1; + unsigned char has_ctrl122_query31:1; + unsigned char has_ctrl123:1; + unsigned char f54_query30_b6:1; + unsigned char has_query32:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_32 { + union { + struct { + unsigned char has_ctrl125:1; + unsigned char has_ctrl126:1; + unsigned char has_ctrl127:1; + unsigned char has_abs_charge_pump_disable:1; + unsigned char has_query33:1; + unsigned char has_data24:1; + unsigned char has_query34:1; + unsigned char has_query35:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_33 { + union { + struct { + unsigned char f54_query33_b0:1; + unsigned char f54_query33_b1:1; + unsigned char f54_query33_b2:1; + unsigned char f54_query33_b3:1; + unsigned char has_ctrl132:1; + unsigned char has_ctrl133:1; + unsigned char has_ctrl134:1; + unsigned char has_query36:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_35 { + union { + struct { + unsigned char has_data25:1; + unsigned char f54_query35_b1:1; + unsigned char f54_query35_b2:1; + unsigned char has_ctrl137:1; + unsigned char has_ctrl138:1; + unsigned char has_ctrl139:1; + unsigned char has_data26:1; + unsigned char has_ctrl140:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_36 { + union { + struct { + unsigned char f54_query36_b0:1; + unsigned char has_ctrl142:1; + unsigned char has_query37:1; + unsigned char has_ctrl143:1; + unsigned char has_ctrl144:1; + unsigned char has_ctrl145:1; + unsigned char has_ctrl146:1; + unsigned char has_query38:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_38 { + union { + struct { + unsigned char has_ctrl147:1; + unsigned char has_ctrl148:1; + unsigned char has_ctrl149:1; + unsigned char f54_query38_b3__6:4; + unsigned char has_query39:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_39 { + union { + struct { + unsigned char f54_query39_b0__6:7; + unsigned char has_query40:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_40 { + union { + struct { + unsigned char f54_query40_b0:1; + unsigned char has_ctrl163_query41:1; + unsigned char f54_query40_b2:1; + unsigned char has_ctrl165_query42:1; + unsigned char f54_query40_b4:1; + unsigned char has_ctrl167:1; + unsigned char f54_query40_b6:1; + unsigned char has_query43:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_43 { + union { + struct { + unsigned char f54_query43_b0__6:7; + unsigned char has_query46:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_46 { + union { + struct { + unsigned char has_ctrl176:1; + unsigned char f54_query46_b1:1; + unsigned char has_ctrl179:1; + unsigned char f54_query46_b3:1; + unsigned char has_data27:1; + unsigned char has_data28:1; + unsigned char f54_query46_b6:1; + unsigned char has_query47:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_47 { + union { + struct { + unsigned char f54_query47_b0__6:7; + unsigned char has_query49:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_49 { + union { + struct { + unsigned char f54_query49_b0__1:2; + unsigned char has_ctrl188:1; + unsigned char has_data31:1; + unsigned char f54_query49_b4__6:3; + unsigned char has_query50:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_50 { + union { + struct { + unsigned char f54_query50_b0__6:7; + unsigned char has_query51:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_query_51 { + union { + struct { + unsigned char f54_query51_b0__4:5; + unsigned char has_query53_query54_ctrl198:1; + unsigned char f54_query51_b6__7:2; + } __packed; + unsigned char data[1]; + }; +}; + +struct f54_data_31 { + union { + struct { + unsigned char is_calibration_crc:1; + unsigned char calibration_crc:1; + unsigned char short_test_row_number:5; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_7 { + union { + struct { + unsigned char cbc_cap:3; + unsigned char cbc_polarity:1; + unsigned char cbc_tx_carrier_selection:1; + unsigned char f54_ctrl7_b5__7:3; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_41 { + union { + struct { + unsigned char no_signal_clarity:1; + unsigned char f54_ctrl41_b1__7:7; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_57 { + union { + struct { + unsigned char cbc_cap:3; + unsigned char cbc_polarity:1; + unsigned char cbc_tx_carrier_selection:1; + unsigned char f54_ctrl57_b5__7:3; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_86 { + union { + struct { + unsigned char enable_high_noise_state:1; + unsigned char dynamic_sense_display_ratio:2; + unsigned char f54_ctrl86_b3__7:5; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_88 { + union { + struct { + unsigned char tx_low_reference_polarity:1; + unsigned char tx_high_reference_polarity:1; + unsigned char abs_low_reference_polarity:1; + unsigned char abs_polarity:1; + unsigned char cbc_polarity:1; + unsigned char cbc_tx_carrier_selection:1; + unsigned char charge_pump_enable:1; + unsigned char cbc_abs_auto_servo:1; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_110 { + union { + struct { + unsigned char active_stylus_rx_feedback_cap; + unsigned char active_stylus_rx_feedback_cap_reference; + unsigned char active_stylus_low_reference; + unsigned char active_stylus_high_reference; + unsigned char active_stylus_gain_control; + unsigned char active_stylus_gain_control_reference; + unsigned char active_stylus_timing_mode; + unsigned char active_stylus_discovery_bursts; + unsigned char active_stylus_detection_bursts; + unsigned char active_stylus_discovery_noise_multiplier; + unsigned char active_stylus_detection_envelope_min; + unsigned char active_stylus_detection_envelope_max; + unsigned char active_stylus_lose_count; + } __packed; + struct { + unsigned char data[13]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_149 { + union { + struct { + unsigned char trans_cbc_global_cap_enable:1; + unsigned char f54_ctrl149_b1__7:7; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control_188 { + union { + struct { + unsigned char start_calibration:1; + unsigned char start_is_calibration:1; + unsigned char frequency:2; + unsigned char start_production_test:1; + unsigned char short_test_calibration:1; + unsigned char f54_ctrl188_b7:1; + } __packed; + struct { + unsigned char data[1]; + unsigned short address; + } __packed; + }; +}; + +struct f54_control { + struct f54_control_7 *reg_7; + struct f54_control_41 *reg_41; + struct f54_control_57 *reg_57; + struct f54_control_86 *reg_86; + struct f54_control_88 *reg_88; + struct f54_control_110 *reg_110; + struct f54_control_149 *reg_149; + struct f54_control_188 *reg_188; +}; + +struct synaptics_rmi4_f54_handle { + bool no_auto_cal; + bool skip_preparation; + unsigned char status; + unsigned char intr_mask; + unsigned char intr_reg_num; + unsigned char tx_assigned; + unsigned char rx_assigned; + unsigned char *report_data; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + unsigned short fifoindex; + unsigned int report_size; + unsigned int data_buffer_size; + unsigned int data_pos; + enum f54_report_types report_type; + struct f54_query query; + struct f54_query_13 query_13; + struct f54_query_15 query_15; + struct f54_query_16 query_16; + struct f54_query_21 query_21; + struct f54_query_22 query_22; + struct f54_query_23 query_23; + struct f54_query_25 query_25; + struct f54_query_27 query_27; + struct f54_query_29 query_29; + struct f54_query_30 query_30; + struct f54_query_32 query_32; + struct f54_query_33 query_33; + struct f54_query_35 query_35; + struct f54_query_36 query_36; + struct f54_query_38 query_38; + struct f54_query_39 query_39; + struct f54_query_40 query_40; + struct f54_query_43 query_43; + struct f54_query_46 query_46; + struct f54_query_47 query_47; + struct f54_query_49 query_49; + struct f54_query_50 query_50; + struct f54_query_51 query_51; + struct f54_data_31 data_31; + struct f54_control control; + struct mutex status_mutex; + struct kobject *sysfs_dir; + struct hrtimer watchdog; + struct work_struct timeout_work; + struct work_struct test_report_work; + struct workqueue_struct *test_report_workqueue; + struct synaptics_rmi4_data *rmi4_data; +}; + +struct f55_query { + union { + struct { + /* query 0 */ + unsigned char num_of_rx_electrodes; + + /* query 1 */ + unsigned char num_of_tx_electrodes; + + /* query 2 */ + unsigned char has_sensor_assignment:1; + unsigned char has_edge_compensation:1; + unsigned char curve_compensation_mode:2; + unsigned char has_ctrl6:1; + unsigned char has_alternate_transmitter_assignment:1; + unsigned char has_single_layer_multi_touch:1; + unsigned char has_query5:1; + } __packed; + unsigned char data[3]; + }; +}; + +struct f55_query_3 { + union { + struct { + unsigned char has_ctrl8:1; + unsigned char has_ctrl9:1; + unsigned char has_oncell_pattern_support:1; + unsigned char has_data0:1; + unsigned char has_single_wide_pattern_support:1; + unsigned char has_mirrored_tx_pattern_support:1; + unsigned char has_discrete_pattern_support:1; + unsigned char has_query9:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_5 { + union { + struct { + unsigned char has_corner_compensation:1; + unsigned char has_ctrl12:1; + unsigned char has_trx_configuration:1; + unsigned char has_ctrl13:1; + unsigned char f55_query5_b4:1; + unsigned char has_ctrl14:1; + unsigned char has_basis_function:1; + unsigned char has_query17:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_17 { + union { + struct { + unsigned char f55_query17_b0:1; + unsigned char has_ctrl16:1; + unsigned char f55_query17_b2:1; + unsigned char has_ctrl17:1; + unsigned char f55_query17_b4__6:3; + unsigned char has_query18:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_18 { + union { + struct { + unsigned char f55_query18_b0__6:7; + unsigned char has_query22:1; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_22 { + union { + struct { + unsigned char f55_query22_b0:1; + unsigned char has_query23:1; + unsigned char has_guard_disable:1; + unsigned char has_ctrl30:1; + unsigned char f55_query22_b4__7:4; + } __packed; + unsigned char data[1]; + }; +}; + +struct f55_query_23 { + union { + struct { + unsigned char amp_sensor_enabled:1; + unsigned char image_transposed:1; + unsigned char first_column_at_left_side:1; + unsigned char size_of_column2mux:5; + } __packed; + unsigned char data[1]; + }; +}; + +struct synaptics_rmi4_f55_handle { + bool amp_sensor; + unsigned char size_of_column2mux; + unsigned char *tx_assignment; + unsigned char *rx_assignment; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + struct f55_query query; + struct f55_query_3 query_3; + struct f55_query_5 query_5; + struct f55_query_17 query_17; + struct f55_query_18 query_18; + struct f55_query_22 query_22; + struct f55_query_23 query_23; +}; + +show_prototype(num_of_mapped_tx) +show_prototype(num_of_mapped_rx) +show_prototype(tx_mapping) +show_prototype(rx_mapping) +show_prototype(report_size) +show_prototype(status) +store_prototype(do_preparation) +store_prototype(force_cal) +store_prototype(get_report) +store_prototype(resume_touch) +store_prototype(do_afe_calibration) +show_store_prototype(report_type) +show_store_prototype(fifoindex) +show_store_prototype(no_auto_cal) +show_store_prototype(read_report) + +static struct attribute *attrs[] = { + attrify(num_of_mapped_tx), + attrify(num_of_mapped_rx), + attrify(tx_mapping), + attrify(rx_mapping), + attrify(report_size), + attrify(status), + attrify(do_preparation), + attrify(force_cal), + attrify(get_report), + attrify(resume_touch), + attrify(do_afe_calibration), + attrify(report_type), + attrify(fifoindex), + attrify(no_auto_cal), + attrify(read_report), + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static ssize_t test_sysfs_data_read(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count); + +static struct bin_attribute test_report_data = { + .attr = { + .name = "report_data", + .mode = S_IRUGO, + }, + .size = 0, + .read = test_sysfs_data_read, +}; + +static struct synaptics_rmi4_f54_handle *f54; +static struct synaptics_rmi4_f55_handle *f55; + +DECLARE_COMPLETION(test_remove_complete); + +static bool test_report_type_valid(enum f54_report_types report_type) +{ + switch (report_type) { + case F54_8BIT_IMAGE: + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_HIGH_RESISTANCE: + case F54_TX_TO_TX_SHORTS: + case F54_RX_TO_RX_SHORTS_1: + case F54_TRUE_BASELINE: + case F54_FULL_RAW_CAP_MIN_MAX: + case F54_RX_OPENS_1: + case F54_TX_OPENS: + case F54_TX_TO_GND_SHORTS: + case F54_RX_TO_RX_SHORTS_2: + case F54_RX_OPENS_2: + case F54_FULL_RAW_CAP: + case F54_FULL_RAW_CAP_NO_RX_COUPLING: + case F54_SENSOR_SPEED: + case F54_ADC_RANGE: + case F54_TRX_OPENS: + case F54_TRX_TO_GND_SHORTS: + case F54_TRX_SHORTS: + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + case F54_AMP_FULL_RAW_CAP: + case F54_AMP_RAW_ADC: + return true; + break; + default: + f54->report_type = INVALID_REPORT_TYPE; + f54->report_size = 0; + return false; + } +} + +static void test_set_report_size(void) +{ + int retval; + unsigned char tx = f54->tx_assigned; + unsigned char rx = f54->rx_assigned; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + switch (f54->report_type) { + case F54_8BIT_IMAGE: + f54->report_size = tx * rx; + break; + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_TRUE_BASELINE: + case F54_FULL_RAW_CAP: + case F54_FULL_RAW_CAP_NO_RX_COUPLING: + case F54_SENSOR_SPEED: + case F54_AMP_FULL_RAW_CAP: + case F54_AMP_RAW_ADC: + f54->report_size = 2 * tx * rx; + break; + case F54_HIGH_RESISTANCE: + f54->report_size = HIGH_RESISTANCE_DATA_SIZE; + break; + case F54_TX_TO_TX_SHORTS: + case F54_TX_OPENS: + case F54_TX_TO_GND_SHORTS: + f54->report_size = (tx + 7) / 8; + break; + case F54_RX_TO_RX_SHORTS_1: + case F54_RX_OPENS_1: + if (rx < tx) + f54->report_size = 2 * rx * rx; + else + f54->report_size = 2 * tx * rx; + break; + case F54_FULL_RAW_CAP_MIN_MAX: + f54->report_size = FULL_RAW_CAP_MIN_MAX_DATA_SIZE; + break; + case F54_RX_TO_RX_SHORTS_2: + case F54_RX_OPENS_2: + if (rx <= tx) + f54->report_size = 0; + else + f54->report_size = 2 * rx * (rx - tx); + break; + case F54_ADC_RANGE: + if (f54->query.has_signal_clarity) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_41->address, + f54->control.reg_41->data, + sizeof(f54->control.reg_41->data)); + if (retval < 0) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Failed to read control reg_41\n", + __func__); + f54->report_size = 0; + break; + } + if (!f54->control.reg_41->no_signal_clarity) { + if (tx % 4) + tx += 4 - (tx % 4); + } + } + f54->report_size = 2 * tx * rx; + break; + case F54_TRX_OPENS: + case F54_TRX_TO_GND_SHORTS: + case F54_TRX_SHORTS: + f54->report_size = TRX_OPEN_SHORT_DATA_SIZE; + break; + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + f54->report_size = 4 * (tx + rx); + break; + default: + f54->report_size = 0; + } + + return; +} + +static int test_set_interrupt(bool set) +{ + int retval; + unsigned char ii; + unsigned char zero = 0x00; + unsigned char *intr_mask; + unsigned short f01_ctrl_reg; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + intr_mask = rmi4_data->intr_mask; + f01_ctrl_reg = rmi4_data->f01_ctrl_base_addr + 1 + f54->intr_reg_num; + + if (!set) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &zero, + sizeof(zero)); + if (retval < 0) + return retval; + } + + for (ii = 0; ii < rmi4_data->num_of_intr_regs; ii++) { + if (intr_mask[ii] != 0x00) { + f01_ctrl_reg = rmi4_data->f01_ctrl_base_addr + 1 + ii; + if (set) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &zero, + sizeof(zero)); + if (retval < 0) + return retval; + } else { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &(intr_mask[ii]), + sizeof(intr_mask[ii])); + if (retval < 0) + return retval; + } + } + } + + f01_ctrl_reg = rmi4_data->f01_ctrl_base_addr + 1 + f54->intr_reg_num; + + if (set) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f01_ctrl_reg, + &f54->intr_mask, + 1); + if (retval < 0) + return retval; + } + + return 0; +} + +static int test_wait_for_command_completion(void) +{ + int retval; + unsigned char value; + unsigned char timeout_count; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + timeout_count = 0; + do { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->command_base_addr, + &value, + sizeof(value)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read command register\n", + __func__); + return retval; + } + + if (value == 0x00) + break; + + msleep(100); + timeout_count++; + } while (timeout_count < COMMAND_TIMEOUT_100MS); + + if (timeout_count == COMMAND_TIMEOUT_100MS) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for command completion\n", + __func__); + return -ETIMEDOUT; + } + + return 0; +} + +static int test_do_command(unsigned char command) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->command_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write command\n", + __func__); + return retval; + } + + retval = test_wait_for_command_completion(); + if (retval < 0) + return retval; + + return 0; +} + +static int test_do_preparation(void) +{ + int retval; + unsigned char value; + unsigned char zero = 0x00; + unsigned char device_ctrl; + struct f54_control_86 reg_86; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set no sleep\n", + __func__); + return retval; + } + + device_ctrl |= NO_SLEEP_ON; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set no sleep\n", + __func__); + return retval; + } + + if ((f54->query.has_query13) && + (f54->query_13.has_ctrl86)) { + reg_86.data[0] = f54->control.reg_86->data[0]; + reg_86.dynamic_sense_display_ratio = 1; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_86->address, + reg_86.data, + sizeof(reg_86.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set sense display ratio\n", + __func__); + return retval; + } + } + + if (f54->skip_preparation) + return 0; + + switch (f54->report_type) { + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_SENSOR_SPEED: + case F54_ADC_RANGE: + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + break; + case F54_AMP_RAW_ADC: + if (f54->query_49.has_ctrl188) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + f54->control.reg_188->start_production_test = 1; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + } + break; + default: + if (f54->query.touch_controller_family == 1) + disable_cbc(reg_7); + else if (f54->query.has_ctrl88) + disable_cbc(reg_88); + + if (f54->query.has_0d_acquisition_control) + disable_cbc(reg_57); + + if ((f54->query.has_query15) && + (f54->query_15.has_query25) && + (f54->query_25.has_query27) && + (f54->query_27.has_query29) && + (f54->query_29.has_query30) && + (f54->query_30.has_query32) && + (f54->query_32.has_query33) && + (f54->query_33.has_query36) && + (f54->query_36.has_query38) && + (f54->query_38.has_ctrl149)) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_149->address, + &zero, + sizeof(f54->control.reg_149->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable global CBC\n", + __func__); + return retval; + } + } + + if (f54->query.has_signal_clarity) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_41->address, + &value, + sizeof(f54->control.reg_41->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable signal clarity\n", + __func__); + return retval; + } + value |= 0x01; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_41->address, + &value, + sizeof(f54->control.reg_41->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to disable signal clarity\n", + __func__); + return retval; + } + } + + retval = test_do_command(COMMAND_FORCE_UPDATE); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do force update\n", + __func__); + return retval; + } + + retval = test_do_command(COMMAND_FORCE_CAL); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do force cal\n", + __func__); + return retval; + } + } + + return 0; +} + +static int test_do_afe_calibration(enum f54_afe_cal mode) +{ + int retval; + unsigned char timeout = CALIBRATION_TIMEOUT_S; + unsigned char timeout_count = 0; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to start calibration\n", + __func__); + return retval; + } + + if (mode == F54_AFE_CAL) + f54->control.reg_188->start_calibration = 1; + else if (mode == F54_AFE_IS_CAL) + f54->control.reg_188->start_is_calibration = 1; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to start calibration\n", + __func__); + return retval; + } + + do { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to complete calibration\n", + __func__); + return retval; + } + + if (mode == F54_AFE_CAL) { + if (!f54->control.reg_188->start_calibration) + break; + } else if (mode == F54_AFE_IS_CAL) { + if (!f54->control.reg_188->start_is_calibration) + break; + } + + if (timeout_count == timeout) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Timed out waiting for calibration completion\n", + __func__); + return -EBUSY; + } + + timeout_count++; + msleep(1000); + } while (true); + + /* check CRC */ + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->data_31.address, + f54->data_31.data, + sizeof(f54->data_31.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read calibration CRC\n", + __func__); + return retval; + } + + if (mode == F54_AFE_CAL) { + if (f54->data_31.calibration_crc == 0) + return 0; + } else if (mode == F54_AFE_IS_CAL) { + if (f54->data_31.is_calibration_crc == 0) + return 0; + } + + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read calibration CRC\n", + __func__); + + return -EINVAL; +} + +static int test_check_for_idle_status(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + switch (f54->status) { + case STATUS_IDLE: + retval = 0; + break; + case STATUS_BUSY: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Status busy\n", + __func__); + retval = -EINVAL; + break; + case STATUS_ERROR: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Status error\n", + __func__); + retval = -EINVAL; + break; + default: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid status (%d)\n", + __func__, f54->status); + retval = -EINVAL; + } + + return retval; +} + +static void test_timeout_work(struct work_struct *work) +{ + int retval; + unsigned char command; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + mutex_lock(&f54->status_mutex); + + if (f54->status == STATUS_BUSY) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->command_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read command register\n", + __func__); + } else if (command & COMMAND_GET_REPORT) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report type not supported by FW\n", + __func__); + } else { + queue_work(f54->test_report_workqueue, + &f54->test_report_work); + goto exit; + } + f54->status = STATUS_ERROR; + f54->report_size = 0; + } + +exit: + mutex_unlock(&f54->status_mutex); + + return; +} + +static enum hrtimer_restart test_get_report_timeout(struct hrtimer *timer) +{ + schedule_work(&(f54->timeout_work)); + + return HRTIMER_NORESTART; +} + +static ssize_t test_sysfs_num_of_mapped_tx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->tx_assigned); +} + +static ssize_t test_sysfs_num_of_mapped_rx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->rx_assigned); +} + +static ssize_t test_sysfs_tx_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cnt; + int count = 0; + unsigned char ii; + unsigned char tx_num; + unsigned char tx_electrodes = f54->query.num_of_tx_electrodes; + + if (!f55) + return -EINVAL; + + for (ii = 0; ii < tx_electrodes; ii++) { + tx_num = f55->tx_assignment[ii]; + if (tx_num == 0xff) + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + else + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", tx_num); + buf += cnt; + count += cnt; + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t test_sysfs_rx_mapping_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cnt; + int count = 0; + unsigned char ii; + unsigned char rx_num; + unsigned char rx_electrodes = f54->query.num_of_rx_electrodes; + + if (!f55) + return -EINVAL; + + for (ii = 0; ii < rx_electrodes; ii++) { + rx_num = f55->rx_assignment[ii]; + if (rx_num == 0xff) + cnt = snprintf(buf, PAGE_SIZE - count, "xx "); + else + cnt = snprintf(buf, PAGE_SIZE - count, "%02u ", rx_num); + buf += cnt; + count += cnt; + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t test_sysfs_report_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->report_size); +} + +static ssize_t test_sysfs_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + + mutex_lock(&f54->status_mutex); + + retval = snprintf(buf, PAGE_SIZE, "%u\n", f54->status); + + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t test_sysfs_do_preparation_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + retval = test_do_preparation(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do preparation\n", + __func__); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t test_sysfs_force_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + retval = test_do_command(COMMAND_FORCE_CAL); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to do force cal\n", + __func__); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t test_sysfs_get_report_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char command; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + if (!test_report_type_valid(f54->report_type)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Invalid report type\n", + __func__); + retval = -EINVAL; + goto exit; + } + + test_set_interrupt(true); + + command = (unsigned char)COMMAND_GET_REPORT; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->command_base_addr, + &command, + sizeof(command)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write get report command\n", + __func__); + goto exit; + } + + f54->status = STATUS_BUSY; + f54->report_size = 0; + f54->data_pos = 0; + + hrtimer_start(&f54->watchdog, + ktime_set(GET_REPORT_TIMEOUT_S, 0), + HRTIMER_MODE_REL); + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t test_sysfs_resume_touch_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char device_ctrl; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting != 1) + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to restore no sleep setting\n", + __func__); + return retval; + } + + device_ctrl = device_ctrl & ~NO_SLEEP_ON; + device_ctrl |= rmi4_data->no_sleep_setting; + + retval = synaptics_rmi4_reg_write(rmi4_data, + rmi4_data->f01_ctrl_base_addr, + &device_ctrl, + sizeof(device_ctrl)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to restore no sleep setting\n", + __func__); + return retval; + } + + if ((f54->query.has_query13) && + (f54->query_13.has_ctrl86)) { + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_86->address, + f54->control.reg_86->data, + sizeof(f54->control.reg_86->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to restore sense display ratio\n", + __func__); + return retval; + } + } + + test_set_interrupt(false); + + if (f54->skip_preparation) + return count; + + switch (f54->report_type) { + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_SENSOR_SPEED: + case F54_ADC_RANGE: + case F54_ABS_RAW_CAP: + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + break; + case F54_AMP_RAW_ADC: + if (f54->query_49.has_ctrl188) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + f54->control.reg_188->start_production_test = 0; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control.reg_188->address, + f54->control.reg_188->data, + sizeof(f54->control.reg_188->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set start production test\n", + __func__); + return retval; + } + } + break; + default: + rmi4_data->reset_device(rmi4_data, false); + } + + return count; +} + +static ssize_t test_sysfs_do_afe_calibration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (!f54->query_49.has_ctrl188) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: F54_ANALOG_Ctrl188 not found\n", + __func__); + return -EINVAL; + } + + if (setting == 0 || setting == 1) + retval = test_do_afe_calibration((enum f54_afe_cal)setting); + else + return -EINVAL; + + if (retval) + return retval; + else + return count; +} + +static ssize_t test_sysfs_report_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->report_type); +} + +static ssize_t test_sysfs_report_type_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char data; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + if (!test_report_type_valid((enum f54_report_types)setting)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report type not supported by driver\n", + __func__); + retval = -EINVAL; + goto exit; + } + + f54->report_type = (enum f54_report_types)setting; + data = (unsigned char)setting; + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->data_base_addr, + &data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write report type\n", + __func__); + goto exit; + } + + retval = count; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static ssize_t test_sysfs_fifoindex_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval; + unsigned char data[2]; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->data_base_addr + REPORT_INDEX_OFFSET, + data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read report index\n", + __func__); + return retval; + } + + batohs(&f54->fifoindex, data); + + return snprintf(buf, PAGE_SIZE, "%u\n", f54->fifoindex); +} + +static ssize_t test_sysfs_fifoindex_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char data[2]; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + f54->fifoindex = setting; + + hstoba(data, (unsigned short)setting); + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->data_base_addr + REPORT_INDEX_OFFSET, + data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write report index\n", + __func__); + return retval; + } + + return count; +} + +static ssize_t test_sysfs_no_auto_cal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%u\n", f54->no_auto_cal); +} + +static ssize_t test_sysfs_no_auto_cal_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char data; + unsigned long setting; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = sstrtoul(buf, 10, &setting); + if (retval) + return retval; + + if (setting > 1) + return -EINVAL; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control_base_addr, + &data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read no auto cal setting\n", + __func__); + return retval; + } + + if (setting) + data |= CONTROL_NO_AUTO_CAL; + else + data &= ~CONTROL_NO_AUTO_CAL; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->control_base_addr, + &data, + sizeof(data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write no auto cal setting\n", + __func__); + return retval; + } + + f54->no_auto_cal = (setting == 1); + + return count; +} + +static ssize_t test_sysfs_read_report_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int ii; + unsigned int jj; + int cnt; + int count = 0; + int tx_num = f54->tx_assigned; + int rx_num = f54->rx_assigned; + char *report_data_8; + short *report_data_16; + int *report_data_32; + unsigned short *report_data_u16; + unsigned int *report_data_u32; + + switch (f54->report_type) { + case F54_8BIT_IMAGE: + report_data_8 = (char *)f54->report_data; + for (ii = 0; ii < f54->report_size; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%03d: %d\n", + ii, *report_data_8); + report_data_8++; + buf += cnt; + count += cnt; + } + break; + case F54_AMP_RAW_ADC: + report_data_u16 = (unsigned short *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "tx = %d\nrx = %d\n", + tx_num, rx_num); + buf += cnt; + count += cnt; + + for (ii = 0; ii < tx_num; ii++) { + for (jj = 0; jj < (rx_num - 1); jj++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d ", + *report_data_u16); + report_data_u16++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d\n", + *report_data_u16); + report_data_u16++; + buf += cnt; + count += cnt; + } + break; + case F54_16BIT_IMAGE: + case F54_RAW_16BIT_IMAGE: + case F54_TRUE_BASELINE: + case F54_FULL_RAW_CAP: + case F54_FULL_RAW_CAP_NO_RX_COUPLING: + case F54_SENSOR_SPEED: + case F54_AMP_FULL_RAW_CAP: + report_data_16 = (short *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "tx = %d\nrx = %d\n", + tx_num, rx_num); + buf += cnt; + count += cnt; + + for (ii = 0; ii < tx_num; ii++) { + for (jj = 0; jj < (rx_num - 1); jj++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d ", + *report_data_16); + report_data_16++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "%-4d\n", + *report_data_16); + report_data_16++; + buf += cnt; + count += cnt; + } + break; + case F54_HIGH_RESISTANCE: + case F54_FULL_RAW_CAP_MIN_MAX: + report_data_16 = (short *)f54->report_data; + for (ii = 0; ii < f54->report_size; ii += 2) { + cnt = snprintf(buf, PAGE_SIZE - count, "%03d: %d\n", + ii / 2, *report_data_16); + report_data_16++; + buf += cnt; + count += cnt; + } + break; + case F54_ABS_RAW_CAP: + report_data_u32 = (unsigned int *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "rx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5u", + *report_data_u32); + report_data_u32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, "tx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5u", + *report_data_u32); + report_data_u32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + break; + case F54_ABS_DELTA_CAP: + case F54_ABS_HYBRID_DELTA_CAP: + case F54_ABS_HYBRID_RAW_CAP: + report_data_32 = (int *)f54->report_data; + cnt = snprintf(buf, PAGE_SIZE - count, "rx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < rx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5d", + *report_data_32); + report_data_32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, "tx "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %2d", ii); + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + + cnt = snprintf(buf, PAGE_SIZE - count, " "); + buf += cnt; + count += cnt; + for (ii = 0; ii < tx_num; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, " %5d", + *report_data_32); + report_data_32++; + buf += cnt; + count += cnt; + } + cnt = snprintf(buf, PAGE_SIZE - count, "\n"); + buf += cnt; + count += cnt; + break; + default: + for (ii = 0; ii < f54->report_size; ii++) { + cnt = snprintf(buf, PAGE_SIZE - count, "%03d: 0x%02x\n", + ii, f54->report_data[ii]); + buf += cnt; + count += cnt; + } + } + + snprintf(buf, PAGE_SIZE - count, "\n"); + count++; + + return count; +} + +static ssize_t test_sysfs_read_report_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned char timeout = GET_REPORT_TIMEOUT_S * 10; + unsigned char timeout_count; + const char cmd[] = {'1', 0}; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = test_sysfs_report_type_store(dev, attr, buf, count); + if (retval < 0) + goto exit; + + retval = test_sysfs_do_preparation_store(dev, attr, cmd, 1); + if (retval < 0) + goto exit; + + retval = test_sysfs_get_report_store(dev, attr, cmd, 1); + if (retval < 0) + goto exit; + + timeout_count = 0; + do { + if (f54->status != STATUS_BUSY) + break; + msleep(100); + timeout_count++; + } while (timeout_count < timeout); + + if ((f54->status != STATUS_IDLE) || (f54->report_size == 0)) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read report\n", + __func__); + retval = -EINVAL; + goto exit; + } + + retval = test_sysfs_resume_touch_store(dev, attr, cmd, 1); + if (retval < 0) + goto exit; + + return count; + +exit: + rmi4_data->reset_device(rmi4_data, false); + + return retval; +} + +static ssize_t test_sysfs_data_read(struct file *data_file, + struct kobject *kobj, struct bin_attribute *attributes, + char *buf, loff_t pos, size_t count) +{ + int retval; + unsigned int read_size; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + mutex_lock(&f54->status_mutex); + + retval = test_check_for_idle_status(); + if (retval < 0) + goto exit; + + if (!f54->report_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report type %d data not available\n", + __func__, f54->report_type); + retval = -EINVAL; + goto exit; + } + + if ((f54->data_pos + count) > f54->report_size) + read_size = f54->report_size - f54->data_pos; + else + read_size = min_t(unsigned int, count, f54->report_size); + + retval = secure_memcpy(buf, count, f54->report_data + f54->data_pos, + f54->data_buffer_size - f54->data_pos, read_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to copy report data\n", + __func__); + goto exit; + } + f54->data_pos += read_size; + retval = read_size; + +exit: + mutex_unlock(&f54->status_mutex); + + return retval; +} + +static void test_report_work(struct work_struct *work) +{ + int retval; + unsigned char report_index[2]; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + mutex_lock(&f54->status_mutex); + + if (f54->status != STATUS_BUSY) { + retval = STATUS_ERROR; + goto exit; + } + + retval = test_wait_for_command_completion(); + if (retval < 0) { + retval = STATUS_ERROR; + goto exit; + } + + test_set_report_size(); + if (f54->report_size == 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Report data size = 0\n", + __func__); + retval = STATUS_ERROR; + goto exit; + } + + if (f54->data_buffer_size < f54->report_size) { + if (f54->data_buffer_size) + kfree(f54->report_data); + f54->report_data = kzalloc(f54->report_size, GFP_KERNEL); + if (!f54->report_data) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for data buffer\n", + __func__); + f54->data_buffer_size = 0; + retval = STATUS_ERROR; + goto exit; + } + f54->data_buffer_size = f54->report_size; + } + + report_index[0] = 0; + report_index[1] = 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + f54->data_base_addr + REPORT_INDEX_OFFSET, + report_index, + sizeof(report_index)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to write report data index\n", + __func__); + retval = STATUS_ERROR; + goto exit; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->data_base_addr + REPORT_DATA_OFFSET, + f54->report_data, + f54->report_size); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read report data\n", + __func__); + retval = STATUS_ERROR; + goto exit; + } + + retval = STATUS_IDLE; + +exit: + mutex_unlock(&f54->status_mutex); + + if (retval == STATUS_ERROR) + f54->report_size = 0; + + f54->status = retval; + + return; +} + +static void test_remove_sysfs(void) +{ + sysfs_remove_group(f54->sysfs_dir, &attr_group); + sysfs_remove_bin_file(f54->sysfs_dir, &test_report_data); + kobject_put(f54->sysfs_dir); + + return; +} + +static int test_set_sysfs(void) +{ + int retval; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + f54->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME, + &rmi4_data->input_dev->dev.kobj); + if (!f54->sysfs_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs directory\n", + __func__); + goto exit_directory; + } + + retval = sysfs_create_bin_file(f54->sysfs_dir, &test_report_data); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs bin file\n", + __func__); + goto exit_bin_file; + } + + retval = sysfs_create_group(f54->sysfs_dir, &attr_group); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + goto exit_attributes; + } + + return 0; + +exit_attributes: + sysfs_remove_group(f54->sysfs_dir, &attr_group); + sysfs_remove_bin_file(f54->sysfs_dir, &test_report_data); + +exit_bin_file: + kobject_put(f54->sysfs_dir); + +exit_directory: + return -ENODEV; +} + +static void test_free_control_mem(void) +{ + struct f54_control control = f54->control; + + kfree(control.reg_7); + kfree(control.reg_41); + kfree(control.reg_57); + kfree(control.reg_86); + kfree(control.reg_88); + kfree(control.reg_110); + kfree(control.reg_149); + kfree(control.reg_188); + + return; +} + +static void test_set_data(void) +{ + unsigned short reg_addr; + + reg_addr = f54->data_base_addr + REPORT_DATA_OFFSET + 1; + + /* data 4 */ + if (f54->query.has_sense_frequency_control) + reg_addr++; + + /* data 5 reserved */ + + /* data 6 */ + if (f54->query.has_interference_metric) + reg_addr += 2; + + /* data 7 */ + if (f54->query.has_one_byte_report_rate | + f54->query.has_two_byte_report_rate) + reg_addr++; + if (f54->query.has_two_byte_report_rate) + reg_addr++; + + /* data 8 */ + if (f54->query.has_variance_metric) + reg_addr += 2; + + /* data 9 */ + if (f54->query.has_multi_metric_state_machine) + reg_addr += 2; + + /* data 10 */ + if (f54->query.has_multi_metric_state_machine | + f54->query.has_noise_state) + reg_addr++; + + /* data 11 */ + if (f54->query.has_status) + reg_addr++; + + /* data 12 */ + if (f54->query.has_slew_metric) + reg_addr += 2; + + /* data 13 */ + if (f54->query.has_multi_metric_state_machine) + reg_addr += 2; + + /* data 14 */ + if (f54->query_13.has_cidim) + reg_addr++; + + /* data 15 */ + if (f54->query_13.has_rail_im) + reg_addr++; + + /* data 16 */ + if (f54->query_13.has_noise_mitigation_enhancement) + reg_addr++; + + /* data 17 */ + if (f54->query_16.has_data17) + reg_addr++; + + /* data 18 */ + if (f54->query_21.has_query24_data18) + reg_addr++; + + /* data 19 */ + if (f54->query_21.has_data19) + reg_addr++; + + /* data_20 */ + if (f54->query_25.has_ctrl109) + reg_addr++; + + /* data 21 */ + if (f54->query_27.has_data21) + reg_addr++; + + /* data 22 */ + if (f54->query_27.has_data22) + reg_addr++; + + /* data 23 */ + if (f54->query_29.has_data23) + reg_addr++; + + /* data 24 */ + if (f54->query_32.has_data24) + reg_addr++; + + /* data 25 */ + if (f54->query_35.has_data25) + reg_addr++; + + /* data 26 */ + if (f54->query_35.has_data26) + reg_addr++; + + /* data 27 */ + if (f54->query_46.has_data27) + reg_addr++; + + /* data 28 */ + if (f54->query_46.has_data28) + reg_addr++; + + /* data 29 30 reserved */ + + /* data 31 */ + if (f54->query_49.has_data31) { + f54->data_31.address = reg_addr; + reg_addr++; + } + + return; +} + +static int test_set_controls(void) +{ + int retval; + unsigned char length; + unsigned char num_of_sensing_freqs; + unsigned short reg_addr = f54->control_base_addr; + struct f54_control *control = &f54->control; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + num_of_sensing_freqs = f54->query.number_of_sensing_frequencies; + + /* control 0 */ + reg_addr += CONTROL_0_SIZE; + + /* control 1 */ + if ((f54->query.touch_controller_family == 0) || + (f54->query.touch_controller_family == 1)) + reg_addr += CONTROL_1_SIZE; + + /* control 2 */ + reg_addr += CONTROL_2_SIZE; + + /* control 3 */ + if (f54->query.has_pixel_touch_threshold_adjustment) + reg_addr += CONTROL_3_SIZE; + + /* controls 4 5 6 */ + if ((f54->query.touch_controller_family == 0) || + (f54->query.touch_controller_family == 1)) + reg_addr += CONTROL_4_6_SIZE; + + /* control 7 */ + if (f54->query.touch_controller_family == 1) { + control->reg_7 = kzalloc(sizeof(*(control->reg_7)), + GFP_KERNEL); + if (!control->reg_7) + goto exit_no_mem; + control->reg_7->address = reg_addr; + reg_addr += CONTROL_7_SIZE; + } + + /* controls 8 9 */ + if ((f54->query.touch_controller_family == 0) || + (f54->query.touch_controller_family == 1)) + reg_addr += CONTROL_8_9_SIZE; + + /* control 10 */ + if (f54->query.has_interference_metric) + reg_addr += CONTROL_10_SIZE; + + /* control 11 */ + if (f54->query.has_ctrl11) + reg_addr += CONTROL_11_SIZE; + + /* controls 12 13 */ + if (f54->query.has_relaxation_control) + reg_addr += CONTROL_12_13_SIZE; + + /* controls 14 15 16 */ + if (f54->query.has_sensor_assignment) { + reg_addr += CONTROL_14_SIZE; + reg_addr += CONTROL_15_SIZE * f54->query.num_of_rx_electrodes; + reg_addr += CONTROL_16_SIZE * f54->query.num_of_tx_electrodes; + } + + /* controls 17 18 19 */ + if (f54->query.has_sense_frequency_control) { + reg_addr += CONTROL_17_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_18_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_19_SIZE * num_of_sensing_freqs; + } + + /* control 20 */ + reg_addr += CONTROL_20_SIZE; + + /* control 21 */ + if (f54->query.has_sense_frequency_control) + reg_addr += CONTROL_21_SIZE; + + /* controls 22 23 24 25 26 */ + if (f54->query.has_firmware_noise_mitigation) + reg_addr += CONTROL_22_26_SIZE; + + /* control 27 */ + if (f54->query.has_iir_filter) + reg_addr += CONTROL_27_SIZE; + + /* control 28 */ + if (f54->query.has_firmware_noise_mitigation) + reg_addr += CONTROL_28_SIZE; + + /* control 29 */ + if (f54->query.has_cmn_removal) + reg_addr += CONTROL_29_SIZE; + + /* control 30 */ + if (f54->query.has_cmn_maximum) + reg_addr += CONTROL_30_SIZE; + + /* control 31 */ + if (f54->query.has_touch_hysteresis) + reg_addr += CONTROL_31_SIZE; + + /* controls 32 33 34 35 */ + if (f54->query.has_edge_compensation) + reg_addr += CONTROL_32_35_SIZE; + + /* control 36 */ + if ((f54->query.curve_compensation_mode == 1) || + (f54->query.curve_compensation_mode == 2)) { + if (f54->query.curve_compensation_mode == 1) { + length = max(f54->query.num_of_rx_electrodes, + f54->query.num_of_tx_electrodes); + } else if (f54->query.curve_compensation_mode == 2) { + length = f54->query.num_of_rx_electrodes; + } + reg_addr += CONTROL_36_SIZE * length; + } + + /* control 37 */ + if (f54->query.curve_compensation_mode == 2) + reg_addr += CONTROL_37_SIZE * f54->query.num_of_tx_electrodes; + + /* controls 38 39 40 */ + if (f54->query.has_per_frequency_noise_control) { + reg_addr += CONTROL_38_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_39_SIZE * num_of_sensing_freqs; + reg_addr += CONTROL_40_SIZE * num_of_sensing_freqs; + } + + /* control 41 */ + if (f54->query.has_signal_clarity) { + control->reg_41 = kzalloc(sizeof(*(control->reg_41)), + GFP_KERNEL); + if (!control->reg_41) + goto exit_no_mem; + control->reg_41->address = reg_addr; + reg_addr += CONTROL_41_SIZE; + } + + /* control 42 */ + if (f54->query.has_variance_metric) + reg_addr += CONTROL_42_SIZE; + + /* controls 43 44 45 46 47 48 49 50 51 52 53 54 */ + if (f54->query.has_multi_metric_state_machine) + reg_addr += CONTROL_43_54_SIZE; + + /* controls 55 56 */ + if (f54->query.has_0d_relaxation_control) + reg_addr += CONTROL_55_56_SIZE; + + /* control 57 */ + if (f54->query.has_0d_acquisition_control) { + control->reg_57 = kzalloc(sizeof(*(control->reg_57)), + GFP_KERNEL); + if (!control->reg_57) + goto exit_no_mem; + control->reg_57->address = reg_addr; + reg_addr += CONTROL_57_SIZE; + } + + /* control 58 */ + if (f54->query.has_0d_acquisition_control) + reg_addr += CONTROL_58_SIZE; + + /* control 59 */ + if (f54->query.has_h_blank) + reg_addr += CONTROL_59_SIZE; + + /* controls 60 61 62 */ + if ((f54->query.has_h_blank) || + (f54->query.has_v_blank) || + (f54->query.has_long_h_blank)) + reg_addr += CONTROL_60_62_SIZE; + + /* control 63 */ + if ((f54->query.has_h_blank) || + (f54->query.has_v_blank) || + (f54->query.has_long_h_blank) || + (f54->query.has_slew_metric) || + (f54->query.has_slew_option) || + (f54->query.has_noise_mitigation2)) + reg_addr += CONTROL_63_SIZE; + + /* controls 64 65 66 67 */ + if (f54->query.has_h_blank) + reg_addr += CONTROL_64_67_SIZE * 7; + else if ((f54->query.has_v_blank) || + (f54->query.has_long_h_blank)) + reg_addr += CONTROL_64_67_SIZE; + + /* controls 68 69 70 71 72 73 */ + if ((f54->query.has_h_blank) || + (f54->query.has_v_blank) || + (f54->query.has_long_h_blank)) + reg_addr += CONTROL_68_73_SIZE; + + /* control 74 */ + if (f54->query.has_slew_metric) + reg_addr += CONTROL_74_SIZE; + + /* control 75 */ + if (f54->query.has_enhanced_stretch) + reg_addr += CONTROL_75_SIZE * num_of_sensing_freqs; + + /* control 76 */ + if (f54->query.has_startup_fast_relaxation) + reg_addr += CONTROL_76_SIZE; + + /* controls 77 78 */ + if (f54->query.has_esd_control) + reg_addr += CONTROL_77_78_SIZE; + + /* controls 79 80 81 82 83 */ + if (f54->query.has_noise_mitigation2) + reg_addr += CONTROL_79_83_SIZE; + + /* controls 84 85 */ + if (f54->query.has_energy_ratio_relaxation) + reg_addr += CONTROL_84_85_SIZE; + + /* control 86 */ + if (f54->query_13.has_ctrl86) { + control->reg_86 = kzalloc(sizeof(*(control->reg_86)), + GFP_KERNEL); + if (!control->reg_86) + goto exit_no_mem; + control->reg_86->address = reg_addr; + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->control.reg_86->address, + f54->control.reg_86->data, + sizeof(f54->control.reg_86->data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read sense display ratio\n", + __func__); + return retval; + } + reg_addr += CONTROL_86_SIZE; + } + + /* control 87 */ + if (f54->query_13.has_ctrl87) + reg_addr += CONTROL_87_SIZE; + + /* control 88 */ + if (f54->query.has_ctrl88) { + control->reg_88 = kzalloc(sizeof(*(control->reg_88)), + GFP_KERNEL); + if (!control->reg_88) + goto exit_no_mem; + control->reg_88->address = reg_addr; + reg_addr += CONTROL_88_SIZE; + } + + /* control 89 */ + if (f54->query_13.has_cidim || + f54->query_13.has_noise_mitigation_enhancement || + f54->query_13.has_rail_im) + reg_addr += CONTROL_89_SIZE; + + /* control 90 */ + if (f54->query_15.has_ctrl90) + reg_addr += CONTROL_90_SIZE; + + /* control 91 */ + if (f54->query_21.has_ctrl91) + reg_addr += CONTROL_91_SIZE; + + /* control 92 */ + if (f54->query_16.has_ctrl92) + reg_addr += CONTROL_92_SIZE; + + /* control 93 */ + if (f54->query_16.has_ctrl93) + reg_addr += CONTROL_93_SIZE; + + /* control 94 */ + if (f54->query_16.has_ctrl94_query18) + reg_addr += CONTROL_94_SIZE; + + /* control 95 */ + if (f54->query_16.has_ctrl95_query19) + reg_addr += CONTROL_95_SIZE; + + /* control 96 */ + if (f54->query_21.has_ctrl96) + reg_addr += CONTROL_96_SIZE; + + /* control 97 */ + if (f54->query_21.has_ctrl97) + reg_addr += CONTROL_97_SIZE; + + /* control 98 */ + if (f54->query_21.has_ctrl98) + reg_addr += CONTROL_98_SIZE; + + /* control 99 */ + if (f54->query.touch_controller_family == 2) + reg_addr += CONTROL_99_SIZE; + + /* control 100 */ + if (f54->query_16.has_ctrl100) + reg_addr += CONTROL_100_SIZE; + + /* control 101 */ + if (f54->query_22.has_ctrl101) + reg_addr += CONTROL_101_SIZE; + + + /* control 102 */ + if (f54->query_23.has_ctrl102) + reg_addr += CONTROL_102_SIZE; + + /* control 103 */ + if (f54->query_22.has_ctrl103_query26) { + f54->skip_preparation = true; + reg_addr += CONTROL_103_SIZE; + } + + /* control 104 */ + if (f54->query_22.has_ctrl104) + reg_addr += CONTROL_104_SIZE; + + /* control 105 */ + if (f54->query_22.has_ctrl105) + reg_addr += CONTROL_105_SIZE; + + /* control 106 */ + if (f54->query_25.has_ctrl106) + reg_addr += CONTROL_106_SIZE; + + /* control 107 */ + if (f54->query_25.has_ctrl107) + reg_addr += CONTROL_107_SIZE; + + /* control 108 */ + if (f54->query_25.has_ctrl108) + reg_addr += CONTROL_108_SIZE; + + /* control 109 */ + if (f54->query_25.has_ctrl109) + reg_addr += CONTROL_109_SIZE; + + /* control 110 */ + if (f54->query_27.has_ctrl110) { + control->reg_110 = kzalloc(sizeof(*(control->reg_110)), + GFP_KERNEL); + if (!control->reg_110) + goto exit_no_mem; + control->reg_110->address = reg_addr; + reg_addr += CONTROL_110_SIZE; + } + + /* control 111 */ + if (f54->query_27.has_ctrl111) + reg_addr += CONTROL_111_SIZE; + + /* control 112 */ + if (f54->query_27.has_ctrl112) + reg_addr += CONTROL_112_SIZE; + + /* control 113 */ + if (f54->query_27.has_ctrl113) + reg_addr += CONTROL_113_SIZE; + + /* control 114 */ + if (f54->query_27.has_ctrl114) + reg_addr += CONTROL_114_SIZE; + + /* control 115 */ + if (f54->query_29.has_ctrl115) + reg_addr += CONTROL_115_SIZE; + + /* control 116 */ + if (f54->query_29.has_ctrl116) + reg_addr += CONTROL_116_SIZE; + + /* control 117 */ + if (f54->query_29.has_ctrl117) + reg_addr += CONTROL_117_SIZE; + + /* control 118 */ + if (f54->query_30.has_ctrl118) + reg_addr += CONTROL_118_SIZE; + + /* control 119 */ + if (f54->query_30.has_ctrl119) + reg_addr += CONTROL_119_SIZE; + + /* control 120 */ + if (f54->query_30.has_ctrl120) + reg_addr += CONTROL_120_SIZE; + + /* control 121 */ + if (f54->query_30.has_ctrl121) + reg_addr += CONTROL_121_SIZE; + + /* control 122 */ + if (f54->query_30.has_ctrl122_query31) + reg_addr += CONTROL_122_SIZE; + + /* control 123 */ + if (f54->query_30.has_ctrl123) + reg_addr += CONTROL_123_SIZE; + + /* control 124 reserved */ + + /* control 125 */ + if (f54->query_32.has_ctrl125) + reg_addr += CONTROL_125_SIZE; + + /* control 126 */ + if (f54->query_32.has_ctrl126) + reg_addr += CONTROL_126_SIZE; + + /* control 127 */ + if (f54->query_32.has_ctrl127) + reg_addr += CONTROL_127_SIZE; + + /* controls 128 129 130 131 reserved */ + + /* control 132 */ + if (f54->query_33.has_ctrl132) + reg_addr += CONTROL_132_SIZE; + + /* control 133 */ + if (f54->query_33.has_ctrl133) + reg_addr += CONTROL_133_SIZE; + + /* control 134 */ + if (f54->query_33.has_ctrl134) + reg_addr += CONTROL_134_SIZE; + + /* controls 135 136 reserved */ + + /* control 137 */ + if (f54->query_35.has_ctrl137) + reg_addr += CONTROL_137_SIZE; + + /* control 138 */ + if (f54->query_35.has_ctrl138) + reg_addr += CONTROL_138_SIZE; + + /* control 139 */ + if (f54->query_35.has_ctrl139) + reg_addr += CONTROL_139_SIZE; + + /* control 140 */ + if (f54->query_35.has_ctrl140) + reg_addr += CONTROL_140_SIZE; + + /* control 141 reserved */ + + /* control 142 */ + if (f54->query_36.has_ctrl142) + reg_addr += CONTROL_142_SIZE; + + /* control 143 */ + if (f54->query_36.has_ctrl143) + reg_addr += CONTROL_143_SIZE; + + /* control 144 */ + if (f54->query_36.has_ctrl144) + reg_addr += CONTROL_144_SIZE; + + /* control 145 */ + if (f54->query_36.has_ctrl145) + reg_addr += CONTROL_145_SIZE; + + /* control 146 */ + if (f54->query_36.has_ctrl146) + reg_addr += CONTROL_146_SIZE; + + /* control 147 */ + if (f54->query_38.has_ctrl147) + reg_addr += CONTROL_147_SIZE; + + /* control 148 */ + if (f54->query_38.has_ctrl148) + reg_addr += CONTROL_148_SIZE; + + /* control 149 */ + if (f54->query_38.has_ctrl149) { + control->reg_149 = kzalloc(sizeof(*(control->reg_149)), + GFP_KERNEL); + if (!control->reg_149) + goto exit_no_mem; + control->reg_149->address = reg_addr; + reg_addr += CONTROL_149_SIZE; + } + + /* controls 150 to 162 reserved */ + + /* control 163 */ + if (f54->query_40.has_ctrl163_query41) + reg_addr += CONTROL_163_SIZE; + + /* control 164 reserved */ + + /* control 165 */ + if (f54->query_40.has_ctrl165_query42) + reg_addr += CONTROL_165_SIZE; + + /* control 166 reserved */ + + /* control 167 */ + if (f54->query_40.has_ctrl167) + reg_addr += CONTROL_167_SIZE; + + /* controls 168 to 175 reserved */ + + /* control 176 */ + if (f54->query_46.has_ctrl176) + reg_addr += CONTROL_176_SIZE; + + /* controls 177 178 reserved */ + + /* control 179 */ + if (f54->query_46.has_ctrl179) + reg_addr += CONTROL_179_SIZE; + + /* controls 180 to 187 reserved */ + + /* control 188 */ + if (f54->query_49.has_ctrl188) { + control->reg_188 = kzalloc(sizeof(*(control->reg_188)), + GFP_KERNEL); + if (!control->reg_188) + goto exit_no_mem; + control->reg_188->address = reg_addr; + reg_addr += CONTROL_188_SIZE; + } + + return 0; + +exit_no_mem: + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for control registers\n", + __func__); + return -ENOMEM; +} + +static int test_set_queries(void) +{ + int retval; + unsigned char offset; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr, + f54->query.data, + sizeof(f54->query.data)); + if (retval < 0) + return retval; + + offset = sizeof(f54->query.data); + + /* query 12 */ + if (f54->query.has_sense_frequency_control == 0) + offset -= 1; + + /* query 13 */ + if (f54->query.has_query13) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_13.data, + sizeof(f54->query_13.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 14 */ + if (f54->query_13.has_ctrl87) + offset += 1; + + /* query 15 */ + if (f54->query.has_query15) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_15.data, + sizeof(f54->query_15.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 16 */ + if (f54->query_15.has_query16) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_16.data, + sizeof(f54->query_16.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 17 */ + if (f54->query_16.has_query17) + offset += 1; + + /* query 18 */ + if (f54->query_16.has_ctrl94_query18) + offset += 1; + + /* query 19 */ + if (f54->query_16.has_ctrl95_query19) + offset += 1; + + /* query 20 */ + if (f54->query_15.has_query20) + offset += 1; + + /* query 21 */ + if (f54->query_15.has_query21) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_21.data, + sizeof(f54->query_21.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 22 */ + if (f54->query_15.has_query22) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_22.data, + sizeof(f54->query_22.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 23 */ + if (f54->query_22.has_query23) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_23.data, + sizeof(f54->query_23.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 24 */ + if (f54->query_21.has_query24_data18) + offset += 1; + + /* query 25 */ + if (f54->query_15.has_query25) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_25.data, + sizeof(f54->query_25.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 26 */ + if (f54->query_22.has_ctrl103_query26) + offset += 1; + + /* query 27 */ + if (f54->query_25.has_query27) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_27.data, + sizeof(f54->query_27.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 28 */ + if (f54->query_22.has_query28) + offset += 1; + + /* query 29 */ + if (f54->query_27.has_query29) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_29.data, + sizeof(f54->query_29.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 30 */ + if (f54->query_29.has_query30) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_30.data, + sizeof(f54->query_30.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 31 */ + if (f54->query_30.has_ctrl122_query31) + offset += 1; + + /* query 32 */ + if (f54->query_30.has_query32) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_32.data, + sizeof(f54->query_32.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 33 */ + if (f54->query_32.has_query33) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_33.data, + sizeof(f54->query_33.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 34 */ + if (f54->query_32.has_query34) + offset += 1; + + /* query 35 */ + if (f54->query_32.has_query35) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_35.data, + sizeof(f54->query_35.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 36 */ + if (f54->query_33.has_query36) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_36.data, + sizeof(f54->query_36.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 37 */ + if (f54->query_36.has_query37) + offset += 1; + + /* query 38 */ + if (f54->query_36.has_query38) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_38.data, + sizeof(f54->query_38.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 39 */ + if (f54->query_38.has_query39) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_39.data, + sizeof(f54->query_39.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 40 */ + if (f54->query_39.has_query40) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_40.data, + sizeof(f54->query_40.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 41 */ + if (f54->query_40.has_ctrl163_query41) + offset += 1; + + /* query 42 */ + if (f54->query_40.has_ctrl165_query42) + offset += 1; + + /* query 43 */ + if (f54->query_40.has_query43) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_43.data, + sizeof(f54->query_43.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* queries 44 45 reserved */ + + /* query 46 */ + if (f54->query_43.has_query46) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_46.data, + sizeof(f54->query_46.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 47 */ + if (f54->query_46.has_query47) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_47.data, + sizeof(f54->query_47.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 48 reserved */ + + /* query 49 */ + if (f54->query_47.has_query49) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_49.data, + sizeof(f54->query_49.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 50 */ + if (f54->query_49.has_query50) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_50.data, + sizeof(f54->query_50.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 51 */ + if (f54->query_50.has_query51) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f54->query_base_addr + offset, + f54->query_51.data, + sizeof(f54->query_51.data)); + if (retval < 0) + return retval; + offset += 1; + } + + return 0; +} + +static void test_f54_set_regs(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn_desc *fd, + unsigned int intr_count, + unsigned char page) +{ + unsigned char ii; + unsigned char intr_offset; + + f54->query_base_addr = fd->query_base_addr | (page << 8); + f54->control_base_addr = fd->ctrl_base_addr | (page << 8); + f54->data_base_addr = fd->data_base_addr | (page << 8); + f54->command_base_addr = fd->cmd_base_addr | (page << 8); + + f54->intr_reg_num = (intr_count + 7) / 8; + if (f54->intr_reg_num != 0) + f54->intr_reg_num -= 1; + + f54->intr_mask = 0; + intr_offset = intr_count % 8; + for (ii = intr_offset; + ii < (fd->intr_src_count + intr_offset); + ii++) { + f54->intr_mask |= 1 << ii; + } + + return; +} + +static int test_f55_set_queries(void) +{ + int retval; + unsigned char offset; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr, + f55->query.data, + sizeof(f55->query.data)); + if (retval < 0) + return retval; + + offset = sizeof(f55->query.data); + + /* query 3 */ + if (f55->query.has_single_layer_multi_touch) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_3.data, + sizeof(f55->query_3.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 4 */ + if ((f55->query.has_single_layer_multi_touch) && + (f55->query_3.has_ctrl9)) + offset += 1; + + /* query 5 */ + if (f55->query.has_query5) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_5.data, + sizeof(f55->query_5.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* queries 6 7 */ + if (f55->query.curve_compensation_mode == 0x3) + offset += 2; + + /* query 8 */ + if ((f55->query.has_single_layer_multi_touch) && + f55->query_3.has_ctrl8) + offset += 1; + + /* query 9 */ + if ((f55->query.has_single_layer_multi_touch) && + f55->query_3.has_query9) + offset += 1; + + /* queries 10 11 12 13 14 15 16 */ + if ((f55->query.has_query5) && (f55->query_5.has_basis_function)) + offset += 7; + + /* query 17 */ + if ((f55->query.has_query5) && (f55->query_5.has_query17)) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_17.data, + sizeof(f55->query_17.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 18 */ + if ((f55->query.has_query5) && + (f55->query_5.has_query17) && + (f55->query_17.has_query18)) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_18.data, + sizeof(f55->query_18.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 22 */ + if ((f55->query.has_query5) && + (f55->query_5.has_query17) && + (f55->query_17.has_query18) && + (f55->query_18.has_query22)) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_22.data, + sizeof(f55->query_22.data)); + if (retval < 0) + return retval; + offset += 1; + } + + /* query 23 */ + if ((f55->query.has_query5) && + (f55->query_5.has_query17) && + (f55->query_17.has_query18) && + (f55->query_18.has_query22) && + (f55->query_22.has_query23)) { + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->query_base_addr + offset, + f55->query_23.data, + sizeof(f55->query_23.data)); + if (retval < 0) + return retval; + offset += 1; + + f55->amp_sensor = f55->query_23.amp_sensor_enabled; + f55->size_of_column2mux = f55->query_23.size_of_column2mux; + } + + return 0; +} + +static void test_f55_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char rx_electrodes = f54->query.num_of_rx_electrodes; + unsigned char tx_electrodes = f54->query.num_of_tx_electrodes; + + retval = test_f55_set_queries(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read f55 query registers\n", + __func__); + return; + } + + if (!f55->query.has_sensor_assignment) + return; + + f55->tx_assignment = kzalloc(tx_electrodes, GFP_KERNEL); + f55->rx_assignment = kzalloc(rx_electrodes, GFP_KERNEL); + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->control_base_addr + SENSOR_TX_MAPPING_OFFSET, + f55->tx_assignment, + tx_electrodes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read f55 tx assignment\n", + __func__); + return; + } + + retval = synaptics_rmi4_reg_read(rmi4_data, + f55->control_base_addr + SENSOR_RX_MAPPING_OFFSET, + f55->rx_assignment, + rx_electrodes); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read f55 rx assignment\n", + __func__); + return; + } + + f54->tx_assigned = 0; + for (ii = 0; ii < tx_electrodes; ii++) { + if (f55->tx_assignment[ii] != 0xff) + f54->tx_assigned++; + } + + f54->rx_assigned = 0; + for (ii = 0; ii < rx_electrodes; ii++) { + if (f55->rx_assignment[ii] != 0xff) + f54->rx_assigned++; + } + + if (f55->amp_sensor) { + f54->tx_assigned = f55->size_of_column2mux; + f54->rx_assigned /= 2; + } + + return; +} + +static void test_f55_set_regs(struct synaptics_rmi4_data *rmi4_data, + struct synaptics_rmi4_fn_desc *fd, + unsigned char page) +{ + f55 = kzalloc(sizeof(*f55), GFP_KERNEL); + if (!f55) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for f55\n", + __func__); + return; + } + + f55->query_base_addr = fd->query_base_addr | (page << 8); + f55->control_base_addr = fd->ctrl_base_addr | (page << 8); + f55->data_base_addr = fd->data_base_addr | (page << 8); + f55->command_base_addr = fd->cmd_base_addr | (page << 8); + + return; +} + +static int test_scan_pdt(void) +{ + int retval; + unsigned char intr_count = 0; + unsigned char page; + unsigned short addr; + bool f54found = false; + bool f55found = false; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_data *rmi4_data = f54->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (!rmi_fd.fn_number) + break; + + switch (rmi_fd.fn_number) { + case SYNAPTICS_RMI4_F54: + test_f54_set_regs(rmi4_data, + &rmi_fd, intr_count, page); + f54found = true; + break; + case SYNAPTICS_RMI4_F55: + test_f55_set_regs(rmi4_data, + &rmi_fd, page); + f55found = true; + break; + default: + break; + } + + if (f54found && f55found) + goto pdt_done; + + intr_count += rmi_fd.intr_src_count; + } + } + + if (!f54found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F54\n", + __func__); + return -EINVAL; + } + +pdt_done: + return 0; +} + +static void synaptics_rmi4_test_attn(struct synaptics_rmi4_data *rmi4_data, + unsigned char intr_mask) +{ + if (!f54) + return; + + if (f54->intr_mask & intr_mask) + queue_work(f54->test_report_workqueue, &f54->test_report_work); + + return; +} + +static int synaptics_rmi4_test_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (f54) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + f54 = kzalloc(sizeof(*f54), GFP_KERNEL); + if (!f54) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for f54\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + f54->rmi4_data = rmi4_data; + + f55 = NULL; + + retval = test_scan_pdt(); + if (retval < 0) + goto exit_free_mem; + + retval = test_set_queries(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read f54 query registers\n", + __func__); + goto exit_free_mem; + } + + f54->tx_assigned = f54->query.num_of_tx_electrodes; + f54->rx_assigned = f54->query.num_of_rx_electrodes; + + retval = test_set_controls(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set up f54 control registers\n", + __func__); + goto exit_free_control; + } + + test_set_data(); + + if (f55) + test_f55_init(rmi4_data); + + if (rmi4_data->external_afe_buttons) + f54->tx_assigned++; + + retval = test_set_sysfs(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs entries\n", + __func__); + goto exit_sysfs; + } + + f54->test_report_workqueue = + create_singlethread_workqueue("test_report_workqueue"); + INIT_WORK(&f54->test_report_work, test_report_work); + + hrtimer_init(&f54->watchdog, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + f54->watchdog.function = test_get_report_timeout; + INIT_WORK(&f54->timeout_work, test_timeout_work); + + mutex_init(&f54->status_mutex); + f54->status = STATUS_IDLE; + + return 0; + +exit_sysfs: + if (f55) { + kfree(f55->tx_assignment); + kfree(f55->rx_assignment); + } + +exit_free_control: + test_free_control_mem(); + +exit_free_mem: + kfree(f55); + f55 = NULL; + kfree(f54); + f54 = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_test_remove(struct synaptics_rmi4_data *rmi4_data) +{ + if (!f54) + goto exit; + + hrtimer_cancel(&f54->watchdog); + + cancel_work_sync(&f54->test_report_work); + flush_workqueue(f54->test_report_workqueue); + destroy_workqueue(f54->test_report_workqueue); + + test_remove_sysfs(); + + if (f55) { + kfree(f55->tx_assignment); + kfree(f55->rx_assignment); + } + + test_free_control_mem(); + + if (f54->data_buffer_size) + kfree(f54->report_data); + + kfree(f55); + f55 = NULL; + + kfree(f54); + f54 = NULL; + +exit: + complete(&test_remove_complete); + + return; +} + +static void synaptics_rmi4_test_reset(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + + if (!f54) { + synaptics_rmi4_test_init(rmi4_data); + return; + } + + if (f55) { + kfree(f55->tx_assignment); + kfree(f55->rx_assignment); + } + + test_free_control_mem(); + + kfree(f55); + f55 = NULL; + + retval = test_scan_pdt(); + if (retval < 0) + goto exit_free_mem; + + retval = test_set_queries(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to read f54 query registers\n", + __func__); + goto exit_free_mem; + } + + f54->tx_assigned = f54->query.num_of_tx_electrodes; + f54->rx_assigned = f54->query.num_of_rx_electrodes; + + retval = test_set_controls(); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to set up f54 control registers\n", + __func__); + goto exit_free_control; + } + + test_set_data(); + + if (f55) + test_f55_init(rmi4_data); + + if (rmi4_data->external_afe_buttons) + f54->tx_assigned++; + + f54->status = STATUS_IDLE; + + return; + +exit_free_control: + test_free_control_mem(); + +exit_free_mem: + hrtimer_cancel(&f54->watchdog); + + cancel_work_sync(&f54->test_report_work); + flush_workqueue(f54->test_report_workqueue); + destroy_workqueue(f54->test_report_workqueue); + + test_remove_sysfs(); + + if (f54->data_buffer_size) + kfree(f54->report_data); + + kfree(f55); + f55 = NULL; + + kfree(f54); + f54 = NULL; + + return; +} + +static struct synaptics_rmi4_exp_fn test_module = { + .fn_type = RMI_TEST_REPORTING, + .init = synaptics_rmi4_test_init, + .remove = synaptics_rmi4_test_remove, + .reset = synaptics_rmi4_test_reset, + .reinit = NULL, + .early_suspend = NULL, + .suspend = NULL, + .resume = NULL, + .late_resume = NULL, + .attn = synaptics_rmi4_test_attn, +}; + +static int __init rmi4_test_module_init(void) +{ + synaptics_rmi4_new_function(&test_module, true); + + return 0; +} + +static void __exit rmi4_test_module_exit(void) +{ + synaptics_rmi4_new_function(&test_module, false); + + wait_for_completion(&test_remove_complete); + + return; +} + +module_init(rmi4_test_module_init); +module_exit(rmi4_test_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Test Reporting Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_video.c b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_video.c new file mode 100644 index 000000000000..493410ee0472 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_video.c @@ -0,0 +1,416 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <linux/input/synaptics_dsx.h> +#include "synaptics_dsx_core.h" + +#define SYSFS_FOLDER_NAME "video" + +/* +#define RMI_DCS_SUSPEND_RESUME +*/ + +static ssize_t video_sysfs_dcs_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static ssize_t video_sysfs_param_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static int video_send_dcs_command(unsigned char command_opcode); + +struct f38_command { + union { + struct { + unsigned char command_opcode; + unsigned char register_access:1; + unsigned char gamma_page:1; + unsigned char f38_control1_b2__7:6; + unsigned char parameter_field_1; + unsigned char parameter_field_2; + unsigned char parameter_field_3; + unsigned char parameter_field_4; + unsigned char send_to_dcs:1; + unsigned char f38_command6_b1__7:7; + } __packed; + unsigned char data[7]; + }; +}; + +struct synaptics_rmi4_video_handle { + unsigned char param; + unsigned short query_base_addr; + unsigned short control_base_addr; + unsigned short data_base_addr; + unsigned short command_base_addr; + struct synaptics_rmi4_data *rmi4_data; + struct kobject *sysfs_dir; +}; + +#ifdef RMI_DCS_SUSPEND_RESUME +struct dcs_command { + unsigned char command; + unsigned int wait_time; +}; + +static struct dcs_command suspend_sequence[] = { + { + .command = 0x28, + .wait_time = 200, + }, + { + .command = 0x10, + .wait_time = 200, + }, +}; + +static struct dcs_command resume_sequence[] = { + { + .command = 0x11, + .wait_time = 200, + }, + { + .command = 0x29, + .wait_time = 200, + }, +}; +#endif + +static struct device_attribute attrs[] = { + __ATTR(dcs_write, S_IWUGO, + synaptics_rmi4_show_error, + video_sysfs_dcs_write_store), + __ATTR(param, S_IWUGO, + synaptics_rmi4_show_error, + video_sysfs_param_store), +}; + +static struct synaptics_rmi4_video_handle *video; + +DECLARE_COMPLETION(video_remove_complete); + +static ssize_t video_sysfs_dcs_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval; + unsigned int input; + + if (sscanf(buf, "%x", &input) != 1) + return -EINVAL; + + retval = video_send_dcs_command((unsigned char)input); + if (retval < 0) + return retval; + + return count; +} + +static ssize_t video_sysfs_param_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int input; + + if (sscanf(buf, "%x", &input) != 1) + return -EINVAL; + + video->param = (unsigned char)input; + + return count; +} + +static int video_send_dcs_command(unsigned char command_opcode) +{ + int retval; + struct f38_command command; + struct synaptics_rmi4_data *rmi4_data = video->rmi4_data; + + memset(&command, 0x00, sizeof(command)); + + command.command_opcode = command_opcode; + command.parameter_field_1 = video->param; + command.send_to_dcs = 1; + + video->param = 0; + + retval = synaptics_rmi4_reg_write(rmi4_data, + video->command_base_addr, + command.data, + sizeof(command.data)); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to send DCS command\n", + __func__); + return retval; + } + + return 0; +} + +static int video_scan_pdt(void) +{ + int retval; + unsigned char page; + unsigned short addr; + bool f38_found = false; + struct synaptics_rmi4_fn_desc rmi_fd; + struct synaptics_rmi4_data *rmi4_data = video->rmi4_data; + + for (page = 0; page < PAGES_TO_SERVICE; page++) { + for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) { + addr |= (page << 8); + + retval = synaptics_rmi4_reg_read(rmi4_data, + addr, + (unsigned char *)&rmi_fd, + sizeof(rmi_fd)); + if (retval < 0) + return retval; + + addr &= ~(MASK_8BIT << 8); + + if (!rmi_fd.fn_number) + break; + + if (rmi_fd.fn_number == SYNAPTICS_RMI4_F38) { + f38_found = true; + goto f38_found; + } + } + } + + if (!f38_found) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to find F38\n", + __func__); + return -EINVAL; + } + +f38_found: + video->query_base_addr = rmi_fd.query_base_addr | (page << 8); + video->control_base_addr = rmi_fd.ctrl_base_addr | (page << 8); + video->data_base_addr = rmi_fd.data_base_addr | (page << 8); + video->command_base_addr = rmi_fd.cmd_base_addr | (page << 8); + + return 0; +} + +static int synaptics_rmi4_video_init(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char attr_count; + + if (video) { + dev_dbg(rmi4_data->pdev->dev.parent, + "%s: Handle already exists\n", + __func__); + return 0; + } + + video = kzalloc(sizeof(*video), GFP_KERNEL); + if (!video) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to alloc mem for video\n", + __func__); + retval = -ENOMEM; + goto exit; + } + + video->rmi4_data = rmi4_data; + + retval = video_scan_pdt(); + if (retval < 0) { + retval = 0; + goto exit_scan_pdt; + } + + video->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME, + &rmi4_data->input_dev->dev.kobj); + if (!video->sysfs_dir) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs directory\n", + __func__); + retval = -ENODEV; + goto exit_sysfs_dir; + } + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) { + retval = sysfs_create_file(video->sysfs_dir, + &attrs[attr_count].attr); + if (retval < 0) { + dev_err(rmi4_data->pdev->dev.parent, + "%s: Failed to create sysfs attributes\n", + __func__); + retval = -ENODEV; + goto exit_sysfs_attrs; + } + } + + return 0; + +exit_sysfs_attrs: + for (attr_count--; attr_count >= 0; attr_count--) + sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr); + + kobject_put(video->sysfs_dir); + +exit_sysfs_dir: +exit_scan_pdt: + kfree(video); + video = NULL; + +exit: + return retval; +} + +static void synaptics_rmi4_video_remove(struct synaptics_rmi4_data *rmi4_data) +{ + unsigned char attr_count; + + if (!video) + goto exit; + + for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) + sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr); + + kobject_put(video->sysfs_dir); + + kfree(video); + video = NULL; + +exit: + complete(&video_remove_complete); + + return; +} + +static void synaptics_rmi4_video_reset(struct synaptics_rmi4_data *rmi4_data) +{ + if (!video) + synaptics_rmi4_video_init(rmi4_data); + + return; +} + +#ifdef RMI_DCS_SUSPEND_RESUME +static void synaptics_rmi4_video_suspend(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char command; + unsigned char num_of_cmds; + + if (!video) + return; + + num_of_cmds = ARRAY_SIZE(suspend_sequence); + + for (ii = 0; ii < num_of_cmds; ii++) { + command = suspend_sequence[ii].command; + retval = video_send_dcs_command(command); + if (retval < 0) + return; + msleep(suspend_sequence[ii].wait_time); + } + + return; +} + +static void synaptics_rmi4_video_resume(struct synaptics_rmi4_data *rmi4_data) +{ + int retval; + unsigned char ii; + unsigned char command; + unsigned char num_of_cmds; + + if (!video) + return; + + num_of_cmds = ARRAY_SIZE(resume_sequence); + + for (ii = 0; ii < num_of_cmds; ii++) { + command = resume_sequence[ii].command; + retval = video_send_dcs_command(command); + if (retval < 0) + return; + msleep(resume_sequence[ii].wait_time); + } + + return; +} +#endif + +static struct synaptics_rmi4_exp_fn video_module = { + .fn_type = RMI_VIDEO, + .init = synaptics_rmi4_video_init, + .remove = synaptics_rmi4_video_remove, + .reset = synaptics_rmi4_video_reset, + .reinit = NULL, + .early_suspend = NULL, +#ifdef RMI_DCS_SUSPEND_RESUME + .suspend = synaptics_rmi4_video_suspend, + .resume = synaptics_rmi4_video_resume, +#else + .suspend = NULL, + .resume = NULL, +#endif + .late_resume = NULL, + .attn = NULL, +}; + +static int __init rmi4_video_module_init(void) +{ + synaptics_rmi4_new_function(&video_module, true); + + return 0; +} + +static void __exit rmi4_video_module_exit(void) +{ + synaptics_rmi4_new_function(&video_module, false); + + wait_for_completion(&video_remove_complete); + + return; +} + +module_init(rmi4_video_module_init); +module_exit(rmi4_video_module_exit); + +MODULE_AUTHOR("Synaptics, Inc."); +MODULE_DESCRIPTION("Synaptics DSX Video Module"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/input/synaptics_dsx_v2_6.h b/include/linux/input/synaptics_dsx_v2_6.h new file mode 100644 index 000000000000..7deef3551b83 --- /dev/null +++ b/include/linux/input/synaptics_dsx_v2_6.h @@ -0,0 +1,111 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved. + * + * 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. + * + * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS + * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, + * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS. + * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION + * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED + * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES + * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS' + * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S. + * DOLLARS. + */ + +#ifndef _SYNAPTICS_DSX_H_ +#define _SYNAPTICS_DSX_H_ + +#define PLATFORM_DRIVER_NAME "synaptics_dsx" +#define STYLUS_DRIVER_NAME "synaptics_dsx_stylus" +#define ACTIVE_PEN_DRIVER_NAME "synaptics_dsx_active_pen" +#define PROXIMITY_DRIVER_NAME "synaptics_dsx_proximity" +#define GESTURE_DRIVER_NAME "synaptics_dsx_gesture" +#define I2C_DRIVER_NAME "synaptics_dsx_i2c" +#define SPI_DRIVER_NAME "synaptics_dsx_spi" + +/* + * struct synaptics_dsx_button_map - button map + * @nbuttons: number of buttons + * @map: pointer to array of button codes + */ +struct synaptics_dsx_button_map { + unsigned char nbuttons; + unsigned int *map; +}; + +/* + * struct synaptics_dsx_board_data - DSX board data + * @x_flip: x flip flag + * @y_flip: y flip flag + * @swap_axes: swap axes flag + * @irq_gpio: attention interrupt GPIO + * @irq_on_state: attention interrupt active state + * @power_gpio: power switch GPIO + * @power_on_state: power switch active state + * @reset_gpio: reset GPIO + * @reset_on_state: reset active state + * @max_y_for_2d: maximum y value for 2D area when virtual buttons are present + * @irq_flags: IRQ flags + * @i2c_addr: I2C slave address + * @ub_i2c_addr: microbootloader mode I2C slave address + * @device_descriptor_addr: HID device descriptor address + * @panel_x: x-axis resolution of display panel + * @panel_y: y-axis resolution of display panel + * @power_delay_ms: delay time to wait after powering up device + * @reset_delay_ms: delay time to wait after resetting device + * @reset_active_ms: reset active time + * @byte_delay_us: delay time between two bytes of SPI data + * @block_delay_us: delay time between two SPI transfers + * @pwr_reg_name: pointer to name of regulator for power control + * @bus_reg_name: pointer to name of regulator for bus pullup control + * @cap_button_map: pointer to 0D button map + * @vir_button_map: pointer to virtual button map + */ +struct synaptics_dsx_board_data { + bool x_flip; + bool y_flip; + bool swap_axes; + int irq_gpio; + int irq_on_state; + int power_gpio; + int power_on_state; + int reset_gpio; + int reset_on_state; + int max_y_for_2d; + unsigned long irq_flags; + unsigned short i2c_addr; + unsigned short ub_i2c_addr; + unsigned short device_descriptor_addr; + unsigned int panel_x; + unsigned int panel_y; + unsigned int power_delay_ms; + unsigned int reset_delay_ms; + unsigned int reset_active_ms; + unsigned int byte_delay_us; + unsigned int block_delay_us; + const char *pwr_reg_name; + const char *bus_reg_name; + struct synaptics_dsx_button_map *cap_button_map; + struct synaptics_dsx_button_map *vir_button_map; +}; + +#endif |