diff mbox series

input/touchscreen: synaptics_tcm_oncell: add driver

Message ID 20240327214643.7055-1-friederhannenheim@riseup.net (mailing list archive)
State New
Headers show
Series input/touchscreen: synaptics_tcm_oncell: add driver | expand

Commit Message

Frieder Hannenheim March 27, 2024, 9:39 p.m. UTC
This is a bit of a stripped down and partially reworked driver for the
synaptics_tcm_oncell touchscreen. I based my work off the driver in the
LineageOS kernel found at
https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
lineage-20. The code was originally written by OnePlus developers but
I'm not sure how to credit them correctly.

Currently the driver is in a pretty good shape, the only thing that is
not working is setting a report config. To me it looks like some data is
sent by the touchscreen firmware after setting the report config that is
making the irq handler crash. Sadly I haven't been able to test out why.
The driver works fine also with the default touch report config so maybe
we can just use that and not set our own. 

Signed-off-by: Frieder Hannenheim <friederhannenheim@riseup.net>
---
 .../input/touchscreen/syna,s3908.yaml         |   63 +
 drivers/input/touchscreen/Kconfig             |   11 +
 drivers/input/touchscreen/Makefile            |    1 +
 .../input/touchscreen/synaptics_tcm_oncell.c  | 1104 +++++++++++++++++
 4 files changed, 1179 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
 create mode 100644 drivers/input/touchscreen/synaptics_tcm_oncell.c

Comments

Frieder Hannenheim March 27, 2024, 10:19 p.m. UTC | #1
Looks like I forgot the most important thing: What is this actually used for. The Synaptics TCM Oncell Touchscreen is built into multiple smartphones: The OnePlus 8T (my device), Realme GT2 Pro, OnePlus 9 Pro, and more. I'm currently trying to get the OnePlus 8T working on mainline Linux that's why I wrote this driver. 

On 2024-03-27 22:39, Frieder Hannenheim wrote:
> This is a bit of a stripped down and partially reworked driver for the
> synaptics_tcm_oncell touchscreen. I based my work off the driver in the
> LineageOS kernel found at
> https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
> lineage-20. The code was originally written by OnePlus developers but
> I'm not sure how to credit them correctly.
> 
> Currently the driver is in a pretty good shape, the only thing that is
> not working is setting a report config. To me it looks like some data is
> sent by the touchscreen firmware after setting the report config that is
> making the irq handler crash. Sadly I haven't been able to test out why.
> The driver works fine also with the default touch report config so maybe
> we can just use that and not set our own. 
> 
> Signed-off-by: Frieder Hannenheim <friederhannenheim@riseup.net>
> ---
>  .../input/touchscreen/syna,s3908.yaml         |   63 +
>  drivers/input/touchscreen/Kconfig             |   11 +
>  drivers/input/touchscreen/Makefile            |    1 +
>  .../input/touchscreen/synaptics_tcm_oncell.c  | 1104 +++++++++++++++++
>  4 files changed, 1179 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
>  create mode 100644 drivers/input/touchscreen/synaptics_tcm_oncell.c
> 
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
> new file mode 100644
> index 000000000000..1a85747e2f52
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
> @@ -0,0 +1,63 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/touchscreen/synaptics,s3908.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Synaptics s3908 touchscreen controller
> +
> +maintainers:
> +  - Frieder Hannenheim <frieder.hannenheim@riseup.net>
> +
> +allOf:
> +  - $ref: touchscreen.yaml#
> +
> +properties:
> +  compatible:
> +    const: syna,s3908
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  reset-gpios:
> +    maxItems: 1
> +    description: reset gpio the chip is connected to.
> +
> +  vdd-supply: true
> +  vcc-supply: true
> +
> +  max-objects: true
> +
> +unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - reset-gpios
> +  - vdd-supply
> +  - vcc-supply
> +  - max-objects
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        touchscreen@48 {
> +            compatible = "syna,s3908";
> +            reg = <0x48>;
> +            interrupt-parent = <&gpa1>;
> +            interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
> +            reset-gpios = <38 0>;
> +            vdd-supply = <&ldo30_reg>;
> +            vcc-supply = <&ldo31_reg>;
> +
> +            max-objects = <10>;
> +        };
> +    };
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 1f8b33c2b03d..eba31ae27391 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -502,6 +502,17 @@ config TOUCHSCREEN_S6SY761
>  
>  	  To compile this driver as module, choose M here: the
>  	  module will be called s6sy761.
> +	  
> +config TOUCHSCREEN_SYNA_TCM_ONCELL
> +	tristate "Synaptics TCM Oncell Touchscreen driver"
> +	depends on I2C
> +	help
> +	  Say Y if you have the Synaptics TCM Oncell driver
> +
> +	  If unsure, say N
> +
> +	  To compile this driver as module, choose M here: the
> +	  module will be called synaptics_tcm_oncell.
>  
>  config TOUCHSCREEN_GUNZE
>  	tristate "Gunze AHL-51S touchscreen"
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 7d52592f4290..f5395ccf09d5 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -86,6 +86,7 @@ obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
>  obj-$(CONFIG_TOUCHSCREEN_SUN4I)		+= sun4i-ts.o
>  obj-$(CONFIG_TOUCHSCREEN_SUR40)		+= sur40.o
>  obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI)	+= surface3_spi.o
> +obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM_ONCELL)	+= synaptics_tcm_oncell.o
>  obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC)	+= ti_am335x_tsc.o
>  obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
>  obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
> diff --git a/drivers/input/touchscreen/synaptics_tcm_oncell.c b/drivers/input/touchscreen/synaptics_tcm_oncell.c
> new file mode 100644
> index 000000000000..b874287f37af
> --- /dev/null
> +++ b/drivers/input/touchscreen/synaptics_tcm_oncell.c
> @@ -0,0 +1,1104 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + *  Driver for Synaptics TCM Oncell Touchscreens
> + *
> + *  Copyright (c) 2024 Frieder Hannenheim <friederhannenheim@riseup.net>
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <asm/unaligned.h>
> +#include <linux/delay.h>
> +#include <linux/input/mt.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regulator/consumer.h>
> +
> +/* Meta Information */
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Frieder Hannenheim");
> +MODULE_DESCRIPTION("A driver for Synaptics TCM Oncell Touchpanels");
> +
> +#define TOUCHPANEL_DEVICE "syna-tcm"
> +
> +#define POWERUP_TO_RESET_TIME 10
> +#define RESET_TO_NORMAL_TIME 80
> +
> +#define RESPONSE_TIMEOUT_MS 1000
> +
> +#define MESSAGE_HEADER_SIZE 4
> +#define MESSAGE_MARKER 0xA5
> +#define MESSAGE_PADDING 0x5A
> +
> +#define READ_LENGTH 9
> +#define MAX_I2C_RETRY_TIME 4
> +
> +#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */
> +#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */
> +
> +#define APP_STATUS_POLL_MS 100
> +#define TOUCH_REPORT_CONFIG_SIZE 128
> +
> +enum command {
> +	CMD_NONE = 0x00,
> +	CMD_CONTINUE_WRITE = 0x01,
> +	CMD_IDENTIFY = 0x02,
> +	CMD_RESET = 0x04,
> +	CMD_ENABLE_REPORT = 0x05,
> +	CMD_DISABLE_REPORT = 0x06,
> +	CMD_GET_BOOT_INFO = 0x10,
> +	CMD_ERASE_FLASH = 0x11,
> +	CMD_WRITE_FLASH = 0x12,
> +	CMD_READ_FLASH = 0x13,
> +	CMD_RUN_APPLICATION_FIRMWARE = 0x14,
> +	CMD_SPI_MASTER_WRITE_THEN_READ = 0x15,
> +	CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16,
> +	CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f,
> +	CMD_GET_APPLICATION_INFO = 0x20,
> +	CMD_GET_STATIC_CONFIG = 0x21,
> +	CMD_SET_STATIC_CONFIG = 0x22,
> +	CMD_GET_DYNAMIC_CONFIG = 0x23,
> +	CMD_SET_DYNAMIC_CONFIG = 0x24,
> +	CMD_GET_TOUCH_REPORT_CONFIG = 0x25,
> +	CMD_SET_TOUCH_REPORT_CONFIG = 0x26,
> +	CMD_REZERO = 0x27,
> +	CMD_COMMIT_CONFIG = 0x28,
> +	CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29,
> +	CMD_PRODUCTION_TEST = 0x2a,
> +	CMD_SET_CONFIG_ID = 0x2b,
> +	CMD_ENTER_DEEP_SLEEP = 0x2c,
> +	CMD_EXIT_DEEP_SLEEP = 0x2d,
> +	CMD_GET_TOUCH_INFO = 0x2e,
> +	CMD_GET_DATA_LOCATION = 0x2f,
> +	CMD_DOWNLOAD_CONFIG = 0xc0,
> +	CMD_GET_NSM_INFO = 0xc3,
> +	CMD_EXIT_ESD = 0xc4,
> +};
> +
> +enum touch_report_code {
> +	TOUCH_END = 0,
> +	TOUCH_FOREACH_ACTIVE_OBJECT = 1,
> +	TOUCH_FOREACH_OBJECT = 2,
> +	TOUCH_FOREACH_END = 3,
> +	TOUCH_PAD_TO_NEXT_BYTE = 4,
> +	TOUCH_TIMESTAMP = 5,
> +	TOUCH_OBJECT_N_INDEX = 6,
> +	TOUCH_OBJECT_N_CLASSIFICATION = 7,
> +	TOUCH_OBJECT_N_X_POSITION = 8,
> +	TOUCH_OBJECT_N_Y_POSITION = 9,
> +	TOUCH_OBJECT_N_Z = 10,
> +	TOUCH_OBJECT_N_X_WIDTH = 11,
> +	TOUCH_OBJECT_N_Y_WIDTH = 12,
> +	TOUCH_OBJECT_N_TX_POSITION_TIXELS = 13,
> +	TOUCH_OBJECT_N_RX_POSITION_TIXELS = 14,
> +	TOUCH_0D_BUTTONS_STATE = 15,
> +	TOUCH_GESTURE_DOUBLE_TAP = 16,
> +	TOUCH_FRAME_RATE = 17,
> +	TOUCH_POWER_IM = 18,
> +	TOUCH_CID_IM = 19,
> +	TOUCH_RAIL_IM = 20,
> +	TOUCH_CID_VARIANCE_IM = 21,
> +	TOUCH_NSM_FREQUENCY = 22,
> +	TOUCH_NSM_STATE = 23,
> +	TOUCH_NUM_OF_ACTIVE_OBJECTS = 23,
> +	TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME = 24,
> +	TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80,
> +	TOUCH_TUNING_SMALL_OBJECT_PARAMS = 0x81,
> +	TOUCH_TUNING_0D_BUTTONS_VARIANCE = 0x82,
> +	TOUCH_REPORT_GESTURE_SWIPE = 193,
> +	TOUCH_REPORT_GESTURE_CIRCLE = 194,
> +	TOUCH_REPORT_GESTURE_UNICODE = 195,
> +	TOUCH_REPORT_GESTURE_VEE = 196,
> +	TOUCH_REPORT_GESTURE_TRIANGLE = 197,
> +	TOUCH_REPORT_GESTURE_INFO = 198,
> +	TOUCH_REPORT_GESTURE_COORDINATE = 199,
> +	TOUCH_REPORT_CUSTOMER_GRIP_INFO = 203,
> +};
> +
> +enum touch_status {
> +	LIFT = 0,
> +	FINGER = 1,
> +	GLOVED_FINGER = 2,
> +	NOP = -1,
> +};
> +
> +enum status_code {
> +	STATUS_IDLE = 0x00,
> +	STATUS_OK = 0x01,
> +	STATUS_BUSY = 0x02,
> +	STATUS_CONTINUED_READ = 0x03,
> +	STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
> +	STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
> +	STATUS_NOT_IMPLEMENTED = 0x0e,
> +	STATUS_ERROR = 0x0f,
> +	STATUS_INVALID = 0xff,
> +};
> +
> +enum report_type {
> +	REPORT_IDENTIFY = 0x10,
> +	REPORT_TOUCH = 0x11,
> +	REPORT_DELTA = 0x12,
> +	REPORT_RAW = 0x13,
> +	REPORT_DEBUG = 0x14,
> +	REPORT_LOG = 0x1d,
> +	REPORT_TOUCH_HOLD = 0x20,
> +};
> +
> +enum syna_tcm_oncell_regulators {
> +	SYNA_TCM_ONCELL_REGULATOR_VDD,
> +	SYNA_TCM_ONCELL_REGULATOR_VCC,
> +};
> +
> +enum boot_mode {
> +	MODE_APPLICATION = 0x01,
> +	MODE_HOST_DOWNLOAD = 0x02,
> +	MODE_BOOTLOADER = 0x0b,
> +	MODE_TDDI_BOOTLOADER = 0x0c,
> +};
> +
> +enum app_status {
> +	APP_STATUS_OK = 0x00,
> +	APP_STATUS_BOOTING = 0x01,
> +	APP_STATUS_UPDATING = 0x02,
> +	APP_STATUS_BAD_APP_CONFIG = 0xff,
> +};
> +
> +struct syna_tcm_app_info {
> +	unsigned char version[2];
> +	unsigned char status[2];
> +	unsigned char static_config_size[2];
> +	unsigned char dynamic_config_size[2];
> +	unsigned char app_config_start_write_block[2];
> +	unsigned char app_config_size[2];
> +	unsigned char max_touch_report_config_size[2];
> +	unsigned char max_touch_report_payload_size[2];
> +	unsigned char customer_config_id[16];
> +	unsigned char max_x[2];
> +	unsigned char max_y[2];
> +	unsigned char max_objects[2];
> +	unsigned char num_of_buttons[2];
> +	unsigned char num_of_image_rows[2];
> +	unsigned char num_of_image_cols[2];
> +	unsigned char has_hybrid_data[2];
> +};
> +
> +struct syna_tcm_identification {
> +	unsigned char version;
> +	unsigned char mode;
> +	unsigned char part_number[16];
> +	unsigned char build_id[4];
> +	unsigned char max_write_size[2];
> +};
> +
> +struct syna_tcm_message_header {
> +	unsigned char marker;
> +	unsigned char code;
> +	unsigned char length[2];
> +};
> +
> +struct touch_data {
> +	unsigned char status;
> +	unsigned int x_pos;
> +	unsigned int y_pos;
> +	unsigned int x_width;
> +	unsigned int y_width;
> +	unsigned int z;
> +};
> +
> +struct syna_tcm_data {
> +	struct i2c_client *client;
> +	struct regulator_bulk_data regulators[2];
> +	struct input_dev *input;
> +	struct gpio_desc *reset_gpio;
> +	struct touchscreen_properties prop;
> +	struct syna_tcm_identification id_info;
> +	struct syna_tcm_app_info app_info;
> +
> +	unsigned char *response_buf;
> +	unsigned int response_length; // Response length including header
> +
> +	unsigned int app_status;
> +
> +	unsigned char *report_config;
> +	unsigned int report_size;
> +
> +	bool initialize_done;
> +
> +	unsigned int touchpanel_max_objects;
> +};
> +
> +DECLARE_COMPLETION(response_complete);
> +
> +static inline unsigned int le2_to_uint(const unsigned char *src)
> +{
> +	return (unsigned int)src[0] + (unsigned int)src[1] * 0x100;
> +}
> +
> +static inline unsigned int ceil_div(unsigned int dividend, unsigned int divisor)
> +{
> +	return (dividend + divisor - 1) / divisor;
> +}
> +
> +static int touch_i2c_continue_read(struct i2c_client *client,
> +				   unsigned char *data, unsigned short length)
> +{
> +	int retval;
> +	unsigned char retry;
> +	struct i2c_msg msg;
> +
> +	msg.addr = client->addr;
> +	msg.flags = I2C_M_RD;
> +	msg.len = length;
> +	msg.buf = data;
> +
> +	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
> +		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
> +			retval = length;
> +			break;
> +		}
> +		msleep(20);
> +	}
> +	if (retry == MAX_I2C_RETRY_TIME) {
> +		pr_warn("%s: I2C read over retry limit\n", __func__);
> +		retval = -EIO;
> +	}
> +	return retval;
> +}
> +
> +static int touch_i2c_continue_write(struct i2c_client *client,
> +				    unsigned char *data, unsigned short length)
> +{
> +	int retval;
> +	unsigned char retry;
> +	struct i2c_msg msg;
> +
> +	msg.addr = client->addr;
> +	msg.flags = 0;
> +	msg.buf = data;
> +	msg.len = length;
> +
> +	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
> +		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
> +			retval = length;
> +			break;
> +		}
> +		msleep(20);
> +	}
> +	if (retry == MAX_I2C_RETRY_TIME) {
> +		pr_warn("%s: I2C write over retry limit\n", __func__);
> +		retval = -EIO;
> +	}
> +	return retval;
> +}
> +
> +static int syna_tcm_continued_read(struct syna_tcm_data *tcm_info,
> +				   unsigned int payload_length,
> +				   unsigned char *message_buffer)
> +{
> +	int retval = 0;
> +	unsigned char marker = 0, code = 0;
> +	unsigned int total_length = 0, remaining_length = 0;
> +	unsigned char *temp_buffer;
> +
> +	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
> +	remaining_length = total_length - READ_LENGTH;
> +
> +	temp_buffer = kmalloc(remaining_length + 2, GFP_KERNEL);
> +	if (!temp_buffer)
> +		return -ENOMEM;
> +
> +	retval = touch_i2c_continue_read(tcm_info->client, temp_buffer,
> +					 remaining_length + 2);
> +	if (retval < 0) {
> +		pr_warn("touch_i2c_continue_read failed");
> +		return retval;
> +	}
> +
> +	marker = temp_buffer[0];
> +	code = temp_buffer[1];
> +
> +	if (marker != MESSAGE_MARKER) {
> +		pr_warn("incorrect header marker (0x%02x)\n", marker);
> +		return -EIO;
> +	}
> +
> +	if (code != STATUS_CONTINUED_READ) {
> +		pr_warn("incorrect header code (0x%02x)\n", code);
> +		return -EIO;
> +	}
> +
> +	memcpy(&message_buffer[READ_LENGTH], &temp_buffer[2], remaining_length);
> +	message_buffer[total_length - 1] = MESSAGE_PADDING;
> +	kfree(temp_buffer);
> +
> +	return 0;
> +}
> +
> +/**
> + * syna_tcm_raw_write() - write command/data to device without receiving
> + * response
> + *
> + * @tcm_info: handle of core module
> + * @command: command to send to device
> + * @data: data to send to device
> + * @length: length of data in bytes
> + * @add_length: wether the length of the message should be added after the command
> + *
> + * A command and its data, if any, are sent to the device.
> + */
> +static int syna_tcm_raw_write(struct syna_tcm_data *tcm_info,
> +			      unsigned char command, unsigned char *data,
> +			      unsigned int length, bool add_length)
> +{
> +	int retval = 0;
> +	unsigned char *out_buf;
> +
> +	unsigned int header_size = add_length && length ? 3 : 1;
> +
> +	out_buf = kmalloc(length + header_size, GFP_KERNEL);
> +	if (retval < 0)
> +		return retval;
> +
> +	out_buf[0] = command;
> +	if (add_length && length) {
> +		out_buf[1] = (unsigned char)length;
> +		out_buf[2] = (unsigned char)(length >> 8);
> +	}
> +
> +	if (length)
> +		memcpy(&out_buf[header_size], data, length);
> +
> +	retval = touch_i2c_continue_write(tcm_info->client, out_buf,
> +					  length + header_size);
> +	if (retval < 0) {
> +		kfree(out_buf);
> +		return retval;
> +	}
> +
> +	kfree(out_buf);
> +	return 0;
> +}
> +
> +/**
> + * syna_tcm_read_message() - read message from device
> + *
> + * @tcm_info: handle of core module
> + * @length: length of data in bytes in raw read mode
> + */
> +static int syna_tcm_read_message(struct syna_tcm_data *tcm_info,
> +				 unsigned int length)
> +{
> +	int retval = 0;
> +	unsigned int total_length = 0, payload_length = 0;
> +	unsigned char *message_buffer;
> +	struct syna_tcm_message_header *header = NULL;
> +
> +	message_buffer = kmalloc(READ_LENGTH, GFP_KERNEL);
> +	if (!message_buffer)
> +		return -ENOMEM;
> +
> +	retval = touch_i2c_continue_read(tcm_info->client, message_buffer,
> +					 READ_LENGTH);
> +	if (retval < 0)
> +		return retval;
> +
> +	header = (struct syna_tcm_message_header *)message_buffer;
> +	if (header->marker != MESSAGE_MARKER) {
> +		pr_warn("wrong header marker:0x%02x\n", header->marker);
> +		return -ENXIO;
> +	}
> +
> +	unsigned char report_code = header->code;
> +
> +	payload_length = le2_to_uint(header->length);
> +
> +	if (report_code <= STATUS_ERROR || report_code == STATUS_INVALID) {
> +		switch (report_code) {
> +		case STATUS_OK:
> +			break;
> +		case STATUS_CONTINUED_READ:
> +		case STATUS_IDLE:
> +		case STATUS_BUSY:
> +			payload_length = 0;
> +			kfree(message_buffer);
> +			return 0;
> +		default:
> +			if (report_code != STATUS_ERROR) {
> +				pr_warn("IO ERROR");
> +				kfree(message_buffer);
> +				return -EIO;
> +			}
> +		}
> +	}
> +
> +	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
> +
> +	unsigned char *temp_buffer = message_buffer;
> +
> +	message_buffer = kmalloc(total_length, GFP_KERNEL);
> +	if (!message_buffer)
> +		return -ENOMEM;
> +
> +	// Copy data already read to message_buffer
> +	memcpy(message_buffer, temp_buffer, READ_LENGTH);
> +	kfree(temp_buffer);
> +
> +	if (payload_length == 0) {
> +		message_buffer[total_length - 1] = MESSAGE_PADDING;
> +		goto check_padding;
> +	}
> +
> +	if (MESSAGE_HEADER_SIZE + payload_length > READ_LENGTH) {
> +		retval = syna_tcm_continued_read(tcm_info, payload_length,
> +						 message_buffer);
> +		if (retval < 0) {
> +			pr_warn("failed to do continued read\n");
> +			return retval;
> +		};
> +	}
> +
> +check_padding:
> +	if (message_buffer[total_length - 1] != MESSAGE_PADDING) {
> +		pr_warn("missing message padding");
> +		return -EIO;
> +	}
> +
> +	if (tcm_info->response_buf != NULL)
> +		kfree(tcm_info->response_buf);
> +
> +	tcm_info->response_buf = message_buffer;
> +	tcm_info->response_length = total_length;
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_write_message(struct syna_tcm_data *tcm_info,
> +				  unsigned char command, unsigned char *payload,
> +				  unsigned int length, unsigned char **resp_buf,
> +				  unsigned int *resp_length)
> +{
> +	int retval = 0;
> +
> +	if (resp_buf == NULL) {
> +		retval = syna_tcm_raw_write(tcm_info, command, payload, length,
> +					    false);
> +		goto exit;
> +	}
> +	reinit_completion(&response_complete);
> +
> +	tcm_info->response_buf = *resp_buf;
> +	tcm_info->response_length = *resp_length;
> +
> +	// Here write command to device
> +	retval = syna_tcm_raw_write(tcm_info, command, payload, length, true);
> +	if (retval != 0)
> +		goto exit;
> +
> +	retval = wait_for_completion_timeout(&response_complete,
> +					     RESPONSE_TIMEOUT_MS);
> +	if (retval == 0) {
> +		pr_warn("timed out waiting for response (command 0x%02x)\n",
> +			command);
> +		retval = -EIO;
> +	}
> +
> +exit:
> +	if (tcm_info->response_length < 0)
> +		return -1;
> +	if (resp_buf != NULL) {
> +		*resp_buf = tcm_info->response_buf;
> +		*resp_length = tcm_info->response_length;
> +	}
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_set_normal_report_config(struct syna_tcm_data *tcm_info)
> +{
> +	int retval;
> +	unsigned int idx = 0;
> +	unsigned int length;
> +
> +	length = le2_to_uint(tcm_info->app_info.max_touch_report_config_size);
> +
> +	if (length < TOUCH_REPORT_CONFIG_SIZE) {
> +		pr_warn("invalid maximum touch report config size: %d\n",
> +			length);
> +		return -EINVAL;
> +	}
> +
> +	unsigned char *report_buf = kmalloc(length, GFP_KERNEL);
> +
> +	if (!report_buf)
> +		return -ENOMEM;
> +
> +	report_buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT;
> +	report_buf[idx++] = TOUCH_OBJECT_N_INDEX;
> +	report_buf[idx++] = 4;
> +	report_buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION;
> +	report_buf[idx++] = 4;
> +	report_buf[idx++] = TOUCH_OBJECT_N_X_POSITION;
> +	report_buf[idx++] = 16;
> +	report_buf[idx++] = TOUCH_OBJECT_N_Y_POSITION;
> +	report_buf[idx++] = 16;
> +	report_buf[idx++] = TOUCH_OBJECT_N_X_WIDTH;
> +	report_buf[idx++] = 12;
> +	report_buf[idx++] = TOUCH_OBJECT_N_Y_WIDTH;
> +	report_buf[idx++] = 12;
> +	report_buf[idx++] = TOUCH_FOREACH_END;
> +	report_buf[idx++] = TOUCH_END;
> +
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_SET_TOUCH_REPORT_CONFIG,
> +					report_buf, length, &resp_buf,
> +					&resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to set report config");
> +		return retval;
> +	}
> +
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_get_report_config(struct syna_tcm_data *tcm_info)
> +{
> +	int retval;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_GET_TOUCH_REPORT_CONFIG,
> +					NULL, 0, &resp_buf, &resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to write command 0x%02x\n",
> +			CMD_GET_TOUCH_REPORT_CONFIG);
> +		return retval;
> +	}
> +
> +	tcm_info->report_size = resp_length - MESSAGE_HEADER_SIZE - 1;
> +
> +	kfree(tcm_info->report_config);
> +	tcm_info->report_config = kmalloc(tcm_info->report_size, GFP_KERNEL);
> +
> +	memcpy(tcm_info->report_config, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       tcm_info->report_size);
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_get_app_info(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +	unsigned int timeout = RESPONSE_TIMEOUT_MS;
> +
> +get_app_info:
> +	retval = syna_tcm_write_message(tcm_info, CMD_GET_APPLICATION_INFO,
> +					NULL, 0, &resp_buf, &resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to write command 0x%02x\n",
> +			CMD_GET_APPLICATION_INFO);
> +		goto exit;
> +	}
> +
> +	memcpy(&tcm_info->app_info, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       sizeof(tcm_info->app_info));
> +
> +	tcm_info->app_status = le2_to_uint(tcm_info->app_info.status);
> +
> +	if (tcm_info->app_status == APP_STATUS_BOOTING ||
> +	    tcm_info->app_status == APP_STATUS_UPDATING) {
> +		if (timeout > 0) {
> +			msleep(APP_STATUS_POLL_MS);
> +			timeout -= APP_STATUS_POLL_MS;
> +			goto get_app_info;
> +		}
> +	}
> +
> +	retval = 0;
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_identify(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_IDENTIFY, NULL, 0,
> +					&resp_buf, &resp_length);
> +	if (retval < 0)
> +		goto exit;
> +
> +	memcpy(&tcm_info->id_info, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       sizeof(tcm_info->id_info));
> +
> +	if (tcm_info->id_info.mode == MODE_APPLICATION)
> +		retval = syna_tcm_get_app_info(tcm_info);
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_run_application_firmware(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned int retry = 3;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +retry:
> +	retval = syna_tcm_write_message(tcm_info, CMD_RUN_APPLICATION_FIRMWARE,
> +					NULL, 0, &resp_buf, &resp_length);
> +
> +	if (retval < 0) {
> +		if (retry--)
> +			goto retry;
> +		goto exit;
> +	}
> +
> +	retval = syna_tcm_identify(tcm_info);
> +	if (retval < 0) {
> +		if (retry--)
> +			goto retry;
> +		goto exit;
> +	}
> +
> +	pr_info("syna_tcm: boot_mode: 0x%02x", tcm_info->id_info.mode);
> +
> +	if (tcm_info->id_info.mode != MODE_APPLICATION) {
> +		if (retry--)
> +			goto retry;
> +		retval = -EINVAL;
> +		goto exit;
> +	} else if (tcm_info->app_status != APP_STATUS_OK) {
> +		pr_warn("syna_tcm: application status = 0x%02x\n",
> +			tcm_info->app_status);
> +		if (retry--)
> +			goto retry;
> +	}
> +
> +	retval = 0;
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +/*
> + * syna_get_report_data - Retrieve data from touch report
> + *
> + * @tcm_info: handle of tcm module
> + * @offset: start bit of retrieved data
> + * @bits: total bits of retrieved data
> + * @data: pointer of data, at most 4 byte
> + * Retrieve data from the touch report based on the bit offset and bit length
> + * information from the touch report configuration.
> + */
> +static int syna_tcm_get_report_data(unsigned char *report_buf,
> +				    unsigned int report_buf_length,
> +				    unsigned int offset, unsigned int num_bits,
> +				    unsigned int *data)
> +{
> +	unsigned char mask = 0;
> +	unsigned char byte_data = 0;
> +	unsigned int output_data = 0;
> +	unsigned int bit_offset = offset % 8;
> +	unsigned int byte_offset = offset / 8;
> +	unsigned int data_bits = 0;
> +	unsigned int available_bits = 0;
> +	unsigned int remaining_bits = num_bits;
> +
> +	if (num_bits == 0 || num_bits > 32) {
> +		pr_warn("report value larger than 32 bits: %d\n", num_bits);
> +		memcpy(data, &report_buf[byte_offset], num_bits / 8);
> +		return 0;
> +	}
> +
> +	if ((offset + num_bits) > report_buf_length * 8) {
> +		pr_warn("report: offset and bits beyond total read length: %d vs %d bits",
> +			offset + num_bits, report_buf_length * 8);
> +		*data = 0;
> +		return -1;
> +	}
> +
> +	while (remaining_bits) {
> +		byte_data = report_buf[byte_offset];
> +		byte_data >>= bit_offset;
> +
> +		available_bits = 8 - bit_offset;
> +		data_bits = available_bits < remaining_bits ? available_bits :
> +							      remaining_bits;
> +		mask = 0xff >> (8 - data_bits);
> +
> +		byte_data &= mask;
> +
> +		output_data |= byte_data << (num_bits - remaining_bits);
> +
> +		bit_offset = 0;
> +		byte_offset += 1;
> +		remaining_bits -= data_bits;
> +	}
> +
> +	*data = output_data;
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_parse_touch_report(struct syna_tcm_data *tcm_info)
> +{
> +	int config_data_pointer = 0, obj = 0, foreach_start = 0, foreach_end,
> +	    retval;
> +	unsigned int offset = 0, num_bits, data = 0;
> +	unsigned char *report_config = tcm_info->report_config;
> +	unsigned int config_size = tcm_info->report_size;
> +
> +	unsigned char *report = &tcm_info->response_buf[MESSAGE_HEADER_SIZE];
> +	unsigned int report_size =
> +		tcm_info->response_length - MESSAGE_HEADER_SIZE - 1;
> +	unsigned char code;
> +
> +	struct touch_data *touch_data =
> +		kmalloc(sizeof(touch_data) * tcm_info->touchpanel_max_objects,
> +			GFP_KERNEL);
> +	memset(touch_data, 0,
> +	       sizeof(touch_data) * tcm_info->touchpanel_max_objects);
> +
> +	while (config_data_pointer < config_size) {
> +		code = report_config[config_data_pointer++];
> +		switch (code) {
> +		case TOUCH_END:
> +			goto exit;
> +		case TOUCH_FOREACH_ACTIVE_OBJECT:
> +			obj = 0;
> +			foreach_start = config_data_pointer;
> +			if (offset >= report_size * 8)
> +				goto exit;
> +			break;
> +		case TOUCH_FOREACH_END:
> +			foreach_end = config_data_pointer;
> +			if (offset < report_size * 8)
> +				config_data_pointer = foreach_start;
> +			break;
> +		// Ignored
> +		case TOUCH_OBJECT_N_TX_POSITION_TIXELS:
> +		case TOUCH_OBJECT_N_RX_POSITION_TIXELS:
> +		case TOUCH_FRAME_RATE:
> +		case TOUCH_0D_BUTTONS_STATE:
> +		case 30: // I don't know what command 30 is and it isn't in the downstream driver. After it comes a length of 8 bits and this is ignored
> +			num_bits = report_config[config_data_pointer++];
> +			offset += num_bits;
> +			break;
> +		case TOUCH_PAD_TO_NEXT_BYTE:
> +			offset = ceil_div(offset, 8) * 8;
> +			break;
> +		case TOUCH_OBJECT_N_INDEX:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &obj);
> +			if (retval < 0) {
> +				pr_warn("failed to get object index\n");
> +				return retval;
> +			}
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_CLASSIFICATION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object classification\n");
> +				return retval;
> +			}
> +			if (obj >= tcm_info->touchpanel_max_objects) {
> +				pr_warn("too many objects\n");
> +				return -1;
> +			}
> +			touch_data[obj].status = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_X_POSITION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object x position\n");
> +				return retval;
> +			}
> +			touch_data[obj].x_pos = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Y_POSITION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object y position\n");
> +				return retval;
> +			}
> +			touch_data[obj].y_pos = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Z:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object z\n");
> +				return retval;
> +			}
> +			touch_data[obj].z = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_X_WIDTH:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object x width\n");
> +				return retval;
> +			}
> +			touch_data[obj].x_width = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Y_WIDTH:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object y width\n");
> +				return retval;
> +			}
> +			touch_data[obj].y_width = data;
> +			offset += num_bits;
> +			break;
> +		default:
> +			pr_notice("unrecognized touch report config code: %d",
> +				  code);
> +		}
> +	}
> +
> +exit:
> +	for (int i = 0; i < tcm_info->touchpanel_max_objects; i++) {
> +		if (touch_data[i].status == NOP)
> +			continue;
> +
> +		bool lift = touch_data[i].status == LIFT ? true : false;
> +
> +		input_mt_slot(tcm_info->input, i);
> +		input_mt_report_slot_state(tcm_info->input, MT_TOOL_FINGER,
> +					   !lift);
> +
> +		int x_width = touch_data[i].x_width;
> +		int y_width = touch_data[i].y_width;
> +		int major_width = x_width > y_width ? x_width : y_width;
> +		int minor_width = x_width > y_width ? y_width : x_width;
> +		int x_is_major = x_width > y_width ? true : false;
> +
> +		if (!lift) {
> +			input_report_abs(tcm_info->input, ABS_MT_POSITION_X,
> +					 touch_data[i].x_pos);
> +			input_report_abs(tcm_info->input, ABS_MT_POSITION_Y,
> +					 touch_data[i].y_pos);
> +			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MAJOR,
> +					 major_width);
> +			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MINOR,
> +					 minor_width);
> +			input_report_abs(tcm_info->input, ABS_MT_ORIENTATION,
> +					 x_is_major);
> +			input_report_abs(tcm_info->input, ABS_MT_PRESSURE,
> +					 touch_data[i].z);
> +		}
> +
> +		input_sync(tcm_info->input);
> +	}
> +
> +	kfree(touch_data);
> +	return 0;
> +}
> +
> +static irqreturn_t syna_irq_handler(int irq, void *dev)
> +{
> +	int retval;
> +	struct syna_tcm_data *tcm_info = dev;
> +
> +	retval = syna_tcm_read_message(tcm_info, 0);
> +	if (retval < 0) {
> +		tcm_info->response_length = -1;
> +		goto exit;
> +	}
> +
> +	if (tcm_info->response_buf[1] == REPORT_TOUCH)
> +		syna_tcm_parse_touch_report(tcm_info);
> +
> +exit:
> +	if (tcm_info->response_buf[1] <= REPORT_IDENTIFY)
> +		complete(&response_complete);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int syna_tcm_power_on(struct syna_tcm_data *tcm_info)
> +{
> +	int ret;
> +
> +	ret = regulator_bulk_enable(ARRAY_SIZE(tcm_info->regulators),
> +				    tcm_info->regulators);
> +	if (ret)
> +		return ret;
> +
> +	gpiod_set_value(tcm_info->reset_gpio, false);
> +	msleep(POWERUP_TO_RESET_TIME);
> +	gpiod_set_value(tcm_info->reset_gpio, true);
> +	msleep(RESET_TO_NORMAL_TIME);
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_probe(struct i2c_client *client)
> +{
> +	struct syna_tcm_data *tcm_info;
> +	int err;
> +
> +	pr_info("starting probe for syna_tcm_oncell touchscreen");
> +
> +	if (!i2c_check_functionality(client->adapter,
> +				     I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA |
> +					     I2C_FUNC_SMBUS_I2C_BLOCK))
> +		return -ENODEV;
> +
> +	tcm_info = devm_kzalloc(&client->dev, sizeof(*tcm_info), GFP_KERNEL);
> +	if (!tcm_info)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(client, tcm_info);
> +	tcm_info->client = client;
> +	tcm_info->response_buf = NULL;
> +
> +	of_property_read_u32(client->dev.of_node, "max-objects",
> +			     &tcm_info->touchpanel_max_objects);
> +
> +	tcm_info->reset_gpio =
> +		gpiod_get_index(&client->dev, "reset", 0, GPIOD_OUT_HIGH);
> +
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VDD].supply = "vdd";
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VCC].supply = "vcc";
> +	err = devm_regulator_bulk_get(&client->dev,
> +				      ARRAY_SIZE(tcm_info->regulators),
> +				      tcm_info->regulators);
> +	if (err)
> +		return err;
> +
> +	// TODO: uncomment once syna_tcm_power_off is implemented
> +	// err = devm_add_action_or_reset(&client->dev, syna_tcm_oncell_power_off, tcm_info);
> +	// if (err)
> +	//     return err;
> +
> +	err = syna_tcm_power_on(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// This needs to happen before the first write to the device
> +	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> +					syna_irq_handler,
> +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +					"syna_tcm_oncell_irq", tcm_info);
> +	if (err)
> +		return err;
> +
> +	err = syna_tcm_run_application_firmware(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// err = syna_tcm_set_normal_report_config(tcm_info);
> +	// if (err < 0)
> +		// pr_err("syna_tcm: failed to set normal touch report config")
> +
> +	err = syna_tcm_get_report_config(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	tcm_info->input = devm_input_allocate_device(&client->dev);
> +	if (!tcm_info->input)
> +		return -ENOMEM;
> +
> +	tcm_info->input->name = TOUCHPANEL_DEVICE;
> +	tcm_info->input->id.bustype = BUS_I2C;
> +
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_X, 0,
> +			     le2_to_uint(tcm_info->app_info.max_x), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_Y, 0,
> +			     le2_to_uint(tcm_info->app_info.max_y), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
> +
> +	touchscreen_parse_properties(tcm_info->input, true, &tcm_info->prop);
> +
> +	err = input_mt_init_slots(tcm_info->input,
> +				  tcm_info->touchpanel_max_objects,
> +				  INPUT_MT_DIRECT);
> +	if (err)
> +		return err;
> +
> +	input_set_drvdata(tcm_info->input, tcm_info);
> +
> +	err = input_register_device(tcm_info->input);
> +	if (err)
> +		return err;
> +
> +	pr_info("syna_tcm: probe done");
> +	tcm_info->initialize_done = true;
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id syna_driver_ids[] = {
> +	{
> +		.compatible = "syna,s3908",
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, syna_driver_ids);
> +
> +static const struct i2c_device_id syna_i2c_ids[] = { { TOUCHPANEL_DEVICE, 0 },
> +						     {} };
> +MODULE_DEVICE_TABLE(i2c, syna_i2c_ids);
> +
> +// static const struct dev_pm_ops syna_pm_ops = {
> +//     .suspend = syna_i2c_suspend,
> +//     .resume = syna_i2c_resume,
> +// };
> +
> +static struct i2c_driver syna_i2c_driver = {
> +	.probe	= syna_tcm_probe,
> +	// .remove	   = syna_i2c_remove,
> +	// .shutdown   = syna_tp_shutdown,
> +	.id_table	= syna_i2c_ids,
> +	.driver	= {
> +	.name	= TOUCHPANEL_DEVICE,
> +	.of_match_table = syna_driver_ids,
> +		// .pm = &syna_pm_ops,
> +	},
> +};
> +
> +module_i2c_driver(syna_i2c_driver);
Krzysztof Kozlowski March 28, 2024, 8:34 a.m. UTC | #2
On 27/03/2024 22:39, Frieder Hannenheim wrote:
> This is a bit of a stripped down and partially reworked driver for the
> synaptics_tcm_oncell touchscreen. I based my work off the driver in the
> LineageOS kernel found at
> https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
> lineage-20. The code was originally written by OnePlus developers but
> I'm not sure how to credit them correctly.
> 
> Currently the driver is in a pretty good shape, the only thing that is
> not working is setting a report config. To me it looks like some data is
> sent by the touchscreen firmware after setting the report config that is
> making the irq handler crash. Sadly I haven't been able to test out why.
> The driver works fine also with the default touch report config so maybe
> we can just use that and not set our own. 
> 
> Signed-off-by: Frieder Hannenheim <friederhannenheim@riseup.net>
> ---
>  .../input/touchscreen/syna,s3908.yaml         |   63 +

Please run scripts/checkpatch.pl and fix reported warnings. Then please
run `scripts/checkpatch.pl --strict` and (probably) fix more warnings.
Some warnings can be ignored, especially from --strict run, but the code
here looks like it needs a fix. Feel free to get in touch if the warning
is not clear.

Please use scripts/get_maintainers.pl to get a list of necessary people
and lists to CC. It might happen, that command when run on an older
kernel, gives you outdated entries. Therefore please be sure you base
your patches on recent Linux kernel.

Tools like b4 or scripts/get_maintainer.pl provide you proper list of
people, so fix your workflow. Tools might also fail if you work on some
ancient tree (don't, instead use mainline), work on fork of kernel
(don't, instead use mainline) or you ignore some maintainers (really
don't). Just use b4 and everything should be fine, although remember
about `b4 prep --auto-to-cc` if you added new patches to the patchset.

You missed at least devicetree list (maybe more), so this won't be
tested by automated tooling. Performing review on untested code might be
a waste of time, thus I will skip this patch entirely till you follow
the process allowing the patch to be tested.

Please kindly resend and include all necessary To/Cc entries.


>  drivers/input/touchscreen/Kconfig             |   11 +


(skipping untested bindings)

...

> +
> +static int syna_tcm_probe(struct i2c_client *client)
> +{
> +	struct syna_tcm_data *tcm_info;
> +	int err;
> +
> +	pr_info("starting probe for syna_tcm_oncell touchscreen");

No, drop. Drop such msgs everywhere if you have more than only here.

> +
> +	if (!i2c_check_functionality(client->adapter,
> +				     I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA |
> +					     I2C_FUNC_SMBUS_I2C_BLOCK))
> +		return -ENODEV;
> +
> +	tcm_info = devm_kzalloc(&client->dev, sizeof(*tcm_info), GFP_KERNEL);
> +	if (!tcm_info)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(client, tcm_info);
> +	tcm_info->client = client;
> +	tcm_info->response_buf = NULL;
> +
> +	of_property_read_u32(client->dev.of_node, "max-objects",
> +			     &tcm_info->touchpanel_max_objects);
> +
> +	tcm_info->reset_gpio =
> +		gpiod_get_index(&client->dev, "reset", 0, GPIOD_OUT_HIGH);

Misaligned / wrongly wrapped. There is no wrapping of code after =.


> +
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VDD].supply = "vdd";
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VCC].supply = "vcc";
> +	err = devm_regulator_bulk_get(&client->dev,
> +				      ARRAY_SIZE(tcm_info->regulators),
> +				      tcm_info->regulators);
> +	if (err)
> +		return err;
> +
> +	// TODO: uncomment once syna_tcm_power_off is implemented
> +	// err = devm_add_action_or_reset(&client->dev, syna_tcm_oncell_power_off, tcm_info);
> +	// if (err)
> +	//     return err;

No dead code.

> +
> +	err = syna_tcm_power_on(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// This needs to happen before the first write to the device
> +	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> +					syna_irq_handler,
> +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +					"syna_tcm_oncell_irq", tcm_info);
> +	if (err)
> +		return err;
> +
> +	err = syna_tcm_run_application_firmware(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// err = syna_tcm_set_normal_report_config(tcm_info);
> +	// if (err < 0)
> +		// pr_err("syna_tcm: failed to set normal touch report config")

No dead code in the driver.

> +
> +	err = syna_tcm_get_report_config(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	tcm_info->input = devm_input_allocate_device(&client->dev);
> +	if (!tcm_info->input)
> +		return -ENOMEM;
> +
> +	tcm_info->input->name = TOUCHPANEL_DEVICE;
> +	tcm_info->input->id.bustype = BUS_I2C;
> +
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_X, 0,
> +			     le2_to_uint(tcm_info->app_info.max_x), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_Y, 0,
> +			     le2_to_uint(tcm_info->app_info.max_y), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
> +
> +	touchscreen_parse_properties(tcm_info->input, true, &tcm_info->prop);
> +
> +	err = input_mt_init_slots(tcm_info->input,
> +				  tcm_info->touchpanel_max_objects,
> +				  INPUT_MT_DIRECT);
> +	if (err)
> +		return err;
> +
> +	input_set_drvdata(tcm_info->input, tcm_info);
> +
> +	err = input_register_device(tcm_info->input);
> +	if (err)
> +		return err;
> +
> +	pr_info("syna_tcm: probe done");

No, no simple function success messages. There is already infrastructure
for this (tracing, sysfs).

> +	tcm_info->initialize_done = true;
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id syna_driver_ids[] = {
> +	{
> +		.compatible = "syna,s3908",
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, syna_driver_ids);
> +
> +static const struct i2c_device_id syna_i2c_ids[] = { { TOUCHPANEL_DEVICE, 0 },

That does not look like kernel coding style.

> +						     {} };
> +MODULE_DEVICE_TABLE(i2c, syna_i2c_ids);
> +
> +// static const struct dev_pm_ops syna_pm_ops = {
> +//     .suspend = syna_i2c_suspend,
> +//     .resume = syna_i2c_resume,
> +// };

Please do not submit dead code.


Best regards,
Krzysztof
Dmitry Torokhov March 28, 2024, 5:52 p.m. UTC | #3
Hi Frieder,

On Wed, Mar 27, 2024 at 10:39:12PM +0100, Frieder Hannenheim wrote:
> This is a bit of a stripped down and partially reworked driver for the
> synaptics_tcm_oncell touchscreen. I based my work off the driver in the
> LineageOS kernel found at
> https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
> lineage-20. The code was originally written by OnePlus developers but
> I'm not sure how to credit them correctly.

So the first question is: does this device not use Synaptics RMI4
protocol?

I am CCing Marge Yang of Synaptics who may shed some light on the kind
of touch controller this is.

Thanks.
Dmitry Torokhov March 28, 2024, 5:53 p.m. UTC | #4
[ now CCing for real ]

Hi Frieder,

On Wed, Mar 27, 2024 at 10:39:12PM +0100, Frieder Hannenheim wrote:
> This is a bit of a stripped down and partially reworked driver for the
> synaptics_tcm_oncell touchscreen. I based my work off the driver in the
> LineageOS kernel found at
> https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
> lineage-20. The code was originally written by OnePlus developers but
> I'm not sure how to credit them correctly.

So the first question is: does this device not use Synaptics RMI4
protocol?

I am CCing Marge Yang of Synaptics who may shed some light on the kind
of touch controller this is.

Thanks.
Frieder Hannenheim March 30, 2024, 8:34 p.m. UTC | #5
On 2024-03-28 18:53, Dmitry Torokhov wrote:
> [ now CCing for real ]
> 
> Hi Frieder,
> 
> On Wed, Mar 27, 2024 at 10:39:12PM +0100, Frieder Hannenheim wrote:
>> This is a bit of a stripped down and partially reworked driver for the
>> synaptics_tcm_oncell touchscreen. I based my work off the driver in the
>> LineageOS kernel found at
>> https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
>> lineage-20. The code was originally written by OnePlus developers but
>> I'm not sure how to credit them correctly.
> 
> So the first question is: does this device not use Synaptics RMI4
> protocol?
> 
> I am CCing Marge Yang of Synaptics who may shed some light on the kind
> of touch controller this is.
> 
> Thanks.
Hi Dmitri,

the synaptics-s3908 uses a command based protocol whereas rmi4 is
register-based (as far as I understand it, I haven't been able to read
up on it properly since information on the internet is sparse). So I'm
pretty sure that it can not be controlled via rmi4.

Best wishes,
Frieder
Caleb Connolly March 31, 2024, 11:11 p.m. UTC | #6
Hi,

Thanks for working on this! I've been doing panel bringup for the 8T, 
it'll be great to get everything working.

I gave this a spin and hit a null pointer in the irq handler during 
probe about 50% of the time, so I think there's some kind of race 
condition going on here.

Please run checkpatch (and maybe some other linter like clang-format?).

I'd recommend giving this a read 
https://wiki.postmarketos.org/wiki/Submitting_Patches

In general, the way this driver was written by OnePlus is pretty 
unmaintainable imo. It does a poor job at modelling the hardware and 
seems to be pretty fragile and hard to follow.


On 27/03/2024 22:39, Frieder Hannenheim wrote:
> This is a bit of a stripped down and partially reworked driver for the
> synaptics_tcm_oncell touchscreen. I based my work off the driver in the
> LineageOS kernel found at
> https://github.com/LineageOS/android_kernel_oneplus_sm8250 branch
> lineage-20. The code was originally written by OnePlus developers but
> I'm not sure how to credit them correctly.
> 
> Currently the driver is in a pretty good shape, the only thing that is
> not working is setting a report config. To me it looks like some data is
> sent by the touchscreen firmware after setting the report config that is
> making the irq handler crash. Sadly I haven't been able to test out why.
> The driver works fine also with the default touch report config so maybe
> we can just use that and not set our own.
> 
> Signed-off-by: Frieder Hannenheim <friederhannenheim@riseup.net>
> ---
>   .../input/touchscreen/syna,s3908.yaml         |   63 +
>   drivers/input/touchscreen/Kconfig             |   11 +
>   drivers/input/touchscreen/Makefile            |    1 +
>   .../input/touchscreen/synaptics_tcm_oncell.c  | 1104 +++++++++++++++++
>   4 files changed, 1179 insertions(+)
>   create mode 100644 Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
>   create mode 100644 drivers/input/touchscreen/synaptics_tcm_oncell.c
> 
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
> new file mode 100644
> index 000000000000..1a85747e2f52
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
> @@ -0,0 +1,63 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/touchscreen/synaptics,s3908.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Synaptics s3908 touchscreen controller
> +
> +maintainers:
> +  - Frieder Hannenheim <frieder.hannenheim@riseup.net>
> +
> +allOf:
> +  - $ref: touchscreen.yaml#
> +
> +properties:
> +  compatible:
> +    const: syna,s3908
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  reset-gpios:
> +    maxItems: 1
> +    description: reset gpio the chip is connected to.
> +
> +  vdd-supply: true
> +  vcc-supply: true
> +
> +  max-objects: true
> +
> +unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - reset-gpios
> +  - vdd-supply
> +  - vcc-supply
> +  - max-objects
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        touchscreen@48 {
> +            compatible = "syna,s3908";
> +            reg = <0x48>;
> +            interrupt-parent = <&gpa1>;
> +            interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
> +            reset-gpios = <38 0>;
> +            vdd-supply = <&ldo30_reg>;
> +            vcc-supply = <&ldo31_reg>;
> +
> +            max-objects = <10>;
> +        };
> +    };
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 1f8b33c2b03d..eba31ae27391 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -502,6 +502,17 @@ config TOUCHSCREEN_S6SY761
>   
>   	  To compile this driver as module, choose M here: the
>   	  module will be called s6sy761.
> +	
> +config TOUCHSCREEN_SYNA_TCM_ONCELL
> +	tristate "Synaptics TCM Oncell Touchscreen driver"
> +	depends on I2C
> +	help
> +	  Say Y if you have the Synaptics TCM Oncell driver
> +
> +	  If unsure, say N
> +
> +	  To compile this driver as module, choose M here: the
> +	  module will be called synaptics_tcm_oncell.
>   
>   config TOUCHSCREEN_GUNZE
>   	tristate "Gunze AHL-51S touchscreen"
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 7d52592f4290..f5395ccf09d5 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -86,6 +86,7 @@ obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
>   obj-$(CONFIG_TOUCHSCREEN_SUN4I)		+= sun4i-ts.o
>   obj-$(CONFIG_TOUCHSCREEN_SUR40)		+= sur40.o
>   obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI)	+= surface3_spi.o
> +obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM_ONCELL)	+= synaptics_tcm_oncell.o
>   obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC)	+= ti_am335x_tsc.o
>   obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
>   obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
> diff --git a/drivers/input/touchscreen/synaptics_tcm_oncell.c b/drivers/input/touchscreen/synaptics_tcm_oncell.c
> new file mode 100644
> index 000000000000..b874287f37af
> --- /dev/null
> +++ b/drivers/input/touchscreen/synaptics_tcm_oncell.c
> @@ -0,0 +1,1104 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + *  Driver for Synaptics TCM Oncell Touchscreens
> + *
> + *  Copyright (c) 2024 Frieder Hannenheim <friederhannenheim@riseup.net>

You should keep the previous "oplus" copyright and just add yours below it.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <asm/unaligned.h>
> +#include <linux/delay.h>
> +#include <linux/input/mt.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/of_gpio.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regulator/consumer.h>
> +
> +/* Meta Information */

you can remove all the comments like ("meta comments?")
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Frieder Hannenheim");
> +MODULE_DESCRIPTION("A driver for Synaptics TCM Oncell Touchpanels");
> +
> +#define TOUCHPANEL_DEVICE "syna-tcm"
> +
> +#define POWERUP_TO_RESET_TIME 10
> +#define RESET_TO_NORMAL_TIME 80
> +
> +#define RESPONSE_TIMEOUT_MS 1000
> +
> +#define MESSAGE_HEADER_SIZE 4
> +#define MESSAGE_MARKER 0xA5
> +#define MESSAGE_PADDING 0x5A
> +
> +#define READ_LENGTH 9
> +#define MAX_I2C_RETRY_TIME 4
> +
> +#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */
> +#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */
> +
> +#define APP_STATUS_POLL_MS 100
> +#define TOUCH_REPORT_CONFIG_SIZE 128
> +
> +enum command {
> +	CMD_NONE = 0x00,
> +	CMD_CONTINUE_WRITE = 0x01,
> +	CMD_IDENTIFY = 0x02,
> +	CMD_RESET = 0x04,
> +	CMD_ENABLE_REPORT = 0x05,
> +	CMD_DISABLE_REPORT = 0x06,
> +	CMD_GET_BOOT_INFO = 0x10,
> +	CMD_ERASE_FLASH = 0x11,
> +	CMD_WRITE_FLASH = 0x12,
> +	CMD_READ_FLASH = 0x13,
> +	CMD_RUN_APPLICATION_FIRMWARE = 0x14,
> +	CMD_SPI_MASTER_WRITE_THEN_READ = 0x15,
> +	CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16,
> +	CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f,
> +	CMD_GET_APPLICATION_INFO = 0x20,
> +	CMD_GET_STATIC_CONFIG = 0x21,
> +	CMD_SET_STATIC_CONFIG = 0x22,
> +	CMD_GET_DYNAMIC_CONFIG = 0x23,
> +	CMD_SET_DYNAMIC_CONFIG = 0x24,
> +	CMD_GET_TOUCH_REPORT_CONFIG = 0x25,
> +	CMD_SET_TOUCH_REPORT_CONFIG = 0x26,
> +	CMD_REZERO = 0x27,
> +	CMD_COMMIT_CONFIG = 0x28,
> +	CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29,
> +	CMD_PRODUCTION_TEST = 0x2a,
> +	CMD_SET_CONFIG_ID = 0x2b,
> +	CMD_ENTER_DEEP_SLEEP = 0x2c,
> +	CMD_EXIT_DEEP_SLEEP = 0x2d,
> +	CMD_GET_TOUCH_INFO = 0x2e,
> +	CMD_GET_DATA_LOCATION = 0x2f,
> +	CMD_DOWNLOAD_CONFIG = 0xc0,
> +	CMD_GET_NSM_INFO = 0xc3,
> +	CMD_EXIT_ESD = 0xc4,
> +};
> +
> +enum touch_report_code {
> +	TOUCH_END = 0,
> +	TOUCH_FOREACH_ACTIVE_OBJECT = 1,
> +	TOUCH_FOREACH_OBJECT = 2,
> +	TOUCH_FOREACH_END = 3,
> +	TOUCH_PAD_TO_NEXT_BYTE = 4,
> +	TOUCH_TIMESTAMP = 5,
> +	TOUCH_OBJECT_N_INDEX = 6,
> +	TOUCH_OBJECT_N_CLASSIFICATION = 7,
> +	TOUCH_OBJECT_N_X_POSITION = 8,
> +	TOUCH_OBJECT_N_Y_POSITION = 9,
> +	TOUCH_OBJECT_N_Z = 10,
> +	TOUCH_OBJECT_N_X_WIDTH = 11,
> +	TOUCH_OBJECT_N_Y_WIDTH = 12,
> +	TOUCH_OBJECT_N_TX_POSITION_TIXELS = 13,
> +	TOUCH_OBJECT_N_RX_POSITION_TIXELS = 14,
> +	TOUCH_0D_BUTTONS_STATE = 15,
> +	TOUCH_GESTURE_DOUBLE_TAP = 16,
> +	TOUCH_FRAME_RATE = 17,
> +	TOUCH_POWER_IM = 18,
> +	TOUCH_CID_IM = 19,
> +	TOUCH_RAIL_IM = 20,
> +	TOUCH_CID_VARIANCE_IM = 21,
> +	TOUCH_NSM_FREQUENCY = 22,
> +	TOUCH_NSM_STATE = 23,
> +	TOUCH_NUM_OF_ACTIVE_OBJECTS = 23,
> +	TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME = 24,
> +	TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80,
> +	TOUCH_TUNING_SMALL_OBJECT_PARAMS = 0x81,
> +	TOUCH_TUNING_0D_BUTTONS_VARIANCE = 0x82,
> +	TOUCH_REPORT_GESTURE_SWIPE = 193,
> +	TOUCH_REPORT_GESTURE_CIRCLE = 194,
> +	TOUCH_REPORT_GESTURE_UNICODE = 195,
> +	TOUCH_REPORT_GESTURE_VEE = 196,
> +	TOUCH_REPORT_GESTURE_TRIANGLE = 197,
> +	TOUCH_REPORT_GESTURE_INFO = 198,
> +	TOUCH_REPORT_GESTURE_COORDINATE = 199,
> +	TOUCH_REPORT_CUSTOMER_GRIP_INFO = 203,
> +};
> +
> +enum touch_status {
> +	LIFT = 0,
> +	FINGER = 1,
> +	GLOVED_FINGER = 2,
> +	NOP = -1,
> +};
> +
> +enum status_code {
> +	STATUS_IDLE = 0x00,
> +	STATUS_OK = 0x01,
> +	STATUS_BUSY = 0x02,
> +	STATUS_CONTINUED_READ = 0x03,
> +	STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
> +	STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
> +	STATUS_NOT_IMPLEMENTED = 0x0e,
> +	STATUS_ERROR = 0x0f,
> +	STATUS_INVALID = 0xff,
> +};
> +
> +enum report_type {
> +	REPORT_IDENTIFY = 0x10,
> +	REPORT_TOUCH = 0x11,
> +	REPORT_DELTA = 0x12,
> +	REPORT_RAW = 0x13,
> +	REPORT_DEBUG = 0x14,
> +	REPORT_LOG = 0x1d,
> +	REPORT_TOUCH_HOLD = 0x20,
> +};
> +
> +enum syna_tcm_oncell_regulators {
> +	SYNA_TCM_ONCELL_REGULATOR_VDD,
> +	SYNA_TCM_ONCELL_REGULATOR_VCC,
> +};
> +
> +enum boot_mode {
> +	MODE_APPLICATION = 0x01,
> +	MODE_HOST_DOWNLOAD = 0x02,
> +	MODE_BOOTLOADER = 0x0b,
> +	MODE_TDDI_BOOTLOADER = 0x0c,
> +};
> +
> +enum app_status {
> +	APP_STATUS_OK = 0x00,
> +	APP_STATUS_BOOTING = 0x01,
> +	APP_STATUS_UPDATING = 0x02,
> +	APP_STATUS_BAD_APP_CONFIG = 0xff,
> +};
> +
> +struct syna_tcm_app_info {
> +	unsigned char version[2];
> +	unsigned char status[2];
> +	unsigned char static_config_size[2];
> +	unsigned char dynamic_config_size[2];
> +	unsigned char app_config_start_write_block[2];
> +	unsigned char app_config_size[2];
> +	unsigned char max_touch_report_config_size[2];
> +	unsigned char max_touch_report_payload_size[2];
> +	unsigned char customer_config_id[16];
> +	unsigned char max_x[2];
> +	unsigned char max_y[2];
> +	unsigned char max_objects[2];
> +	unsigned char num_of_buttons[2];
> +	unsigned char num_of_image_rows[2];
> +	unsigned char num_of_image_cols[2];
> +	unsigned char has_hybrid_data[2];
> +};
> +
> +struct syna_tcm_identification {
> +	unsigned char version;
> +	unsigned char mode;
> +	unsigned char part_number[16];
> +	unsigned char build_id[4];
> +	unsigned char max_write_size[2];
> +};
> +
> +struct syna_tcm_message_header {
> +	unsigned char marker;
> +	unsigned char code;
> +	unsigned char length[2];
> +};
> +
> +struct touch_data {
> +	unsigned char status;
> +	unsigned int x_pos;
> +	unsigned int y_pos;
> +	unsigned int x_width;
> +	unsigned int y_width;
> +	unsigned int z;
> +};
> +
> +struct syna_tcm_data {
> +	struct i2c_client *client;
> +	struct regulator_bulk_data regulators[2];
> +	struct input_dev *input;
> +	struct gpio_desc *reset_gpio;
> +	struct touchscreen_properties prop;
> +	struct syna_tcm_identification id_info;
> +	struct syna_tcm_app_info app_info;
> +
> +	unsigned char *response_buf;
> +	unsigned int response_length; // Response length including header
> +
> +	unsigned int app_status;
> +
> +	unsigned char *report_config;
> +	unsigned int report_size;
> +
> +	bool initialize_done;

This value is never read.
> +
> +	unsigned int touchpanel_max_objects;
> +};
> +
> +DECLARE_COMPLETION(response_complete);
> +
> +static inline unsigned int le2_to_uint(const unsigned char *src)
> +{
> +	return (unsigned int)src[0] + (unsigned int)src[1] * 0x100;

Shouldn't this just be a cast?
> +}
> +
> +static inline unsigned int ceil_div(unsigned int dividend, unsigned int divisor)
> +{
> +	return (dividend + divisor - 1) / divisor;
> +}
> +
> +static int touch_i2c_continue_read(struct i2c_client *client,
> +				   unsigned char *data, unsigned short length)
> +{
> +	int retval;
> +	unsigned char retry;
> +	struct i2c_msg msg;
> +
> +	msg.addr = client->addr;
> +	msg.flags = I2C_M_RD;
> +	msg.len = length;
> +	msg.buf = data;
> +
> +	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
> +		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
> +			retval = length;
> +			break;
> +		}
> +		msleep(20);
> +	}
> +	if (retry == MAX_I2C_RETRY_TIME) {
> +		pr_warn("%s: I2C read over retry limit\n", __func__);
> +		retval = -EIO;
> +	}
> +	return retval;
> +}
> +
> +static int touch_i2c_continue_write(struct i2c_client *client,
> +				    unsigned char *data, unsigned short length)
> +{
> +	int retval;
> +	unsigned char retry;
> +	struct i2c_msg msg;
> +
> +	msg.addr = client->addr;
> +	msg.flags = 0;
> +	msg.buf = data;
> +	msg.len = length;
> +
> +	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
> +		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
> +			retval = length;
> +			break;
> +		}
> +		msleep(20);
> +	}
> +	if (retry == MAX_I2C_RETRY_TIME) {
> +		pr_warn("%s: I2C write over retry limit\n", __func__);
> +		retval = -EIO;
> +	}
> +	return retval;
> +}
> +
> +static int syna_tcm_continued_read(struct syna_tcm_data *tcm_info,

continue_write and continueD_read, please make these consistent.
> +				   unsigned int payload_length,
> +				   unsigned char *message_buffer)
> +{
> +	int retval = 0;
> +	unsigned char marker = 0, code = 0;
> +	unsigned int total_length = 0, remaining_length = 0;
> +	unsigned char *temp_buffer;
> +
> +	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
> +	remaining_length = total_length - READ_LENGTH;
> +
> +	temp_buffer = kmalloc(remaining_length + 2, GFP_KERNEL);
> +	if (!temp_buffer)
> +		return -ENOMEM;
> +
> +	retval = touch_i2c_continue_read(tcm_info->client, temp_buffer,
> +					 remaining_length + 2);
> +	if (retval < 0) {
> +		pr_warn("touch_i2c_continue_read failed");
dev_err??
> +		return retval;
> +	}
> +
> +	marker = temp_buffer[0];
> +	code = temp_buffer[1];
> +
> +	if (marker != MESSAGE_MARKER) {
> +		pr_warn("incorrect header marker (0x%02x)\n", marker);
> +		return -EIO;
> +	}
> +
> +	if (code != STATUS_CONTINUED_READ) {
> +		pr_warn("incorrect header code (0x%02x)\n", code);
> +		return -EIO;
> +	}
> +
> +	memcpy(&message_buffer[READ_LENGTH], &temp_buffer[2], remaining_length);
> +	message_buffer[total_length - 1] = MESSAGE_PADDING;
> +	kfree(temp_buffer);
> +
> +	return 0;
> +}
> +
> +/**
> + * syna_tcm_raw_write() - write command/data to device without receiving
> + * response
> + *
> + * @tcm_info: handle of core module
> + * @command: command to send to device
> + * @data: data to send to device
> + * @length: length of data in bytes
> + * @add_length: wether the length of the message should be added after the command
> + *
> + * A command and its data, if any, are sent to the device.
> + */
> +static int syna_tcm_raw_write(struct syna_tcm_data *tcm_info,
> +			      unsigned char command, unsigned char *data,
> +			      unsigned int length, bool add_length)
> +{
> +	int retval = 0;
> +	unsigned char *out_buf;
> +
> +	unsigned int header_size = add_length && length ? 3 : 1;
> +
> +	out_buf = kmalloc(length + header_size, GFP_KERNEL);
> +	if (retval < 0)
> +		return retval;
> +
> +	out_buf[0] = command;
> +	if (add_length && length) {
> +		out_buf[1] = (unsigned char)length;
> +		out_buf[2] = (unsigned char)(length >> 8);
> +	}
> +
> +	if (length)
> +		memcpy(&out_buf[header_size], data, length);
> +
> +	retval = touch_i2c_continue_write(tcm_info->client, out_buf,
> +					  length + header_size);
> +	if (retval < 0) {
> +		kfree(out_buf);
> +		return retval;
> +	}
> +
> +	kfree(out_buf);
> +	return 0;
> +}
> +
> +/**
> + * syna_tcm_read_message() - read message from device
> + *
> + * @tcm_info: handle of core module
> + * @length: length of data in bytes in raw read mode
> + */
> +static int syna_tcm_read_message(struct syna_tcm_data *tcm_info,
> +				 unsigned int length)
> +{
> +	int retval = 0;
> +	unsigned int total_length = 0, payload_length = 0;
> +	unsigned char *message_buffer;
> +	struct syna_tcm_message_header *header = NULL;
> +
> +	message_buffer = kmalloc(READ_LENGTH, GFP_KERNEL);
> +	if (!message_buffer)
> +		return -ENOMEM;
> +
> +	retval = touch_i2c_continue_read(tcm_info->client, message_buffer,
> +					 READ_LENGTH);
> +	if (retval < 0)
> +		return retval;
> +
> +	header = (struct syna_tcm_message_header *)message_buffer;
> +	if (header->marker != MESSAGE_MARKER) {
> +		pr_warn("wrong header marker:0x%02x\n", header->marker);
> +		return -ENXIO;
> +	}
> +
> +	unsigned char report_code = header->code;
> +
> +	payload_length = le2_to_uint(header->length);
> +
> +	if (report_code <= STATUS_ERROR || report_code == STATUS_INVALID) {
> +		switch (report_code) {
> +		case STATUS_OK:
> +			break;
> +		case STATUS_CONTINUED_READ:
> +		case STATUS_IDLE:
> +		case STATUS_BUSY:
> +			payload_length = 0;
> +			kfree(message_buffer);
> +			return 0;
> +		default:
> +			if (report_code != STATUS_ERROR) {
> +				pr_warn("IO ERROR");
> +				kfree(message_buffer);
> +				return -EIO;
> +			}
> +		}
> +	}
> +
> +	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
> +
> +	unsigned char *temp_buffer = message_buffer;
> +
> +	message_buffer = kmalloc(total_length, GFP_KERNEL);
> +	if (!message_buffer)
> +		return -ENOMEM;
> +
> +	// Copy data already read to message_buffer
> +	memcpy(message_buffer, temp_buffer, READ_LENGTH);
> +	kfree(temp_buffer);
> +
> +	if (payload_length == 0) {
> +		message_buffer[total_length - 1] = MESSAGE_PADDING;
> +		goto check_padding;
> +	}
> +
> +	if (MESSAGE_HEADER_SIZE + payload_length > READ_LENGTH) {
> +		retval = syna_tcm_continued_read(tcm_info, payload_length,
> +						 message_buffer);
> +		if (retval < 0) {
> +			pr_warn("failed to do continued read\n");
> +			return retval;
> +		};
> +	}
> +
> +check_padding:
> +	if (message_buffer[total_length - 1] != MESSAGE_PADDING) {
> +		pr_warn("missing message padding");
> +		return -EIO;
> +	}
> +
> +	if (tcm_info->response_buf != NULL)
> +		kfree(tcm_info->response_buf);
> +
> +	tcm_info->response_buf = message_buffer;
> +	tcm_info->response_length = total_length;
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_write_message(struct syna_tcm_data *tcm_info,
> +				  unsigned char command, unsigned char *payload,
> +				  unsigned int length, unsigned char **resp_buf,
> +				  unsigned int *resp_length)
> +{
> +	int retval = 0;
> +
> +	if (resp_buf == NULL) {
> +		retval = syna_tcm_raw_write(tcm_info, command, payload, length,
> +					    false);
> +		goto exit;
> +	}
> +	reinit_completion(&response_complete);
> +
> +	tcm_info->response_buf = *resp_buf;
> +	tcm_info->response_length = *resp_length;
> +
> +	// Here write command to device

Please follow kernel comment style (/* ... */)
> +	retval = syna_tcm_raw_write(tcm_info, command, payload, length, true);
> +	if (retval != 0)
> +		goto exit;
> +
> +	retval = wait_for_completion_timeout(&response_complete,
> +					     RESPONSE_TIMEOUT_MS);
> +	if (retval == 0) {
> +		pr_warn("timed out waiting for response (command 0x%02x)\n",
> +			command);
> +		retval = -EIO;
> +	}
> +
> +exit:
> +	if (tcm_info->response_length < 0)
> +		return -1;
> +	if (resp_buf != NULL) {
> +		*resp_buf = tcm_info->response_buf;
> +		*resp_length = tcm_info->response_length;
> +	}
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_set_normal_report_config(struct syna_tcm_data *tcm_info)

This prints a warning on every compile because it's unused. Add the 
__maybe_unused attribute and a comment explaining what it does and why 
it's not used if it's really useful to have it here, or just remove it 
from this version of the driver.
> +{
> +	int retval;
> +	unsigned int idx = 0;
> +	unsigned int length;
> +
> +	length = le2_to_uint(tcm_info->app_info.max_touch_report_config_size);
> +
> +	if (length < TOUCH_REPORT_CONFIG_SIZE) {
> +		pr_warn("invalid maximum touch report config size: %d\n",
> +			length);
> +		return -EINVAL;
> +	}
> +
> +	unsigned char *report_buf = kmalloc(length, GFP_KERNEL);
> +
> +	if (!report_buf)
> +		return -ENOMEM;
> +
> +	report_buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT;
> +	report_buf[idx++] = TOUCH_OBJECT_N_INDEX;
> +	report_buf[idx++] = 4;
> +	report_buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION;
> +	report_buf[idx++] = 4;
> +	report_buf[idx++] = TOUCH_OBJECT_N_X_POSITION;
> +	report_buf[idx++] = 16;
> +	report_buf[idx++] = TOUCH_OBJECT_N_Y_POSITION;
> +	report_buf[idx++] = 16;
> +	report_buf[idx++] = TOUCH_OBJECT_N_X_WIDTH;
> +	report_buf[idx++] = 12;
> +	report_buf[idx++] = TOUCH_OBJECT_N_Y_WIDTH;
> +	report_buf[idx++] = 12;
> +	report_buf[idx++] = TOUCH_FOREACH_END;
> +	report_buf[idx++] = TOUCH_END;
> +
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_SET_TOUCH_REPORT_CONFIG,
> +					report_buf, length, &resp_buf,
> +					&resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to set report config");
> +		return retval;
> +	}
> +
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_get_report_config(struct syna_tcm_data *tcm_info)
> +{
> +	int retval;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_GET_TOUCH_REPORT_CONFIG,
> +					NULL, 0, &resp_buf, &resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to write command 0x%02x\n",
> +			CMD_GET_TOUCH_REPORT_CONFIG);
> +		return retval;
> +	}
> +
> +	tcm_info->report_size = resp_length - MESSAGE_HEADER_SIZE - 1;
> +
> +	kfree(tcm_info->report_config);
> +	tcm_info->report_config = kmalloc(tcm_info->report_size, GFP_KERNEL);
> +
> +	memcpy(tcm_info->report_config, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       tcm_info->report_size);
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_get_app_info(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +	unsigned int timeout = RESPONSE_TIMEOUT_MS;
> +
> +get_app_info:
> +	retval = syna_tcm_write_message(tcm_info, CMD_GET_APPLICATION_INFO,
> +					NULL, 0, &resp_buf, &resp_length);
> +	if (retval < 0) {
> +		pr_warn("failed to write command 0x%02x\n",
> +			CMD_GET_APPLICATION_INFO);
> +		goto exit;
> +	}
> +
> +	memcpy(&tcm_info->app_info, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       sizeof(tcm_info->app_info));
> +
> +	tcm_info->app_status = le2_to_uint(tcm_info->app_info.status);
> +
> +	if (tcm_info->app_status == APP_STATUS_BOOTING ||
> +	    tcm_info->app_status == APP_STATUS_UPDATING) {
> +		if (timeout > 0) {
> +			msleep(APP_STATUS_POLL_MS);
> +			timeout -= APP_STATUS_POLL_MS;
> +			goto get_app_info;
> +		}
> +	}
> +
> +	retval = 0;
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_identify(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +	retval = syna_tcm_write_message(tcm_info, CMD_IDENTIFY, NULL, 0,
> +					&resp_buf, &resp_length);
> +	if (retval < 0)
> +		goto exit;
> +
> +	memcpy(&tcm_info->id_info, &resp_buf[MESSAGE_HEADER_SIZE],
> +	       sizeof(tcm_info->id_info));
> +
> +	if (tcm_info->id_info.mode == MODE_APPLICATION)
> +		retval = syna_tcm_get_app_info(tcm_info);
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +static int syna_tcm_run_application_firmware(struct syna_tcm_data *tcm_info)
> +{
> +	int retval = 0;
> +	unsigned int retry = 3;
> +	unsigned char *resp_buf = NULL;
> +	unsigned int resp_length = 0;
> +
> +retry:
> +	retval = syna_tcm_write_message(tcm_info, CMD_RUN_APPLICATION_FIRMWARE,
> +					NULL, 0, &resp_buf, &resp_length);
> +
> +	if (retval < 0) {
> +		if (retry--)
> +			goto retry;
> +		goto exit;
> +	}
> +
> +	retval = syna_tcm_identify(tcm_info);
> +	if (retval < 0) {
> +		if (retry--)
> +			goto retry;
> +		goto exit;
> +	}
> +
> +	pr_info("syna_tcm: boot_mode: 0x%02x", tcm_info->id_info.mode);
> +
> +	if (tcm_info->id_info.mode != MODE_APPLICATION) {
> +		if (retry--)
> +			goto retry;
> +		retval = -EINVAL;
> +		goto exit;
> +	} else if (tcm_info->app_status != APP_STATUS_OK) {
> +		pr_warn("syna_tcm: application status = 0x%02x\n",
> +			tcm_info->app_status);
> +		if (retry--)
> +			goto retry;
> +	}
> +
> +	retval = 0;
> +
> +exit:
> +	kfree(resp_buf);
> +
> +	return retval;
> +}
> +
> +/*
> + * syna_get_report_data - Retrieve data from touch report
> + *
> + * @tcm_info: handle of tcm module
> + * @offset: start bit of retrieved data
> + * @bits: total bits of retrieved data
> + * @data: pointer of data, at most 4 byte
> + * Retrieve data from the touch report based on the bit offset and bit length
> + * information from the touch report configuration.
> + */
> +static int syna_tcm_get_report_data(unsigned char *report_buf,
> +				    unsigned int report_buf_length,
> +				    unsigned int offset, unsigned int num_bits,
> +				    unsigned int *data)
> +{
> +	unsigned char mask = 0;
> +	unsigned char byte_data = 0;
> +	unsigned int output_data = 0;
> +	unsigned int bit_offset = offset % 8;
> +	unsigned int byte_offset = offset / 8;
> +	unsigned int data_bits = 0;
> +	unsigned int available_bits = 0;
> +	unsigned int remaining_bits = num_bits;
> +
> +	if (num_bits == 0 || num_bits > 32) {
> +		pr_warn("report value larger than 32 bits: %d\n", num_bits);
> +		memcpy(data, &report_buf[byte_offset], num_bits / 8);
> +		return 0;
> +	}
> +
> +	if ((offset + num_bits) > report_buf_length * 8) {
> +		pr_warn("report: offset and bits beyond total read length: %d vs %d bits",
> +			offset + num_bits, report_buf_length * 8);
> +		*data = 0;
> +		return -1;
> +	}
> +
> +	while (remaining_bits) {
> +		byte_data = report_buf[byte_offset];
> +		byte_data >>= bit_offset;
> +
> +		available_bits = 8 - bit_offset;
> +		data_bits = available_bits < remaining_bits ? available_bits :
> +							      remaining_bits;
> +		mask = 0xff >> (8 - data_bits);
> +
> +		byte_data &= mask;
> +
> +		output_data |= byte_data << (num_bits - remaining_bits);
> +
> +		bit_offset = 0;
> +		byte_offset += 1;
> +		remaining_bits -= data_bits;
> +	}
> +
> +	*data = output_data;
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_parse_touch_report(struct syna_tcm_data *tcm_info)
> +{
> +	int config_data_pointer = 0, obj = 0, foreach_start = 0, foreach_end,
> +	    retval;
> +	unsigned int offset = 0, num_bits, data = 0;
> +	unsigned char *report_config = tcm_info->report_config;
> +	unsigned int config_size = tcm_info->report_size;
> +
> +	unsigned char *report = &tcm_info->response_buf[MESSAGE_HEADER_SIZE];
> +	unsigned int report_size =
> +		tcm_info->response_length - MESSAGE_HEADER_SIZE - 1;
> +	unsigned char code;
> +
> +	struct touch_data *touch_data =
> +		kmalloc(sizeof(touch_data) * tcm_info->touchpanel_max_objects,
> +			GFP_KERNEL);

Calling kmalloc in the hot patch for every single touch event is not 
great, we have IRQF_ONESHOT so it's fine to have a buffer allocated once 
and re-use it here, you don't need any locking.
> +	memset(touch_data, 0,
> +	       sizeof(touch_data) * tcm_info->touchpanel_max_objects);
> +
> +	while (config_data_pointer < config_size) {
> +		code = report_config[config_data_pointer++];
> +		switch (code) {
> +		case TOUCH_END:
> +			goto exit;
> +		case TOUCH_FOREACH_ACTIVE_OBJECT:
> +			obj = 0;
> +			foreach_start = config_data_pointer;
> +			if (offset >= report_size * 8)
> +				goto exit;
> +			break;
> +		case TOUCH_FOREACH_END:
> +			foreach_end = config_data_pointer;
> +			if (offset < report_size * 8)
> +				config_data_pointer = foreach_start;
> +			break;
> +		// Ignored
> +		case TOUCH_OBJECT_N_TX_POSITION_TIXELS:
> +		case TOUCH_OBJECT_N_RX_POSITION_TIXELS:
> +		case TOUCH_FRAME_RATE:
> +		case TOUCH_0D_BUTTONS_STATE:
> +		case 30: // I don't know what command 30 is and it isn't in the downstream driver. After it comes a length of 8 bits and this is ignored
> +			num_bits = report_config[config_data_pointer++];
> +			offset += num_bits;
> +			break;
> +		case TOUCH_PAD_TO_NEXT_BYTE:
> +			offset = ceil_div(offset, 8) * 8;
> +			break;
> +		case TOUCH_OBJECT_N_INDEX:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &obj);
> +			if (retval < 0) {
> +				pr_warn("failed to get object index\n");
> +				return retval;
> +			}
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_CLASSIFICATION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object classification\n");
> +				return retval;
> +			}
> +			if (obj >= tcm_info->touchpanel_max_objects) {
> +				pr_warn("too many objects\n");
> +				return -1;
> +			}
> +			touch_data[obj].status = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_X_POSITION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object x position\n");
> +				return retval;
> +			}
> +			touch_data[obj].x_pos = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Y_POSITION:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object y position\n");
> +				return retval;
> +			}
> +			touch_data[obj].y_pos = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Z:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object z\n");
> +				return retval;
> +			}
> +			touch_data[obj].z = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_X_WIDTH:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object x width\n");
> +				return retval;
> +			}
> +			touch_data[obj].x_width = data;
> +			offset += num_bits;
> +			break;
> +		case TOUCH_OBJECT_N_Y_WIDTH:
> +			num_bits = report_config[config_data_pointer++];
> +			retval = syna_tcm_get_report_data(
> +				report, report_size, offset, num_bits, &data);
> +			if (retval < 0) {
> +				pr_warn("failed to get object y width\n");
> +				return retval;
> +			}
> +			touch_data[obj].y_width = data;
> +			offset += num_bits;
> +			break;
> +		default:
> +			pr_notice("unrecognized touch report config code: %d",
> +				  code);

This should be a debug print
> +		}
> +	}
> +
> +exit:
> +	for (int i = 0; i < tcm_info->touchpanel_max_objects; i++) {
> +		if (touch_data[i].status == NOP)
> +			continue;
> +
> +		bool lift = touch_data[i].status == LIFT ? true : false;
> +
> +		input_mt_slot(tcm_info->input, i);
> +		input_mt_report_slot_state(tcm_info->input, MT_TOOL_FINGER,
> +					   !lift);
> +
> +		int x_width = touch_data[i].x_width;
> +		int y_width = touch_data[i].y_width;
> +		int major_width = x_width > y_width ? x_width : y_width;
> +		int minor_width = x_width > y_width ? y_width : x_width;
> +		int x_is_major = x_width > y_width ? true : false;
> +
> +		if (!lift) {
> +			input_report_abs(tcm_info->input, ABS_MT_POSITION_X,
> +					 touch_data[i].x_pos);
> +			input_report_abs(tcm_info->input, ABS_MT_POSITION_Y,
> +					 touch_data[i].y_pos);
> +			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MAJOR,
> +					 major_width);
> +			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MINOR,
> +					 minor_width);
> +			input_report_abs(tcm_info->input, ABS_MT_ORIENTATION,
> +					 x_is_major);
> +			input_report_abs(tcm_info->input, ABS_MT_PRESSURE,
> +					 touch_data[i].z);
> +		}
> +
> +		input_sync(tcm_info->input);
> +	}
> +
> +	kfree(touch_data);
> +	return 0;
> +}
> +
> +static irqreturn_t syna_irq_handler(int irq, void *dev)
> +{
> +	int retval;
> +	struct syna_tcm_data *tcm_info = dev;
> +
> +	retval = syna_tcm_read_message(tcm_info, 0);
> +	if (retval < 0) {
> +		tcm_info->response_length = -1;
> +		goto exit;
> +	}
> +
> +	if (tcm_info->response_buf[1] == REPORT_TOUCH)

Aha, found your bug... I see places where response_buf gets free'd 
(why??) and I guess you're hitting this codepath after it's free. The 
null pointer is actually address 0x1.
> +		syna_tcm_parse_touch_report(tcm_info);
> +
> +exit:
> +	if (tcm_info->response_buf[1] <= REPORT_IDENTIFY)

Or here...
> +		complete(&response_complete);

I'll leave it here for now, I'd appreciate it if you CC me in the next 
revision (and the other lists as Krzyzstof said -- don't forget 
phone-devel!).

Kind regards,
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int syna_tcm_power_on(struct syna_tcm_data *tcm_info)
> +{
> +	int ret;
> +
> +	ret = regulator_bulk_enable(ARRAY_SIZE(tcm_info->regulators),
> +				    tcm_info->regulators);
> +	if (ret)
> +		return ret;
> +
> +	gpiod_set_value(tcm_info->reset_gpio, false);
> +	msleep(POWERUP_TO_RESET_TIME);
> +	gpiod_set_value(tcm_info->reset_gpio, true);
> +	msleep(RESET_TO_NORMAL_TIME);
> +
> +	return 0;
> +}
> +
> +static int syna_tcm_probe(struct i2c_client *client)
> +{
> +	struct syna_tcm_data *tcm_info;
> +	int err;
> +
> +	pr_info("starting probe for syna_tcm_oncell touchscreen");
> +
> +	if (!i2c_check_functionality(client->adapter,
> +				     I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA |
> +					     I2C_FUNC_SMBUS_I2C_BLOCK))
> +		return -ENODEV;
> +
> +	tcm_info = devm_kzalloc(&client->dev, sizeof(*tcm_info), GFP_KERNEL);
> +	if (!tcm_info)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(client, tcm_info);
> +	tcm_info->client = client;
> +	tcm_info->response_buf = NULL;
> +
> +	of_property_read_u32(client->dev.of_node, "max-objects",
> +			     &tcm_info->touchpanel_max_objects);
> +
> +	tcm_info->reset_gpio =
> +		gpiod_get_index(&client->dev, "reset", 0, GPIOD_OUT_HIGH);
> +
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VDD].supply = "vdd";
> +	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VCC].supply = "vcc";
> +	err = devm_regulator_bulk_get(&client->dev,
> +				      ARRAY_SIZE(tcm_info->regulators),
> +				      tcm_info->regulators);
> +	if (err)
> +		return err;
> +
> +	// TODO: uncomment once syna_tcm_power_off is implemented
> +	// err = devm_add_action_or_reset(&client->dev, syna_tcm_oncell_power_off, tcm_info);
> +	// if (err)
> +	//     return err;
> +
> +	err = syna_tcm_power_on(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// This needs to happen before the first write to the device
> +	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> +					syna_irq_handler,
> +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +					"syna_tcm_oncell_irq", tcm_info);
> +	if (err)
> +		return err;
> +
> +	err = syna_tcm_run_application_firmware(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	// err = syna_tcm_set_normal_report_config(tcm_info);
> +	// if (err < 0)
> +		// pr_err("syna_tcm: failed to set normal touch report config")
> +
> +	err = syna_tcm_get_report_config(tcm_info);
> +	if (err < 0)
> +		return err;
> +
> +	tcm_info->input = devm_input_allocate_device(&client->dev);
> +	if (!tcm_info->input)
> +		return -ENOMEM;
> +
> +	tcm_info->input->name = TOUCHPANEL_DEVICE;
> +	tcm_info->input->id.bustype = BUS_I2C;
> +
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_X, 0,
> +			     le2_to_uint(tcm_info->app_info.max_x), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_Y, 0,
> +			     le2_to_uint(tcm_info->app_info.max_y), 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
> +	input_set_abs_params(tcm_info->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
> +
> +	touchscreen_parse_properties(tcm_info->input, true, &tcm_info->prop);
> +
> +	err = input_mt_init_slots(tcm_info->input,
> +				  tcm_info->touchpanel_max_objects,
> +				  INPUT_MT_DIRECT);
> +	if (err)
> +		return err;
> +
> +	input_set_drvdata(tcm_info->input, tcm_info);
> +
> +	err = input_register_device(tcm_info->input);
> +	if (err)
> +		return err;
> +
> +	pr_info("syna_tcm: probe done");
> +	tcm_info->initialize_done = true;
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id syna_driver_ids[] = {
> +	{
> +		.compatible = "syna,s3908",
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, syna_driver_ids);
> +
> +static const struct i2c_device_id syna_i2c_ids[] = { { TOUCHPANEL_DEVICE, 0 },
> +						     {} };
> +MODULE_DEVICE_TABLE(i2c, syna_i2c_ids);
> +
> +// static const struct dev_pm_ops syna_pm_ops = {
> +//     .suspend = syna_i2c_suspend,
> +//     .resume = syna_i2c_resume,
> +// };
> +
> +static struct i2c_driver syna_i2c_driver = {
> +	.probe	= syna_tcm_probe,
> +	// .remove	   = syna_i2c_remove,
> +	// .shutdown   = syna_tp_shutdown,
> +	.id_table	= syna_i2c_ids,
> +	.driver	= {
> +	.name	= TOUCHPANEL_DEVICE,
> +	.of_match_table = syna_driver_ids,
> +		// .pm = &syna_pm_ops,
> +	},
> +};
> +
> +module_i2c_driver(syna_i2c_driver);
Marge Yang April 1, 2024, 6:18 a.m. UTC | #7
Loop Scott Lin

Scott Lin is responsible for mobile products.

Thanks
Marge Yang


-----Original Message-----
From: friederhannenheim@riseup.net <friederhannenheim@riseup.net> 
Sent: Sunday, March 31, 2024 4:35 AM
To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Marge Yang <Marge.Yang@tw.synaptics.com>; linux-input@vger.kernel.org; linux-kernel@vger.kernel.org
Subject: Re: [PATCH] input/touchscreen: synaptics_tcm_oncell: add driver

CAUTION: Email originated externally, do not click links or open attachments unless you recognize the sender and know the content is safe.


On 2024-03-28 18:53, Dmitry Torokhov wrote:
> [ now CCing for real ]
>
> Hi Frieder,
>
> On Wed, Mar 27, 2024 at 10:39:12PM +0100, Frieder Hannenheim wrote:
>> This is a bit of a stripped down and partially reworked driver for 
>> the synaptics_tcm_oncell touchscreen. I based my work off the driver 
>> in the LineageOS kernel found at 
>> https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_Linea
>> geOS_android-5Fkernel-5Foneplus-5Fsm8250&d=DwICAg&c=7dfBJ8cXbWjhc0BhI
>> mu8wVIoUFmBzj1s88r8EGyM0UY&r=ddk_91asmhyAjxFmXHNIQZ2mVcW0D_eq4tb4409nZ94&m=2_iuhvyQkYcT-qsozPf_h9irH_AlUtaQ020UAxVQhZ3SuHgXYihgVocHyTV-zNSV&s=FT4Hkpxkhqktmhyz4RWC9lGAD4DvNBS06wQnn4ofQkk&e=  branch lineage-20. The code was originally written by OnePlus developers but I'm not sure how to credit them correctly.
>
> So the first question is: does this device not use Synaptics RMI4 
> protocol?
>
> I am CCing Marge Yang of Synaptics who may shed some light on the kind 
> of touch controller this is.
>
> Thanks.
Hi Dmitri,

the synaptics-s3908 uses a command based protocol whereas rmi4 is register-based (as far as I understand it, I haven't been able to read up on it properly since information on the internet is sparse). So I'm pretty sure that it can not be controlled via rmi4.

Best wishes,
Frieder
diff mbox series

Patch

diff --git a/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
new file mode 100644
index 000000000000..1a85747e2f52
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml
@@ -0,0 +1,63 @@ 
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/synaptics,s3908.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Synaptics s3908 touchscreen controller
+
+maintainers:
+  - Frieder Hannenheim <frieder.hannenheim@riseup.net>
+
+allOf:
+  - $ref: touchscreen.yaml#
+
+properties:
+  compatible:
+    const: syna,s3908
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+    description: reset gpio the chip is connected to.
+
+  vdd-supply: true
+  vcc-supply: true
+
+  max-objects: true
+
+unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - reset-gpios
+  - vdd-supply
+  - vcc-supply
+  - max-objects
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        touchscreen@48 {
+            compatible = "syna,s3908";
+            reg = <0x48>;
+            interrupt-parent = <&gpa1>;
+            interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
+            reset-gpios = <38 0>;
+            vdd-supply = <&ldo30_reg>;
+            vcc-supply = <&ldo31_reg>;
+
+            max-objects = <10>;
+        };
+    };
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 1f8b33c2b03d..eba31ae27391 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -502,6 +502,17 @@  config TOUCHSCREEN_S6SY761
 
 	  To compile this driver as module, choose M here: the
 	  module will be called s6sy761.
+	  
+config TOUCHSCREEN_SYNA_TCM_ONCELL
+	tristate "Synaptics TCM Oncell Touchscreen driver"
+	depends on I2C
+	help
+	  Say Y if you have the Synaptics TCM Oncell driver
+
+	  If unsure, say N
+
+	  To compile this driver as module, choose M here: the
+	  module will be called synaptics_tcm_oncell.
 
 config TOUCHSCREEN_GUNZE
 	tristate "Gunze AHL-51S touchscreen"
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 7d52592f4290..f5395ccf09d5 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -86,6 +86,7 @@  obj-$(CONFIG_TOUCHSCREEN_STMPE)		+= stmpe-ts.o
 obj-$(CONFIG_TOUCHSCREEN_SUN4I)		+= sun4i-ts.o
 obj-$(CONFIG_TOUCHSCREEN_SUR40)		+= sur40.o
 obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI)	+= surface3_spi.o
+obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM_ONCELL)	+= synaptics_tcm_oncell.o
 obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC)	+= ti_am335x_tsc.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
diff --git a/drivers/input/touchscreen/synaptics_tcm_oncell.c b/drivers/input/touchscreen/synaptics_tcm_oncell.c
new file mode 100644
index 000000000000..b874287f37af
--- /dev/null
+++ b/drivers/input/touchscreen/synaptics_tcm_oncell.c
@@ -0,0 +1,1104 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Driver for Synaptics TCM Oncell Touchscreens
+ *
+ *  Copyright (c) 2024 Frieder Hannenheim <friederhannenheim@riseup.net>
+ */
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of_gpio.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+/* Meta Information */
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Frieder Hannenheim");
+MODULE_DESCRIPTION("A driver for Synaptics TCM Oncell Touchpanels");
+
+#define TOUCHPANEL_DEVICE "syna-tcm"
+
+#define POWERUP_TO_RESET_TIME 10
+#define RESET_TO_NORMAL_TIME 80
+
+#define RESPONSE_TIMEOUT_MS 1000
+
+#define MESSAGE_HEADER_SIZE 4
+#define MESSAGE_MARKER 0xA5
+#define MESSAGE_PADDING 0x5A
+
+#define READ_LENGTH 9
+#define MAX_I2C_RETRY_TIME 4
+
+#define RD_CHUNK_SIZE 0 /* read length limit in bytes, 0 = unlimited */
+#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 = unlimited */
+
+#define APP_STATUS_POLL_MS 100
+#define TOUCH_REPORT_CONFIG_SIZE 128
+
+enum command {
+	CMD_NONE = 0x00,
+	CMD_CONTINUE_WRITE = 0x01,
+	CMD_IDENTIFY = 0x02,
+	CMD_RESET = 0x04,
+	CMD_ENABLE_REPORT = 0x05,
+	CMD_DISABLE_REPORT = 0x06,
+	CMD_GET_BOOT_INFO = 0x10,
+	CMD_ERASE_FLASH = 0x11,
+	CMD_WRITE_FLASH = 0x12,
+	CMD_READ_FLASH = 0x13,
+	CMD_RUN_APPLICATION_FIRMWARE = 0x14,
+	CMD_SPI_MASTER_WRITE_THEN_READ = 0x15,
+	CMD_REBOOT_TO_ROM_BOOTLOADER = 0x16,
+	CMD_RUN_BOOTLOADER_FIRMWARE = 0x1f,
+	CMD_GET_APPLICATION_INFO = 0x20,
+	CMD_GET_STATIC_CONFIG = 0x21,
+	CMD_SET_STATIC_CONFIG = 0x22,
+	CMD_GET_DYNAMIC_CONFIG = 0x23,
+	CMD_SET_DYNAMIC_CONFIG = 0x24,
+	CMD_GET_TOUCH_REPORT_CONFIG = 0x25,
+	CMD_SET_TOUCH_REPORT_CONFIG = 0x26,
+	CMD_REZERO = 0x27,
+	CMD_COMMIT_CONFIG = 0x28,
+	CMD_DESCRIBE_DYNAMIC_CONFIG = 0x29,
+	CMD_PRODUCTION_TEST = 0x2a,
+	CMD_SET_CONFIG_ID = 0x2b,
+	CMD_ENTER_DEEP_SLEEP = 0x2c,
+	CMD_EXIT_DEEP_SLEEP = 0x2d,
+	CMD_GET_TOUCH_INFO = 0x2e,
+	CMD_GET_DATA_LOCATION = 0x2f,
+	CMD_DOWNLOAD_CONFIG = 0xc0,
+	CMD_GET_NSM_INFO = 0xc3,
+	CMD_EXIT_ESD = 0xc4,
+};
+
+enum touch_report_code {
+	TOUCH_END = 0,
+	TOUCH_FOREACH_ACTIVE_OBJECT = 1,
+	TOUCH_FOREACH_OBJECT = 2,
+	TOUCH_FOREACH_END = 3,
+	TOUCH_PAD_TO_NEXT_BYTE = 4,
+	TOUCH_TIMESTAMP = 5,
+	TOUCH_OBJECT_N_INDEX = 6,
+	TOUCH_OBJECT_N_CLASSIFICATION = 7,
+	TOUCH_OBJECT_N_X_POSITION = 8,
+	TOUCH_OBJECT_N_Y_POSITION = 9,
+	TOUCH_OBJECT_N_Z = 10,
+	TOUCH_OBJECT_N_X_WIDTH = 11,
+	TOUCH_OBJECT_N_Y_WIDTH = 12,
+	TOUCH_OBJECT_N_TX_POSITION_TIXELS = 13,
+	TOUCH_OBJECT_N_RX_POSITION_TIXELS = 14,
+	TOUCH_0D_BUTTONS_STATE = 15,
+	TOUCH_GESTURE_DOUBLE_TAP = 16,
+	TOUCH_FRAME_RATE = 17,
+	TOUCH_POWER_IM = 18,
+	TOUCH_CID_IM = 19,
+	TOUCH_RAIL_IM = 20,
+	TOUCH_CID_VARIANCE_IM = 21,
+	TOUCH_NSM_FREQUENCY = 22,
+	TOUCH_NSM_STATE = 23,
+	TOUCH_NUM_OF_ACTIVE_OBJECTS = 23,
+	TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME = 24,
+	TOUCH_TUNING_GAUSSIAN_WIDTHS = 0x80,
+	TOUCH_TUNING_SMALL_OBJECT_PARAMS = 0x81,
+	TOUCH_TUNING_0D_BUTTONS_VARIANCE = 0x82,
+	TOUCH_REPORT_GESTURE_SWIPE = 193,
+	TOUCH_REPORT_GESTURE_CIRCLE = 194,
+	TOUCH_REPORT_GESTURE_UNICODE = 195,
+	TOUCH_REPORT_GESTURE_VEE = 196,
+	TOUCH_REPORT_GESTURE_TRIANGLE = 197,
+	TOUCH_REPORT_GESTURE_INFO = 198,
+	TOUCH_REPORT_GESTURE_COORDINATE = 199,
+	TOUCH_REPORT_CUSTOMER_GRIP_INFO = 203,
+};
+
+enum touch_status {
+	LIFT = 0,
+	FINGER = 1,
+	GLOVED_FINGER = 2,
+	NOP = -1,
+};
+
+enum status_code {
+	STATUS_IDLE = 0x00,
+	STATUS_OK = 0x01,
+	STATUS_BUSY = 0x02,
+	STATUS_CONTINUED_READ = 0x03,
+	STATUS_RECEIVE_BUFFER_OVERFLOW = 0x0c,
+	STATUS_PREVIOUS_COMMAND_PENDING = 0x0d,
+	STATUS_NOT_IMPLEMENTED = 0x0e,
+	STATUS_ERROR = 0x0f,
+	STATUS_INVALID = 0xff,
+};
+
+enum report_type {
+	REPORT_IDENTIFY = 0x10,
+	REPORT_TOUCH = 0x11,
+	REPORT_DELTA = 0x12,
+	REPORT_RAW = 0x13,
+	REPORT_DEBUG = 0x14,
+	REPORT_LOG = 0x1d,
+	REPORT_TOUCH_HOLD = 0x20,
+};
+
+enum syna_tcm_oncell_regulators {
+	SYNA_TCM_ONCELL_REGULATOR_VDD,
+	SYNA_TCM_ONCELL_REGULATOR_VCC,
+};
+
+enum boot_mode {
+	MODE_APPLICATION = 0x01,
+	MODE_HOST_DOWNLOAD = 0x02,
+	MODE_BOOTLOADER = 0x0b,
+	MODE_TDDI_BOOTLOADER = 0x0c,
+};
+
+enum app_status {
+	APP_STATUS_OK = 0x00,
+	APP_STATUS_BOOTING = 0x01,
+	APP_STATUS_UPDATING = 0x02,
+	APP_STATUS_BAD_APP_CONFIG = 0xff,
+};
+
+struct syna_tcm_app_info {
+	unsigned char version[2];
+	unsigned char status[2];
+	unsigned char static_config_size[2];
+	unsigned char dynamic_config_size[2];
+	unsigned char app_config_start_write_block[2];
+	unsigned char app_config_size[2];
+	unsigned char max_touch_report_config_size[2];
+	unsigned char max_touch_report_payload_size[2];
+	unsigned char customer_config_id[16];
+	unsigned char max_x[2];
+	unsigned char max_y[2];
+	unsigned char max_objects[2];
+	unsigned char num_of_buttons[2];
+	unsigned char num_of_image_rows[2];
+	unsigned char num_of_image_cols[2];
+	unsigned char has_hybrid_data[2];
+};
+
+struct syna_tcm_identification {
+	unsigned char version;
+	unsigned char mode;
+	unsigned char part_number[16];
+	unsigned char build_id[4];
+	unsigned char max_write_size[2];
+};
+
+struct syna_tcm_message_header {
+	unsigned char marker;
+	unsigned char code;
+	unsigned char length[2];
+};
+
+struct touch_data {
+	unsigned char status;
+	unsigned int x_pos;
+	unsigned int y_pos;
+	unsigned int x_width;
+	unsigned int y_width;
+	unsigned int z;
+};
+
+struct syna_tcm_data {
+	struct i2c_client *client;
+	struct regulator_bulk_data regulators[2];
+	struct input_dev *input;
+	struct gpio_desc *reset_gpio;
+	struct touchscreen_properties prop;
+	struct syna_tcm_identification id_info;
+	struct syna_tcm_app_info app_info;
+
+	unsigned char *response_buf;
+	unsigned int response_length; // Response length including header
+
+	unsigned int app_status;
+
+	unsigned char *report_config;
+	unsigned int report_size;
+
+	bool initialize_done;
+
+	unsigned int touchpanel_max_objects;
+};
+
+DECLARE_COMPLETION(response_complete);
+
+static inline unsigned int le2_to_uint(const unsigned char *src)
+{
+	return (unsigned int)src[0] + (unsigned int)src[1] * 0x100;
+}
+
+static inline unsigned int ceil_div(unsigned int dividend, unsigned int divisor)
+{
+	return (dividend + divisor - 1) / divisor;
+}
+
+static int touch_i2c_continue_read(struct i2c_client *client,
+				   unsigned char *data, unsigned short length)
+{
+	int retval;
+	unsigned char retry;
+	struct i2c_msg msg;
+
+	msg.addr = client->addr;
+	msg.flags = I2C_M_RD;
+	msg.len = length;
+	msg.buf = data;
+
+	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
+		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
+			retval = length;
+			break;
+		}
+		msleep(20);
+	}
+	if (retry == MAX_I2C_RETRY_TIME) {
+		pr_warn("%s: I2C read over retry limit\n", __func__);
+		retval = -EIO;
+	}
+	return retval;
+}
+
+static int touch_i2c_continue_write(struct i2c_client *client,
+				    unsigned char *data, unsigned short length)
+{
+	int retval;
+	unsigned char retry;
+	struct i2c_msg msg;
+
+	msg.addr = client->addr;
+	msg.flags = 0;
+	msg.buf = data;
+	msg.len = length;
+
+	for (retry = 0; retry < MAX_I2C_RETRY_TIME; retry++) {
+		if (i2c_transfer(client->adapter, &msg, 1) == 1) {
+			retval = length;
+			break;
+		}
+		msleep(20);
+	}
+	if (retry == MAX_I2C_RETRY_TIME) {
+		pr_warn("%s: I2C write over retry limit\n", __func__);
+		retval = -EIO;
+	}
+	return retval;
+}
+
+static int syna_tcm_continued_read(struct syna_tcm_data *tcm_info,
+				   unsigned int payload_length,
+				   unsigned char *message_buffer)
+{
+	int retval = 0;
+	unsigned char marker = 0, code = 0;
+	unsigned int total_length = 0, remaining_length = 0;
+	unsigned char *temp_buffer;
+
+	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
+	remaining_length = total_length - READ_LENGTH;
+
+	temp_buffer = kmalloc(remaining_length + 2, GFP_KERNEL);
+	if (!temp_buffer)
+		return -ENOMEM;
+
+	retval = touch_i2c_continue_read(tcm_info->client, temp_buffer,
+					 remaining_length + 2);
+	if (retval < 0) {
+		pr_warn("touch_i2c_continue_read failed");
+		return retval;
+	}
+
+	marker = temp_buffer[0];
+	code = temp_buffer[1];
+
+	if (marker != MESSAGE_MARKER) {
+		pr_warn("incorrect header marker (0x%02x)\n", marker);
+		return -EIO;
+	}
+
+	if (code != STATUS_CONTINUED_READ) {
+		pr_warn("incorrect header code (0x%02x)\n", code);
+		return -EIO;
+	}
+
+	memcpy(&message_buffer[READ_LENGTH], &temp_buffer[2], remaining_length);
+	message_buffer[total_length - 1] = MESSAGE_PADDING;
+	kfree(temp_buffer);
+
+	return 0;
+}
+
+/**
+ * syna_tcm_raw_write() - write command/data to device without receiving
+ * response
+ *
+ * @tcm_info: handle of core module
+ * @command: command to send to device
+ * @data: data to send to device
+ * @length: length of data in bytes
+ * @add_length: wether the length of the message should be added after the command
+ *
+ * A command and its data, if any, are sent to the device.
+ */
+static int syna_tcm_raw_write(struct syna_tcm_data *tcm_info,
+			      unsigned char command, unsigned char *data,
+			      unsigned int length, bool add_length)
+{
+	int retval = 0;
+	unsigned char *out_buf;
+
+	unsigned int header_size = add_length && length ? 3 : 1;
+
+	out_buf = kmalloc(length + header_size, GFP_KERNEL);
+	if (retval < 0)
+		return retval;
+
+	out_buf[0] = command;
+	if (add_length && length) {
+		out_buf[1] = (unsigned char)length;
+		out_buf[2] = (unsigned char)(length >> 8);
+	}
+
+	if (length)
+		memcpy(&out_buf[header_size], data, length);
+
+	retval = touch_i2c_continue_write(tcm_info->client, out_buf,
+					  length + header_size);
+	if (retval < 0) {
+		kfree(out_buf);
+		return retval;
+	}
+
+	kfree(out_buf);
+	return 0;
+}
+
+/**
+ * syna_tcm_read_message() - read message from device
+ *
+ * @tcm_info: handle of core module
+ * @length: length of data in bytes in raw read mode
+ */
+static int syna_tcm_read_message(struct syna_tcm_data *tcm_info,
+				 unsigned int length)
+{
+	int retval = 0;
+	unsigned int total_length = 0, payload_length = 0;
+	unsigned char *message_buffer;
+	struct syna_tcm_message_header *header = NULL;
+
+	message_buffer = kmalloc(READ_LENGTH, GFP_KERNEL);
+	if (!message_buffer)
+		return -ENOMEM;
+
+	retval = touch_i2c_continue_read(tcm_info->client, message_buffer,
+					 READ_LENGTH);
+	if (retval < 0)
+		return retval;
+
+	header = (struct syna_tcm_message_header *)message_buffer;
+	if (header->marker != MESSAGE_MARKER) {
+		pr_warn("wrong header marker:0x%02x\n", header->marker);
+		return -ENXIO;
+	}
+
+	unsigned char report_code = header->code;
+
+	payload_length = le2_to_uint(header->length);
+
+	if (report_code <= STATUS_ERROR || report_code == STATUS_INVALID) {
+		switch (report_code) {
+		case STATUS_OK:
+			break;
+		case STATUS_CONTINUED_READ:
+		case STATUS_IDLE:
+		case STATUS_BUSY:
+			payload_length = 0;
+			kfree(message_buffer);
+			return 0;
+		default:
+			if (report_code != STATUS_ERROR) {
+				pr_warn("IO ERROR");
+				kfree(message_buffer);
+				return -EIO;
+			}
+		}
+	}
+
+	total_length = MESSAGE_HEADER_SIZE + payload_length + 1;
+
+	unsigned char *temp_buffer = message_buffer;
+
+	message_buffer = kmalloc(total_length, GFP_KERNEL);
+	if (!message_buffer)
+		return -ENOMEM;
+
+	// Copy data already read to message_buffer
+	memcpy(message_buffer, temp_buffer, READ_LENGTH);
+	kfree(temp_buffer);
+
+	if (payload_length == 0) {
+		message_buffer[total_length - 1] = MESSAGE_PADDING;
+		goto check_padding;
+	}
+
+	if (MESSAGE_HEADER_SIZE + payload_length > READ_LENGTH) {
+		retval = syna_tcm_continued_read(tcm_info, payload_length,
+						 message_buffer);
+		if (retval < 0) {
+			pr_warn("failed to do continued read\n");
+			return retval;
+		};
+	}
+
+check_padding:
+	if (message_buffer[total_length - 1] != MESSAGE_PADDING) {
+		pr_warn("missing message padding");
+		return -EIO;
+	}
+
+	if (tcm_info->response_buf != NULL)
+		kfree(tcm_info->response_buf);
+
+	tcm_info->response_buf = message_buffer;
+	tcm_info->response_length = total_length;
+
+	return 0;
+}
+
+static int syna_tcm_write_message(struct syna_tcm_data *tcm_info,
+				  unsigned char command, unsigned char *payload,
+				  unsigned int length, unsigned char **resp_buf,
+				  unsigned int *resp_length)
+{
+	int retval = 0;
+
+	if (resp_buf == NULL) {
+		retval = syna_tcm_raw_write(tcm_info, command, payload, length,
+					    false);
+		goto exit;
+	}
+	reinit_completion(&response_complete);
+
+	tcm_info->response_buf = *resp_buf;
+	tcm_info->response_length = *resp_length;
+
+	// Here write command to device
+	retval = syna_tcm_raw_write(tcm_info, command, payload, length, true);
+	if (retval != 0)
+		goto exit;
+
+	retval = wait_for_completion_timeout(&response_complete,
+					     RESPONSE_TIMEOUT_MS);
+	if (retval == 0) {
+		pr_warn("timed out waiting for response (command 0x%02x)\n",
+			command);
+		retval = -EIO;
+	}
+
+exit:
+	if (tcm_info->response_length < 0)
+		return -1;
+	if (resp_buf != NULL) {
+		*resp_buf = tcm_info->response_buf;
+		*resp_length = tcm_info->response_length;
+	}
+
+	return retval;
+}
+
+static int syna_tcm_set_normal_report_config(struct syna_tcm_data *tcm_info)
+{
+	int retval;
+	unsigned int idx = 0;
+	unsigned int length;
+
+	length = le2_to_uint(tcm_info->app_info.max_touch_report_config_size);
+
+	if (length < TOUCH_REPORT_CONFIG_SIZE) {
+		pr_warn("invalid maximum touch report config size: %d\n",
+			length);
+		return -EINVAL;
+	}
+
+	unsigned char *report_buf = kmalloc(length, GFP_KERNEL);
+
+	if (!report_buf)
+		return -ENOMEM;
+
+	report_buf[idx++] = TOUCH_FOREACH_ACTIVE_OBJECT;
+	report_buf[idx++] = TOUCH_OBJECT_N_INDEX;
+	report_buf[idx++] = 4;
+	report_buf[idx++] = TOUCH_OBJECT_N_CLASSIFICATION;
+	report_buf[idx++] = 4;
+	report_buf[idx++] = TOUCH_OBJECT_N_X_POSITION;
+	report_buf[idx++] = 16;
+	report_buf[idx++] = TOUCH_OBJECT_N_Y_POSITION;
+	report_buf[idx++] = 16;
+	report_buf[idx++] = TOUCH_OBJECT_N_X_WIDTH;
+	report_buf[idx++] = 12;
+	report_buf[idx++] = TOUCH_OBJECT_N_Y_WIDTH;
+	report_buf[idx++] = 12;
+	report_buf[idx++] = TOUCH_FOREACH_END;
+	report_buf[idx++] = TOUCH_END;
+
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+	retval = syna_tcm_write_message(tcm_info, CMD_SET_TOUCH_REPORT_CONFIG,
+					report_buf, length, &resp_buf,
+					&resp_length);
+	if (retval < 0) {
+		pr_warn("failed to set report config");
+		return retval;
+	}
+
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_get_report_config(struct syna_tcm_data *tcm_info)
+{
+	int retval;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+	retval = syna_tcm_write_message(tcm_info, CMD_GET_TOUCH_REPORT_CONFIG,
+					NULL, 0, &resp_buf, &resp_length);
+	if (retval < 0) {
+		pr_warn("failed to write command 0x%02x\n",
+			CMD_GET_TOUCH_REPORT_CONFIG);
+		return retval;
+	}
+
+	tcm_info->report_size = resp_length - MESSAGE_HEADER_SIZE - 1;
+
+	kfree(tcm_info->report_config);
+	tcm_info->report_config = kmalloc(tcm_info->report_size, GFP_KERNEL);
+
+	memcpy(tcm_info->report_config, &resp_buf[MESSAGE_HEADER_SIZE],
+	       tcm_info->report_size);
+
+	return 0;
+}
+
+static int syna_tcm_get_app_info(struct syna_tcm_data *tcm_info)
+{
+	int retval = 0;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+	unsigned int timeout = RESPONSE_TIMEOUT_MS;
+
+get_app_info:
+	retval = syna_tcm_write_message(tcm_info, CMD_GET_APPLICATION_INFO,
+					NULL, 0, &resp_buf, &resp_length);
+	if (retval < 0) {
+		pr_warn("failed to write command 0x%02x\n",
+			CMD_GET_APPLICATION_INFO);
+		goto exit;
+	}
+
+	memcpy(&tcm_info->app_info, &resp_buf[MESSAGE_HEADER_SIZE],
+	       sizeof(tcm_info->app_info));
+
+	tcm_info->app_status = le2_to_uint(tcm_info->app_info.status);
+
+	if (tcm_info->app_status == APP_STATUS_BOOTING ||
+	    tcm_info->app_status == APP_STATUS_UPDATING) {
+		if (timeout > 0) {
+			msleep(APP_STATUS_POLL_MS);
+			timeout -= APP_STATUS_POLL_MS;
+			goto get_app_info;
+		}
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_identify(struct syna_tcm_data *tcm_info)
+{
+	int retval = 0;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+	retval = syna_tcm_write_message(tcm_info, CMD_IDENTIFY, NULL, 0,
+					&resp_buf, &resp_length);
+	if (retval < 0)
+		goto exit;
+
+	memcpy(&tcm_info->id_info, &resp_buf[MESSAGE_HEADER_SIZE],
+	       sizeof(tcm_info->id_info));
+
+	if (tcm_info->id_info.mode == MODE_APPLICATION)
+		retval = syna_tcm_get_app_info(tcm_info);
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+static int syna_tcm_run_application_firmware(struct syna_tcm_data *tcm_info)
+{
+	int retval = 0;
+	unsigned int retry = 3;
+	unsigned char *resp_buf = NULL;
+	unsigned int resp_length = 0;
+
+retry:
+	retval = syna_tcm_write_message(tcm_info, CMD_RUN_APPLICATION_FIRMWARE,
+					NULL, 0, &resp_buf, &resp_length);
+
+	if (retval < 0) {
+		if (retry--)
+			goto retry;
+		goto exit;
+	}
+
+	retval = syna_tcm_identify(tcm_info);
+	if (retval < 0) {
+		if (retry--)
+			goto retry;
+		goto exit;
+	}
+
+	pr_info("syna_tcm: boot_mode: 0x%02x", tcm_info->id_info.mode);
+
+	if (tcm_info->id_info.mode != MODE_APPLICATION) {
+		if (retry--)
+			goto retry;
+		retval = -EINVAL;
+		goto exit;
+	} else if (tcm_info->app_status != APP_STATUS_OK) {
+		pr_warn("syna_tcm: application status = 0x%02x\n",
+			tcm_info->app_status);
+		if (retry--)
+			goto retry;
+	}
+
+	retval = 0;
+
+exit:
+	kfree(resp_buf);
+
+	return retval;
+}
+
+/*
+ * syna_get_report_data - Retrieve data from touch report
+ *
+ * @tcm_info: handle of tcm module
+ * @offset: start bit of retrieved data
+ * @bits: total bits of retrieved data
+ * @data: pointer of data, at most 4 byte
+ * Retrieve data from the touch report based on the bit offset and bit length
+ * information from the touch report configuration.
+ */
+static int syna_tcm_get_report_data(unsigned char *report_buf,
+				    unsigned int report_buf_length,
+				    unsigned int offset, unsigned int num_bits,
+				    unsigned int *data)
+{
+	unsigned char mask = 0;
+	unsigned char byte_data = 0;
+	unsigned int output_data = 0;
+	unsigned int bit_offset = offset % 8;
+	unsigned int byte_offset = offset / 8;
+	unsigned int data_bits = 0;
+	unsigned int available_bits = 0;
+	unsigned int remaining_bits = num_bits;
+
+	if (num_bits == 0 || num_bits > 32) {
+		pr_warn("report value larger than 32 bits: %d\n", num_bits);
+		memcpy(data, &report_buf[byte_offset], num_bits / 8);
+		return 0;
+	}
+
+	if ((offset + num_bits) > report_buf_length * 8) {
+		pr_warn("report: offset and bits beyond total read length: %d vs %d bits",
+			offset + num_bits, report_buf_length * 8);
+		*data = 0;
+		return -1;
+	}
+
+	while (remaining_bits) {
+		byte_data = report_buf[byte_offset];
+		byte_data >>= bit_offset;
+
+		available_bits = 8 - bit_offset;
+		data_bits = available_bits < remaining_bits ? available_bits :
+							      remaining_bits;
+		mask = 0xff >> (8 - data_bits);
+
+		byte_data &= mask;
+
+		output_data |= byte_data << (num_bits - remaining_bits);
+
+		bit_offset = 0;
+		byte_offset += 1;
+		remaining_bits -= data_bits;
+	}
+
+	*data = output_data;
+
+	return 0;
+}
+
+static int syna_tcm_parse_touch_report(struct syna_tcm_data *tcm_info)
+{
+	int config_data_pointer = 0, obj = 0, foreach_start = 0, foreach_end,
+	    retval;
+	unsigned int offset = 0, num_bits, data = 0;
+	unsigned char *report_config = tcm_info->report_config;
+	unsigned int config_size = tcm_info->report_size;
+
+	unsigned char *report = &tcm_info->response_buf[MESSAGE_HEADER_SIZE];
+	unsigned int report_size =
+		tcm_info->response_length - MESSAGE_HEADER_SIZE - 1;
+	unsigned char code;
+
+	struct touch_data *touch_data =
+		kmalloc(sizeof(touch_data) * tcm_info->touchpanel_max_objects,
+			GFP_KERNEL);
+	memset(touch_data, 0,
+	       sizeof(touch_data) * tcm_info->touchpanel_max_objects);
+
+	while (config_data_pointer < config_size) {
+		code = report_config[config_data_pointer++];
+		switch (code) {
+		case TOUCH_END:
+			goto exit;
+		case TOUCH_FOREACH_ACTIVE_OBJECT:
+			obj = 0;
+			foreach_start = config_data_pointer;
+			if (offset >= report_size * 8)
+				goto exit;
+			break;
+		case TOUCH_FOREACH_END:
+			foreach_end = config_data_pointer;
+			if (offset < report_size * 8)
+				config_data_pointer = foreach_start;
+			break;
+		// Ignored
+		case TOUCH_OBJECT_N_TX_POSITION_TIXELS:
+		case TOUCH_OBJECT_N_RX_POSITION_TIXELS:
+		case TOUCH_FRAME_RATE:
+		case TOUCH_0D_BUTTONS_STATE:
+		case 30: // I don't know what command 30 is and it isn't in the downstream driver. After it comes a length of 8 bits and this is ignored
+			num_bits = report_config[config_data_pointer++];
+			offset += num_bits;
+			break;
+		case TOUCH_PAD_TO_NEXT_BYTE:
+			offset = ceil_div(offset, 8) * 8;
+			break;
+		case TOUCH_OBJECT_N_INDEX:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &obj);
+			if (retval < 0) {
+				pr_warn("failed to get object index\n");
+				return retval;
+			}
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_CLASSIFICATION:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object classification\n");
+				return retval;
+			}
+			if (obj >= tcm_info->touchpanel_max_objects) {
+				pr_warn("too many objects\n");
+				return -1;
+			}
+			touch_data[obj].status = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_X_POSITION:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object x position\n");
+				return retval;
+			}
+			touch_data[obj].x_pos = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_Y_POSITION:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object y position\n");
+				return retval;
+			}
+			touch_data[obj].y_pos = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_Z:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object z\n");
+				return retval;
+			}
+			touch_data[obj].z = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_X_WIDTH:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object x width\n");
+				return retval;
+			}
+			touch_data[obj].x_width = data;
+			offset += num_bits;
+			break;
+		case TOUCH_OBJECT_N_Y_WIDTH:
+			num_bits = report_config[config_data_pointer++];
+			retval = syna_tcm_get_report_data(
+				report, report_size, offset, num_bits, &data);
+			if (retval < 0) {
+				pr_warn("failed to get object y width\n");
+				return retval;
+			}
+			touch_data[obj].y_width = data;
+			offset += num_bits;
+			break;
+		default:
+			pr_notice("unrecognized touch report config code: %d",
+				  code);
+		}
+	}
+
+exit:
+	for (int i = 0; i < tcm_info->touchpanel_max_objects; i++) {
+		if (touch_data[i].status == NOP)
+			continue;
+
+		bool lift = touch_data[i].status == LIFT ? true : false;
+
+		input_mt_slot(tcm_info->input, i);
+		input_mt_report_slot_state(tcm_info->input, MT_TOOL_FINGER,
+					   !lift);
+
+		int x_width = touch_data[i].x_width;
+		int y_width = touch_data[i].y_width;
+		int major_width = x_width > y_width ? x_width : y_width;
+		int minor_width = x_width > y_width ? y_width : x_width;
+		int x_is_major = x_width > y_width ? true : false;
+
+		if (!lift) {
+			input_report_abs(tcm_info->input, ABS_MT_POSITION_X,
+					 touch_data[i].x_pos);
+			input_report_abs(tcm_info->input, ABS_MT_POSITION_Y,
+					 touch_data[i].y_pos);
+			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MAJOR,
+					 major_width);
+			input_report_abs(tcm_info->input, ABS_MT_TOUCH_MINOR,
+					 minor_width);
+			input_report_abs(tcm_info->input, ABS_MT_ORIENTATION,
+					 x_is_major);
+			input_report_abs(tcm_info->input, ABS_MT_PRESSURE,
+					 touch_data[i].z);
+		}
+
+		input_sync(tcm_info->input);
+	}
+
+	kfree(touch_data);
+	return 0;
+}
+
+static irqreturn_t syna_irq_handler(int irq, void *dev)
+{
+	int retval;
+	struct syna_tcm_data *tcm_info = dev;
+
+	retval = syna_tcm_read_message(tcm_info, 0);
+	if (retval < 0) {
+		tcm_info->response_length = -1;
+		goto exit;
+	}
+
+	if (tcm_info->response_buf[1] == REPORT_TOUCH)
+		syna_tcm_parse_touch_report(tcm_info);
+
+exit:
+	if (tcm_info->response_buf[1] <= REPORT_IDENTIFY)
+		complete(&response_complete);
+
+	return IRQ_HANDLED;
+}
+
+static int syna_tcm_power_on(struct syna_tcm_data *tcm_info)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(tcm_info->regulators),
+				    tcm_info->regulators);
+	if (ret)
+		return ret;
+
+	gpiod_set_value(tcm_info->reset_gpio, false);
+	msleep(POWERUP_TO_RESET_TIME);
+	gpiod_set_value(tcm_info->reset_gpio, true);
+	msleep(RESET_TO_NORMAL_TIME);
+
+	return 0;
+}
+
+static int syna_tcm_probe(struct i2c_client *client)
+{
+	struct syna_tcm_data *tcm_info;
+	int err;
+
+	pr_info("starting probe for syna_tcm_oncell touchscreen");
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA |
+					     I2C_FUNC_SMBUS_I2C_BLOCK))
+		return -ENODEV;
+
+	tcm_info = devm_kzalloc(&client->dev, sizeof(*tcm_info), GFP_KERNEL);
+	if (!tcm_info)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, tcm_info);
+	tcm_info->client = client;
+	tcm_info->response_buf = NULL;
+
+	of_property_read_u32(client->dev.of_node, "max-objects",
+			     &tcm_info->touchpanel_max_objects);
+
+	tcm_info->reset_gpio =
+		gpiod_get_index(&client->dev, "reset", 0, GPIOD_OUT_HIGH);
+
+	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VDD].supply = "vdd";
+	tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VCC].supply = "vcc";
+	err = devm_regulator_bulk_get(&client->dev,
+				      ARRAY_SIZE(tcm_info->regulators),
+				      tcm_info->regulators);
+	if (err)
+		return err;
+
+	// TODO: uncomment once syna_tcm_power_off is implemented
+	// err = devm_add_action_or_reset(&client->dev, syna_tcm_oncell_power_off, tcm_info);
+	// if (err)
+	//     return err;
+
+	err = syna_tcm_power_on(tcm_info);
+	if (err < 0)
+		return err;
+
+	// This needs to happen before the first write to the device
+	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+					syna_irq_handler,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"syna_tcm_oncell_irq", tcm_info);
+	if (err)
+		return err;
+
+	err = syna_tcm_run_application_firmware(tcm_info);
+	if (err < 0)
+		return err;
+
+	// err = syna_tcm_set_normal_report_config(tcm_info);
+	// if (err < 0)
+		// pr_err("syna_tcm: failed to set normal touch report config")
+
+	err = syna_tcm_get_report_config(tcm_info);
+	if (err < 0)
+		return err;
+
+	tcm_info->input = devm_input_allocate_device(&client->dev);
+	if (!tcm_info->input)
+		return -ENOMEM;
+
+	tcm_info->input->name = TOUCHPANEL_DEVICE;
+	tcm_info->input->id.bustype = BUS_I2C;
+
+	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_X, 0,
+			     le2_to_uint(tcm_info->app_info.max_x), 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_POSITION_Y, 0,
+			     le2_to_uint(tcm_info->app_info.max_y), 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
+	input_set_abs_params(tcm_info->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
+
+	touchscreen_parse_properties(tcm_info->input, true, &tcm_info->prop);
+
+	err = input_mt_init_slots(tcm_info->input,
+				  tcm_info->touchpanel_max_objects,
+				  INPUT_MT_DIRECT);
+	if (err)
+		return err;
+
+	input_set_drvdata(tcm_info->input, tcm_info);
+
+	err = input_register_device(tcm_info->input);
+	if (err)
+		return err;
+
+	pr_info("syna_tcm: probe done");
+	tcm_info->initialize_done = true;
+
+	return 0;
+}
+
+static const struct of_device_id syna_driver_ids[] = {
+	{
+		.compatible = "syna,s3908",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, syna_driver_ids);
+
+static const struct i2c_device_id syna_i2c_ids[] = { { TOUCHPANEL_DEVICE, 0 },
+						     {} };
+MODULE_DEVICE_TABLE(i2c, syna_i2c_ids);
+
+// static const struct dev_pm_ops syna_pm_ops = {
+//     .suspend = syna_i2c_suspend,
+//     .resume = syna_i2c_resume,
+// };
+
+static struct i2c_driver syna_i2c_driver = {
+	.probe	= syna_tcm_probe,
+	// .remove	   = syna_i2c_remove,
+	// .shutdown   = syna_tp_shutdown,
+	.id_table	= syna_i2c_ids,
+	.driver	= {
+	.name	= TOUCHPANEL_DEVICE,
+	.of_match_table = syna_driver_ids,
+		// .pm = &syna_pm_ops,
+	},
+};
+
+module_i2c_driver(syna_i2c_driver);