From nobody Fri Sep 25 16:51:05 2026 Received: from mail-wr2-f12.google.com (mail-wr2-f12.google.com [74.125.225.76]) (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 6DF323DDB1C for ; Thu, 10 Sep 2026 09:14:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.76 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789031700; cv=none; b=H4UNlgseg449sTQnAXugS6GZreLDjIhQXWwL6ZFoZwXys4e6wm5FYm+LCci9G4Nk9Pv62zW0IzMYjZXPnkZxEXfz8d73DuQ1wSyX02mJa+xLAaEvxmdFHglrvYlIIrmt6hADH9Ow+jl6utLhY94chGW5wHGKS/y3d6khOAAsG/Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789031700; c=relaxed/simple; bh=P2OqeAVCWHFS1/46fjXp/GqTmb5OLthjWFq4IcxNSrc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qSVxIr+2K2YGG2JmbP1r7SP1NvFzA1ItXlvxWLALz8a8kzSfwNK/b0FPBJvFBaRTt8T34iQw28JRECPjflxc19iyGgzK4571gXhz1AmRVMezi3ArGevb1O6Z85NjxidzctRn9hSlMxh3uZAgpqlbjdUR7oEiAf9D0/pF9AHXDM0= 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=ZxnMWR72; arc=none smtp.client-ip=74.125.225.76 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="ZxnMWR72" Received: by mail-wr2-f12.google.com with SMTP id ffacd0b85a97d-4843cedd129so1242134f8f.0 for ; Thu, 10 Sep 2026 02:14:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789031696; x=1789636496; 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=ZxnMWR72/gX/rO1xUmdhzOYteRXubworPwEIhaY5T7L3lDtJS3N5fXdaNFrj03HNGX qJbxdgZb/HEJwmmBmat9D9zcPX7lQQplVOqKTmMeqyWym3QAIRZYMxMkVZ5l17lpnfCg NoQj+gR3/I2M6HUg6rlLel5Bdgy6T0X2MQsP63mdOewA6vAYmhytBt2LWZ65hraoFpFW sU58eCrpg3qHEWiIfA/yDQHBMpd/22ml8s/Ms4BKLL3qUrZoaCwk+4gGYLjgniJJ1vRh YiGWYhkeJlxTi8Re0LzddI59dTSU38ArAUN0LoKjc2rnyiOWDo8xBqptAMB9RDczCzpS rPgw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789031696; x=1789636496; 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=nfqSVZff5bOTzKm4GqEOFLtneABP0jif7av7ilys/Rr/jONBuXtll/cmsmtHLLFx02 wBlxmwAIKTjer6K0uWv7NIhG3SUD9SAkqWySIhVmti4TIVK8QP8PUrmzFbT99Ex7mPGi lzvCJ1WQOdvY9EvcUA4BjSaeQ+1rLSwKfmDe79CoYKgpKZnn7KqcYubxAyvVoJ6a0zux wQAl1eM4YTrinmCqI6HfBMhQQEhgcgi6NQJKD5Mghamp3MBreu1azXedfVbcNu7jHSDa Nrrr8vZK9IXEa2sHitk2RwFLOZTva6ZiKSFHgvwEmu1/XfecqvbaRL1sED2Q8PT2WOoH kZmA== X-Forwarded-Encrypted: i=1; AKwUvBz6ICkIjyqGwJzjTeLO4x0H+ZHZDsKzYGKmJ+13nCo45YbkTgL6fWSJwgLB7fr2wh/GJD2niokV9sgFZ0k=@vger.kernel.org X-Gm-Message-State: AFuF++kIMODWJWMVyRg5I13oiVF8IXOxtNlT8YmYWBMtvNALJ1WsbHLH CGsssSpaz6RCISgBTd1j/4p2ah1Y59y4YCSdSw4og3HMDAwSJjWWTmp2 X-Gm-Gg: AYBFou1olyPxD4SiD1SNpL+zXUy0ssbbUjRr8Ey9QwiSH7NzLX63vjNCVRsDFKB7PfB VZAHd0gvvDYLJ2ZyWcXWvpmX90AaSv54WExiglPHXXfBQO+Lyca31kQWtUgcwjONc4ZF+64xTsy A9vdRmD28d57KenXb1j/eTJyLQiL8b1QnQ4xEW4pbmYr4fLn8XF9XZaUHq6dDuYfJz0cSzmpdyC /CnK4mR9NMItwt/2L43bZO6FRt33HjCI4TgMsUjjCyvBCJMgEGHKbf2QFkParVm9vK2SpTIxIPn IkRd90p6uEz12ul6q47mMPsTGjQ3znmCDyQe2fNvnZ3bso6VZ/xEl02D1acgSeosji1OYRjjR3e Nw4qaV9fM/IM9yKZJ9SYhWv2LdveLcm0XI0uJvbfEyUB3MuV4WQA+Ca6XQ2Ar6N2VL1Gyo8RFOW 64ETDNUKy18bym4kmAEXB48IVtZ5FtF6XC1y/PHIegzIeKzvmVuPjBveFYhdWr1JWHjja2VTs9n lY= X-Received: by 2002:a05:6000:38f:b0:482:f0ee:1390 with SMTP id ffacd0b85a97d-485aaddef92mr23597846f8f.22.1789031696449; Thu, 10 Sep 2026 02:14:56 -0700 (PDT) Received: from Empers0n ([193.23.219.104]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-485885bbb51sm52065435f8f.30.2026.09.10.02.14.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 10 Sep 2026 02:14:55 -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 v2 1/2] dt-bindings: input: touchscreen: add Synaptics S3908 controller Date: Thu, 10 Sep 2026 16:14:04 +0700 Message-ID: <20260910091405.354058-2-sevanevorotov29@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260910091405.354058-1-sevanevorotov29@gmail.com> References: <20260910091405.354058-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 Reviewed-by: Rob Herring (Arm) --- .../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:05 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 71AAA3E073E for ; Thu, 10 Sep 2026 09:15:01 +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=1789031705; cv=none; b=WT9ta+xuCbaGiJ8k3CkzoTFYGIwCAEgI6t2fgOS1rEhyjYlJxoE2aficW9xoyC6CmeN/fVCVozKrNdM8n5d0/hFjnTbZkARjGjJJ2SWkiFAX2fxv0U9FZixCCf+JOC5Ft17iIhkS0aPl6OHGx5biC8OPwlyuxCCH4Jyb5rGnG3M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789031705; c=relaxed/simple; bh=ZFkPZuJGvqIbNNGyTykDN6HMDIRqCAEfKujhRGR8cJc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=h5M7y0mR/PF9Sbmrgsh737mJK2wJAvgpPwnERWCA0QuoCsUeSQSDafJeBexLWVWJQxeyQVxGqSUT37arjr9WrOEM2iwUn/EdOmSdBRyxgNjBekLNi9EvAFcrUnCwJ7bXjJLrAVHfXhlP9LdjaIwj4q5GIn4CJZ9HqQW8zVWspY4= 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=aXrRIcJn; 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="aXrRIcJn" Received: by mail-wm2-f12.google.com with SMTP id 5b1f17b1804b1-49b912d37b6so11073355e9.0 for ; Thu, 10 Sep 2026 02:15:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789031699; x=1789636499; 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=9SI3M7G179GhEA3Znz3AUMzTlISfEPDr10Zpf0U0MI4=; b=aXrRIcJnPGxxAaNXTni6A0egc4D1eR3hOpwsl8RfdZbjEHI43WHXyLTjvukY/v4CpX FX/XoQIZsYXv98X8ZVA8Wqw8jFxe5MbFhkFTButC5cHFGbiRe5/VJ2SIh3zKgu9bL9vk Kc5ktzifPALHKUkV/TFa4Oro0Mi3EDyBX234YCGGB3sNpVCUYd8/X54xyXfsIEBYXtNv y7zs00NNUJeSySnxwtd1TysFR6jHePmYsG4ab2axmcI8CBaXT9JPPSjK2OEuqbcLoLMa Nwc7jYFIRj4We/fblb3F7cjOmnHfN9JUgioohbYJWexN9ecs2x+e2AAvryqKWjlY/6XQ YBGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789031699; x=1789636499; 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=9SI3M7G179GhEA3Znz3AUMzTlISfEPDr10Zpf0U0MI4=; b=Oqna8JpgTdkrXXxVD7cX/6lHe+qo5uEl/QZNjM+hcGbDmZUxQ5ra5XNcZdpQvPCHlG gRceTm4i+5zdXXeIn5bt8NtxyRavILjNFzipC7sBUS4ep9W2CQPvz6qCojOCs6CYlZVw oxjiY5kInp7h/347FUNJ6afBUGJCtYPA1oWd6rBH4DaCFKYrdBCzpyW6OY5MlnPIXQis eIWDuvoU7/kqU3pU+SrzUZx48EsUAOzvAqVhr0M33EaeGJbTz6zsQGdm4nhL8Rxo7lLT Jh60B6tdJMCsiATZb554n9B8U6MZbVFcKf3FexAOwEAJ9r0jBAWCU9L2dhAuBpDj/Mco QrQQ== X-Forwarded-Encrypted: i=1; AKwUvBzwVo05+16SwUnpbViCm1dJ2N0pyLHZZpyLcFA2v13+//vC2ZlBNVUnToh+//L5Z8YKSZCFjnLbS1Dp1NA=@vger.kernel.org X-Gm-Message-State: AFuF++npr5UKoRUFENMywE7y0A8Rq4dww/I+Q/miAYsQgFvyERuWRnsC zwMo1v/wvu1tAKt/UCrt24g2xNrqSzVKw6B8hZg3I9uZ5hgJV1Y9dVqQ X-Gm-Gg: AYBFou2Ozc+6R0WOGlTJodzoCOKnM8nfXsK6UpF8c7ZUdLqOEeGG5tk8JTMPZ1fLgun MeO8OviMVJjm1cwKucyRMlTvs+PvFofeZm2fCtZGIRuY4kRB5cWXvWSnboNjAmnY+j1n4U9bY36 /pJIlDOB4dtX66t/7t2rrDxh9SNYtvkT1aDaq0L0nzIz+P+/r9RaCjXw+8EX4YC0lImB4jB7sAu 1ilOn37QYWzQOYGBIYtKIfQWXROdLuDTiAntwUvkMSiKJkf36OquAh46TCNFykGcpyfCblOScdX 6sUq3mU2dmeLkyb+H/8chzwLTI9GizVxYkNkQWzkyUL+P4rq4PvpZsnkhDlwoS+ttbLm55+eSBM k/0J09aGPA2HEBTe5zUldVR7rleMZ7/USIKwlxqPFKpll/58b2xKeoQrZiG8AtZ7e4UWNFR7yJ0 SseH2cmUoZpUo+nuApwgAlQa7VkjKkdINW7TYOV43AVilawoBiCZpWvvTSRPaZNL6fe747kOum6 4IwIVVTcYv3jA== X-Received: by 2002:a05:600c:154f:b0:49d:2607:4ada with SMTP id 5b1f17b1804b1-49d26074b9amr86172155e9.14.1789031699349; Thu, 10 Sep 2026 02:14:59 -0700 (PDT) Received: from Empers0n ([193.23.219.104]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-485885bbb51sm52065435f8f.30.2026.09.10.02.14.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 10 Sep 2026 02:14:58 -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 v2 2/2] Input: synaptics_tcm_i2c - add Synaptics TouchComm I2C touchscreen driver Date: Thu, 10 Sep 2026 16:14:05 +0700 Message-ID: <20260910091405.354058-3-sevanevorotov29@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260910091405.354058-1-sevanevorotov29@gmail.com> References: <20260910091405.354058-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 | 1066 +++++++++++++++++ 4 files changed, 1089 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..406db582f2c --- /dev/null +++ b/drivers/input/touchscreen/synaptics_tcm_i2c.c @@ -0,0 +1,1066 @@ +// 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; + bool powered; + 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, stream_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; + } + + /* Total bytes on wire for the message: header + payload + padding. */ + 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: remaining stream bytes (payload + trailing 0x5a + * padding) arrive in chunks with a 2-byte header (marker + code 0x03). + * Each continued-read transfer can carry up to SYNA_RD_CHUNK_SIZE - 2 + * bytes of stream data. + */ + stream_remaining =3D total - SYNA_RD_CHUNK_SIZE; + while (stream_remaining) { + unsigned int chunk =3D min(stream_remaining, + (unsigned int)(SYNA_RD_CHUNK_SIZE - 2)); + unsigned int read_len =3D chunk + 2; + unsigned int payload_chunk; + + 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; + } + + payload_chunk =3D min(chunk, ts->payload_len - copied); + if (payload_chunk) { + memcpy(&ts->payload[copied], &ts->rxbuf[2], payload_chunk); + copied +=3D payload_chunk; + } + + stream_remaining -=3D chunk; + + if (!stream_remaining && + ts->rxbuf[2 + chunk - 1] !=3D SYNA_MESSAGE_PADDING) { + dev_dbg(dev, "bad continued-read padding 0x%02x\n", + ts->rxbuf[2 + chunk - 1]); + 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, bit, done =3D 0; + u32 out =3D 0; + + if (!bits || bits > 32 || bits > buf_len * 8 || + offset > buf_len * 8 - bits) + return 0; + + byte =3D offset / 8; + bit =3D offset % 8; + + 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 || offset >=3D ts->payload_len * 8) + 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 min_t(unsigned int, data, ts->max_objects); + 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_on(struct syna_tcm *ts) +{ + int ret; + + if (ts->powered) + return 0; + + /* 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; + + ts->powered =3D true; + + 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_off(struct syna_tcm *ts) +{ + if (!ts->powered) + return; + + gpiod_set_value_cansleep(ts->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ts->supplies), ts->supplies); + ts->powered =3D false; +} + +static void syna_power_off_action(void *data) +{ + struct syna_tcm *ts =3D data; + + syna_power_off(ts); +} + +/* + * 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_off(ts); + ret =3D syna_power_on(ts); + if (ret) + return ret; + + ret =3D syna_exec_command(ts, SYNA_CMD_IDENTIFY, NULL, 0); + if (ret) + goto err_power_off; + + if (ts->payload_len < 18 || ts->payload[1] !=3D SYNA_MODE_APPLICATION) { + dev_err(dev, "controller not in application mode after recovery\n"); + ret =3D -ENODEV; + goto err_power_off; + } + + ret =3D syna_exec_command(ts, SYNA_CMD_SET_TOUCH_REPORT_CONFIG, + ts->normal_config, ts->normal_config_len); + if (ret) + goto err_power_off; + + ret =3D syna_read_touch_report_config(ts); + if (ret) + goto err_power_off; + + ret =3D syna_exec_command(ts, SYNA_CMD_ENABLE_REPORT, &report, 1); + if (ret) + goto err_power_off; + + return 0; + +err_power_off: + syna_power_off(ts); + return ret; +} + +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_on(ts); + if (ret) + return dev_err_probe(dev, ret, "failed to power up\n"); + + ret =3D devm_add_action_or_reset(dev, syna_power_off_action, 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); + mutex_unlock(&ts->lock); + return 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