From nobody Mon Feb 9 06:49:55 2026 Received: from mx1.riseup.net (mx1.riseup.net [198.252.153.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6BEA153371; Wed, 27 Mar 2024 21:47:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=198.252.153.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711576034; cv=none; b=p94usjXuMjpwI6Vgxq3SEeJtM8Tb2HA/wyZNNuylH1l8k0Jq6gjegydFMq61a2e8ne+AylbXchHp5SWZgmkARl7y69t2ajvLBEfELxFW8Oc75AHhgwrdj+3xnYbOPhUsNzib40elOypBTedDaFwpILFK15VTtt62U8xmFQsEBS0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711576034; c=relaxed/simple; bh=7qfXR2AKAP/kRifpsRWmwVXvRvaFwoffUIlPpaB2YiI=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=CKCaFFyFksO+ceTIog15AlP+EQu8nBqO1Vb5bmnnFQQhb+Kst38QutVp49NcIdV4woPBrh4kHV6fnzPjrh7zub4/L7A/sxCebmuNiFpU8x5z3vexm7yz2nxWfjHB6F7DkfL+oRD8oGM3ruoc13PqhfSKdBxF9oHfr66Vg1yqqeE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riseup.net; spf=pass smtp.mailfrom=riseup.net; dkim=pass (1024-bit key) header.d=riseup.net header.i=@riseup.net header.b=PmtO3OQv; arc=none smtp.client-ip=198.252.153.129 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riseup.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=riseup.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=riseup.net header.i=@riseup.net header.b="PmtO3OQv" Received: from fews02-sea.riseup.net (fews02-sea-pn.riseup.net [10.0.1.112]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx1.riseup.net (Postfix) with ESMTPS id 4V4gHY0nM1zDqF9; Wed, 27 Mar 2024 21:47:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=riseup.net; s=squak; t=1711576025; bh=7qfXR2AKAP/kRifpsRWmwVXvRvaFwoffUIlPpaB2YiI=; h=From:To:Cc:Subject:Date:From; b=PmtO3OQv1t0cfig9p3HGH2e+z6VDET3uulBvzwoslyN18Jlaag1GVrWRQCSPfsDgB 0riiHIkJH64Cuhf9YuRH32kVhGI6i0Als+FmCho84D8hNqssFbXlVMtVQ+mj2yxB7u Ki4XIcbBsTl5NPX8codFUwnIbBhoGea5A+VU9Pow= X-Riseup-User-ID: 1926C463DF06304D0EEF3A37227E323A16B2DF826ADEF559B17825A954D04A80 Received: from [127.0.0.1] (localhost [127.0.0.1]) by fews02-sea.riseup.net (Postfix) with ESMTPSA id 4V4gHW2c3QzFwK5; Wed, 27 Mar 2024 21:47:03 +0000 (UTC) From: Frieder Hannenheim To: dmitry.torokhov@gmail.com, linux-input@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Frieder Hannenheim Subject: [PATCH] input/touchscreen: synaptics_tcm_oncell: add driver Date: Wed, 27 Mar 2024 22:39:12 +0100 Message-ID: <20240327214643.7055-1-friederhannenheim@riseup.net> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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.=20 Signed-off-by: Frieder Hannenheim --- .../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/syn= a,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 + +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 + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + touchscreen@48 { + compatible =3D "syna,s3908"; + reg =3D <0x48>; + interrupt-parent =3D <&gpa1>; + interrupts =3D <1 IRQ_TYPE_LEVEL_HIGH>; + reset-gpios =3D <38 0>; + vdd-supply =3D <&ldo30_reg>; + vcc-supply =3D <&ldo31_reg>; + + max-objects =3D <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 =20 To compile this driver as module, choose M here: the module will be called s6sy761. + =20 +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. =20 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) +=3D stmpe-ts.o obj-$(CONFIG_TOUCHSCREEN_SUN4I) +=3D sun4i-ts.o obj-$(CONFIG_TOUCHSCREEN_SUR40) +=3D sur40.o obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI) +=3D surface3_spi.o +obj-$(CONFIG_TOUCHSCREEN_SYNA_TCM_ONCELL) +=3D synaptics_tcm_oncell.o obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) +=3D ti_am335x_tsc.o obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) +=3D touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) +=3D touchright.o diff --git a/drivers/input/touchscreen/synaptics_tcm_oncell.c b/drivers/inp= ut/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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 =3D unlimited */ +#define WR_CHUNK_SIZE 0 /* write length limit in bytes, 0 =3D unlimited */ + +#define APP_STATUS_POLL_MS 100 +#define TOUCH_REPORT_CONFIG_SIZE 128 + +enum command { + CMD_NONE =3D 0x00, + CMD_CONTINUE_WRITE =3D 0x01, + CMD_IDENTIFY =3D 0x02, + CMD_RESET =3D 0x04, + CMD_ENABLE_REPORT =3D 0x05, + CMD_DISABLE_REPORT =3D 0x06, + CMD_GET_BOOT_INFO =3D 0x10, + CMD_ERASE_FLASH =3D 0x11, + CMD_WRITE_FLASH =3D 0x12, + CMD_READ_FLASH =3D 0x13, + CMD_RUN_APPLICATION_FIRMWARE =3D 0x14, + CMD_SPI_MASTER_WRITE_THEN_READ =3D 0x15, + CMD_REBOOT_TO_ROM_BOOTLOADER =3D 0x16, + CMD_RUN_BOOTLOADER_FIRMWARE =3D 0x1f, + CMD_GET_APPLICATION_INFO =3D 0x20, + CMD_GET_STATIC_CONFIG =3D 0x21, + CMD_SET_STATIC_CONFIG =3D 0x22, + CMD_GET_DYNAMIC_CONFIG =3D 0x23, + CMD_SET_DYNAMIC_CONFIG =3D 0x24, + CMD_GET_TOUCH_REPORT_CONFIG =3D 0x25, + CMD_SET_TOUCH_REPORT_CONFIG =3D 0x26, + CMD_REZERO =3D 0x27, + CMD_COMMIT_CONFIG =3D 0x28, + CMD_DESCRIBE_DYNAMIC_CONFIG =3D 0x29, + CMD_PRODUCTION_TEST =3D 0x2a, + CMD_SET_CONFIG_ID =3D 0x2b, + CMD_ENTER_DEEP_SLEEP =3D 0x2c, + CMD_EXIT_DEEP_SLEEP =3D 0x2d, + CMD_GET_TOUCH_INFO =3D 0x2e, + CMD_GET_DATA_LOCATION =3D 0x2f, + CMD_DOWNLOAD_CONFIG =3D 0xc0, + CMD_GET_NSM_INFO =3D 0xc3, + CMD_EXIT_ESD =3D 0xc4, +}; + +enum touch_report_code { + TOUCH_END =3D 0, + TOUCH_FOREACH_ACTIVE_OBJECT =3D 1, + TOUCH_FOREACH_OBJECT =3D 2, + TOUCH_FOREACH_END =3D 3, + TOUCH_PAD_TO_NEXT_BYTE =3D 4, + TOUCH_TIMESTAMP =3D 5, + TOUCH_OBJECT_N_INDEX =3D 6, + TOUCH_OBJECT_N_CLASSIFICATION =3D 7, + TOUCH_OBJECT_N_X_POSITION =3D 8, + TOUCH_OBJECT_N_Y_POSITION =3D 9, + TOUCH_OBJECT_N_Z =3D 10, + TOUCH_OBJECT_N_X_WIDTH =3D 11, + TOUCH_OBJECT_N_Y_WIDTH =3D 12, + TOUCH_OBJECT_N_TX_POSITION_TIXELS =3D 13, + TOUCH_OBJECT_N_RX_POSITION_TIXELS =3D 14, + TOUCH_0D_BUTTONS_STATE =3D 15, + TOUCH_GESTURE_DOUBLE_TAP =3D 16, + TOUCH_FRAME_RATE =3D 17, + TOUCH_POWER_IM =3D 18, + TOUCH_CID_IM =3D 19, + TOUCH_RAIL_IM =3D 20, + TOUCH_CID_VARIANCE_IM =3D 21, + TOUCH_NSM_FREQUENCY =3D 22, + TOUCH_NSM_STATE =3D 23, + TOUCH_NUM_OF_ACTIVE_OBJECTS =3D 23, + TOUCH_NUM_OF_CPU_CYCLES_USED_SINCE_LAST_FRAME =3D 24, + TOUCH_TUNING_GAUSSIAN_WIDTHS =3D 0x80, + TOUCH_TUNING_SMALL_OBJECT_PARAMS =3D 0x81, + TOUCH_TUNING_0D_BUTTONS_VARIANCE =3D 0x82, + TOUCH_REPORT_GESTURE_SWIPE =3D 193, + TOUCH_REPORT_GESTURE_CIRCLE =3D 194, + TOUCH_REPORT_GESTURE_UNICODE =3D 195, + TOUCH_REPORT_GESTURE_VEE =3D 196, + TOUCH_REPORT_GESTURE_TRIANGLE =3D 197, + TOUCH_REPORT_GESTURE_INFO =3D 198, + TOUCH_REPORT_GESTURE_COORDINATE =3D 199, + TOUCH_REPORT_CUSTOMER_GRIP_INFO =3D 203, +}; + +enum touch_status { + LIFT =3D 0, + FINGER =3D 1, + GLOVED_FINGER =3D 2, + NOP =3D -1, +}; + +enum status_code { + STATUS_IDLE =3D 0x00, + STATUS_OK =3D 0x01, + STATUS_BUSY =3D 0x02, + STATUS_CONTINUED_READ =3D 0x03, + STATUS_RECEIVE_BUFFER_OVERFLOW =3D 0x0c, + STATUS_PREVIOUS_COMMAND_PENDING =3D 0x0d, + STATUS_NOT_IMPLEMENTED =3D 0x0e, + STATUS_ERROR =3D 0x0f, + STATUS_INVALID =3D 0xff, +}; + +enum report_type { + REPORT_IDENTIFY =3D 0x10, + REPORT_TOUCH =3D 0x11, + REPORT_DELTA =3D 0x12, + REPORT_RAW =3D 0x13, + REPORT_DEBUG =3D 0x14, + REPORT_LOG =3D 0x1d, + REPORT_TOUCH_HOLD =3D 0x20, +}; + +enum syna_tcm_oncell_regulators { + SYNA_TCM_ONCELL_REGULATOR_VDD, + SYNA_TCM_ONCELL_REGULATOR_VCC, +}; + +enum boot_mode { + MODE_APPLICATION =3D 0x01, + MODE_HOST_DOWNLOAD =3D 0x02, + MODE_BOOTLOADER =3D 0x0b, + MODE_TDDI_BOOTLOADER =3D 0x0c, +}; + +enum app_status { + APP_STATUS_OK =3D 0x00, + APP_STATUS_BOOTING =3D 0x01, + APP_STATUS_UPDATING =3D 0x02, + APP_STATUS_BAD_APP_CONFIG =3D 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 di= visor) +{ + 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 =3D client->addr; + msg.flags =3D I2C_M_RD; + msg.len =3D length; + msg.buf =3D data; + + for (retry =3D 0; retry < MAX_I2C_RETRY_TIME; retry++) { + if (i2c_transfer(client->adapter, &msg, 1) =3D=3D 1) { + retval =3D length; + break; + } + msleep(20); + } + if (retry =3D=3D MAX_I2C_RETRY_TIME) { + pr_warn("%s: I2C read over retry limit\n", __func__); + retval =3D -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 =3D client->addr; + msg.flags =3D 0; + msg.buf =3D data; + msg.len =3D length; + + for (retry =3D 0; retry < MAX_I2C_RETRY_TIME; retry++) { + if (i2c_transfer(client->adapter, &msg, 1) =3D=3D 1) { + retval =3D length; + break; + } + msleep(20); + } + if (retry =3D=3D MAX_I2C_RETRY_TIME) { + pr_warn("%s: I2C write over retry limit\n", __func__); + retval =3D -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 =3D 0; + unsigned char marker =3D 0, code =3D 0; + unsigned int total_length =3D 0, remaining_length =3D 0; + unsigned char *temp_buffer; + + total_length =3D MESSAGE_HEADER_SIZE + payload_length + 1; + remaining_length =3D total_length - READ_LENGTH; + + temp_buffer =3D kmalloc(remaining_length + 2, GFP_KERNEL); + if (!temp_buffer) + return -ENOMEM; + + retval =3D 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 =3D temp_buffer[0]; + code =3D temp_buffer[1]; + + if (marker !=3D MESSAGE_MARKER) { + pr_warn("incorrect header marker (0x%02x)\n", marker); + return -EIO; + } + + if (code !=3D 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] =3D 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 =3D 0; + unsigned char *out_buf; + + unsigned int header_size =3D add_length && length ? 3 : 1; + + out_buf =3D kmalloc(length + header_size, GFP_KERNEL); + if (retval < 0) + return retval; + + out_buf[0] =3D command; + if (add_length && length) { + out_buf[1] =3D (unsigned char)length; + out_buf[2] =3D (unsigned char)(length >> 8); + } + + if (length) + memcpy(&out_buf[header_size], data, length); + + retval =3D 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 =3D 0; + unsigned int total_length =3D 0, payload_length =3D 0; + unsigned char *message_buffer; + struct syna_tcm_message_header *header =3D NULL; + + message_buffer =3D kmalloc(READ_LENGTH, GFP_KERNEL); + if (!message_buffer) + return -ENOMEM; + + retval =3D touch_i2c_continue_read(tcm_info->client, message_buffer, + READ_LENGTH); + if (retval < 0) + return retval; + + header =3D (struct syna_tcm_message_header *)message_buffer; + if (header->marker !=3D MESSAGE_MARKER) { + pr_warn("wrong header marker:0x%02x\n", header->marker); + return -ENXIO; + } + + unsigned char report_code =3D header->code; + + payload_length =3D le2_to_uint(header->length); + + if (report_code <=3D STATUS_ERROR || report_code =3D=3D STATUS_INVALID) { + switch (report_code) { + case STATUS_OK: + break; + case STATUS_CONTINUED_READ: + case STATUS_IDLE: + case STATUS_BUSY: + payload_length =3D 0; + kfree(message_buffer); + return 0; + default: + if (report_code !=3D STATUS_ERROR) { + pr_warn("IO ERROR"); + kfree(message_buffer); + return -EIO; + } + } + } + + total_length =3D MESSAGE_HEADER_SIZE + payload_length + 1; + + unsigned char *temp_buffer =3D message_buffer; + + message_buffer =3D 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 =3D=3D 0) { + message_buffer[total_length - 1] =3D MESSAGE_PADDING; + goto check_padding; + } + + if (MESSAGE_HEADER_SIZE + payload_length > READ_LENGTH) { + retval =3D 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] !=3D MESSAGE_PADDING) { + pr_warn("missing message padding"); + return -EIO; + } + + if (tcm_info->response_buf !=3D NULL) + kfree(tcm_info->response_buf); + + tcm_info->response_buf =3D message_buffer; + tcm_info->response_length =3D 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 =3D 0; + + if (resp_buf =3D=3D NULL) { + retval =3D syna_tcm_raw_write(tcm_info, command, payload, length, + false); + goto exit; + } + reinit_completion(&response_complete); + + tcm_info->response_buf =3D *resp_buf; + tcm_info->response_length =3D *resp_length; + + // Here write command to device + retval =3D syna_tcm_raw_write(tcm_info, command, payload, length, true); + if (retval !=3D 0) + goto exit; + + retval =3D wait_for_completion_timeout(&response_complete, + RESPONSE_TIMEOUT_MS); + if (retval =3D=3D 0) { + pr_warn("timed out waiting for response (command 0x%02x)\n", + command); + retval =3D -EIO; + } + +exit: + if (tcm_info->response_length < 0) + return -1; + if (resp_buf !=3D NULL) { + *resp_buf =3D tcm_info->response_buf; + *resp_length =3D tcm_info->response_length; + } + + return retval; +} + +static int syna_tcm_set_normal_report_config(struct syna_tcm_data *tcm_inf= o) +{ + int retval; + unsigned int idx =3D 0; + unsigned int length; + + length =3D 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 =3D kmalloc(length, GFP_KERNEL); + + if (!report_buf) + return -ENOMEM; + + report_buf[idx++] =3D TOUCH_FOREACH_ACTIVE_OBJECT; + report_buf[idx++] =3D TOUCH_OBJECT_N_INDEX; + report_buf[idx++] =3D 4; + report_buf[idx++] =3D TOUCH_OBJECT_N_CLASSIFICATION; + report_buf[idx++] =3D 4; + report_buf[idx++] =3D TOUCH_OBJECT_N_X_POSITION; + report_buf[idx++] =3D 16; + report_buf[idx++] =3D TOUCH_OBJECT_N_Y_POSITION; + report_buf[idx++] =3D 16; + report_buf[idx++] =3D TOUCH_OBJECT_N_X_WIDTH; + report_buf[idx++] =3D 12; + report_buf[idx++] =3D TOUCH_OBJECT_N_Y_WIDTH; + report_buf[idx++] =3D 12; + report_buf[idx++] =3D TOUCH_FOREACH_END; + report_buf[idx++] =3D TOUCH_END; + + unsigned char *resp_buf =3D NULL; + unsigned int resp_length =3D 0; + + retval =3D 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 =3D NULL; + unsigned int resp_length =3D 0; + + retval =3D 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 =3D resp_length - MESSAGE_HEADER_SIZE - 1; + + kfree(tcm_info->report_config); + tcm_info->report_config =3D 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 =3D 0; + unsigned char *resp_buf =3D NULL; + unsigned int resp_length =3D 0; + unsigned int timeout =3D RESPONSE_TIMEOUT_MS; + +get_app_info: + retval =3D 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 =3D le2_to_uint(tcm_info->app_info.status); + + if (tcm_info->app_status =3D=3D APP_STATUS_BOOTING || + tcm_info->app_status =3D=3D APP_STATUS_UPDATING) { + if (timeout > 0) { + msleep(APP_STATUS_POLL_MS); + timeout -=3D APP_STATUS_POLL_MS; + goto get_app_info; + } + } + + retval =3D 0; + +exit: + kfree(resp_buf); + + return retval; +} + +static int syna_tcm_identify(struct syna_tcm_data *tcm_info) +{ + int retval =3D 0; + unsigned char *resp_buf =3D NULL; + unsigned int resp_length =3D 0; + + retval =3D 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 =3D=3D MODE_APPLICATION) + retval =3D 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_inf= o) +{ + int retval =3D 0; + unsigned int retry =3D 3; + unsigned char *resp_buf =3D NULL; + unsigned int resp_length =3D 0; + +retry: + retval =3D 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 =3D 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 !=3D MODE_APPLICATION) { + if (retry--) + goto retry; + retval =3D -EINVAL; + goto exit; + } else if (tcm_info->app_status !=3D APP_STATUS_OK) { + pr_warn("syna_tcm: application status =3D 0x%02x\n", + tcm_info->app_status); + if (retry--) + goto retry; + } + + retval =3D 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 len= gth + * 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 =3D 0; + unsigned char byte_data =3D 0; + unsigned int output_data =3D 0; + unsigned int bit_offset =3D offset % 8; + unsigned int byte_offset =3D offset / 8; + unsigned int data_bits =3D 0; + unsigned int available_bits =3D 0; + unsigned int remaining_bits =3D num_bits; + + if (num_bits =3D=3D 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 =3D 0; + return -1; + } + + while (remaining_bits) { + byte_data =3D report_buf[byte_offset]; + byte_data >>=3D bit_offset; + + available_bits =3D 8 - bit_offset; + data_bits =3D available_bits < remaining_bits ? available_bits : + remaining_bits; + mask =3D 0xff >> (8 - data_bits); + + byte_data &=3D mask; + + output_data |=3D byte_data << (num_bits - remaining_bits); + + bit_offset =3D 0; + byte_offset +=3D 1; + remaining_bits -=3D data_bits; + } + + *data =3D output_data; + + return 0; +} + +static int syna_tcm_parse_touch_report(struct syna_tcm_data *tcm_info) +{ + int config_data_pointer =3D 0, obj =3D 0, foreach_start =3D 0, foreach_en= d, + retval; + unsigned int offset =3D 0, num_bits, data =3D 0; + unsigned char *report_config =3D tcm_info->report_config; + unsigned int config_size =3D tcm_info->report_size; + + unsigned char *report =3D &tcm_info->response_buf[MESSAGE_HEADER_SIZE]; + unsigned int report_size =3D + tcm_info->response_length - MESSAGE_HEADER_SIZE - 1; + unsigned char code; + + struct touch_data *touch_data =3D + 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 =3D report_config[config_data_pointer++]; + switch (code) { + case TOUCH_END: + goto exit; + case TOUCH_FOREACH_ACTIVE_OBJECT: + obj =3D 0; + foreach_start =3D config_data_pointer; + if (offset >=3D report_size * 8) + goto exit; + break; + case TOUCH_FOREACH_END: + foreach_end =3D config_data_pointer; + if (offset < report_size * 8) + config_data_pointer =3D 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 downstre= am driver. After it comes a length of 8 bits and this is ignored + num_bits =3D report_config[config_data_pointer++]; + offset +=3D num_bits; + break; + case TOUCH_PAD_TO_NEXT_BYTE: + offset =3D ceil_div(offset, 8) * 8; + break; + case TOUCH_OBJECT_N_INDEX: + num_bits =3D report_config[config_data_pointer++]; + retval =3D 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 +=3D num_bits; + break; + case TOUCH_OBJECT_N_CLASSIFICATION: + num_bits =3D report_config[config_data_pointer++]; + retval =3D 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 >=3D tcm_info->touchpanel_max_objects) { + pr_warn("too many objects\n"); + return -1; + } + touch_data[obj].status =3D data; + offset +=3D num_bits; + break; + case TOUCH_OBJECT_N_X_POSITION: + num_bits =3D report_config[config_data_pointer++]; + retval =3D 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 =3D data; + offset +=3D num_bits; + break; + case TOUCH_OBJECT_N_Y_POSITION: + num_bits =3D report_config[config_data_pointer++]; + retval =3D 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 =3D data; + offset +=3D num_bits; + break; + case TOUCH_OBJECT_N_Z: + num_bits =3D report_config[config_data_pointer++]; + retval =3D 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 =3D data; + offset +=3D num_bits; + break; + case TOUCH_OBJECT_N_X_WIDTH: + num_bits =3D report_config[config_data_pointer++]; + retval =3D 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 =3D data; + offset +=3D num_bits; + break; + case TOUCH_OBJECT_N_Y_WIDTH: + num_bits =3D report_config[config_data_pointer++]; + retval =3D 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 =3D data; + offset +=3D num_bits; + break; + default: + pr_notice("unrecognized touch report config code: %d", + code); + } + } + +exit: + for (int i =3D 0; i < tcm_info->touchpanel_max_objects; i++) { + if (touch_data[i].status =3D=3D NOP) + continue; + + bool lift =3D touch_data[i].status =3D=3D 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 =3D touch_data[i].x_width; + int y_width =3D touch_data[i].y_width; + int major_width =3D x_width > y_width ? x_width : y_width; + int minor_width =3D x_width > y_width ? y_width : x_width; + int x_is_major =3D 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 =3D dev; + + retval =3D syna_tcm_read_message(tcm_info, 0); + if (retval < 0) { + tcm_info->response_length =3D -1; + goto exit; + } + + if (tcm_info->response_buf[1] =3D=3D REPORT_TOUCH) + syna_tcm_parse_touch_report(tcm_info); + +exit: + if (tcm_info->response_buf[1] <=3D REPORT_IDENTIFY) + complete(&response_complete); + + return IRQ_HANDLED; +} + +static int syna_tcm_power_on(struct syna_tcm_data *tcm_info) +{ + int ret; + + ret =3D 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 =3D devm_kzalloc(&client->dev, sizeof(*tcm_info), GFP_KERNEL); + if (!tcm_info) + return -ENOMEM; + + i2c_set_clientdata(client, tcm_info); + tcm_info->client =3D client; + tcm_info->response_buf =3D NULL; + + of_property_read_u32(client->dev.of_node, "max-objects", + &tcm_info->touchpanel_max_objects); + + tcm_info->reset_gpio =3D + gpiod_get_index(&client->dev, "reset", 0, GPIOD_OUT_HIGH); + + tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VDD].supply =3D "vdd"; + tcm_info->regulators[SYNA_TCM_ONCELL_REGULATOR_VCC].supply =3D "vcc"; + err =3D 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 =3D devm_add_action_or_reset(&client->dev, syna_tcm_oncell_power_o= ff, tcm_info); + // if (err) + // return err; + + err =3D syna_tcm_power_on(tcm_info); + if (err < 0) + return err; + + // This needs to happen before the first write to the device + err =3D 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 =3D syna_tcm_run_application_firmware(tcm_info); + if (err < 0) + return err; + + // err =3D syna_tcm_set_normal_report_config(tcm_info); + // if (err < 0) + // pr_err("syna_tcm: failed to set normal touch report config") + + err =3D syna_tcm_get_report_config(tcm_info); + if (err < 0) + return err; + + tcm_info->input =3D devm_input_allocate_device(&client->dev); + if (!tcm_info->input) + return -ENOMEM; + + tcm_info->input->name =3D TOUCHPANEL_DEVICE; + tcm_info->input->id.bustype =3D 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 =3D 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 =3D input_register_device(tcm_info->input); + if (err) + return err; + + pr_info("syna_tcm: probe done"); + tcm_info->initialize_done =3D true; + + return 0; +} + +static const struct of_device_id syna_driver_ids[] =3D { + { + .compatible =3D "syna,s3908", + }, + {} +}; +MODULE_DEVICE_TABLE(of, syna_driver_ids); + +static const struct i2c_device_id syna_i2c_ids[] =3D { { TOUCHPANEL_DEVICE= , 0 }, + {} }; +MODULE_DEVICE_TABLE(i2c, syna_i2c_ids); + +// static const struct dev_pm_ops syna_pm_ops =3D { +// .suspend =3D syna_i2c_suspend, +// .resume =3D syna_i2c_resume, +// }; + +static struct i2c_driver syna_i2c_driver =3D { + .probe =3D syna_tcm_probe, + // .remove =3D syna_i2c_remove, + // .shutdown =3D syna_tp_shutdown, + .id_table =3D syna_i2c_ids, + .driver =3D { + .name =3D TOUCHPANEL_DEVICE, + .of_match_table =3D syna_driver_ids, + // .pm =3D &syna_pm_ops, + }, +}; + +module_i2c_driver(syna_i2c_driver); --=20 2.44.0