From nobody Fri Sep 25 16:51:04 2026 Received: from mail-wr1-f42.google.com (mail-wr1-f42.google.com [209.85.221.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9E2353BBFB3 for ; Thu, 10 Sep 2026 07:07:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789024070; cv=none; b=QWkzgvrZMWwF4vAc/rCccG5J+KMTvQEUfQNUJ+34obShj2ySXz7mx/Bkt7+mU4irX45g1Zt/Ru2O6ktRSCliFYPQ0ruj6WgtspXCIXd3SOlac7gOXcQ7vgDQ35ct60t67SHTJuwpji6M78jC9gC32GQZQkTdHWBuCTR7EzYDMso= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789024070; c=relaxed/simple; bh=P2OqeAVCWHFS1/46fjXp/GqTmb5OLthjWFq4IcxNSrc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KqKCsExXclW3d9leksRPr0Xab1Gn3eeQQoaoSN+YF8rkWfd4hSt2piMij4hLr2XOAKDHSeCPN61PRLFKQTx3x0Vl1uypneaQKWL6oYxBWbAp439AH3XDLS+njUjrizVD7m+IbSk8RJ6IhxQkkeXdgKbxAbVbReG8KJUcJhnBbgw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=O28Mq88m; arc=none smtp.client-ip=209.85.221.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="O28Mq88m" Received: by mail-wr1-f42.google.com with SMTP id ffacd0b85a97d-482f2ee53e7so4877133f8f.1 for ; Thu, 10 Sep 2026 00:07:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789024064; x=1789628864; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6N41mPYMtwHJxcU+b3y0AU5P4+rfzOFqefvh/tO1NTI=; b=O28Mq88mXSz6XKCUzS9M/KBDQXXgZURxNuPPzcjkA7ep1BYoB8jn6395/uA9wyUm81 PzfM6rSpniSRzSLbNKxRWuGScTYsGJRnCcGx3U9DuJX/ZjBr2cSlOj5YBU/LqwB/waex 1gP6XtZpdb6sWSOmTBcEoz8dSIVt7Cc9FAv0ta8ICEApMq2cDji08BiC4DMW7tzGYJVO +H6TpqIVfpztHFkhOHE0gPNJ9cfwN7IssO67B+m9YFaeTVHXHP0HJtKvqN5r0tBJL95b 7Cji+9o4CYFKmioJSZsu3SdmmxmRN/5MV4xfKuQlmkOv7zDPWiGqvNj+kTwQ00x80St+ KaaA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789024064; x=1789628864; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=6N41mPYMtwHJxcU+b3y0AU5P4+rfzOFqefvh/tO1NTI=; b=ffyk1PjFsEGHU59xsLZZv7TDmKcXD+zIfa1FlZZog56d2bAP6r4K7s0wgIhCOjDdtT goeHWYC1d7bpLmluIMz4u/dxpbKpUuc+srEuILEn9z+MJe4IERuiAJNw85zmVUJ4p0tG iV7tk4gphtc0q8Gm9R9kELyWzFtJpO0U3x5IbGbG9a2gG2HFHAvuD49grR/+V1VeP0Y0 ugKCwsn+4/tkMjADgh8TFSGrnrRlFe5DhBOiuwtgMILffpHLyb1P3YflnlAQX/rXaXSB IRvdghcOWMHeHsbwEVkTZNA3vplHS1BnGtD+Z7YpiQR80+cbZfo9c8X2oIRT38FVHb9i gRjQ== X-Forwarded-Encrypted: i=1; AKwUvBxz/F2Bti/IdMxkLOHbwMr5zzKl0wVW9rPbBtOFc/0gg/DejVEUfQ5OL8Sg416G7MImQBv2gCSLBE6J2z4=@vger.kernel.org X-Gm-Message-State: AFuF++nr3/gDoU9p8xTZBjsl8tv/Vp7S6Qeu3zJQbKjkOzm7Si0t+6pS MGEUuuVkzN7QhyZAbTYqrpGEh9JgT1+czjWCXFuAZ6xIHw+s3FDOcDfR X-Gm-Gg: AYBFou0pPO/pbF54V8GM6cr2nY8IyXiQOpvNKjzjBJeh7PHtCKA99t/pmCEos+hC6gS SEilsDz4dOWm1XQT6D5bWd/ZFrkDXo5g3jmlohQgTA09CYPhxFQ3dlhLROXKmiJjIEBbYzUz0Qc a0Xa7nDD0yOigng2MZyP7k/XaOoDhyVGLsfbvSmoQmfEUT+NpwyIVQnDvg70+lMgbogXkzmmHKK 1ZvEWNfTdFMgH9CxLxoF2A7Xse+vPdhjJwWc6E88Iqij/GxnGXu8jb4oCvpSnWwKKynAk+HLQtH VJk9181I8kfuiPZY2vRDXZgaVdpQ7qRpozEomNFK0w0yoi10/A/EfbtLEwpJguY94u9/g2u0Di6 zc2ayyL8jCDGej2Gny1ZUb/Ia1Z51BscUd+5S0BqSOPTOvLEx8WmOTOu6BsWBtGiTMusv8N7IUK dqfoxNXy3eko8/tVNk/j2HQNTpCh94Txh2RVtdFqfmE4uX7WbAAzuSjioWHx9KacekNHxVD8Gk9 c4= X-Received: by 2002:a05:6000:38e:b0:485:ea30:1f72 with SMTP id ffacd0b85a97d-485ea301fb5mr5498696f8f.27.1789024064007; Thu, 10 Sep 2026 00:07:44 -0700 (PDT) Received: from Empers0n ([193.23.219.104]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-485883959f0sm52244311f8f.10.2026.09.10.00.07.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 10 Sep 2026 00:07:43 -0700 (PDT) From: Vsevolod Nevorotov To: Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Henrik Rydberg , linux-input@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Vsevolod Nevorotov Subject: [PATCH 1/2] dt-bindings: input: touchscreen: add Synaptics S3908 controller Date: Thu, 10 Sep 2026 14:07:22 +0700 Message-ID: <20260910070723.347768-2-sevanevorotov29@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260910070723.347768-1-sevanevorotov29@gmail.com> References: <20260910070723.347768-1-sevanevorotov29@gmail.com> 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" Add devicetree schema binding for the Synaptics S3908 TouchComm generation-1 I2C touchscreen controller, found in devices such as the OnePlus 9RT. Signed-off-by: Vsevolod Nevorotov --- .../input/touchscreen/syna,s3908.yaml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/touchscreen/syn= a,s3908.yaml 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 00000000000..68479b0b7d7 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/touchscreen/syna,s3908.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Synaptics S3908 TouchComm I2C Touchscreen + +maintainers: + - Vsevolod Nevorotov + +description: + Synaptics S3908 is a TouchComm generation-1 touchscreen controller conne= cted + over I2C. + +allOf: + - $ref: touchscreen.yaml# + +properties: + compatible: + const: syna,s3908 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + vdd-supply: + description: Core / digital power supply regulator (VDD) + + avdd-supply: + description: Analog power supply regulator (AVDD) + + wakeup-source: true + +required: + - compatible + - reg + - interrupts + - reset-gpios + - vdd-supply + - avdd-supply + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + touchscreen@4b { + compatible =3D "syna,s3908"; + reg =3D <0x4b>; + interrupt-parent =3D <&tlmm>; + interrupts =3D <23 IRQ_TYPE_LEVEL_LOW>; + reset-gpios =3D <&tlmm 22 GPIO_ACTIVE_LOW>; + vdd-supply =3D <&vreg_l3c_3p0>; + avdd-supply =3D <&vreg_l8c_1p8>; + wakeup-source; + }; + }; --=20 2.55.0 From nobody Fri Sep 25 16:51:04 2026 Received: from mail-wm2-f12.google.com (mail-wm2-f12.google.com [74.125.225.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4C2713B05BF for ; Thu, 10 Sep 2026 07:07:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789024076; cv=none; b=BmiaefxA4HBMrnybzk/gGBu22oa2bjQMYDMRjFXiXTWq2Y/FhX4HBMFrv+O/jgVYd7Sj35ZCXMl381FilbV5ETwfAVffQggAzWDnEKux+il6Xu89ephXJS2fqd2Pq8lJnqvRyUP+7CpML2BEWM6nklFSNrNCQEGMGK3hsYmm6uM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789024076; c=relaxed/simple; bh=g30GxTxRTeYhPCuhArek43Km8phjr9JeT/EUOgfn3qE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lPmfl6jntzr50tJrrvrqRD4jEugErlOlzE3JyPYNWLwwYbc/lrAxt/SRZH6l2rDGcShno3NJoib45fA7YX/3GlcpH1nQu/n0vQXxyV7AgX7G/7UkUnImvfa8W3wcdJz3/VTcRLKr1cENEOvdfW8BmoIjDjXhxqagMugJc+IXdNk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=O5m/zz3k; arc=none smtp.client-ip=74.125.225.140 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="O5m/zz3k" Received: by mail-wm2-f12.google.com with SMTP id 5b1f17b1804b1-49cd38e0f79so10214825e9.3 for ; Thu, 10 Sep 2026 00:07:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789024068; x=1789628868; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=NFxyNPFRY/OF265DXDt5lJagpiOTcC5Yc4VHeaRnB2Y=; b=O5m/zz3krUWPsU+tqH+7VKywsI5AMneFKYj/ZfV3qAKe4RqANHI2/XNOOrpAYjGGXj AHVCb4yNVfCizpuKWZhRBmmustMDK5hjvBfFrAF3/0uIjPpEh3gD8fS6BPa+UwhWCaRC 96azRL9yZHbDuDHacXDPZSz79Zp8XlsejqA1/P7BNRw9UZkZBggyLHJQ9JQrG3wtIia4 l0hWh1BR21gwauHAaP4kmifSKrHE4rdAEQvBDLomCdPb8evLH4A5MoaMsl2Tlsy2x02q Snpn4iJHvqLHefZM3+nQwR5H5o0Z4UqZaKBRW4FJWAx58xQ5cbWzAJzkDYT2JQoajR9s /OyA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789024068; x=1789628868; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=NFxyNPFRY/OF265DXDt5lJagpiOTcC5Yc4VHeaRnB2Y=; b=VpEKkgx54TJyoO26gf1e7N3x7vzVkPg62zZdztnlkkGRNAw3finCUJRiWWmASZxyMi dBQlB1XZMnOXYlrJBgAVv2x+JhQG21BT/XYsM63Yp2Fdm/S0+yCs7SRpp1evFtejEFHV wevtHiQK229ijM73mFJa4oRO/Em2yJJPHzekyq5q157UpbJMBcCA9s7kMPCfm14d5RyC YspRjnTX1PdZrrZgW1lsCoMMNZ8otw+CNmeeNF9B5diVsvX8Ul6gcpSaCM0MkKO+CuSh fgQd8nXyxLxcTgokWhhJZlL7IazTbykFtok217tmdJ2a7ftunJoEDnDmWyiDQGKpBHUp 09Lw== X-Forwarded-Encrypted: i=1; AKwUvBx3M/JcXeUy0hmw7FicIeu0IKLIgTQE5biu49WBlqWjqwcGiojh3TP+dotjXCMlc8zX939g+AyPT1VaTeo=@vger.kernel.org X-Gm-Message-State: AFuF++nlHm+ITOyVUtE+daINyB8Wm6pnLY5sUB8ne1T3SEp/z5k7cAS9 UL7v+1nD+Ww7xsuyKtd/aDko8eadf3XS4Gf+7VzPi0elkItBRhh/MMjWvsu81Wo+ X-Gm-Gg: AYBFou034m/D9/QMVshdz/Hio4wJabLyKnyjTWalRjhTEnf21cEq7qFc5pH7+7d7eDa bdu19FY7QEEa96XHlRx4w6BFz+Gp2Is8SmUN+Z5MdEsW/NQaxyTyYOCBZ1jnbtc2RHyc1cpToVr Y1gC3tG7349PRYVkiwDQxURQLyaTnCz87BjF+kUSXq/qvQwsRfViKp7Azhz2TWP9PV1RCHC04iu r17Ql1gjsRS0nOsFUph0t1ZhhxCmKY25QFhY8HI43FOiBh+DAFyCel7leSsViYAB69JT5RPMTRh yl+K3xapD4ulNQZxSzxz6IMVylpaFZ589jeJblkmVjUgoTPPTtfxF+8ka2AEcWZBF7gYaXJTuBO mEqpjMbkSEBj0SMUOSPh+yZIKrr+AxM43JhuQBN2+YMA6tCWN6zf/Q5/6TtLhEPs5jlNNpY0TyO 401vPbrNzm8sStGzLCWLqNZwVR8Lv7JhhCimRL7I/gzWox6MYQ6YUfGtUehfEV0n2Ih8q/63Y2b 6KzQ2TXyd8fvQ== X-Received: by 2002:a05:600c:698c:b0:49c:edfe:d525 with SMTP id 5b1f17b1804b1-49d1f35366fmr219751575e9.10.1789024067422; Thu, 10 Sep 2026 00:07:47 -0700 (PDT) Received: from Empers0n ([193.23.219.104]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-485883959f0sm52244311f8f.10.2026.09.10.00.07.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 10 Sep 2026 00:07:46 -0700 (PDT) From: Vsevolod Nevorotov To: Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Henrik Rydberg , linux-input@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Vsevolod Nevorotov Subject: [PATCH 2/2] Input: synaptics_tcm_i2c - add Synaptics TouchComm I2C touchscreen driver Date: Thu, 10 Sep 2026 14:07:23 +0700 Message-ID: <20260910070723.347768-3-sevanevorotov29@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260910070723.347768-1-sevanevorotov29@gmail.com> References: <20260910070723.347768-1-sevanevorotov29@gmail.com> 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" Add input touchscreen driver for Synaptics TouchComm (TCM) generation-1 controllers connected over I2C that boot into application mode out of their own flash (such as the Synaptics S3908 fitted to the OnePlus 9RT). The driver supports: - Multi-touch reporting via input_mt framework - Configurable touch report layouts - Standard touchscreen properties (axis swapping/inversion) - Low-power wakeup gestures (double-tap to wake) - Deep sleep mode during system suspend Signed-off-by: Vsevolod Nevorotov --- MAINTAINERS | 7 + drivers/input/touchscreen/Kconfig | 15 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/synaptics_tcm_i2c.c | 1033 +++++++++++++++++ 4 files changed, 1056 insertions(+) create mode 100644 drivers/input/touchscreen/synaptics_tcm_i2c.c diff --git a/MAINTAINERS b/MAINTAINERS index 0d7987278c0..aa20d9153ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26125,6 +26125,13 @@ S: Maintained F: Documentation/devicetree/bindings/regulator/silergy,sy8106a.yaml F: drivers/regulator/sy8106a-regulator.c =20 +SYNAPTICS TOUCHCOMM I2C TOUCHSCREEN DRIVER +M: Vsevolod Nevorotov +L: linux-input@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/input/touchscreen/syna,s3908.yaml +F: drivers/input/touchscreen/synaptics_tcm_i2c.c + SYNC FILE FRAMEWORK M: Sumit Semwal L: linux-media@vger.kernel.org diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/= Kconfig index 9b9ae8ac3f7..c58e3e4cc1c 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -1310,6 +1310,21 @@ config TOUCHSCREEN_SX8654 To compile this driver as a module, choose M here: the module will be called sx8654. =20 +config TOUCHSCREEN_SYNAPTICS_TCM_I2C + tristate "Synaptics TouchComm I2C touchscreen" + depends on I2C + help + Say Y here if you have a Synaptics TouchComm (TCM) touch controller + attached over I2C, such as the S3908 found in the OnePlus 9RT. + + Only controllers that boot into application mode out of their own + flash are supported; there is no host firmware download. + + If unsure, say N. + + To compile this driver as a module, choose M here: the module will be + called synaptics_tcm_i2c. + config TOUCHSCREEN_TPS6507X tristate "TPS6507x based touchscreens" depends on I2C diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen= /Makefile index bfd9de83389..a1debe134a0 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_SYNAPTICS_TCM_I2C) +=3D synaptics_tcm_i2c.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_i2c.c b/drivers/input/= touchscreen/synaptics_tcm_i2c.c new file mode 100644 index 00000000000..2553dabd392 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_tcm_i2c.c @@ -0,0 +1,1033 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Synaptics TouchComm (TCM) I2C touchscreen driver. + * + * Covers TouchComm generation-1 controllers that boot into application mo= de out + * of their own flash, i.e. the ones that need no host firmware download at + * probe time. The Synaptics S3908 fitted to the OnePlus 9RT (oneplus,mart= ini) + * is one of those. + * + * Protocol, as implemented by the vendor driver: + * + * A command is one plain I2C write of + * + * [ cmd | len_lo | len_hi | payload... ] + * + * A message -- both command responses and asynchronous reports -- is one + * plain I2C read of + * + * [ 0xa5 | code | len_lo | len_hi | payload... | 0x5a ] + * + * code <=3D 0x0f (and 0xff) is a status, so the message is a command re= sponse; + * code >=3D 0x10 is a report id. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SYNA_MESSAGE_MARKER 0xa5 +#define SYNA_MESSAGE_PADDING 0x5a +#define SYNA_HEADER_SIZE 4 + +/* + * Read length limit of the controller (RD_CHUNK_SIZE in the vendor driver= ). + * One read may not exceed this, including the header. + */ +#define SYNA_RD_CHUNK_SIZE 64 + +/* Commands. */ +#define SYNA_CMD_IDENTIFY 0x02 +#define SYNA_CMD_ENABLE_REPORT 0x05 +#define SYNA_CMD_DISABLE_REPORT 0x06 +#define SYNA_CMD_GET_APP_INFO 0x20 +#define SYNA_CMD_GET_DYNAMIC_CONFIG 0x23 +#define SYNA_CMD_SET_DYNAMIC_CONFIG 0x24 +#define SYNA_CMD_GET_TOUCH_REPORT_CONFIG 0x25 +#define SYNA_CMD_SET_TOUCH_REPORT_CONFIG 0x26 +#define SYNA_CMD_ENTER_DEEP_SLEEP 0x2c +#define SYNA_CMD_EXIT_DEEP_SLEEP 0x2d + +/* Dynamic config IDs. */ +#define SYNA_DC_IN_WAKEUP_GESTURE_MODE 0x09 +#define SYNA_DC_GESTURE_MASK 0xfe + +/* Status codes, i.e. header codes of a command response. */ +#define SYNA_STATUS_IDLE 0x00 +#define SYNA_STATUS_OK 0x01 +#define SYNA_STATUS_BUSY 0x02 +#define SYNA_STATUS_CONTINUED_READ 0x03 +#define SYNA_STATUS_ERROR 0x0f +#define SYNA_STATUS_INVALID 0xff + +/* Report ids. Anything >=3D SYNA_REPORT_IDENTIFY is a report, not a statu= s. */ +#define SYNA_REPORT_IDENTIFY 0x10 +#define SYNA_REPORT_TOUCH 0x11 +#define SYNA_REPORT_LOG 0x1d + +#define SYNA_MODE_APPLICATION 0x01 + +/* Gesture opcodes & IDs. */ +#define SYNA_GESTURE_DTAP 0x01 + +/* Opcodes of the touch report config. */ +enum syna_touch_report_code { + SYNA_TOUCH_END =3D 0, + SYNA_TOUCH_FOREACH_ACTIVE_OBJECT =3D 1, + SYNA_TOUCH_FOREACH_OBJECT =3D 2, + SYNA_TOUCH_FOREACH_END =3D 3, + SYNA_TOUCH_PAD_TO_NEXT_BYTE =3D 4, + SYNA_TOUCH_TIMESTAMP =3D 5, + SYNA_TOUCH_OBJECT_N_INDEX =3D 6, + SYNA_TOUCH_OBJECT_N_CLASSIFICATION =3D 7, + SYNA_TOUCH_OBJECT_N_X_POSITION =3D 8, + SYNA_TOUCH_OBJECT_N_Y_POSITION =3D 9, + SYNA_TOUCH_OBJECT_N_Z =3D 10, + SYNA_TOUCH_OBJECT_N_X_WIDTH =3D 11, + SYNA_TOUCH_OBJECT_N_Y_WIDTH =3D 12, + SYNA_TOUCH_GESTURE_DOUBLE_TAP =3D 16, + SYNA_TOUCH_NUM_OF_ACTIVE_OBJECTS =3D 24, + SYNA_TOUCH_REPORT_GESTURE_SWIPE =3D 193, + SYNA_TOUCH_REPORT_GESTURE_CIRCLE =3D 194, + SYNA_TOUCH_REPORT_GESTURE_UNICODE =3D 195, + SYNA_TOUCH_REPORT_GESTURE_VEE =3D 196, + SYNA_TOUCH_REPORT_GESTURE_TRIANGLE =3D 197, + SYNA_TOUCH_REPORT_GESTURE_INFO =3D 198, + SYNA_TOUCH_REPORT_GESTURE_COORDINATE =3D 199, +}; + +/* Object classification. Anything other than LIFT counts as a contact. */ +#define SYNA_OBJECT_LIFT 0 + +#define SYNA_MAX_OBJECTS 16 +#define SYNA_MAX_CONFIG_SIZE 128 +#define SYNA_MAX_MESSAGE_SIZE 1024 + +/* Vendor driver: POWERUP_TO_RESET_TIME and RESET_TO_NORMAL_TIME. */ +#define SYNA_POWERUP_TO_RESET_MS 10 +#define SYNA_RESET_TO_NORMAL_MS 1000 + +#define SYNA_RESPONSE_TIMEOUT_MS 500 + +struct syna_tcm_app_info { + __le16 version; + __le16 status; + __le16 static_config_size; + __le16 dynamic_config_size; + __le16 app_config_start_write_block; + __le16 app_config_size; + __le16 max_touch_report_config_size; + __le16 max_touch_report_payload_size; + u8 customer_config_id[16]; + __le16 max_x; + __le16 max_y; + __le16 max_objects; + __le16 num_of_buttons; + __le16 num_of_image_rows; + __le16 num_of_image_cols; + __le16 has_hybrid_data; +} __packed; + +struct syna_object { + u32 status; + u32 x; + u32 y; + u32 z; + u32 wx; + u32 wy; +}; + +struct syna_tcm { + struct i2c_client *client; + struct input_dev *input; + struct touchscreen_properties prop; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; + + /* Protects hardware I2C transfers and internal state */ + struct mutex lock; + bool suspended; + bool gesture_enabled; + u32 gesture_type; + + /* Scratch for one I2C read, and the message assembled out of them. */ + u8 rxbuf[SYNA_RD_CHUNK_SIZE]; + u8 payload[SYNA_MAX_MESSAGE_SIZE]; + unsigned int payload_len; + u8 code; + + u8 cmdbuf[SYNA_MAX_CONFIG_SIZE + 3]; + + u8 config[SYNA_MAX_CONFIG_SIZE]; + unsigned int config_len; + + u8 normal_config[SYNA_MAX_CONFIG_SIZE]; + unsigned int normal_config_len; + + unsigned int max_objects; + struct syna_object objects[SYNA_MAX_OBJECTS]; +}; + +/* + * Low-power gesture report config matching the stock vendor driver: + * Double-tap ID, gesture info, coordinates, and active object loop. + */ +static const u8 syna_gesture_config[] =3D { + SYNA_TOUCH_GESTURE_DOUBLE_TAP, 8, + SYNA_TOUCH_REPORT_GESTURE_INFO, 48, + SYNA_TOUCH_REPORT_GESTURE_COORDINATE, 192, + SYNA_TOUCH_FOREACH_ACTIVE_OBJECT, + SYNA_TOUCH_OBJECT_N_INDEX, 4, + SYNA_TOUCH_OBJECT_N_CLASSIFICATION, 4, + SYNA_TOUCH_OBJECT_N_X_POSITION, 16, + SYNA_TOUCH_OBJECT_N_Y_POSITION, 16, + SYNA_TOUCH_FOREACH_END, + SYNA_TOUCH_END, +}; + +/* + * Receive one whole message. On success ts->code holds the header code an= d the + * first ts->payload_len bytes of ts->payload hold its payload. + * + * Must be called with ts->lock held. + */ +static int syna_recv_message(struct syna_tcm *ts) +{ + struct device *dev =3D &ts->client->dev; + unsigned int total, copied, remaining; + int ret; + + ret =3D i2c_master_recv(ts->client, ts->rxbuf, SYNA_RD_CHUNK_SIZE); + if (ret < 0) + return ret; + + if (ret < SYNA_HEADER_SIZE) { + dev_warn_ratelimited(dev, "i2c recv short read: %d\n", ret); + return -EIO; + } + + if (ts->rxbuf[0] !=3D SYNA_MESSAGE_MARKER) { + dev_dbg(dev, "bad message marker 0x%02x\n", ts->rxbuf[0]); + return -EPROTO; + } + + ts->code =3D ts->rxbuf[1]; + ts->payload_len =3D get_unaligned_le16(&ts->rxbuf[2]); + + /* + * An idle or busy controller answers with a header and nothing behind + * it, whatever the length field happens to say. + */ + if (ts->code =3D=3D SYNA_STATUS_IDLE || ts->code =3D=3D SYNA_STATUS_BUSY = || + ts->code =3D=3D SYNA_STATUS_CONTINUED_READ) { + ts->payload_len =3D 0; + return 0; + } + + if (ts->payload_len > sizeof(ts->payload)) { + dev_err(dev, "message payload of %u bytes is too long\n", + ts->payload_len); + return -EMSGSIZE; + } + + /* Header, payload, and the trailing 0x5a padding byte. */ + total =3D SYNA_HEADER_SIZE + ts->payload_len + 1; + copied =3D min(ts->payload_len, SYNA_RD_CHUNK_SIZE - SYNA_HEADER_SIZE); + memcpy(ts->payload, &ts->rxbuf[SYNA_HEADER_SIZE], copied); + + if (total <=3D SYNA_RD_CHUNK_SIZE) { + if (ts->rxbuf[total - 1] !=3D SYNA_MESSAGE_PADDING) { + dev_dbg(dev, "bad message padding 0x%02x\n", + ts->rxbuf[total - 1]); + return -EPROTO; + } + return 0; + } + + /* + * Continued read: the rest arrives in chunks that repeat the marker and + * carry code 0x03, so only SYNA_RD_CHUNK_SIZE - 2 bytes of each one are + * payload. The trailing padding byte (0x5a) is read and verified in the + * final chunk. + */ + remaining =3D ts->payload_len - copied; + while (remaining) { + bool last =3D (remaining <=3D SYNA_RD_CHUNK_SIZE - 3); + unsigned int chunk =3D last ? remaining : + min(remaining - 1, + (unsigned int)(SYNA_RD_CHUNK_SIZE - 3)); + unsigned int read_len =3D chunk + 2 + (last ? 1 : 0); + + ret =3D i2c_master_recv(ts->client, ts->rxbuf, read_len); + if (ret < 0) + return ret; + + if (ret < read_len) + return -EIO; + + if (ts->rxbuf[0] !=3D SYNA_MESSAGE_MARKER || + ts->rxbuf[1] !=3D SYNA_STATUS_CONTINUED_READ) { + dev_err(dev, "bad continued-read header 0x%02x 0x%02x\n", + ts->rxbuf[0], ts->rxbuf[1]); + return -EPROTO; + } + + memcpy(&ts->payload[copied], &ts->rxbuf[2], chunk); + copied +=3D chunk; + remaining -=3D chunk; + + if (last && ts->rxbuf[2 + chunk] !=3D SYNA_MESSAGE_PADDING) { + dev_dbg(dev, "bad continued-read padding 0x%02x\n", + ts->rxbuf[2 + chunk]); + return -EPROTO; + } + } + + return 0; +} + +/** + * syna_exec_command() - Issue a command and collect its response + * @ts: Synaptics TCM device context + * @cmd: Command opcode (SYNA_CMD_*) + * @payload: Command payload buffer, or NULL + * @len: Length of @payload in bytes + * + * Issue a command and collect its response into ts->payload. Reports that + * arrive while waiting are dropped. + * + * Context: Must be called with @ts->lock held (except during probe before + * the interrupt handler is registered). + * Return: 0 on success, or a negative errno. + */ +static int syna_exec_command(struct syna_tcm *ts, u8 cmd, + const u8 *payload, unsigned int len) +{ + struct device *dev =3D &ts->client->dev; + unsigned long deadline; + u8 *buf =3D ts->cmdbuf; + int ret; + + if (len + 3 > sizeof(ts->cmdbuf)) + return -EINVAL; + + buf[0] =3D cmd; + put_unaligned_le16(len, &buf[1]); + if (len) + memcpy(&buf[3], payload, len); + + ret =3D i2c_master_send(ts->client, buf, len + 3); + if (ret < 0) { + dev_err(dev, "failed to write command 0x%02x: %d\n", cmd, ret); + return ret; + } + + deadline =3D jiffies + msecs_to_jiffies(SYNA_RESPONSE_TIMEOUT_MS); + do { + usleep_range(15000, 20000); + + ret =3D syna_recv_message(ts); + if (ret =3D=3D -EPROTO) + continue; /* controller not talking yet */ + if (ret < 0) + return ret; + + if (ts->code =3D=3D SYNA_REPORT_IDENTIFY && cmd =3D=3D SYNA_CMD_IDENTIFY) + return 0; + + if (ts->code >=3D SYNA_REPORT_IDENTIFY) + continue; /* a report, not our response */ + + switch (ts->code) { + case SYNA_STATUS_OK: + return 0; + case SYNA_STATUS_IDLE: + case SYNA_STATUS_BUSY: + continue; + default: + dev_err(dev, "command 0x%02x failed with status 0x%02x\n", + cmd, ts->code); + return -EIO; + } + } while (time_before(jiffies, deadline)); + + dev_err(dev, "command 0x%02x timed out\n", cmd); + return -ETIMEDOUT; +} + +static int syna_set_dynamic_config(struct syna_tcm *ts, u8 id, u16 value) +{ + u8 buf[3]; + + buf[0] =3D id; + put_unaligned_le16(value, &buf[1]); + + return syna_exec_command(ts, SYNA_CMD_SET_DYNAMIC_CONFIG, buf, sizeof(buf= )); +} + +/* Extract a big field of @bits from the report's bit stream at @offset. */ +static u32 syna_get_bits(const u8 *buf, unsigned int buf_len, + unsigned int offset, unsigned int bits) +{ + unsigned int byte =3D offset / 8; + unsigned int bit =3D offset % 8; + unsigned int done =3D 0; + u32 out =3D 0; + + if (!bits || bits > 32 || offset + bits > buf_len * 8) + return 0; + + while (done < bits) { + unsigned int avail =3D 8 - bit; + unsigned int take =3D min(avail, bits - done); + u8 val =3D (buf[byte] >> bit) & (0xff >> (8 - take)); + + out |=3D (u32)val << done; + done +=3D take; + bit =3D 0; + byte++; + } + + return out; +} + +/* + * Walk the touch report config and unpack the report accordingly. + */ +static int syna_parse_touch_report(struct syna_tcm *ts) +{ + const u8 *cfg =3D ts->config; + unsigned int idx =3D 0, loop_start =3D 0, loop_bits =3D 0; + unsigned int offset =3D 0; /* bit offset into the payload */ + unsigned int obj =3D 0; + unsigned int seen =3D 0, active =3D 0; + bool active_only =3D false, have_active_count =3D false; + bool done =3D false; + unsigned int i; + + memset(ts->objects, 0, sizeof(ts->objects)); + ts->gesture_type =3D 0; + + while (idx < ts->config_len && !done) { + u8 code =3D cfg[idx++]; + unsigned int bits; + u32 data; + + switch (code) { + case SYNA_TOUCH_END: + done =3D true; + continue; + + case SYNA_TOUCH_FOREACH_ACTIVE_OBJECT: + case SYNA_TOUCH_FOREACH_OBJECT: + active_only =3D code =3D=3D SYNA_TOUCH_FOREACH_ACTIVE_OBJECT; + loop_start =3D idx; + obj =3D 0; + seen =3D 0; + loop_bits =3D 0; + for (i =3D idx; i < ts->config_len; i++) { + if (cfg[i] =3D=3D SYNA_TOUCH_FOREACH_END || + cfg[i] =3D=3D SYNA_TOUCH_END) + break; + if (cfg[i] =3D=3D SYNA_TOUCH_PAD_TO_NEXT_BYTE) + continue; + if (++i < ts->config_len) + loop_bits +=3D cfg[i]; + } + if (active_only && !have_active_count && + offset + loop_bits > ts->payload_len * 8) { + /* Not enough payload for even one object */ + if (i < ts->config_len && cfg[i] =3D=3D SYNA_TOUCH_FOREACH_END) + idx =3D i + 1; + else + idx =3D i; + } + continue; + + case SYNA_TOUCH_FOREACH_END: + if (!active_only) { + if (++obj < ts->max_objects) + idx =3D loop_start; + } else if (have_active_count) { + if (++seen < active) + idx =3D loop_start; + } else if (++seen < ts->max_objects && + offset + loop_bits <=3D ts->payload_len * 8) { + idx =3D loop_start; + } + continue; + + case SYNA_TOUCH_PAD_TO_NEXT_BYTE: + offset =3D ALIGN(offset, 8); + continue; + } + + /* Everything else is (opcode, bit width). */ + if (idx >=3D ts->config_len) + break; + + bits =3D cfg[idx++]; + data =3D syna_get_bits(ts->payload, ts->payload_len, offset, bits); + offset +=3D bits; + + switch (code) { + case SYNA_TOUCH_OBJECT_N_INDEX: + if (data >=3D ts->max_objects || data >=3D SYNA_MAX_OBJECTS) { + dev_dbg(&ts->client->dev, + "object index %u out of range\n", data); + return -ERANGE; + } + obj =3D data; + break; + case SYNA_TOUCH_NUM_OF_ACTIVE_OBJECTS: + active =3D data; + have_active_count =3D true; + if (!active) + done =3D true; + break; + case SYNA_TOUCH_OBJECT_N_CLASSIFICATION: + ts->objects[obj].status =3D data; + break; + case SYNA_TOUCH_OBJECT_N_X_POSITION: + ts->objects[obj].x =3D data; + break; + case SYNA_TOUCH_OBJECT_N_Y_POSITION: + ts->objects[obj].y =3D data; + break; + case SYNA_TOUCH_OBJECT_N_Z: + ts->objects[obj].z =3D data; + break; + case SYNA_TOUCH_OBJECT_N_X_WIDTH: + ts->objects[obj].wx =3D data; + break; + case SYNA_TOUCH_OBJECT_N_Y_WIDTH: + ts->objects[obj].wy =3D data; + break; + case SYNA_TOUCH_GESTURE_DOUBLE_TAP: + case SYNA_TOUCH_REPORT_GESTURE_SWIPE: + case SYNA_TOUCH_REPORT_GESTURE_CIRCLE: + case SYNA_TOUCH_REPORT_GESTURE_UNICODE: + case SYNA_TOUCH_REPORT_GESTURE_VEE: + case SYNA_TOUCH_REPORT_GESTURE_TRIANGLE: + ts->gesture_type =3D data; + break; + default: + /* Timestamp, grip info, coordinates: skipped. */ + break; + } + } + + return 0; +} + +static void syna_report_touch(struct syna_tcm *ts) +{ + unsigned int i; + + for (i =3D 0; i < ts->max_objects; i++) { + struct syna_object *obj =3D &ts->objects[i]; + bool down =3D obj->status !=3D SYNA_OBJECT_LIFT; + + if (down) + dev_dbg(&ts->client->dev, + "slot %u: %u,%u z=3D%u w=3D%ux%u class=3D%u\n", + i, obj->x, obj->y, obj->z, obj->wx, obj->wy, + obj->status); + + input_mt_slot(ts->input, i); + if (!input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, down)) + continue; + + touchscreen_report_pos(ts->input, &ts->prop, obj->x, obj->y, true); + input_report_abs(ts->input, ABS_MT_PRESSURE, obj->z); + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, + max(obj->wx, obj->wy)); + input_report_abs(ts->input, ABS_MT_TOUCH_MINOR, + min(obj->wx, obj->wy)); + } + + input_mt_sync_frame(ts->input); + input_sync(ts->input); +} + +static irqreturn_t syna_irq(int irq, void *data) +{ + struct syna_tcm *ts =3D data; + + mutex_lock(&ts->lock); + + if (syna_recv_message(ts) < 0) { + mutex_unlock(&ts->lock); + return IRQ_HANDLED; + } + + if (ts->code !=3D SYNA_REPORT_TOUCH) { + mutex_unlock(&ts->lock); + return IRQ_HANDLED; + } + + if (syna_parse_touch_report(ts) =3D=3D 0) { + if (ts->suspended) { + if (ts->gesture_type =3D=3D SYNA_GESTURE_DTAP) { + dev_dbg(&ts->client->dev, + "wakeup gesture detected (type=3D0x%02x)\n", + ts->gesture_type); + + input_report_key(ts->input, KEY_WAKEUP, 1); + input_sync(ts->input); + input_report_key(ts->input, KEY_WAKEUP, 0); + input_sync(ts->input); + + pm_wakeup_event(&ts->client->dev, 0); + } + } else { + syna_report_touch(ts); + } + } + + mutex_unlock(&ts->lock); + return IRQ_HANDLED; +} + +static int syna_power_up(struct syna_tcm *ts) +{ + int ret; + + /* Held in reset across power-up; the DT flag makes this drive low. */ + gpiod_set_value_cansleep(ts->reset_gpio, 1); + + ret =3D regulator_bulk_enable(ARRAY_SIZE(ts->supplies), ts->supplies); + if (ret) + return ret; + + msleep(SYNA_POWERUP_TO_RESET_MS); + gpiod_set_value_cansleep(ts->reset_gpio, 0); + msleep(SYNA_RESET_TO_NORMAL_MS); + + return 0; +} + +static void syna_power_down(void *data) +{ + struct syna_tcm *ts =3D data; + + gpiod_set_value_cansleep(ts->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ts->supplies), ts->supplies); +} + +/* + * Fallback touch report config installed only if the firmware comes up + * without coordinates. + */ +static const u8 syna_default_config[] =3D { + SYNA_TOUCH_FOREACH_ACTIVE_OBJECT, + SYNA_TOUCH_OBJECT_N_INDEX, 4, + SYNA_TOUCH_OBJECT_N_CLASSIFICATION, 4, + SYNA_TOUCH_OBJECT_N_X_POSITION, 16, + SYNA_TOUCH_OBJECT_N_Y_POSITION, 16, + SYNA_TOUCH_OBJECT_N_Z, 8, + SYNA_TOUCH_OBJECT_N_X_WIDTH, 12, + SYNA_TOUCH_OBJECT_N_Y_WIDTH, 12, + SYNA_TOUCH_FOREACH_END, + SYNA_TOUCH_END, +}; + +static bool syna_config_has_coords(const u8 *cfg, unsigned int len) +{ + bool x =3D false, y =3D false; + unsigned int i; + + for (i =3D 0; i < len; i++) { + switch (cfg[i]) { + case SYNA_TOUCH_END: + return x && y; + case SYNA_TOUCH_FOREACH_ACTIVE_OBJECT: + case SYNA_TOUCH_FOREACH_OBJECT: + case SYNA_TOUCH_FOREACH_END: + case SYNA_TOUCH_PAD_TO_NEXT_BYTE: + break; + default: + if (cfg[i] =3D=3D SYNA_TOUCH_OBJECT_N_X_POSITION) + x =3D true; + else if (cfg[i] =3D=3D SYNA_TOUCH_OBJECT_N_Y_POSITION) + y =3D true; + i++; /* skip the bit width */ + break; + } + } + + return x && y; +} + +static int syna_read_touch_report_config(struct syna_tcm *ts) +{ + struct device *dev =3D &ts->client->dev; + int ret; + + ret =3D syna_exec_command(ts, SYNA_CMD_GET_TOUCH_REPORT_CONFIG, NULL, 0); + if (ret) + return ret; + + if (!ts->payload_len || ts->payload_len > sizeof(ts->config)) { + dev_err(dev, "implausible touch report config size %u\n", + ts->payload_len); + return -EPROTO; + } + + ts->config_len =3D ts->payload_len; + memcpy(ts->config, ts->payload, ts->config_len); + + dev_dbg(dev, "touch report config: %*ph\n", (int)ts->config_len, ts->conf= ig); + + return 0; +} + +static int syna_setup(struct syna_tcm *ts) +{ + struct device *dev =3D &ts->client->dev; + struct syna_tcm_app_info *app_info; + unsigned int max_x =3D 0, max_y =3D 0; + u8 report =3D SYNA_REPORT_TOUCH; + int ret; + + ret =3D syna_exec_command(ts, SYNA_CMD_IDENTIFY, NULL, 0); + if (ret) + return ret; + + if (ts->payload_len < 18) + return -EPROTO; + + dev_info(dev, "TouchComm v%u, mode 0x%02x, part %.16s\n", + ts->payload[0], ts->payload[1], &ts->payload[2]); + + if (ts->payload[1] !=3D SYNA_MODE_APPLICATION) { + dev_err(dev, + "controller is in mode 0x%02x, not application mode; this driver cannot= download firmware\n", + ts->payload[1]); + return -ENODEV; + } + + ret =3D syna_exec_command(ts, SYNA_CMD_GET_APP_INFO, NULL, 0); + if (ret) + return ret; + + if (ts->payload_len < sizeof(*app_info)) + return -EPROTO; + + app_info =3D (struct syna_tcm_app_info *)ts->payload; + max_x =3D le16_to_cpu(app_info->max_x); + max_y =3D le16_to_cpu(app_info->max_y); + ts->max_objects =3D le16_to_cpu(app_info->max_objects); + + if (!ts->max_objects) + ts->max_objects =3D 10; + + if (ts->max_objects > SYNA_MAX_OBJECTS) { + dev_warn(dev, "clamping %u objects to %u\n", + ts->max_objects, SYNA_MAX_OBJECTS); + ts->max_objects =3D SYNA_MAX_OBJECTS; + } + + if (!max_x || !max_y) { + dev_err(dev, "touchscreen size not reported by firmware\n"); + return -EINVAL; + } + + dev_info(dev, "%ux%u, %u touch objects\n", max_x + 1, max_y + 1, + ts->max_objects); + + ret =3D syna_read_touch_report_config(ts); + if (ret) + return ret; + + if (!syna_config_has_coords(ts->config, ts->config_len)) { + dev_warn(dev, + "firmware touch report config carries no coordinates, installing our o= wn\n"); + + ret =3D syna_exec_command(ts, SYNA_CMD_SET_TOUCH_REPORT_CONFIG, + syna_default_config, + sizeof(syna_default_config)); + if (ret) + return ret; + + ret =3D syna_read_touch_report_config(ts); + if (ret) + return ret; + + if (!syna_config_has_coords(ts->config, ts->config_len)) { + dev_err(dev, "controller kept a config we cannot parse\n"); + return -ENODEV; + } + } + + /* Cache the normal report config for restoring after gesture wakeup */ + ts->normal_config_len =3D ts->config_len; + memcpy(ts->normal_config, ts->config, ts->normal_config_len); + + ret =3D syna_exec_command(ts, SYNA_CMD_ENABLE_REPORT, &report, 1); + if (ret) + return ret; + + ts->input->name =3D "Synaptics TouchComm Touchscreen"; + ts->input->id.bustype =3D BUS_I2C; + + input_set_capability(ts->input, EV_KEY, KEY_WAKEUP); + + input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, max_x, 0, 0); + input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, max_y, 0, 0); + /* + * ABS_MT_PRESSURE (8-bit, 0-255) and ABS_MT_TOUCH_MAJOR/MINOR (12-bit, + * 0-4095) ranges are tailored for the S3908 / generation-1 family with + * the default touch report layout (syna_default_config). + */ + input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0); + input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 4095, 0, 0); + input_set_abs_params(ts->input, ABS_MT_TOUCH_MINOR, 0, 4095, 0, 0); + + touchscreen_parse_properties(ts->input, true, &ts->prop); + + return input_mt_init_slots(ts->input, ts->max_objects, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); +} + +static int syna_reinit_hw(struct syna_tcm *ts) +{ + struct device *dev =3D &ts->client->dev; + u8 report =3D SYNA_REPORT_TOUCH; + int ret; + + syna_power_down(ts); + ret =3D syna_power_up(ts); + if (ret) + return ret; + + ret =3D syna_exec_command(ts, SYNA_CMD_IDENTIFY, NULL, 0); + if (ret) + return ret; + + if (ts->payload_len < 18 || ts->payload[1] !=3D SYNA_MODE_APPLICATION) { + dev_err(dev, "controller not in application mode after recovery\n"); + return -ENODEV; + } + + ret =3D syna_exec_command(ts, SYNA_CMD_SET_TOUCH_REPORT_CONFIG, + ts->normal_config, ts->normal_config_len); + if (ret) + return ret; + + ret =3D syna_read_touch_report_config(ts); + if (ret) + return ret; + + return syna_exec_command(ts, SYNA_CMD_ENABLE_REPORT, &report, 1); +} + +static int syna_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct syna_tcm *ts; + int ret; + + ts =3D devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->client =3D client; + ret =3D devm_mutex_init(dev, &ts->lock); + if (ret) + return ret; + + i2c_set_clientdata(client, ts); + + ts->input =3D devm_input_allocate_device(dev); + if (!ts->input) + return -ENOMEM; + + ts->supplies[0].supply =3D "vdd"; + ts->supplies[1].supply =3D "avdd"; + ret =3D devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->supplies), + ts->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get supplies\n"); + + ts->reset_gpio =3D devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ts->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ts->reset_gpio), + "failed to get reset GPIO\n"); + + ret =3D syna_power_up(ts); + if (ret) + return dev_err_probe(dev, ret, "failed to power up\n"); + + ret =3D devm_add_action_or_reset(dev, syna_power_down, ts); + if (ret) + return ret; + + ret =3D syna_setup(ts); + if (ret) + return ret; + + ret =3D input_register_device(ts->input); + if (ret) + return dev_err_probe(dev, ret, "failed to register input device\n"); + + if (device_property_read_bool(dev, "wakeup-source")) { + ret =3D devm_device_init_wakeup(dev); + if (ret) + return ret; + } + + ret =3D devm_request_threaded_irq(dev, client->irq, NULL, syna_irq, + IRQF_ONESHOT, "synaptics-tcm", ts); + if (ret) + return dev_err_probe(dev, ret, "failed to request IRQ %d\n", + client->irq); + + return 0; +} + +static int syna_suspend(struct device *dev) +{ + struct i2c_client *client =3D to_i2c_client(dev); + struct syna_tcm *ts =3D i2c_get_clientdata(client); + int ret; + + disable_irq(client->irq); + mutex_lock(&ts->lock); + + if (device_may_wakeup(dev)) { + /* Exit deep sleep if the controller was in sleep mode */ + ret =3D syna_exec_command(ts, SYNA_CMD_EXIT_DEEP_SLEEP, NULL, 0); + if (ret && ret !=3D -ETIMEDOUT) + dev_dbg(dev, "exit deep sleep status: %d\n", ret); + + /* Switch to gesture touch report layout */ + ret =3D syna_exec_command(ts, SYNA_CMD_SET_TOUCH_REPORT_CONFIG, + syna_gesture_config, + sizeof(syna_gesture_config)); + if (ret) + dev_warn(dev, "failed to set gesture report config: %d\n", ret); + + ret =3D syna_read_touch_report_config(ts); + if (ret) + dev_warn(dev, "failed to read touch report config: %d\n", ret); + + /* Enable Low Power Wakeup Gesture Mode */ + ret =3D syna_set_dynamic_config(ts, SYNA_DC_IN_WAKEUP_GESTURE_MODE, 1); + if (ret) + dev_warn(dev, "failed to enable wakeup gesture mode: %d\n", ret); + + /* Set gesture mask: bit 0 =3D DTAP (double-tap to wake) */ + ret =3D syna_set_dynamic_config(ts, SYNA_DC_GESTURE_MASK, BIT(0)); + if (ret) + dev_warn(dev, "failed to set gesture mask: %d\n", ret); + + enable_irq_wake(client->irq); + enable_irq(client->irq); + ts->gesture_enabled =3D true; + } else { + /* Deep sleep mode when wakeup is disabled */ + ret =3D syna_exec_command(ts, SYNA_CMD_ENTER_DEEP_SLEEP, NULL, 0); + if (ret) + dev_warn(dev, "failed to enter deep sleep: %d\n", ret); + + ts->gesture_enabled =3D false; + } + + ts->suspended =3D true; + mutex_unlock(&ts->lock); + + return 0; +} + +static int syna_resume(struct device *dev) +{ + struct i2c_client *client =3D to_i2c_client(dev); + struct syna_tcm *ts =3D i2c_get_clientdata(client); + int ret; + + if (ts->gesture_enabled) { + disable_irq(client->irq); + mutex_lock(&ts->lock); + disable_irq_wake(client->irq); + + /* Exit Wakeup Gesture Mode */ + ret =3D syna_set_dynamic_config(ts, SYNA_DC_IN_WAKEUP_GESTURE_MODE, 0); + if (ret) + dev_warn(dev, "failed to disable wakeup gesture mode: %d\n", ret); + + /* Restore normal touch report layout */ + ret =3D syna_exec_command(ts, SYNA_CMD_SET_TOUCH_REPORT_CONFIG, + ts->normal_config, + ts->normal_config_len); + if (ret) + dev_warn(dev, "failed to restore normal report config: %d\n", ret); + + ret =3D syna_read_touch_report_config(ts); + if (ret) + dev_warn(dev, "failed to read touch report config: %d\n", ret); + + ts->gesture_enabled =3D false; + ts->suspended =3D false; + enable_irq(client->irq); + } else { + mutex_lock(&ts->lock); + + /* Wake up controller from deep sleep */ + ret =3D syna_exec_command(ts, SYNA_CMD_EXIT_DEEP_SLEEP, NULL, 0); + if (ret) { + dev_warn(dev, "failed to exit deep sleep (%d), recovering...\n", ret); + ret =3D syna_reinit_hw(ts); + if (ret) + dev_err(dev, "failed to recover controller: %d\n", ret); + } + + ts->suspended =3D false; + enable_irq(client->irq); + } + + mutex_unlock(&ts->lock); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(syna_tcm_pm_ops, syna_suspend, syna_resume= ); + +static const struct i2c_device_id syna_tcm_i2c_id[] =3D { + { "synaptics-tcm" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, syna_tcm_i2c_id); + +static const struct of_device_id syna_tcm_of_match[] =3D { + { .compatible =3D "syna,s3908" }, + { } +}; +MODULE_DEVICE_TABLE(of, syna_tcm_of_match); + +static struct i2c_driver syna_tcm_i2c_driver =3D { + .driver =3D { + .name =3D "synaptics-tcm-i2c", + .of_match_table =3D syna_tcm_of_match, + .pm =3D pm_sleep_ptr(&syna_tcm_pm_ops), + }, + .probe =3D syna_probe, + .id_table =3D syna_tcm_i2c_id, +}; +module_i2c_driver(syna_tcm_i2c_driver); + +MODULE_DESCRIPTION("Synaptics TouchComm I2C touchscreen driver"); +MODULE_AUTHOR("Vsevolod Nevorotov "); +MODULE_LICENSE("GPL"); --=20 2.55.0