From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pj1-f42.google.com (mail-pj1-f42.google.com [209.85.216.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 DADCC3C5532 for ; Tue, 24 Mar 2026 06:40:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334407; cv=none; b=PtiyOitbtIYPUR32snzpw63tnsN8a6zz9tWYeZQYnNvHgwMzS5N/VTYrsa9fh7d2fTwsydo2D/ui5c9PiONL3rxh7WJj4F0L25qSrrYxVk4ZYlAoMnaf0/hpfNYkoV/7+3MCrFdAAtLcI4krfMrePf5VLK7TWi12gyuyBlDAMgg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334407; c=relaxed/simple; bh=0MRwoqw5wBg+zBFOjDilpjbp56zFoFLhnDMQTh6a7UY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=JHAFqjv0sbSTtB30eOm9g9lGm/v+oIhC8K3iid3kweNDaUc8hHMZmq/+XQmxR/vjOIkDW+v/V1+fZcLUiGihyG8lxrMfuRoaElgqyGi5XmYMMPwuy+CKw/e3OWVnwh7TBJ1qxJJEUOv/pkCwM3Mt3bNF+USUl2xPB00Buw6BSkQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=CeY9lp5Y; arc=none smtp.client-ip=209.85.216.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="CeY9lp5Y" Received: by mail-pj1-f42.google.com with SMTP id 98e67ed59e1d1-35ba2ae4df3so2465361a91.2 for ; Mon, 23 Mar 2026 23:40:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334403; x=1774939203; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=LhItGXqvjBzAtcGycfEOYT3KW2DCoWkdBzqgD2ulkLc=; b=CeY9lp5YU2BQ6NgnzLbQIEn+Ur+2f5QHwOmeVWdz/5n2YnouTw+AjJr7MWVg64nDAA M5hBSFDSSOVHbaEXiSQKCshYNoGtoW3V0x3ICDqPv0pd9K2KyqMqm2HBXbVuO+0Lq+ry rap5uiysgw+4GOvebqoZQmuJIJXPQqzsl1QMM= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334403; x=1774939203; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=LhItGXqvjBzAtcGycfEOYT3KW2DCoWkdBzqgD2ulkLc=; b=aSDIf7/ENKuWRK8zzOLavtOe54SfGlRAvYV/DCJ0GQxyx9scCV79hI+XpfK7P6x6CT LZ0M3vavLp0EigkeNYXxENFCMzfDRySEgCTR18BE/+HhiKKvMhW7hGpIWM3KOISiO5HG Puh/ra+XzYltkjIDx+ANFiJmtNza7vEON+oSi5TD74uoLzKFOkwKRpWXwitBKXo/PVep 6h8pZZOlpidetY/nZ3AaAKLWPukKMmsdDQre4dXKjdmiitojcYG0MWtts4K8VJ7rwnvs 0NpcuY7gDPGefygJUY9PDrhbH+8a0DrGURzMlVgIg70Fo+UePofPH5MOu1UyQYhpjTxk kCVg== X-Forwarded-Encrypted: i=1; AJvYcCXcs8UpdU9qeYj1qJlMmxTxuXjDdsX61fTG7FD3+N3xpQFaB52lXqAE0fXV80o8yyECwn68Dx3twXnEb6M=@vger.kernel.org X-Gm-Message-State: AOJu0YwAXFnOn4zRD8irXaJYOJ6TCHqnqs9jgidURZQdByM8kgl8nMQF deBX3ncoc1uWWlm4eGCZ2MBQWHHR8qwxYibJi0XzJ7yZWDdTtGyBCJ8JDbxvVsFo5A== X-Gm-Gg: ATEYQzzTUPN8TqrI0mGM7cXMmDGBzvcBUQ1HpyRTxFfelMbVgLM2n3bUvPevhJ5k/Bw ilD0gj8GWFXMivONLZkWgdwoH9Ub04FidAsV3dXmVCC4bTKeKfTgySq7jqNTRbIXUrvbaNW+VK6 QwAPi0ugCZgolPn7NmhvUqRO8GIhV6P0fyGCvnxANQjq1mDFslI6yo2drnfhrzk1CQ1ATaMsysn pt4tFxUeS1igZ+c6DKEl7LmhPGfUDeupN4Z5bcj8I0Wo4xgJjqzY044KPG3ljzmAYWIJ8Gj2zde RxHhnIIQJnCdRY7KtTezsjaUrmkwSMp1Ayk70xM/pUSX73wLUH9zqPji7SXPS5EbR8X6moTN+5c LnjzSEcMQrUjdU7iM9PQAYhIubpnVA6poVQM6uPG4megsLfhuSMEPSq2SFrFXaK062eW9ao0CfT FegPb/9EbnbNNEokKZ+JuDWB2e3c4nYx6F+YnzNcm9qDqnw0Fo1MnuaXZ0bSijGD6i5Qk+J0fTc ylqUYeK68FlaZPrE8DjfbxU1NnDb/3+3A== X-Received: by 2002:a17:902:f690:b0:2b0:7ab2:d6b8 with SMTP id d9443c01a7336-2b0827a927bmr149389995ad.36.1774334402723; Mon, 23 Mar 2026 23:40:02 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:02 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:34 +0000 Subject: [PATCH v2 01/11] Documentation: Correction in HID output_report callback description. Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-1-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Jarrett Schultz , Dmitry Antipov X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=1212; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=g0tTzL+m8OoXUsAyqNr7/kY2I7leLpyd2bbtWafD9xc=; b=e9WXzKEaP6dKZsubkQtfy4jQ5em+iOpYLHwdy+xoCyJJ+pWSMrJ8EtnerWdutqavxYwIMquzd W+ZBPSj7126A1zOwtkXYQKscPXUXvqC5leMmWC5njPZNofkO+xsI7Tm X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= From: Jarrett Schultz Originally output_report callback was described as must-be asynchronous, but that is not the case in some implementations, namely i2c-hid. Correct the documentation to say that it may be asynchronous. Signed-off-by: Dmitry Antipov Reviewed-by: Dmitry Torokhov Signed-off-by: Jingyuan Liang --- Documentation/hid/hid-transport.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/hid/hid-transport.rst b/Documentation/hid/hid-tr= ansport.rst index 6f1692da296c..2008cf432af1 100644 --- a/Documentation/hid/hid-transport.rst +++ b/Documentation/hid/hid-transport.rst @@ -327,8 +327,8 @@ The available HID callbacks are: =20 Send raw output report via intr channel. Used by some HID device drivers which require high throughput for outgoing requests on the intr channel= . This - must not cause SET_REPORT calls! This must be implemented as asynchrono= us - output report on the intr channel! + must not cause SET_REPORT calls! This call might be asynchronous, so the + caller should not expect an immediate response! =20 :: =20 --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f169.google.com (mail-pl1-f169.google.com [209.85.214.169]) (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 8DF493C5543 for ; Tue, 24 Mar 2026 06:40:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334408; cv=none; b=nsJUZ3+8exA5hmLeHB+1veH/uo41xRIHkIFwY9eC4bATxWXCLheTpNjV3tz0scjolrK4m8QYLp8tsg57//KxMuLmkCRrxDmjU23ckT5Os7NIKuYyGWfSGyN0g6YqsDOPk57Tfcua7N4sCfPxSyW83DVFvY/jAMuhJNeISUxsfZg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334408; c=relaxed/simple; bh=qznTTsH95JMiWz2KpE1aJItnwW6ycamhHq8hzhCMPB8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=P/VH/sD7JuNzd6ta4KqQ0ZslxXCK3OFRQEZUt7GKJwyfYjjvKEozae/2dWSKfnLTrFbk4UIssHNzh11foEXczv9xbGnQWGRgwtEZCHYdIJChoaFHOGVdTrDUcAwfXQhnYlzHmBYbnTATe95tUta5/6tc6BceUgFm3qyBaigQ54E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=f9aYIaU5; arc=none smtp.client-ip=209.85.214.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="f9aYIaU5" Received: by mail-pl1-f169.google.com with SMTP id d9443c01a7336-2b04e6a989eso5866555ad.3 for ; Mon, 23 Mar 2026 23:40:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334404; x=1774939204; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=ws1XJhm562F8Eqr4Gpp6hR2Els0noubs7wtAB+jE3IQ=; b=f9aYIaU5D8+n175pz0sVt1dDRcFNP3LrPfin9laH7FEGhusp3Jum5kWNRAryiFQSA2 ASiCLsCmAb7iuq404pHLcbvo4pcOuGVJJFXChFM8+VOONN16R6/8feAvlAiqj1d/doT3 q2K3lltJ27gylknDg9m+qxUjhX10d7KONwdtQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334404; x=1774939204; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=ws1XJhm562F8Eqr4Gpp6hR2Els0noubs7wtAB+jE3IQ=; b=ZcFCQmjHdWgu1lgK8Y9IJamJQQ3gmIfxuXWLfJxYDhXurdlolAsKDwHTwv7dq23fsY xsxl8Xme42TRk0nqf0Ejk8aFqWVhfhTlrU/YzcgdIKjIqQ+yU3Xb/qn7yyt2OVOejMwN hVlAGnynN25ZJqFZp7oPttw2JOjlYlJMd2WDiLOlqdn05RDTisJpfuy9jL3VQGt0V9G2 7S0+tC335/sNVfSyyJPd4G99Ybn4vHATYKrmPrr27zZ6SMPFx5qd2kVtrgBvridW6kfR j3BpfjceGxmC1BuolIbXIJNuWak5aB3hAjKO8T3dYSdsaA0ot/TmjKF5ZVzVBrE8rotA xhLw== X-Forwarded-Encrypted: i=1; AJvYcCVV0pOZynuOKHt3KqwkiJxpdpcX5nb7KDaU/DgRQIk2JRRNBa17YK6Jm6M1aHz7v8wdNZZ8vIu84ABkaUc=@vger.kernel.org X-Gm-Message-State: AOJu0YyZwSqq74OYDYbjJ9NMIMJ2GwPb+eO7HqPETT+sWmxH0wLeea+Z iaL/ukstyPxh1d0+ZJXpj50xLgZQS9t6y5G9lZaaa7/psHuI8LNdceyhJS0DJtq+Tg== X-Gm-Gg: ATEYQzxCZVFr0nY/mU8A4wnuL7jhdrYY99yuCJzQ/1ebiEsbzwk8Qx/VHBxQ6XO0332 hMFneFLOvi+YpCWg3jIACMRSFzCfomjxatG9atIDFpQxVXXTLRmM3ibZnIEkbfnxOVDljMJKk6S 0j9pRCFuDClRoPFDBJjMXTTgM2lK84tHWQfpC9lNJjSS9BSzXZ92E5ksQDldb+ImHxDXyT0nDx/ 4ELLr58xPWVD26bQp+w5lFdp7scM0RkUhI2SrxxPmIO0Lwuug8UXhya7DHg/hPzjROiEU6FEQLQ jbvEX+mjS/wWHpkUpEWdvAZumoGuxJ2jOksYI4Xu1firu6FbD2InWA3HB1bS7I+c4FOvxd+6jo/ HMse8CA9LQy3sorUFLgOAcQVwuzj3PhSVOHT9M/+mvHpzsqg5biHY4iBDJ27qq0TJ03lt0PZG0X GwomGE98kj8H6T3Wglj99d0BWcxCACLr3elc4IDgec52UUxFRnlMm2+LrtKmUtRJ7Jsl2SE/Vpz OwYe5fjKzRm/PUIKyvX7vpFacoY0jc7vg== X-Received: by 2002:a17:903:94e:b0:2ae:826f:2c50 with SMTP id d9443c01a7336-2b0826e2f0emr127644455ad.12.1774334403691; Mon, 23 Mar 2026 23:40:03 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:02 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:35 +0000 Subject: [PATCH v2 02/11] HID: Add BUS_SPI support and define HID_SPI_DEVICE macro Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-2-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Jarrett Schultz , Dmitry Antipov X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=1401; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=uFPHOo5tVahmnQsO+3HulhbmLaF0uQlDRKXTc/zQ45Y=; b=FzRz6hRBlzD7ymlf0UgsS6IIaaR2bkVOGVToLSaLqiBXlBFUdjUt7+8GdOcAKeSszYW95MDk7 /3ijkLg5nuzBKxgO95bX3yQFQEwML0ujPgq48j85CrKspcQ4tbxqT5x X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= From: Jarrett Schultz If connecting a hid_device with bus field indicating BUS_SPI print out "SPI" in the debug print. Macro sets the bus field to BUS_SPI and uses arguments to set vendor product fields. Signed-off-by: Dmitry Antipov Reviewed-by: Dmitry Torokhov Signed-off-by: Jingyuan Liang --- drivers/hid/hid-core.c | 3 +++ include/linux/hid.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index a5b3a8ca2fcb..813c9c743ccd 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2316,6 +2316,9 @@ int hid_connect(struct hid_device *hdev, unsigned int= connect_mask) case BUS_I2C: bus =3D "I2C"; break; + case BUS_SPI: + bus =3D "SPI"; + break; case BUS_SDW: bus =3D "SOUNDWIRE"; break; diff --git a/include/linux/hid.h b/include/linux/hid.h index dce862cafbbd..957f322a0ebd 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -786,6 +786,8 @@ struct hid_descriptor { .bus =3D BUS_BLUETOOTH, .vendor =3D (ven), .product =3D (prod) #define HID_I2C_DEVICE(ven, prod) \ .bus =3D BUS_I2C, .vendor =3D (ven), .product =3D (prod) +#define HID_SPI_DEVICE(ven, prod) \ + .bus =3D BUS_SPI, .vendor =3D (ven), .product =3D (prod) =20 #define HID_REPORT_ID(rep) \ .report_type =3D (rep) --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (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 BC2EA3C343A for ; Tue, 24 Mar 2026 06:40:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334411; cv=none; b=Uoy7jaO7bdd17hwzHFFGQOe+kXPdCJC8eQC4pprXA1MigukbjgkcvT8psoUFBdK89QHwskh4YeL5r9kKysfBG3G1ud4HST4sr4zJDdKtNh1vC1FWQV0iDf+Rikro7qwv4QoUIqh8tm57ma7+OMaZbxeOkUY/Z2XkAZcMcwO72vQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334411; c=relaxed/simple; bh=TEbSYZKRwGzcQOFNZmwisPiv3M7EV8TIVYx6/aQsx3Q=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=strBzjt04tqliUQILIGdkXdneX68FMvUUWqNUuf1zOEcBbpurqppZlgZ4KfEWkiPIv2iDB7HcAbr1BUnP9QgTcNFrvVbTBKtqTVDRUXJLBfTm0aCua+DUrk3W6Ha4B8hz5puIHtinABd84JhTAawZWJuhET1Vo4SGDpWC7oFIug= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=jf3TuqNa; arc=none smtp.client-ip=209.85.214.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="jf3TuqNa" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2adff872068so16446025ad.1 for ; Mon, 23 Mar 2026 23:40:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334405; x=1774939205; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=OdiTVktaNJKPAFFBB2+Mmld2k3+GDPzpp+ZjhY/2cm8=; b=jf3TuqNaBFZqvwYiM2We2Q3pnwDwW4R2v4p2M8nOZVJok39i6mSRpLaBADxXgm9pFi J2N7Xg7Y7ZBJgTKf0eYTbJsH/9offCYLnq1/Jy/kEb2zaFZDRzbTNxAN+9kBIkSHr/Q7 s3Nk6U+hovu0nMpQqFHVO96KvMYAXZyArzhbc= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334405; x=1774939205; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=OdiTVktaNJKPAFFBB2+Mmld2k3+GDPzpp+ZjhY/2cm8=; b=rjBRoA5QDmulPgEMvaHRMJUqjUPGHmNBBdV8/kc4orOuXcwmEU6G4UJmdJViCvsugJ FjMuDNa6FYkQx/tvmdMwOoDpn45q9BDFvQtPcD929nDqtN+lKv/IFFa8IekrRKqvAqcR uqrT+G52rwAVAgJMHySKdCCTK/sStYAjBu86kn7j3RBP8k7EpOk7LKCDtsQTTIVc6AF8 ZugbKRgg6nI4ssLzhKlTeCbGAnAOn5EQMhCwqzGxYyN3/wyrkf9C0l/0FzOCOKKG4Xa9 oyXEOrZ7Pnf6/+hOoLSOgh9x4hvSx/Lm6+M/NaOnl38GRq9C4nu98Iarz5rR3tdHVKpQ wjcQ== X-Forwarded-Encrypted: i=1; AJvYcCX22ccfmODcl8dQ0twgIaZ3s+5EXs64aR0yyBfI6RvBAd4yLqxG7KTkjy2ZrkTVZrGwRTd6VNL4HxVj20Y=@vger.kernel.org X-Gm-Message-State: AOJu0Yw6KS3/AFNG44MmA+X8jpBwf6XzBceO8fQyG+1u2FcivYDgbQJw +I1CkhUNMfU40TFPhndxa4ahjTuhcVPH7Sgr8iuq7Ca0LU5uD60CqomO6qfaBPokeQ== X-Gm-Gg: ATEYQzxjdcmX8K1EjSqt7k68fF662XlqKfI26zFOALx1ccj/XSyJbmQ1FmPlxmHlUmf aOG8JattNyryNTG70FklkDYEG7Fq8SLzQG8aNkblpMlKEEmxvtMaSnCYE9h4JdNq0r+WymAAbLv 3dd6/pW/B8hXyVW/LIY3jgl++GPxjeFMR91iLYkFXxz+WxH3dz7Rv4G+fI2EiClNMepboHkrci9 Gq4MGXiCz6n6bfwaVSrjV1OQCpzH7gYMrLXIpv4JlMy1nlMI8qn48IdoTgHd7sFR1bK3pmFkRaX 3aWLi3Ilucq5dYCSIM6cJ3tLP8qrTPdLJB1PWEqkfF+oB8aBf5fIOgVx8BJBno/1PsvRSElJmOM GD+C3vBhw1tqzPRFOyweF7dTBU1iXE5wcj5GJ//Fh37ACz1UcwTAiKwUUr/3y0kkp4jXALLQclQ fte/aVXLnTBszfj9W1TUM4AazOht5FcLA8jsloarE6qaGMPc0dhXfqCE9cscwK/D+F6iAddUrRS YY2hX+Ukyygc7clV7gGJNchYUToMx/3hw== X-Received: by 2002:a17:903:2c10:b0:2ae:4445:f39a with SMTP id d9443c01a7336-2b0826d8056mr147407955ad.7.1774334404807; Mon, 23 Mar 2026 23:40:04 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:03 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:36 +0000 Subject: [PATCH v2 03/11] HID: spi-hid: add transport driver skeleton for HID over SPI bus Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-3-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Angela Czubak , Dmitry Antipov X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=8511; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=CK6lqq0ppMsKyiadyo5S1i8Q+O8bJVTtTlJsv0tPAdo=; b=cWFaWMBmCVPeSwuVv08rj8r2qD5OBejFEsPgfbyosF+/8kYUo8a1BJ2H7c5eNn20scrQ4MRqY atlMkDtSMf1Bf8DdcF44jkrYl/m859BTubSF6tkAfZuP0NSrhIEVy1F X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= From: Angela Czubak Create spi-hid folder and add Kconfig and Makefile for spi-hid driver. Add basic device structure, definitions, and probe/remove functions. Signed-off-by: Dmitry Antipov Signed-off-by: Angela Czubak Signed-off-by: Jingyuan Liang --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/spi-hid/Kconfig | 15 +++ drivers/hid/spi-hid/Makefile | 9 ++ drivers/hid/spi-hid/spi-hid-core.c | 213 +++++++++++++++++++++++++++++++++= ++++ 5 files changed, 241 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 920a64b66b25..c6ae23bfb75d 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1434,6 +1434,8 @@ source "drivers/hid/bpf/Kconfig" =20 source "drivers/hid/i2c-hid/Kconfig" =20 +source "drivers/hid/spi-hid/Kconfig" + source "drivers/hid/intel-ish-hid/Kconfig" =20 source "drivers/hid/amd-sfh-hid/Kconfig" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 361a7daedeb8..6b43e789b39a 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -169,6 +169,8 @@ obj-$(CONFIG_USB_KBD) +=3D usbhid/ =20 obj-$(CONFIG_I2C_HID_CORE) +=3D i2c-hid/ =20 +obj-$(CONFIG_SPI_HID_CORE) +=3D spi-hid/ + obj-$(CONFIG_INTEL_ISH_HID) +=3D intel-ish-hid/ =20 obj-$(CONFIG_AMD_SFH_HID) +=3D amd-sfh-hid/ diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig new file mode 100644 index 000000000000..836fdefe8345 --- /dev/null +++ b/drivers/hid/spi-hid/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (c) 2021 Microsoft Corporation +# + +menuconfig SPI_HID + tristate "SPI HID support" + default y + depends on SPI + +if SPI_HID + +config SPI_HID_CORE + tristate +endif diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile new file mode 100644 index 000000000000..92e24cddbfc2 --- /dev/null +++ b/drivers/hid/spi-hid/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the SPI HID input drivers +# +# Copyright (c) 2021 Microsoft Corporation +# + +obj-$(CONFIG_SPI_HID_CORE) +=3D spi-hid.o +spi-hid-objs =3D spi-hid-core.o diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c new file mode 100644 index 000000000000..d7b4d4adad95 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HID over SPI protocol implementation + * + * Copyright (c) 2021 Microsoft Corporation + * Copyright (c) 2026 Google LLC + * + * This code is partly based on "HID over I2C protocol implementation: + * + * Copyright (c) 2012 Benjamin Tissoires + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * Copyright (c) 2012 Red Hat, Inc + * + * which in turn is partly based on "USB HID support for Linux": + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2= , Inc + * Copyright (c) 2007-2008 Oliver Neukum + * Copyright (c) 2006-2010 Jiri Kosina + */ + +#include +#include +#include +#include +#include +#include +#include + +/* struct spi_hid_conf - Conf provided to the core */ +struct spi_hid_conf { + u32 input_report_header_address; + u32 input_report_body_address; + u32 output_report_address; + u8 read_opcode; + u8 write_opcode; +}; + +/** + * struct spihid_ops - Ops provided to the core + * @power_up: do sequencing to power up the device + * @power_down: do sequencing to power down the device + * @assert_reset: do sequencing to assert the reset line + * @deassert_reset: do sequencing to deassert the reset line + * @sleep_minimal_reset_delay: minimal sleep delay during reset + */ +struct spihid_ops { + int (*power_up)(struct spihid_ops *ops); + int (*power_down)(struct spihid_ops *ops); + int (*assert_reset)(struct spihid_ops *ops); + int (*deassert_reset)(struct spihid_ops *ops); + void (*sleep_minimal_reset_delay)(struct spihid_ops *ops); +}; + +/* Driver context */ +struct spi_hid { + struct spi_device *spi; /* spi device. */ + struct hid_device *hid; /* pointer to corresponding HID dev. */ + + struct spihid_ops *ops; + struct spi_hid_conf *conf; + + enum hidspi_power_state power_state; + + u32 regulator_error_count; + int regulator_last_error; + u32 bus_error_count; + int bus_last_error; + u32 dir_count; /* device initiated reset count. */ +}; + +static const char *spi_hid_power_mode_string(enum hidspi_power_state power= _state) +{ + switch (power_state) { + case HIDSPI_ON: + return "d0"; + case HIDSPI_SLEEP: + return "d2"; + case HIDSPI_OFF: + return "d3"; + default: + return "unknown"; + } +} + +static irqreturn_t spi_hid_dev_irq(int irq, void *_shid) +{ + return IRQ_HANDLED; +} + +static ssize_t bus_error_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct spi_hid *shid =3D dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d (%d)\n", + shid->bus_error_count, shid->bus_last_error); +} +static DEVICE_ATTR_RO(bus_error_count); + +static ssize_t regulator_error_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_hid *shid =3D dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d (%d)\n", + shid->regulator_error_count, + shid->regulator_last_error); +} +static DEVICE_ATTR_RO(regulator_error_count); + +static ssize_t device_initiated_reset_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_hid *shid =3D dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", shid->dir_count); +} +static DEVICE_ATTR_RO(device_initiated_reset_count); + +static struct attribute *spi_hid_attrs[] =3D { + &dev_attr_bus_error_count.attr, + &dev_attr_regulator_error_count.attr, + &dev_attr_device_initiated_reset_count.attr, + NULL /* Terminator */ +}; + +static const struct attribute_group spi_hid_group =3D { + .attrs =3D spi_hid_attrs, +}; + +const struct attribute_group *spi_hid_groups[] =3D { + &spi_hid_group, + NULL +}; +EXPORT_SYMBOL_GPL(spi_hid_groups); + +int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops, + struct spi_hid_conf *conf) +{ + struct device *dev =3D &spi->dev; + struct spi_hid *shid; + int error; + + if (spi->irq <=3D 0) + return dev_err_probe(dev, spi->irq ?: -EINVAL, "Missing IRQ\n"); + + shid =3D devm_kzalloc(dev, sizeof(*shid), GFP_KERNEL); + if (!shid) + return -ENOMEM; + + shid->spi =3D spi; + shid->power_state =3D HIDSPI_ON; + shid->ops =3D ops; + shid->conf =3D conf; + + spi_set_drvdata(spi, shid); + + /* + * At the end of probe we initialize the device: + * 0) assert reset, bias the interrupt line + * 1) sleep minimal reset delay + * 2) request IRQ + * 3) power up the device + * 4) deassert reset (high) + * After this we expect an IRQ with a reset response. + */ + + shid->ops->assert_reset(shid->ops); + + shid->ops->sleep_minimal_reset_delay(shid->ops); + + error =3D devm_request_threaded_irq(dev, spi->irq, NULL, spi_hid_dev_irq, + IRQF_ONESHOT, dev_name(&spi->dev), shid); + if (error) { + dev_err(dev, "%s: unable to request threaded IRQ.", __func__); + return error; + } + + error =3D shid->ops->power_up(shid->ops); + if (error) { + dev_err(dev, "%s: could not power up.", __func__); + return error; + } + + shid->ops->deassert_reset(shid->ops); + + dev_dbg(dev, "%s: d3 -> %s.", __func__, + spi_hid_power_mode_string(shid->power_state)); + + return 0; +} +EXPORT_SYMBOL_GPL(spi_hid_core_probe); + +void spi_hid_core_remove(struct spi_device *spi) +{ + struct spi_hid *shid =3D spi_get_drvdata(spi); + struct device *dev =3D &spi->dev; + int error; + + shid->ops->assert_reset(shid->ops); + error =3D shid->ops->power_down(shid->ops); + if (error) + dev_err(dev, "failed to disable regulator."); +} +EXPORT_SYMBOL_GPL(spi_hid_core_remove); + +MODULE_DESCRIPTION("HID over SPI transport driver"); +MODULE_AUTHOR("Dmitry Antipov "); +MODULE_LICENSE("GPL"); --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (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 A10173C5DD9 for ; Tue, 24 Mar 2026 06:40:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334415; cv=none; b=AftV7qCl/wj2Bxr0NON+dHP81Ibdt80ThiK6Up8MKitUws3Ng5Bz9oWlt5p5zFa0qTXlysygeMzahrxefatj0sC9sfym5lIzNn8qiu67gWoeWPHLdisiToJRdV0Kia1H3ym6hwVDeZslouFKxQkWiiPq5SgxCpvZIrB8SZ85MCM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334415; c=relaxed/simple; bh=7nG5FewarcmnExFB11BtZwUYGr2qKgV1hcDP53J9Wm4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=M3qVDt7i3IlfbFNPGtNvrL7pbEN8IRCsgPBxVCX+NIfF3OcSOXLIuKFsbfvaMZNs/htiIwCuGRuHlZRZgUNMkqq/6aWDJFVF8qKheb/WUgWRSaXNsSEiEFFu4vHEriT5zuIPXBYeg5fqn6BVWGpMyrB/rI5xtK4zvJ9Q0zVwPLk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=d/XHjDm/; arc=none smtp.client-ip=209.85.214.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="d/XHjDm/" Received: by mail-pl1-f176.google.com with SMTP id d9443c01a7336-2b062069f80so24487315ad.0 for ; Mon, 23 Mar 2026 23:40:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334406; x=1774939206; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=mpA1qjX0LOAvh60zS5DJMjDP/lo2zTTME9Z/fu0N2i0=; b=d/XHjDm/I1YnO1czN8Z6jPKEUIQmLhB8+qj61jq59cTJsXwinvX7tDSCR/lism/t+4 nGKa6tf2v8RnellFAUXVRj1Q/yjmz6u2bsts6WZrMXxuBRpNXgee7/Eko/sZytzjzhs1 EJnRiHhZg9vGU/2upMQkZ7l97hhJP5qq/4nfw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334406; x=1774939206; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=mpA1qjX0LOAvh60zS5DJMjDP/lo2zTTME9Z/fu0N2i0=; b=I7JMRYG9DKyGyfWfXIyplA8X65E5e19nd+Zq1P0Jpgj+ezUcQPQIoiUDoZbegfZrPq 70W3YXr4CnyVrWxRcTkO3Iynzd3t0Vr91Laxkc1uXbmTKdHJXzU6WvN8SPjz+S++DKK7 pepK44FqygbQXcikdzDc94qfCMK9SdG9R3RcPJb384131lMm8+0q2+ZVjlP4dr0tPRPV 5cFmSYcdER/vkN8uc2B0sCQwYfDohjtQMHipnO4h4DmKc/Uh4rT5BqSHw4TBS8oebluT y7irDZcCF1OCgJ8okVo5nGRBSH1tEWWFtGUXRU9xDHMTi8W5epKbQYcJD1ZMORTakK7f uVwA== X-Forwarded-Encrypted: i=1; AJvYcCX/fW/OoyUglyAz8cfN/2vdDB6T/sar/cbqDoGuweUOK+AEH0z8hK3LwqhGaaByDEDJ/8woF8alqzlA5Dk=@vger.kernel.org X-Gm-Message-State: AOJu0YxgEv1RF7iJP+a/DDOoBOEY+xAlPSoip1u9TOi7yTBcPuIDAe2/ BovmmGjg1y7fh44knuVK3+2sOtt4PIaw9N/Gx4gcU8I6A1NTRjJieZm8HFyAkmC+aA== X-Gm-Gg: ATEYQzynsbYxJXsl51KqmtTM6qZCzB6dVGxMc+5FqfNP1aXGpjGiapTBEbG3rh+OdYR cWH+k7FCkd2dkrHR3b3y52sRmv5VBRX1PiOBHzKLTmlfnsPuBL1Rs72S1M5pkySxIJm9DQqeeSL gU4vSJZg72UegoTe2a4PEcr8NSeOVZrTuoQwMkOM3yvPRx8cU/JElfLaUXEkkiLhChusACbmdrd FefVlXUxyKt1pjG8c1PuixQg6ojHW8XcRJg1VbY26a95dh2KAuGF35aaiXa4uLIdJsRz5sYGaUU Psq2tg0rKkuM+lpbKZHA4trLDRFIlJlUsjUdYYYpHPvNxUr+cIngAJSzJOXvd59o0MBvWvhsDM9 VqFG/HGseBGLG/6cTJpAburQqgkj06xPo6wqZdC5U01vgD4pX9kpR0qEX+7uxTp9o7V9HR+ONLl vjJe116PsTLgfdYZjUbqlKbFXo6JEPpN8AmDx2OXcilo+zRFGB2AOkQEy2LKMn7yVlOin8GBOpT aYrOGw98KFAZyGNT8K+N63mH0lurxaZzQ== X-Received: by 2002:a17:903:2348:b0:2b0:5738:9817 with SMTP id d9443c01a7336-2b0827a7989mr146111855ad.31.1774334405635; Mon, 23 Mar 2026 23:40:05 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:05 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:37 +0000 Subject: [PATCH v2 04/11] HID: spi-hid: add spi-hid driver HID layer Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-4-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Dmitry Antipov , Angela Czubak X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=16406; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=7nG5FewarcmnExFB11BtZwUYGr2qKgV1hcDP53J9Wm4=; b=BUkq/t+ozWghdNHFPqGCr7H8Q0Q8r9O1kk2gm75jjEHHEdlodxS10lm4a3P8yGbI6WVK0mnna NVYTVF0BFw0B6UDurdMSDiKitUhdwbSjQg5e9AUdOB948eezXyWCf6o X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= Add HID low level driver callbacks to register SPI as a HID driver, and an external touch device as a HID device. Signed-off-by: Dmitry Antipov Signed-off-by: Angela Czubak Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/spi-hid-core.c | 519 +++++++++++++++++++++++++++++++++= ++++ 1 file changed, 519 insertions(+) diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index d7b4d4adad95..4723b87346d4 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -20,13 +20,69 @@ * Copyright (c) 2006-2010 Jiri Kosina */ =20 +#include +#include #include +#include #include #include #include +#include #include +#include #include #include +#include +#include +#include + +#define SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST 0x00 + +#define SPI_HID_RESP_TIMEOUT 1000 + +/* Protocol message size constants */ +#define SPI_HID_OUTPUT_HEADER_LEN 8 + +/* flags */ +/* + * ready flag indicates that the FW is ready to accept commands and + * requests. The FW becomes ready after sending the report descriptor. + */ +#define SPI_HID_READY 0 + +/* Raw input buffer with data from the bus */ +struct spi_hid_input_buf { + u8 header[HIDSPI_INPUT_HEADER_SIZE]; + u8 body[HIDSPI_INPUT_BODY_HEADER_SIZE]; + u8 content[]; +}; + +/* Raw output report buffer to be put on the bus */ +struct spi_hid_output_buf { + u8 header[SPI_HID_OUTPUT_HEADER_LEN]; + u8 content[]; +}; + +/* Data necessary to send an output report */ +struct spi_hid_output_report { + u8 report_type; + u16 content_length; + u8 content_id; + u8 *content; +}; + +/* Processed data from a device descriptor */ +struct spi_hid_device_descriptor { + u16 hid_version; + u16 report_descriptor_length; + u16 max_input_length; + u16 max_output_length; + u16 max_fragment_length; + u16 vendor_id; + u16 product_id; + u16 version_id; + u8 no_output_report_ack; +}; =20 /* struct spi_hid_conf - Conf provided to the core */ struct spi_hid_conf { @@ -61,8 +117,26 @@ struct spi_hid { struct spihid_ops *ops; struct spi_hid_conf *conf; =20 + struct spi_hid_device_descriptor desc; /* HID device descriptor. */ + struct spi_hid_output_buf *output; /* Output buffer. */ + struct spi_hid_input_buf *input; /* Input buffer. */ + struct spi_hid_input_buf *response; /* Response buffer. */ + + u16 response_length; + u16 bufsize; + enum hidspi_power_state power_state; =20 + u8 reset_attempts; /* The number of reset attempts. */ + + unsigned long flags; /* device flags. */ + + /* Control lock to make sure one output transaction at a time. */ + struct mutex output_lock; + struct completion output_done; + + u32 report_descriptor_crc32; /* HID report descriptor crc32 checksum. */ + u32 regulator_error_count; int regulator_last_error; u32 bus_error_count; @@ -70,6 +144,33 @@ struct spi_hid { u32 dir_count; /* device initiated reset count. */ }; =20 +static struct hid_ll_driver spi_hid_ll_driver; + +static void spi_hid_populate_output_header(u8 *buf, + const struct spi_hid_conf *conf, + const struct spi_hid_output_report *report) +{ + buf[0] =3D conf->write_opcode; + put_unaligned_be24(conf->output_report_address, &buf[1]); + buf[4] =3D report->report_type; + put_unaligned_le16(report->content_length, &buf[5]); + buf[7] =3D report->content_id; +} + +static int spi_hid_output(struct spi_hid *shid, const void *buf, u16 lengt= h) +{ + int error; + + error =3D spi_write(shid->spi, buf, length); + + if (error) { + shid->bus_error_count++; + shid->bus_last_error =3D error; + } + + return error; +} + static const char *spi_hid_power_mode_string(enum hidspi_power_state power= _state) { switch (power_state) { @@ -84,11 +185,416 @@ static const char *spi_hid_power_mode_string(enum hid= spi_power_state power_state } } =20 +static void spi_hid_stop_hid(struct spi_hid *shid) +{ + struct hid_device *hid =3D shid->hid; + + shid->hid =3D NULL; + clear_bit(SPI_HID_READY, &shid->flags); + + if (hid) + hid_destroy_device(hid); +} + +static int spi_hid_send_output_report(struct spi_hid *shid, + struct spi_hid_output_report *report) +{ + struct spi_hid_output_buf *buf =3D shid->output; + struct device *dev =3D &shid->spi->dev; + u16 report_length; + u16 padded_length; + u8 padding; + int error; + + guard(mutex)(&shid->output_lock); + if (report->content_length > shid->desc.max_output_length) { + dev_err(dev, "Output report too big, content_length 0x%x.", + report->content_length); + return -E2BIG; + } + + spi_hid_populate_output_header(buf->header, shid->conf, report); + + if (report->content_length) + memcpy(&buf->content, report->content, report->content_length); + + report_length =3D sizeof(buf->header) + report->content_length; + padded_length =3D round_up(report_length, 4); + padding =3D padded_length - report_length; + memset(&buf->content[report->content_length], 0, padding); + + error =3D spi_hid_output(shid, buf, padded_length); + if (error) + dev_err(dev, "Failed output transfer: %d.", error); + + return error; +} + +static int spi_hid_sync_request(struct spi_hid *shid, + struct spi_hid_output_report *report) +{ + struct device *dev =3D &shid->spi->dev; + int error; + + error =3D spi_hid_send_output_report(shid, report); + if (error) + return error; + + error =3D wait_for_completion_interruptible_timeout(&shid->output_done, + msecs_to_jiffies(SPI_HID_RESP_TIMEOUT)); + if (error =3D=3D 0) { + dev_err(dev, "Response timed out."); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * This function returns the length of the report descriptor, or a negative + * error code if something went wrong. + */ +static int spi_hid_report_descriptor_request(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D REPORT_DESCRIPTOR, + .content_length =3D 0, + .content_id =3D SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST, + .content =3D NULL, + }; + int ret; + + ret =3D spi_hid_sync_request(shid, &report); + if (ret) { + dev_err(dev, + "Expected report descriptor not received: %d.", ret); + return ret; + } + + ret =3D shid->response_length; + if (ret !=3D shid->desc.report_descriptor_length) { + ret =3D min_t(unsigned int, ret, shid->desc.report_descriptor_length); + dev_err(dev, "Received report descriptor length doesn't match device des= criptor field, using min of the two: %d.", + ret); + } + + return ret; +} + +static int spi_hid_create_device(struct spi_hid *shid) +{ + struct hid_device *hid; + struct device *dev =3D &shid->spi->dev; + int error; + + hid =3D hid_allocate_device(); + error =3D PTR_ERR_OR_ZERO(hid); + if (error) { + dev_err(dev, "Failed to allocate hid device: %d.", error); + return error; + } + + hid->driver_data =3D shid->spi; + hid->ll_driver =3D &spi_hid_ll_driver; + hid->dev.parent =3D &shid->spi->dev; + hid->bus =3D BUS_SPI; + hid->version =3D shid->desc.hid_version; + hid->vendor =3D shid->desc.vendor_id; + hid->product =3D shid->desc.product_id; + + snprintf(hid->name, sizeof(hid->name), "spi %04X:%04X", + hid->vendor, hid->product); + strscpy(hid->phys, dev_name(&shid->spi->dev), sizeof(hid->phys)); + + shid->hid =3D hid; + + error =3D hid_add_device(hid); + if (error) { + dev_err(dev, "Failed to add hid device: %d.", error); + /* + * We likely got here because report descriptor request timed + * out. Let's disconnect and destroy the hid_device structure. + */ + spi_hid_stop_hid(shid); + return error; + } + + return 0; +} + +static int spi_hid_get_request(struct spi_hid *shid, u8 content_id) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D GET_FEATURE, + .content_length =3D 0, + .content_id =3D content_id, + .content =3D NULL, + }; + int error; + + error =3D spi_hid_sync_request(shid, &report); + if (error) { + dev_err(dev, + "Expected get request response not received! Error %d.", + error); + return error; + } + + return 0; +} + +static int spi_hid_set_request(struct spi_hid *shid, u8 *arg_buf, u16 arg_= len, + u8 content_id) +{ + struct spi_hid_output_report report =3D { + .report_type =3D SET_FEATURE, + .content_length =3D arg_len, + .content_id =3D content_id, + .content =3D arg_buf, + }; + + return spi_hid_sync_request(shid, &report); +} + +/* This is a placeholder. Will be implemented in the next patch. */ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid) { return IRQ_HANDLED; } =20 +static int spi_hid_alloc_buffers(struct spi_hid *shid, size_t report_size) +{ + struct device *dev =3D &shid->spi->dev; + int inbufsize =3D sizeof(shid->input->header) + sizeof(shid->input->body)= + report_size; + int outbufsize =3D sizeof(shid->output->header) + report_size; + + // devm_krealloc with __GFP_ZERO ensures the new memory is initialized + shid->output =3D devm_krealloc(dev, shid->output, outbufsize, GFP_KERNEL = | __GFP_ZERO); + shid->input =3D devm_krealloc(dev, shid->input, inbufsize, GFP_KERNEL | _= _GFP_ZERO); + shid->response =3D devm_krealloc(dev, shid->response, inbufsize, GFP_KERN= EL | __GFP_ZERO); + + if (!shid->output || !shid->input || !shid->response) + return -ENOMEM; + + shid->bufsize =3D report_size; + + return 0; +} + +static int spi_hid_get_report_length(struct hid_report *report) +{ + return ((report->size - 1) >> 3) + 1 + + report->device->report_enum[report->type].numbered + 2; +} + +/* + * Traverse the supplied list of reports and find the longest + */ +static void spi_hid_find_max_report(struct hid_device *hid, u32 type, + u16 *max) +{ + struct hid_report *report; + u16 size; + + /* + * We should not rely on wMaxInputLength, as some devices may set it to + * a wrong length. + */ + list_for_each_entry(report, &hid->report_enum[type].report_list, list) { + size =3D spi_hid_get_report_length(report); + if (*max < size) + *max =3D size; + } +} + +/* hid_ll_driver interface functions */ + +static int spi_hid_ll_start(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + int error =3D 0; + u16 bufsize =3D 0; + + spi_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize); + spi_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize); + spi_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize); + + if (bufsize < HID_MIN_BUFFER_SIZE) { + dev_err(&spi->dev, + "HID_MIN_BUFFER_SIZE > max_input_length (%d).", + bufsize); + return -EINVAL; + } + + if (bufsize > shid->bufsize) { + guard(disable_irq)(&shid->spi->irq); + + error =3D spi_hid_alloc_buffers(shid, bufsize); + if (error) + return error; + } + + return 0; +} + +static void spi_hid_ll_stop(struct hid_device *hid) +{ + hid->claimed =3D 0; +} + +static int spi_hid_ll_open(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + + set_bit(SPI_HID_READY, &shid->flags); + return 0; +} + +static void spi_hid_ll_close(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + + clear_bit(SPI_HID_READY, &shid->flags); + shid->reset_attempts =3D 0; +} + +static int spi_hid_ll_power(struct hid_device *hid, int level) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + int error =3D 0; + + guard(mutex)(&shid->output_lock); + if (!shid->hid) + error =3D -ENODEV; + + return error; +} + +static int spi_hid_ll_parse(struct hid_device *hid) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + struct device *dev =3D &spi->dev; + int error, len; + + len =3D spi_hid_report_descriptor_request(shid); + if (len < 0) { + dev_err(dev, "Report descriptor request failed, %d.", len); + return len; + } + + /* + * FIXME: below call returning 0 doesn't mean that the report descriptor + * is good. We might be caching a crc32 of a corrupted r. d. or who + * knows what the FW sent. Need to have a feedback loop about r. d. + * being ok and only then cache it. + */ + error =3D hid_parse_report(hid, (u8 *)shid->response->content, len); + if (error) { + dev_err(dev, "failed parsing report: %d.", error); + return error; + } + shid->report_descriptor_crc32 =3D crc32_le(0, + (unsigned char const *)shid->response->content, + len); + + return 0; +} + +static int spi_hid_ll_raw_request(struct hid_device *hid, + unsigned char reportnum, __u8 *buf, + size_t len, unsigned char rtype, int reqtype) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + struct device *dev =3D &spi->dev; + int ret; + + switch (reqtype) { + case HID_REQ_SET_REPORT: + if (buf[0] !=3D reportnum) { + dev_err(dev, "report id mismatch."); + return -EINVAL; + } + + ret =3D spi_hid_set_request(shid, &buf[1], len - 1, + reportnum); + if (ret) { + dev_err(dev, "failed to set report."); + return ret; + } + + ret =3D len; + break; + case HID_REQ_GET_REPORT: + ret =3D spi_hid_get_request(shid, reportnum); + if (ret) { + dev_err(dev, "failed to get report."); + return ret; + } + + ret =3D min_t(size_t, len, + (shid->response->body[1] | (shid->response->body[2] << 8)) + 1); + buf[0] =3D shid->response->body[3]; + memcpy(&buf[1], &shid->response->content, ret); + break; + default: + dev_err(dev, "invalid request type."); + return -EIO; + } + + return ret; +} + +static int spi_hid_ll_output_report(struct hid_device *hid, __u8 *buf, + size_t len) +{ + struct spi_device *spi =3D hid->driver_data; + struct spi_hid *shid =3D spi_get_drvdata(spi); + struct device *dev =3D &spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D OUTPUT_REPORT, + .content_length =3D len - 1, + .content_id =3D buf[0], + .content =3D &buf[1], + }; + int error; + + if (!test_bit(SPI_HID_READY, &shid->flags)) { + dev_err(dev, "%s called in unready state", __func__); + return -ENODEV; + } + + if (shid->desc.no_output_report_ack) + error =3D spi_hid_send_output_report(shid, &report); + else + error =3D spi_hid_sync_request(shid, &report); + + if (error) { + dev_err(dev, "failed to send output report."); + return error; + } + + return len; +} + +static struct hid_ll_driver spi_hid_ll_driver =3D { + .start =3D spi_hid_ll_start, + .stop =3D spi_hid_ll_stop, + .open =3D spi_hid_ll_open, + .close =3D spi_hid_ll_close, + .power =3D spi_hid_ll_power, + .parse =3D spi_hid_ll_parse, + .output_report =3D spi_hid_ll_output_report, + .raw_request =3D spi_hid_ll_raw_request, +}; + static ssize_t bus_error_count_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -159,6 +665,15 @@ int spi_hid_core_probe(struct spi_device *spi, struct = spihid_ops *ops, =20 spi_set_drvdata(spi, shid); =20 + /* + * we need to allocate the buffer without knowing the maximum + * size of the reports. Let's use SZ_2K, then we do the + * real computation later. + */ + error =3D spi_hid_alloc_buffers(shid, SZ_2K); + if (error) + return error; + /* * At the end of probe we initialize the device: * 0) assert reset, bias the interrupt line @@ -191,6 +706,8 @@ int spi_hid_core_probe(struct spi_device *spi, struct s= pihid_ops *ops, dev_dbg(dev, "%s: d3 -> %s.", __func__, spi_hid_power_mode_string(shid->power_state)); =20 + spi_hid_create_device(shid); + return 0; } EXPORT_SYMBOL_GPL(spi_hid_core_probe); @@ -201,6 +718,8 @@ void spi_hid_core_remove(struct spi_device *spi) struct device *dev =3D &spi->dev; int error; =20 + spi_hid_stop_hid(shid); + shid->ops->assert_reset(shid->ops); error =3D shid->ops->power_down(shid->ops); if (error) --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f173.google.com (mail-pl1-f173.google.com [209.85.214.173]) (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 D0B443C6A37 for ; Tue, 24 Mar 2026 06:40:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334417; cv=none; b=dsInXphJtevXb7GsLioWX5Qk2GerwotS+Q4pm/VJP+G8lkZUbEbJmqpI4sXmFEF15hGdPSiPBYYLggPEYO11iQStE4kFpD35//+XaSNloIzB8rwtdw6zS0AEvAX5BzmHLerxgByyhtZMm+KBS5F6NlM10gCzMkh5KYPXeReprHA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334417; c=relaxed/simple; bh=Nk6TMz3gATQxP2tJVjnvFBAdOOfrIYM1qwnKqS7z+4U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=DdJj5GgDgmI7rOMQ5X8XOJoktFnMt9XRhPTNUKvDfSWKfI1FrixG0dqI/R10fjHxcT0P71zCKYaK/hH0+a18YZLsVAYwN6fbcOcT/IsjcOju8VTr5UP58TY0UZ0YKsocJJJZq6r/zdf17ugyW+5zlxooAQC1QxvstJHay5m4uzo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=PKzZkWge; arc=none smtp.client-ip=209.85.214.173 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="PKzZkWge" Received: by mail-pl1-f173.google.com with SMTP id d9443c01a7336-2ab39b111b9so15972765ad.1 for ; Mon, 23 Mar 2026 23:40:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334407; x=1774939207; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=A4UWxWM8bTsfx8dDXh7aUXeI85fsX1VHQqdV3p02XRM=; b=PKzZkWgeGfv/TOZBRtTlsk9eQlJSvX3eqDv2cbnzrZoEFD7Y26PMONP4njww5TMAmx 7vbMf2p4G+8iW3G4h/W0lXhUQwF5Uc4cGjcTDsyC5W5zAOJEqLEnxMzYgHwzIk5nWobu ypJLkzV/RgmKSI8DMAODBhLYqayedzJAALJM8= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334407; x=1774939207; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=A4UWxWM8bTsfx8dDXh7aUXeI85fsX1VHQqdV3p02XRM=; b=q1FmYossxHXU4vNwGkTprhO7N4h3xMG4aoJgSLgJh5rrnY8XYtZ8P30pr1roKnPgxL uZMrqgApAhJSfzFD1ItMJ+V4cMwruWGSdC92T4FbplUXL2D6ec/wUajV7rGYbcCwx8qb D1cUDYUSWe4N/HlJ73Tknz8Sk9iMjHwFzpe2BhENRXAxFE6SDdzA8gb1TjUQoled8W0/ oB5ysuKQlc8WoI1A5MMIp5A5KDGtQzopZLeEjJzCRRH/M8UhZADsw6V13K+7L1/Mbvme 2sj5CIRKTFb0GlWPddw4fqgvVfTvZ0CXh9RbQ9ZTenToOQ0oQs3La6WmtAghlE24R+KI ipNw== X-Forwarded-Encrypted: i=1; AJvYcCXrYG8BvDJYWRpmi6bd8oX4IWfNNRC7xTbxMpFfr1M5OcjGHHZWmDi2cLQ7jRSGjB6g9hAT1HsrOxs8HMc=@vger.kernel.org X-Gm-Message-State: AOJu0YwjKWDmoC1S88UIVJVcAmXcnkhofGtR0+a38fRB4CKuyvSh8upJ 0XLv6HeEwVG+xd5tfqxR0dl6spca4HiZ5Bq3ljm09MWkXB7/s0CtwFN9OsPBbCYFaQ== X-Gm-Gg: ATEYQzx6i/wiXsDx/O53H3Lp8GGJCMdiIvzM9lFlbWmR/e0gEDPsPEgjHjBUATC6qFe 8q4GuQa9u//MNslGYdKTMy3hzqWlMZMLAzU7vvqXPQJw2FiF1VZzG1fwyKfrBrNecnZwn8IGcqZ 2sYrb2dnSjX1JrElzjvWM/2HbhUpDasypL6Ak5p3GCxqt86A5GZqKjHzKo5kR7o52rwXocJj8Xd tOa3Ccmk/rL9gUAxjOtYy8rO/l6WqlwXKEQ6IM/UCtwnPI9mabWgo8N9P2mUcT6FDYX4hVx5m6w 7k2M7vwQKWJlIwKIUtMqdPte4wAysUX7iNtJymtkj4bQku74i7WN4Utcgf5wnJXzHzUGKcCwPgU VpuvmsjxM+/bMtH2XPG0dubxbDexSI4vFSAAZve6VKjDCcaY499I0gFo2liDPyTebZsvAxQ0Y9b TsYkoSROS7HS4dT6GCq3Qfwpim100BLlWhJx5fj8lUEZwXihu6e46ZePyykvtQ92ja0aOnk0lkW u1n2q1GHMBM1nHpeI9tjh1lMb4HRB6n0Q== X-Received: by 2002:a17:902:cece:b0:2b0:69bf:a3df with SMTP id d9443c01a7336-2b0827a4fd6mr144604265ad.27.1774334406751; Mon, 23 Mar 2026 23:40:06 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:05 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:38 +0000 Subject: [PATCH v2 05/11] HID: spi-hid: add HID SPI protocol implementation Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-5-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Dmitry Antipov , Angela Czubak X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=23192; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=Nk6TMz3gATQxP2tJVjnvFBAdOOfrIYM1qwnKqS7z+4U=; b=3tkHSVgXm6x6tY2ZvHp4FUQ1sXo4iXwa277nR+b8z9f5C3NkOTYkB8tGTAwMZV2O3cxL7o4M6 BAoIi1WnO7KBix133i+91VC9shgbiijwnMAmWsDlGW2aUowGNzQ0LAK X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= This driver follows HID Over SPI Protocol Specification 1.0 available at https://www.microsoft.com/en-us/download/details.aspx?id=3D103325. The initial version of the driver does not support: 1) multi-fragment input reports, 2) sending GET_INPUT and COMMAND output report types and processing their respective acknowledge input reports, and 3) device sleep power state. Signed-off-by: Dmitry Antipov Signed-off-by: Angela Czubak Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/spi-hid-core.c | 581 +++++++++++++++++++++++++++++++++= +++- 1 file changed, 571 insertions(+), 10 deletions(-) diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index 4723b87346d4..79bec3303968 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -23,11 +23,16 @@ #include #include #include +#include #include #include #include +#include #include +#include #include +#include +#include #include #include #include @@ -35,12 +40,22 @@ #include #include #include +#include +#include + +/* Protocol constants */ +#define SPI_HID_READ_APPROVAL_CONSTANT 0xff +#define SPI_HID_INPUT_HEADER_SYNC_BYTE 0x5a +#define SPI_HID_INPUT_HEADER_VERSION 0x03 +#define SPI_HID_SUPPORTED_VERSION 0x0300 =20 #define SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST 0x00 =20 -#define SPI_HID_RESP_TIMEOUT 1000 +#define SPI_HID_MAX_RESET_ATTEMPTS 3 +#define SPI_HID_RESP_TIMEOUT 1000 =20 /* Protocol message size constants */ +#define SPI_HID_READ_APPROVAL_LEN 5 #define SPI_HID_OUTPUT_HEADER_LEN 8 =20 /* flags */ @@ -49,6 +64,22 @@ * requests. The FW becomes ready after sending the report descriptor. */ #define SPI_HID_READY 0 +/* + * refresh_in_progress is set to true while the refresh_device worker + * thread is destroying and recreating the hidraw device. When this flag + * is set to true, the ll_close and ll_open functions will not cause + * power state changes. + */ +#define SPI_HID_REFRESH_IN_PROGRESS 1 +/* + * reset_pending indicates that the device is being reset. When this flag + * is set to true, garbage interrupts triggered during reset will be + * dropped and will not cause error handling. + */ +#define SPI_HID_RESET_PENDING 2 +#define SPI_HID_RESET_RESPONSE 3 +#define SPI_HID_CREATE_DEVICE 4 +#define SPI_HID_ERROR 5 =20 /* Raw input buffer with data from the bus */ struct spi_hid_input_buf { @@ -57,6 +88,22 @@ struct spi_hid_input_buf { u8 content[]; }; =20 +/* Processed data from input report header */ +struct spi_hid_input_header { + u8 version; + u16 report_length; + u8 last_fragment_flag; + u8 sync_const; +}; + +/* Processed data from an input report */ +struct spi_hid_input_report { + u8 report_type; + u16 content_length; + u8 content_id; + u8 *content; +}; + /* Raw output report buffer to be put on the bus */ struct spi_hid_output_buf { u8 header[SPI_HID_OUTPUT_HEADER_LEN]; @@ -114,6 +161,9 @@ struct spi_hid { struct spi_device *spi; /* spi device. */ struct hid_device *hid; /* pointer to corresponding HID dev. */ =20 + struct spi_transfer input_transfer[2]; /* Transfer buffer for read and wr= ite. */ + struct spi_message input_message; /* used to execute a sequence of spi tr= ansfers. */ + struct spihid_ops *ops; struct spi_hid_conf *conf; =20 @@ -131,10 +181,20 @@ struct spi_hid { =20 unsigned long flags; /* device flags. */ =20 - /* Control lock to make sure one output transaction at a time. */ + struct work_struct reset_work; + + /* Control lock to ensure complete output transaction. */ struct mutex output_lock; + /* Power lock to make sure one power state change at a time. */ + struct mutex power_lock; + /* I/O lock to prevent concurrent output writes during the input read. */ + struct mutex io_lock; + struct completion output_done; =20 + u8 read_approval_header[SPI_HID_READ_APPROVAL_LEN]; + u8 read_approval_body[SPI_HID_READ_APPROVAL_LEN]; + u32 report_descriptor_crc32; /* HID report descriptor crc32 checksum. */ =20 u32 regulator_error_count; @@ -146,6 +206,66 @@ struct spi_hid { =20 static struct hid_ll_driver spi_hid_ll_driver; =20 +static void spi_hid_populate_read_approvals(const struct spi_hid_conf *con= f, + u8 *header_buf, u8 *body_buf) +{ + header_buf[0] =3D conf->read_opcode; + put_unaligned_be24(conf->input_report_header_address, &header_buf[1]); + header_buf[4] =3D SPI_HID_READ_APPROVAL_CONSTANT; + + body_buf[0] =3D conf->read_opcode; + put_unaligned_be24(conf->input_report_body_address, &body_buf[1]); + body_buf[4] =3D SPI_HID_READ_APPROVAL_CONSTANT; +} + +static void spi_hid_parse_dev_desc(const struct hidspi_dev_descriptor *raw, + struct spi_hid_device_descriptor *desc) +{ + desc->hid_version =3D le16_to_cpu(raw->bcd_ver); + desc->report_descriptor_length =3D le16_to_cpu(raw->rep_desc_len); + desc->max_input_length =3D le16_to_cpu(raw->max_input_len); + desc->max_output_length =3D le16_to_cpu(raw->max_output_len); + + /* FIXME: multi-fragment not supported, field below not used */ + desc->max_fragment_length =3D le16_to_cpu(raw->max_frag_len); + + desc->vendor_id =3D le16_to_cpu(raw->vendor_id); + desc->product_id =3D le16_to_cpu(raw->product_id); + desc->version_id =3D le16_to_cpu(raw->version_id); + desc->no_output_report_ack =3D le16_to_cpu(raw->flags) & BIT(0); +} + +static void spi_hid_populate_input_header(const u8 *buf, + struct spi_hid_input_header *header) +{ + header->version =3D buf[0] & 0xf; + header->report_length =3D (get_unaligned_le16(&buf[1]) & 0x3fff) * 4; + header->last_fragment_flag =3D (buf[2] & 0x40) >> 6; + header->sync_const =3D buf[3]; +} + +static void spi_hid_populate_input_body(const u8 *buf, + struct input_report_body_header *body) +{ + body->input_report_type =3D buf[0]; + body->content_len =3D get_unaligned_le16(&buf[1]); + body->content_id =3D buf[3]; +} + +static void spi_hid_input_report_prepare(struct spi_hid_input_buf *buf, + struct spi_hid_input_report *report) +{ + struct spi_hid_input_header header; + struct input_report_body_header body; + + spi_hid_populate_input_header(buf->header, &header); + spi_hid_populate_input_body(buf->body, &body); + report->report_type =3D body.input_report_type; + report->content_length =3D body.content_len; + report->content_id =3D body.content_id; + report->content =3D buf->content; +} + static void spi_hid_populate_output_header(u8 *buf, const struct spi_hid_conf *conf, const struct spi_hid_output_report *report) @@ -157,6 +277,33 @@ static void spi_hid_populate_output_header(u8 *buf, buf[7] =3D report->content_id; } =20 +static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length, + bool is_header) +{ + int error; + + shid->input_transfer[0].tx_buf =3D is_header ? + shid->read_approval_header : + shid->read_approval_body; + shid->input_transfer[0].len =3D SPI_HID_READ_APPROVAL_LEN; + + shid->input_transfer[1].rx_buf =3D buf; + shid->input_transfer[1].len =3D length; + + spi_message_init_with_transfers(&shid->input_message, + shid->input_transfer, 2); + + error =3D spi_sync(shid->spi, &shid->input_message); + if (error) { + dev_err(&shid->spi->dev, "Error starting sync transfer: %d.", error); + shid->bus_error_count++; + shid->bus_last_error =3D error; + return error; + } + + return 0; +} + static int spi_hid_output(struct spi_hid *shid, const void *buf, u16 lengt= h) { int error; @@ -196,6 +343,50 @@ static void spi_hid_stop_hid(struct spi_hid *shid) hid_destroy_device(hid); } =20 +static void spi_hid_error(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + int error; + + guard(mutex)(&shid->power_lock); + if (shid->power_state =3D=3D HIDSPI_OFF) + return; + + if (shid->reset_attempts++ >=3D SPI_HID_MAX_RESET_ATTEMPTS) { + dev_err(dev, "unresponsive device, aborting."); + spi_hid_stop_hid(shid); + shid->ops->assert_reset(shid->ops); + error =3D shid->ops->power_down(shid->ops); + if (error) { + dev_err(dev, "failed to disable regulator."); + shid->regulator_error_count++; + shid->regulator_last_error =3D error; + } + return; + } + + clear_bit(SPI_HID_READY, &shid->flags); + set_bit(SPI_HID_RESET_PENDING, &shid->flags); + + shid->ops->assert_reset(shid->ops); + + shid->power_state =3D HIDSPI_OFF; + + /* + * We want to cancel pending reset work as the device is being reset + * to recover from an error. cancel_work_sync will put us in a deadlock + * because this function is scheduled in 'reset_work' and we should + * avoid waiting for itself. + */ + cancel_work(&shid->reset_work); + + shid->ops->sleep_minimal_reset_delay(shid->ops); + + shid->power_state =3D HIDSPI_ON; + + shid->ops->deassert_reset(shid->ops); +} + static int spi_hid_send_output_report(struct spi_hid *shid, struct spi_hid_output_report *report) { @@ -206,13 +397,13 @@ static int spi_hid_send_output_report(struct spi_hid = *shid, u8 padding; int error; =20 - guard(mutex)(&shid->output_lock); if (report->content_length > shid->desc.max_output_length) { dev_err(dev, "Output report too big, content_length 0x%x.", report->content_length); return -E2BIG; } =20 + guard(mutex)(&shid->io_lock); spi_hid_populate_output_header(buf->header, shid->conf, report); =20 if (report->content_length) @@ -236,6 +427,7 @@ static int spi_hid_sync_request(struct spi_hid *shid, struct device *dev =3D &shid->spi->dev; int error; =20 + guard(mutex)(&shid->output_lock); error =3D spi_hid_send_output_report(shid, report); if (error) return error; @@ -250,6 +442,86 @@ static int spi_hid_sync_request(struct spi_hid *shid, return 0; } =20 +/* + * Handle the reset response from the FW by sending a request for the devi= ce + * descriptor. + */ +static void spi_hid_reset_response(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D DEVICE_DESCRIPTOR, + .content_length =3D 0x0, + .content_id =3D SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST, + .content =3D NULL, + }; + int error; + + if (test_bit(SPI_HID_READY, &shid->flags)) { + dev_err(dev, "Spontaneous FW reset!"); + clear_bit(SPI_HID_READY, &shid->flags); + shid->dir_count++; + } + + if (shid->power_state =3D=3D HIDSPI_OFF) + return; + + error =3D spi_hid_sync_request(shid, &report); + if (error) { + dev_WARN_ONCE(dev, true, + "Failed to send device descriptor request: %d.", error); + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); + } +} + +static int spi_hid_input_report_handler(struct spi_hid *shid, + struct spi_hid_input_buf *buf) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_input_report r; + int error =3D 0; + + if (!test_bit(SPI_HID_READY, &shid->flags) || + test_bit(SPI_HID_REFRESH_IN_PROGRESS, &shid->flags) || !shid->hid) { + dev_err(dev, "HID not ready"); + return 0; + } + + spi_hid_input_report_prepare(buf, &r); + + error =3D hid_input_report(shid->hid, HID_INPUT_REPORT, + r.content - 1, r.content_length + 1, 1); + + if (error =3D=3D -ENODEV || error =3D=3D -EBUSY) { + dev_err(dev, "ignoring report --> %d.", error); + return 0; + } else if (error) { + dev_err(dev, "Bad input report: %d.", error); + } + + return error; +} + +static void spi_hid_response_handler(struct spi_hid *shid, + struct input_report_body_header *body) +{ + shid->response_length =3D body->content_len; + /* completion_done returns 0 if there are waiters, otherwise 1 */ + if (completion_done(&shid->output_done)) { + dev_err(&shid->spi->dev, "Unexpected response report."); + } else { + if (body->input_report_type =3D=3D REPORT_DESCRIPTOR_RESPONSE || + body->input_report_type =3D=3D GET_FEATURE_RESPONSE) { + memcpy(shid->response->body, shid->input->body, + sizeof(shid->input->body)); + memcpy(shid->response->content, shid->input->content, + body->content_len); + } + complete(&shid->output_done); + } +} + /* * This function returns the length of the report descriptor, or a negative * error code if something went wrong. @@ -269,6 +541,8 @@ static int spi_hid_report_descriptor_request(struct spi= _hid *shid) if (ret) { dev_err(dev, "Expected report descriptor not received: %d.", ret); + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); return ret; } =20 @@ -323,6 +597,205 @@ static int spi_hid_create_device(struct spi_hid *shid) return 0; } =20 +static void spi_hid_refresh_device(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + u32 new_crc32 =3D 0; + int error =3D 0; + + error =3D spi_hid_report_descriptor_request(shid); + if (error < 0) { + dev_err(dev, + "%s: failed report descriptor request: %d", + __func__, error); + return; + } + new_crc32 =3D crc32_le(0, (unsigned char const *)shid->response->content, + (size_t)error); + + /* Same report descriptor, so no need to create a new hid device. */ + if (new_crc32 =3D=3D shid->report_descriptor_crc32) { + set_bit(SPI_HID_READY, &shid->flags); + return; + } + + shid->report_descriptor_crc32 =3D new_crc32; + + set_bit(SPI_HID_REFRESH_IN_PROGRESS, &shid->flags); + + spi_hid_stop_hid(shid); + + error =3D spi_hid_create_device(shid); + if (error) { + dev_err(dev, "%s: Failed to create hid device: %d.", __func__, error); + return; + } + + clear_bit(SPI_HID_REFRESH_IN_PROGRESS, &shid->flags); +} + +static void spi_hid_reset_work(struct work_struct *work) +{ + struct spi_hid *shid =3D + container_of(work, struct spi_hid, reset_work); + struct device *dev =3D &shid->spi->dev; + int error =3D 0; + + if (test_and_clear_bit(SPI_HID_RESET_RESPONSE, &shid->flags)) { + spi_hid_reset_response(shid); + return; + } + + if (test_and_clear_bit(SPI_HID_CREATE_DEVICE, &shid->flags)) { + guard(mutex)(&shid->power_lock); + if (shid->power_state =3D=3D HIDSPI_OFF) { + dev_err(dev, "%s: Powered off, returning", __func__); + return; + } + + if (!shid->hid) { + error =3D spi_hid_create_device(shid); + if (error) { + dev_err(dev, "%s: Failed to create hid device: %d.", + __func__, error); + return; + } + } else { + spi_hid_refresh_device(shid); + } + + return; + } + + if (test_and_clear_bit(SPI_HID_ERROR, &shid->flags)) { + spi_hid_error(shid); + return; + } +} + +static int spi_hid_process_input_report(struct spi_hid *shid, + struct spi_hid_input_buf *buf) +{ + struct spi_hid_input_header header; + struct input_report_body_header body; + struct device *dev =3D &shid->spi->dev; + struct hidspi_dev_descriptor *raw; + + spi_hid_populate_input_header(buf->header, &header); + spi_hid_populate_input_body(buf->body, &body); + + if (body.content_len > header.report_length) { + dev_err(dev, "Bad body length %d > %d.", body.content_len, + header.report_length); + return -EPROTO; + } + + switch (body.input_report_type) { + case DATA: + return spi_hid_input_report_handler(shid, buf); + case RESET_RESPONSE: + clear_bit(SPI_HID_RESET_PENDING, &shid->flags); + set_bit(SPI_HID_RESET_RESPONSE, &shid->flags); + schedule_work(&shid->reset_work); + break; + case DEVICE_DESCRIPTOR_RESPONSE: + /* Mark the completion done to avoid timeout */ + spi_hid_response_handler(shid, &body); + + /* Reset attempts at every device descriptor fetch */ + shid->reset_attempts =3D 0; + raw =3D (struct hidspi_dev_descriptor *)buf->content; + + /* Validate device descriptor length before parsing */ + if (body.content_len !=3D HIDSPI_DEVICE_DESCRIPTOR_SIZE) { + dev_err(dev, "Invalid content length %d, expected %lu.", + body.content_len, + HIDSPI_DEVICE_DESCRIPTOR_SIZE); + return -EPROTO; + } + + if (le16_to_cpu(raw->dev_desc_len) !=3D + HIDSPI_DEVICE_DESCRIPTOR_SIZE) { + dev_err(dev, + "Invalid wDeviceDescLength %d, expected %lu.", + raw->dev_desc_len, + HIDSPI_DEVICE_DESCRIPTOR_SIZE); + return -EPROTO; + } + + spi_hid_parse_dev_desc(raw, &shid->desc); + + if (shid->desc.hid_version !=3D SPI_HID_SUPPORTED_VERSION) { + dev_err(dev, + "Unsupported device descriptor version %4x.", + shid->desc.hid_version); + return -EPROTONOSUPPORT; + } + + set_bit(SPI_HID_CREATE_DEVICE, &shid->flags); + schedule_work(&shid->reset_work); + + break; + case OUTPUT_REPORT_RESPONSE: + if (shid->desc.no_output_report_ack) { + dev_err(dev, "Unexpected output report response."); + break; + } + fallthrough; + case GET_FEATURE_RESPONSE: + case SET_FEATURE_RESPONSE: + case REPORT_DESCRIPTOR_RESPONSE: + spi_hid_response_handler(shid, &body); + break; + /* + * FIXME: sending GET_INPUT and COMMAND reports not supported, thus + * throw away responses to those, they should never come. + */ + case GET_INPUT_REPORT_RESPONSE: + case COMMAND_RESPONSE: + dev_err(dev, "Not a supported report type: 0x%x.", + body.input_report_type); + break; + default: + dev_err(dev, "Unknown input report: 0x%x.", body.input_report_type); + return -EPROTO; + } + + return 0; +} + +static int spi_hid_bus_validate_header(struct spi_hid *shid, + struct spi_hid_input_header *header) +{ + struct device *dev =3D &shid->spi->dev; + + if (header->version !=3D SPI_HID_INPUT_HEADER_VERSION) { + dev_err(dev, "Unknown input report version (v 0x%x).", + header->version); + return -EINVAL; + } + + if (shid->desc.max_input_length !=3D 0 && + header->report_length > shid->desc.max_input_length) { + dev_err(dev, "Input report body size %u > max expected of %u.", + header->report_length, shid->desc.max_input_length); + return -EMSGSIZE; + } + + if (header->last_fragment_flag !=3D 1) { + dev_err(dev, "Multi-fragment reports not supported."); + return -EOPNOTSUPP; + } + + if (header->sync_const !=3D SPI_HID_INPUT_HEADER_SYNC_BYTE) { + dev_err(dev, "Invalid input report sync constant (0x%x).", + header->sync_const); + return -EINVAL; + } + + return 0; +} + static int spi_hid_get_request(struct spi_hid *shid, u8 content_id) { struct device *dev =3D &shid->spi->dev; @@ -339,6 +812,8 @@ static int spi_hid_get_request(struct spi_hid *shid, u8= content_id) dev_err(dev, "Expected get request response not received! Error %d.", error); + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); return error; } =20 @@ -358,9 +833,83 @@ static int spi_hid_set_request(struct spi_hid *shid, u= 8 *arg_buf, u16 arg_len, return spi_hid_sync_request(shid, &report); } =20 -/* This is a placeholder. Will be implemented in the next patch. */ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid) { + struct spi_hid *shid =3D _shid; + struct device *dev =3D &shid->spi->dev; + struct spi_hid_input_header header; + int error =3D 0; + + scoped_guard(mutex, &shid->io_lock) { + error =3D spi_hid_input_sync(shid, shid->input->header, + sizeof(shid->input->header), true); + if (error) { + dev_err(dev, "Failed to transfer header: %d.", error); + goto err; + } + + if (shid->power_state =3D=3D HIDSPI_OFF) { + dev_warn(dev, "Device is off after header was received."); + goto out; + } + + if (shid->input_message.status < 0) { + dev_warn(dev, "Error reading header: %d.", + shid->input_message.status); + shid->bus_error_count++; + shid->bus_last_error =3D shid->input_message.status; + goto err; + } + + spi_hid_populate_input_header(shid->input->header, &header); + + error =3D spi_hid_bus_validate_header(shid, &header); + if (error) { + if (!test_bit(SPI_HID_RESET_PENDING, &shid->flags)) { + dev_err(dev, "Failed to validate header: %d.", error); + print_hex_dump(KERN_ERR, "spi_hid: header buffer: ", + DUMP_PREFIX_NONE, 16, 1, shid->input->header, + sizeof(shid->input->header), false); + shid->bus_error_count++; + shid->bus_last_error =3D error; + goto err; + } + goto out; + } + + error =3D spi_hid_input_sync(shid, shid->input->body, header.report_leng= th, + false); + if (error) { + dev_err(dev, "Failed to transfer body: %d.", error); + goto err; + } + + if (shid->power_state =3D=3D HIDSPI_OFF) { + dev_warn(dev, "Device is off after body was received."); + goto out; + } + + if (shid->input_message.status < 0) { + dev_warn(dev, "Error reading body: %d.", + shid->input_message.status); + shid->bus_error_count++; + shid->bus_last_error =3D shid->input_message.status; + goto err; + } + } + + error =3D spi_hid_process_input_report(shid, shid->input); + if (error) { + dev_err(dev, "Failed to process input report: %d.", error); + goto err; + } + +out: + return IRQ_HANDLED; + +err: + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); return IRQ_HANDLED; } =20 @@ -571,10 +1120,13 @@ static int spi_hid_ll_output_report(struct hid_devic= e *hid, __u8 *buf, return -ENODEV; } =20 - if (shid->desc.no_output_report_ack) - error =3D spi_hid_send_output_report(shid, &report); - else + if (shid->desc.no_output_report_ack) { + scoped_guard(mutex, &shid->output_lock) { + error =3D spi_hid_send_output_report(shid, &report); + } + } else { error =3D spi_hid_sync_request(shid, &report); + } =20 if (error) { dev_err(dev, "failed to send output report."); @@ -662,11 +1214,22 @@ int spi_hid_core_probe(struct spi_device *spi, struc= t spihid_ops *ops, shid->power_state =3D HIDSPI_ON; shid->ops =3D ops; shid->conf =3D conf; + set_bit(SPI_HID_RESET_PENDING, &shid->flags); =20 spi_set_drvdata(spi, shid); =20 + /* Using now populated conf let's pre-calculate the read approvals */ + spi_hid_populate_read_approvals(shid->conf, shid->read_approval_header, + shid->read_approval_body); + + mutex_init(&shid->output_lock); + mutex_init(&shid->power_lock); + init_completion(&shid->output_done); + + INIT_WORK(&shid->reset_work, spi_hid_reset_work); + /* - * we need to allocate the buffer without knowing the maximum + * We need to allocate the buffer without knowing the maximum * size of the reports. Let's use SZ_2K, then we do the * real computation later. */ @@ -706,8 +1269,6 @@ int spi_hid_core_probe(struct spi_device *spi, struct = spihid_ops *ops, dev_dbg(dev, "%s: d3 -> %s.", __func__, spi_hid_power_mode_string(shid->power_state)); =20 - spi_hid_create_device(shid); - return 0; } EXPORT_SYMBOL_GPL(spi_hid_core_probe); --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pg1-f180.google.com (mail-pg1-f180.google.com [209.85.215.180]) (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 0945D3C73C4 for ; Tue, 24 Mar 2026 06:40:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334421; cv=none; b=RwkuzgGeIL8NQKggms9twHIZykb3hJJrUrJF4kfn4M81S+0UNC4Diyiv/z9a+4Lwmx2IumZSgTxY6UyV3/GApc7xFgCsjlDuhnfr+6eJ6k2gvJKCZMyQ+rHjM50UNY5k9FQWIjsyZmHMdc+4DZHdOahl7BHZk4EVaM8kZEt8KY4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334421; c=relaxed/simple; bh=rY2qv0nwiQXwqu4138tpVek9SPsqYr/bD9nk0GjmQaI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=G8JvR6W/m2rrfahOUusXHEQG2L+U2X2ZBGf/jVLTNjadIX6SjraRF0K3rdQhBPH7MRXXJVM7fZjy96STTzgf91nJbw6LN9HYcC3rUQA+fl5bYkvlPEpJkJvlmU4VcNVGlyWMAYxvVAzBekrA8ATOJrxHDOlUFpJDh8iBkxgbadE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=OI3mybbD; arc=none smtp.client-ip=209.85.215.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="OI3mybbD" Received: by mail-pg1-f180.google.com with SMTP id 41be03b00d2f7-c7412b07f22so2596326a12.0 for ; Mon, 23 Mar 2026 23:40:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334408; x=1774939208; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=4/HQ1k4gbALz2JM52q8iV6sF5/svhBt6Kuq82Depajo=; b=OI3mybbDOw/EhHu/ZcVdP0nK23LGhn0aa/X4JV+44z1dORHFhvTO575IUghMOLikh2 cXcomvH1KPyhLNjDjxgD3beHXmGPvY6UcNxqQmi/V4G/lt5YBPC6UNUJHrab7aZaFCXN 9qOIH2p6mdKHlRD5pM+u2M8YUCIOlM+UmmuX4= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334408; x=1774939208; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=4/HQ1k4gbALz2JM52q8iV6sF5/svhBt6Kuq82Depajo=; b=LSakJckdNPpY8wMLiOBbR1TcHpvz4zS80huOZSbc6oU6kNkrLBAVfFAzfYhXeUxN/i limmpTNREqZs4x/8gnnURvEj4svRiB5nB1d0Pe2Q57TjrHW5b9dhf4Cf+mbJZfpdyUsP 1ue3ned7E5wY5ceroWW5fQVvzyDf3nwZiQNAqSFGjUB3N0QzQd3ZacBVKDJxoB0p+cgg 36uS9vMfaeGuUlfjiU8hFDSy4aX0gujAbHtNIOcE/S6j0HhSlMm5TdiGd/N6NikwjrtV YBz3/mWgWDyBdc1FtBOCcg88FJPwNQplYFiGuWgo0enk0pqfXjk87Wj2AdwnIzkOZqov Ncvw== X-Forwarded-Encrypted: i=1; AJvYcCWOzG21EscXyGBO8RQo6tfzUJKnDNB3gbRoPynDTVkVEAFYSW++HVvyh2I3Ufo6ue+PqrbD89Mssetibm8=@vger.kernel.org X-Gm-Message-State: AOJu0YxsHeQW4VoV9ojXLGLtIn57mG3JZdUxApHd2goEDWOdu+b4cxL4 cO7jf6zToMXn188o0r96fZQPp6xCmx6cdw5q3F1o8NvUaTeBBSacm4jJlmp7PLFXDQ== X-Gm-Gg: ATEYQzzAlwLo2aJNMCntmKaGPVjg3cshQx8Y78zIuwJactCE9Aap5goo6eqX+607aEv 9p/Dfk53tLYKsztjkIa7qUjsFscHcv0v5hDi9U/U7UzGJDeUeM5asKcJj2IK1NF6kImojrlZh6w 4pgi/X10FbV9raa+CRn5oxjv8Hl72gL108yz84Gr5cusSbbZRTxlks5vF/xReGFMnn0tYDNzPSb M+sHsJFux1us8XLUPQIq4FzrXWnbTVOZ1zS3oF7UfbfvSEH2gjHX3SwhZgWiHlCIfIEnZ1e7Ry5 +ObI3F46nyoIA3FbpkyO/sig8OAg12RpA4dSMyA3cT0PJDlUZeUl+694CFcLB7wVdqi/jx4Eho4 iFHnMbVQEzpHYwtqadxtTLuzwtGJ8W3ggNGTZsoGOfdiEtTMcjisEC/elgTDM7lKED472Wj84m+ efBDpqKwKJQ+MMxK6m7rQF/gTsVYeYTRLsF9M9kv292vqblWDw7VzUVbninlMgiMM0W5BLFdZli CGXXF+Ysbh756cPc04ACabDUtk2EDaRPg== X-Received: by 2002:a17:902:f552:b0:2b0:62cf:3537 with SMTP id d9443c01a7336-2b0a4db2991mr19875505ad.6.1774334407596; Mon, 23 Mar 2026 23:40:07 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:07 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:39 +0000 Subject: [PATCH v2 06/11] HID: spi_hid: add spi_hid traces Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-6-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Dmitry Antipov , Angela Czubak X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=11791; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=rY2qv0nwiQXwqu4138tpVek9SPsqYr/bD9nk0GjmQaI=; b=BkH3K4lWcvMw6A1eN8KuZE+c3hleZnv7JFhMYxRqPr9xPssXpr5lDxrekSft1IDnKhFLP4APO 5Rw+mI33JoHCqoLjQsNcxc6WfOgFLZtfa68O315MQq9KAKj1JM5gOM9 X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= Add traces for purposed of debugging spi_hid driver. Signed-off-by: Dmitry Antipov Signed-off-by: Angela Czubak Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/spi-hid-core.c | 74 ------------------ drivers/hid/spi-hid/spi-hid-core.h | 86 ++++++++++++++++++++ include/trace/events/spi_hid.h | 156 +++++++++++++++++++++++++++++++++= ++++ 3 files changed, 242 insertions(+), 74 deletions(-) diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index 79bec3303968..3105884b656e 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -81,13 +81,6 @@ #define SPI_HID_CREATE_DEVICE 4 #define SPI_HID_ERROR 5 =20 -/* Raw input buffer with data from the bus */ -struct spi_hid_input_buf { - u8 header[HIDSPI_INPUT_HEADER_SIZE]; - u8 body[HIDSPI_INPUT_BODY_HEADER_SIZE]; - u8 content[]; -}; - /* Processed data from input report header */ struct spi_hid_input_header { u8 version; @@ -104,12 +97,6 @@ struct spi_hid_input_report { u8 *content; }; =20 -/* Raw output report buffer to be put on the bus */ -struct spi_hid_output_buf { - u8 header[SPI_HID_OUTPUT_HEADER_LEN]; - u8 content[]; -}; - /* Data necessary to send an output report */ struct spi_hid_output_report { u8 report_type; @@ -118,19 +105,6 @@ struct spi_hid_output_report { u8 *content; }; =20 -/* Processed data from a device descriptor */ -struct spi_hid_device_descriptor { - u16 hid_version; - u16 report_descriptor_length; - u16 max_input_length; - u16 max_output_length; - u16 max_fragment_length; - u16 vendor_id; - u16 product_id; - u16 version_id; - u8 no_output_report_ack; -}; - /* struct spi_hid_conf - Conf provided to the core */ struct spi_hid_conf { u32 input_report_header_address; @@ -156,54 +130,6 @@ struct spihid_ops { void (*sleep_minimal_reset_delay)(struct spihid_ops *ops); }; =20 -/* Driver context */ -struct spi_hid { - struct spi_device *spi; /* spi device. */ - struct hid_device *hid; /* pointer to corresponding HID dev. */ - - struct spi_transfer input_transfer[2]; /* Transfer buffer for read and wr= ite. */ - struct spi_message input_message; /* used to execute a sequence of spi tr= ansfers. */ - - struct spihid_ops *ops; - struct spi_hid_conf *conf; - - struct spi_hid_device_descriptor desc; /* HID device descriptor. */ - struct spi_hid_output_buf *output; /* Output buffer. */ - struct spi_hid_input_buf *input; /* Input buffer. */ - struct spi_hid_input_buf *response; /* Response buffer. */ - - u16 response_length; - u16 bufsize; - - enum hidspi_power_state power_state; - - u8 reset_attempts; /* The number of reset attempts. */ - - unsigned long flags; /* device flags. */ - - struct work_struct reset_work; - - /* Control lock to ensure complete output transaction. */ - struct mutex output_lock; - /* Power lock to make sure one power state change at a time. */ - struct mutex power_lock; - /* I/O lock to prevent concurrent output writes during the input read. */ - struct mutex io_lock; - - struct completion output_done; - - u8 read_approval_header[SPI_HID_READ_APPROVAL_LEN]; - u8 read_approval_body[SPI_HID_READ_APPROVAL_LEN]; - - u32 report_descriptor_crc32; /* HID report descriptor crc32 checksum. */ - - u32 regulator_error_count; - int regulator_last_error; - u32 bus_error_count; - int bus_last_error; - u32 dir_count; /* device initiated reset count. */ -}; - static struct hid_ll_driver spi_hid_ll_driver; =20 static void spi_hid_populate_read_approvals(const struct spi_hid_conf *con= f, diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-h= id-core.h new file mode 100644 index 000000000000..61b35a4a4180 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-core.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Microsoft Corporation + * Copyright (c) 2026 Google LLC + */ + +#include +#include + +/* Protocol message size constants */ +#define SPI_HID_READ_APPROVAL_LEN 5 +#define SPI_HID_OUTPUT_HEADER_LEN 8 + +/* Raw input buffer with data from the bus */ +struct spi_hid_input_buf { + u8 header[HIDSPI_INPUT_HEADER_SIZE]; + u8 body[HIDSPI_INPUT_BODY_HEADER_SIZE]; + u8 content[]; +}; + +/* Raw output report buffer to be put on the bus */ +struct spi_hid_output_buf { + u8 header[SPI_HID_OUTPUT_HEADER_LEN]; + u8 content[]; +}; + +/* Processed data from a device descriptor */ +struct spi_hid_device_descriptor { + u16 hid_version; + u16 report_descriptor_length; + u16 max_input_length; + u16 max_output_length; + u16 max_fragment_length; + u16 vendor_id; + u16 product_id; + u16 version_id; + u8 no_output_report_ack; +}; + +/* Driver context */ +struct spi_hid { + struct spi_device *spi; /* spi device. */ + struct hid_device *hid; /* pointer to corresponding HID dev. */ + + struct spi_transfer input_transfer[2]; /* Transfer buffer for read and wr= ite. */ + struct spi_message input_message; /* used to execute a sequence of spi tr= ansfers. */ + + struct spihid_ops *ops; + struct spi_hid_conf *conf; + + struct spi_hid_device_descriptor desc; /* HID device descriptor. */ + struct spi_hid_output_buf *output; /* Output buffer. */ + struct spi_hid_input_buf *input; /* Input buffer. */ + struct spi_hid_input_buf *response; /* Response buffer. */ + + u16 response_length; + u16 bufsize; + + enum hidspi_power_state power_state; + + u8 reset_attempts; /* The number of reset attempts. */ + + unsigned long flags; /* device flags. */ + + struct work_struct reset_work; + + /* Control lock to ensure complete output transaction. */ + struct mutex output_lock; + /* Power lock to make sure one power state change at a time. */ + struct mutex power_lock; + /* I/O lock to prevent concurrent output writes during the input read. */ + struct mutex io_lock; + + struct completion output_done; + + u8 read_approval_header[SPI_HID_READ_APPROVAL_LEN]; + u8 read_approval_body[SPI_HID_READ_APPROVAL_LEN]; + + u32 report_descriptor_crc32; /* HID report descriptor crc32 checksum. */ + + u32 regulator_error_count; + int regulator_last_error; + u32 bus_error_count; + int bus_last_error; + u32 dir_count; /* device initiated reset count. */ +}; diff --git a/include/trace/events/spi_hid.h b/include/trace/events/spi_hid.h new file mode 100644 index 000000000000..e9a579b3806c --- /dev/null +++ b/include/trace/events/spi_hid.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Microsoft Corporation + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM spi_hid + +#if !defined(_SPI_HID_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SPI_HID_TRACE_H + +#include +#include + +DECLARE_EVENT_CLASS(spi_hid_transfer, + TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len, + const void *rx_buf, u16 rx_len, int ret), + + TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret), + + TP_STRUCT__entry( + __field(int, bus_num) + __field(int, chip_select) + __field(int, ret) + __dynamic_array(u8, rx_buf, rx_len) + __dynamic_array(u8, tx_buf, tx_len) + ), + + TP_fast_assign( + __entry->bus_num =3D shid->spi->controller->bus_num; + __entry->chip_select =3D shid->spi->chip_select; + __entry->ret =3D ret; + + memcpy(__get_dynamic_array(tx_buf), tx_buf, tx_len); + memcpy(__get_dynamic_array(rx_buf), rx_buf, rx_len); + ), + + TP_printk("spi%d.%d: len=3D%d tx=3D[%*phD] rx=3D[%*phD] --> %d", + __entry->bus_num, __entry->chip_select, + __get_dynamic_array_len(tx_buf) + __get_dynamic_array_len(rx_buf), + __get_dynamic_array_len(tx_buf), __get_dynamic_array(tx_buf), + __get_dynamic_array_len(rx_buf), __get_dynamic_array(rx_buf), + __entry->ret) +); + +DEFINE_EVENT(spi_hid_transfer, spi_hid_input_sync, + TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len, + const void *rx_buf, u16 rx_len, int ret), + TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)); + +DEFINE_EVENT(spi_hid_transfer, spi_hid_input_header_complete, + TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len, + const void *rx_buf, u16 rx_len, int ret), + TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)); + +DEFINE_EVENT(spi_hid_transfer, spi_hid_input_body_complete, + TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len, + const void *rx_buf, u16 rx_len, int ret), + TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret)); + +DECLARE_EVENT_CLASS(spi_hid_irq, + TP_PROTO(struct spi_hid *shid, int irq), + + TP_ARGS(shid, irq), + + TP_STRUCT__entry( + __field(int, bus_num) + __field(int, chip_select) + __field(int, irq) + ), + + TP_fast_assign( + __entry->bus_num =3D shid->spi->controller->bus_num; + __entry->chip_select =3D shid->spi->chip_select; + __entry->irq =3D irq; + ), + + TP_printk("spi%d.%d: IRQ %d", + __entry->bus_num, __entry->chip_select, __entry->irq) +); + +DEFINE_EVENT(spi_hid_irq, spi_hid_dev_irq, + TP_PROTO(struct spi_hid *shid, int irq), TP_ARGS(shid, irq)); + +DECLARE_EVENT_CLASS(spi_hid, + TP_PROTO(struct spi_hid *shid), + + TP_ARGS(shid), + + TP_STRUCT__entry( + __field(int, bus_num) + __field(int, chip_select) + __field(int, power_state) + __field(u32, flags) + + __field(int, vendor_id) + __field(int, product_id) + __field(int, max_input_length) + __field(int, max_output_length) + __field(u16, hid_version) + __field(u16, report_descriptor_length) + __field(u16, version_id) + ), + + TP_fast_assign( + __entry->bus_num =3D shid->spi->controller->bus_num; + __entry->chip_select =3D shid->spi->chip_select; + __entry->power_state =3D shid->power_state; + __entry->flags =3D shid->flags; + + __entry->vendor_id =3D shid->desc.vendor_id; + __entry->product_id =3D shid->desc.product_id; + __entry->max_input_length =3D shid->desc.max_input_length; + __entry->max_output_length =3D shid->desc.max_output_length; + __entry->hid_version =3D shid->desc.hid_version; + __entry->report_descriptor_length =3D + shid->desc.report_descriptor_length; + __entry->version_id =3D shid->desc.version_id; + ), + + TP_printk("spi%d.%d: (%04x:%04x v%d) HID v%d.%d state p:%d len i:%d o:%d = r:%d flags 0x%08x", + __entry->bus_num, __entry->chip_select, + __entry->vendor_id, __entry->product_id, __entry->version_id, + __entry->hid_version >> 8, __entry->hid_version & 0xff, + __entry->power_state, __entry->max_input_length, + __entry->max_output_length, __entry->report_descriptor_length, + __entry->flags) +); + +DEFINE_EVENT(spi_hid, spi_hid_header_transfer, TP_PROTO(struct spi_hid *sh= id), + TP_ARGS(shid)); + +DEFINE_EVENT(spi_hid, spi_hid_process_input_report, + TP_PROTO(struct spi_hid *shid), TP_ARGS(shid)); + +DEFINE_EVENT(spi_hid, spi_hid_input_report_handler, + TP_PROTO(struct spi_hid *shid), TP_ARGS(shid)); + +DEFINE_EVENT(spi_hid, spi_hid_reset_response, TP_PROTO(struct spi_hid *shi= d), + TP_ARGS(shid)); + +DEFINE_EVENT(spi_hid, spi_hid_create_device, TP_PROTO(struct spi_hid *shid= ), + TP_ARGS(shid)); + +DEFINE_EVENT(spi_hid, spi_hid_refresh_device, TP_PROTO(struct spi_hid *shi= d), + TP_ARGS(shid)); + +DEFINE_EVENT(spi_hid, spi_hid_response_handler, TP_PROTO(struct spi_hid *s= hid), + TP_ARGS(shid)); + +DEFINE_EVENT(spi_hid, spi_hid_error_handler, TP_PROTO(struct spi_hid *shid= ), + TP_ARGS(shid)); + +#endif /* _SPI_HID_TRACE_H */ + +#include --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.181]) (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 C285B3C73EA for ; Tue, 24 Mar 2026 06:40:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334419; cv=none; b=YwWECAF2V63cemJ4ou5NYnlBn3uuDf2Ib1qpFKfjgYYLKDbwuHSHffb6GzSHxAgGLjd+JgVuMuU9envLFx5yvO5bfKJYCtQ0uYUicRSnDza8pNZqBFBsIgUnZA2H3Nlyl9pdam4KZ+Y2q1CFCqXmHXJpTlP3qSnL6N8MUQ27Bog= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334419; c=relaxed/simple; bh=NZNDQBy4FM8hWUA/HtonC5/Pro41zcPZPKJ7wit4sjY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=EhyCKz/Jt1FmO6JkDM6mCOKARXbd4GuFIHhzv1vG/+IuDbZYmsAlhgtsUGDbLwxQc+b8EMRmT9UVtlEYMa2WbDM9bMubX6b+BljLispHmJxzH1Z7HC1gts+pIdSKG9AveS7OQJQCT7Y1S3gAqrzgEQcY2gjhxmME46GlMmkg9rI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=Bgcf2Pz5; arc=none smtp.client-ip=209.85.214.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="Bgcf2Pz5" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-2aecc6b0861so28893185ad.2 for ; Mon, 23 Mar 2026 23:40:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334409; x=1774939209; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=vJShaFyH7SsMV5XhFzz5d1slssB1tZVh818Gmau3rAM=; b=Bgcf2Pz5HRJDw59vgrrzHrBRUnaYEZdsILIneHFNp6kJX/VvcFFzGrJP/YtwRjFmcx 0TENCGTjhfZW5AIgEK1DHhBR8KXTY44eLPR1PHJVT1QuBzAeEHSmk45AO+TPoEIClQvr lhlSFnQa1MQDbmuSJEDFnzBoOcwJEThkk3470= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334409; x=1774939209; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=vJShaFyH7SsMV5XhFzz5d1slssB1tZVh818Gmau3rAM=; b=XovCHkHzSEc+TINg9kauhBBqeW/rUrBSNytdxwBuqKlJSXd+FW/xUG2Xf5VFhU5nMi FqD1NV13ZAS5kWEg1KuBGFGGBW4i2LNBghgdoSKccAXJPBt9OhAnh+KqZvnlnlpa8b4t sNz6P58sbtsKAOT/Bl/UCA2hTw4a1yGiDNHx4gvoDq2swTmkPYnma++/JW0mCTL1/hrW uodlFC+BprXZYuYEQCC3VlRJj1ir/zW0xtxtEk4+OtmLUx/HvntCWv6mQtrNZttONOMQ d/+gTLHXCcqRfNou8BfCV6dbF4ePODlc6NFI018BEaLAb8oYQ8fMW6oBxvyG+Zk+3n+D pXSA== X-Forwarded-Encrypted: i=1; AJvYcCXRNRW1GJYjI7YLqrgyCp69Vbwv8OJATMP0TwOjTv+z+HELeDpAHfJQY5/hDc4LGDo5b3PD0hWrM6Ew3GU=@vger.kernel.org X-Gm-Message-State: AOJu0YxcSIt1Ac0zvQCH7PyqM9sgsp8FDComUfZHDPs+8QgAtdqRfjpQ hcXLgaKx+GUaxvyMMFhh5o2AHZdufWhPFoSkjLsPz52ZOLfmOFepaW0KeFFDHOStcw== X-Gm-Gg: ATEYQzxEuaZsNKdvhKmHCUAF0Nn20R2USPHVP6DMZk9Ugv8bkFwr8+sgeHrdbWOKpq2 lDRcpba572GAum/A6xOcKvuuk0iPuZpVT1eNOKg/TITBYYUvGLb1Soc3TUPiguDuLBzO0fLp2Y6 kv01E6wIa6TJM2xPODqVhP0+NvQMuL4BGMXSmo0JptG2XazUR7K42FrICa0SBog24EnOLTbgQ3P S7mzRptYP/Szhc0Yhoi/id9iOem8zeGwKGeQyyZGTNePPgoPY6gVWIbMvHJKNqLx5St611RKA8l ojmSBQvagl3bqwCAfVJksvOnPQndY7k1SDSJq02wdVugURhwJUZqlo9yABlzddi0YwvuNueg4EJ BFkMtufOolUU0f9FbFHpUKdXCmHvidaiT7MiTyGeIXBrrB9MzM8gD0VSJhkYiguzqDua+1O/qRt 0tarzlbgjQuA+MlAlV7Oww7C4+9c1pixKsX4tJ+wFQl0/HQ1lqdhJGXRUJ6FvOjSClbo79HHak5 hyL9nFfZUjV1M4dOo1p94zZa6AJVfKV6w== X-Received: by 2002:a17:903:22cf:b0:2ae:6579:4795 with SMTP id d9443c01a7336-2b0827798a8mr141250635ad.21.1774334408611; Mon, 23 Mar 2026 23:40:08 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:07 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:40 +0000 Subject: [PATCH v2 07/11] HID: spi_hid: add ACPI support for SPI over HID Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-7-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Angela Czubak X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=12343; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=7/hy9ximZ7baaszOX2J4hT4+hpDPEMexrHHJRCl/8fM=; b=S56sUP2ilzWOwMbulF4q5ABvYE86ryv7K/btWZUCe7paWeb4aPAUSFW2bIeDEV4dJyqAUFqMX aVIpKxl4vh3AmmFUoKPoQ6n9Z7FFphGfaQHe5LoNZtI16JAyOQN7xkd X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= From: Angela Czubak Detect SPI HID devices described in ACPI. Signed-off-by: Angela Czubak Reviewed-by: Dmitry Torokhov Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/Kconfig | 15 +++ drivers/hid/spi-hid/Makefile | 1 + drivers/hid/spi-hid/spi-hid-acpi.c | 253 +++++++++++++++++++++++++++++++++= ++++ drivers/hid/spi-hid/spi-hid-core.c | 28 +--- drivers/hid/spi-hid/spi-hid.h | 45 +++++++ 5 files changed, 317 insertions(+), 25 deletions(-) diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig index 836fdefe8345..114b1e00da39 100644 --- a/drivers/hid/spi-hid/Kconfig +++ b/drivers/hid/spi-hid/Kconfig @@ -10,6 +10,21 @@ menuconfig SPI_HID =20 if SPI_HID =20 +config SPI_HID_ACPI + tristate "HID over SPI transport layer ACPI driver" + depends on ACPI + select SPI_HID_CORE + help + Say Y here if you use a keyboard, a touchpad, a touchscreen, or any + other HID based devices which are connected to your computer via SPI. + This driver supports ACPI-based systems. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called spi-hid-acpi. It will also build/depend on the + module spi-hid. + config SPI_HID_CORE tristate endif diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile index 92e24cddbfc2..753c7b7a7844 100644 --- a/drivers/hid/spi-hid/Makefile +++ b/drivers/hid/spi-hid/Makefile @@ -7,3 +7,4 @@ =20 obj-$(CONFIG_SPI_HID_CORE) +=3D spi-hid.o spi-hid-objs =3D spi-hid-core.o +obj-$(CONFIG_SPI_HID_ACPI) +=3D spi-hid-acpi.o diff --git a/drivers/hid/spi-hid/spi-hid-acpi.c b/drivers/hid/spi-hid/spi-h= id-acpi.c new file mode 100644 index 000000000000..298e3ba44d8a --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-acpi.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HID over SPI protocol, ACPI related code + * + * Copyright (c) 2021 Microsoft Corporation + * Copyright (c) 2026 Google LLC + * + * This code was forked out of the HID over SPI core code, which is partia= lly + * based on "HID over I2C protocol implementation: + * + * Copyright (c) 2012 Benjamin Tissoires + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * Copyright (c) 2012 Red Hat, Inc + * + * which in turn is partially based on "USB HID support for Linux": + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2,= Inc + * Copyright (c) 2007-2008 Oliver Neukum + * Copyright (c) 2006-2010 Jiri Kosina + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "spi-hid.h" + +/* Config structure is filled with data from ACPI */ +struct spi_hid_acpi_config { + struct spihid_ops ops; + + struct spi_hid_conf property_conf; + u32 post_power_on_delay_ms; + u32 minimal_reset_delay_ms; + struct acpi_device *adev; +}; + +/* HID SPI Device: 6e2ac436-0fcf41af-a265-b32a220dcfab */ +static guid_t spi_hid_guid =3D + GUID_INIT(0x6E2AC436, 0x0FCF, 0x41AF, + 0xA2, 0x65, 0xB3, 0x2A, 0x22, 0x0D, 0xCF, 0xAB); + +static int spi_hid_acpi_populate_config(struct spi_hid_acpi_config *conf, + struct acpi_device *adev) +{ + acpi_handle handle =3D acpi_device_handle(adev); + union acpi_object *obj; + + conf->adev =3D adev; + + /* Revision 3 for HID over SPI V1, see specification. */ + obj =3D acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 1, NULL, + ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call to get HID input report header address failed"); + return -ENODEV; + } + conf->property_conf.input_report_header_address =3D obj->integer.value; + ACPI_FREE(obj); + + obj =3D acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 2, NULL, + ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call to get HID input report body address failed"); + return -ENODEV; + } + conf->property_conf.input_report_body_address =3D obj->integer.value; + ACPI_FREE(obj); + + obj =3D acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 3, NULL, + ACPI_TYPE_INTEGER); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call to get HID output report header address failed"); + return -ENODEV; + } + conf->property_conf.output_report_address =3D obj->integer.value; + ACPI_FREE(obj); + + obj =3D acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 4, NULL, + ACPI_TYPE_BUFFER); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call to get HID read opcode failed"); + return -ENODEV; + } + if (obj->buffer.length =3D=3D 1) { + conf->property_conf.read_opcode =3D obj->buffer.pointer[0]; + } else { + acpi_handle_err(handle, + "Error _DSM call to get HID read opcode, too long buffer"); + ACPI_FREE(obj); + return -ENODEV; + } + ACPI_FREE(obj); + + obj =3D acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 5, NULL, + ACPI_TYPE_BUFFER); + if (!obj) { + acpi_handle_err(handle, + "Error _DSM call to get HID write opcode failed"); + return -ENODEV; + } + if (obj->buffer.length =3D=3D 1) { + conf->property_conf.write_opcode =3D obj->buffer.pointer[0]; + } else { + acpi_handle_err(handle, + "Error _DSM call to get HID write opcode, too long buffer"); + ACPI_FREE(obj); + return -ENODEV; + } + ACPI_FREE(obj); + + /* Value not provided in ACPI,*/ + conf->post_power_on_delay_ms =3D 5; + conf->minimal_reset_delay_ms =3D 150; + + if (!acpi_has_method(handle, "_RST")) { + acpi_handle_err(handle, "No reset method for acpi handle"); + return -EINVAL; + } + + /* FIXME: not reading hid-over-spi-flags, multi-SPI not supported */ + + return 0; +} + +static int spi_hid_acpi_power_none(struct spihid_ops *ops) +{ + return 0; +} + +static int spi_hid_acpi_power_down(struct spihid_ops *ops) +{ + struct spi_hid_acpi_config *conf =3D container_of(ops, + struct spi_hid_acpi_config, + ops); + + return acpi_device_set_power(conf->adev, ACPI_STATE_D3); +} + +static int spi_hid_acpi_power_up(struct spihid_ops *ops) +{ + struct spi_hid_acpi_config *conf =3D container_of(ops, + struct spi_hid_acpi_config, + ops); + int error; + + error =3D acpi_device_set_power(conf->adev, ACPI_STATE_D0); + if (error) { + dev_err(&conf->adev->dev, "Error could not power up ACPI device: %d.", e= rror); + return error; + } + + if (conf->post_power_on_delay_ms) + msleep(conf->post_power_on_delay_ms); + + return 0; +} + +static int spi_hid_acpi_assert_reset(struct spihid_ops *ops) +{ + return 0; +} + +static int spi_hid_acpi_deassert_reset(struct spihid_ops *ops) +{ + struct spi_hid_acpi_config *conf =3D container_of(ops, + struct spi_hid_acpi_config, + ops); + + return device_reset(&conf->adev->dev); +} + +static void spi_hid_acpi_sleep_minimal_reset_delay(struct spihid_ops *ops) +{ + struct spi_hid_acpi_config *conf =3D container_of(ops, + struct spi_hid_acpi_config, + ops); + fsleep(1000 * conf->minimal_reset_delay_ms); +} + +static int spi_hid_acpi_probe(struct spi_device *spi) +{ + struct device *dev =3D &spi->dev; + struct acpi_device *adev; + struct spi_hid_acpi_config *config; + int error; + + adev =3D ACPI_COMPANION(dev); + if (!adev) { + dev_err(dev, "Error could not get ACPI device."); + return -ENODEV; + } + + config =3D devm_kzalloc(dev, sizeof(struct spi_hid_acpi_config), + GFP_KERNEL); + if (!config) + return -ENOMEM; + + if (acpi_device_power_manageable(adev)) { + config->ops.power_up =3D spi_hid_acpi_power_up; + config->ops.power_down =3D spi_hid_acpi_power_down; + } else { + config->ops.power_up =3D spi_hid_acpi_power_none; + config->ops.power_down =3D spi_hid_acpi_power_none; + } + config->ops.assert_reset =3D spi_hid_acpi_assert_reset; + config->ops.deassert_reset =3D spi_hid_acpi_deassert_reset; + config->ops.sleep_minimal_reset_delay =3D + spi_hid_acpi_sleep_minimal_reset_delay; + + error =3D spi_hid_acpi_populate_config(config, adev); + if (error) { + dev_err(dev, "%s: unable to populate config data.", __func__); + return error; + } + + return spi_hid_core_probe(spi, &config->ops, &config->property_conf); +} + +static const struct acpi_device_id spi_hid_acpi_match[] =3D { + { "ACPI0C51", 0 }, + { "PNP0C51", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, spi_hid_acpi_match); + +static struct spi_driver spi_hid_acpi_driver =3D { + .driver =3D { + .name =3D "spi_hid_acpi", + .owner =3D THIS_MODULE, + .acpi_match_table =3D spi_hid_acpi_match, + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + .dev_groups =3D spi_hid_groups, + }, + .probe =3D spi_hid_acpi_probe, + .remove =3D spi_hid_core_remove, +}; + +module_spi_driver(spi_hid_acpi_driver); + +MODULE_DESCRIPTION("HID over SPI ACPI transport driver"); +MODULE_AUTHOR("Angela Czubak "); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index 3105884b656e..5411425710e2 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -43,6 +43,9 @@ #include #include =20 +#include "spi-hid.h" +#include "spi-hid-core.h" + /* Protocol constants */ #define SPI_HID_READ_APPROVAL_CONSTANT 0xff #define SPI_HID_INPUT_HEADER_SYNC_BYTE 0x5a @@ -105,31 +108,6 @@ struct spi_hid_output_report { u8 *content; }; =20 -/* struct spi_hid_conf - Conf provided to the core */ -struct spi_hid_conf { - u32 input_report_header_address; - u32 input_report_body_address; - u32 output_report_address; - u8 read_opcode; - u8 write_opcode; -}; - -/** - * struct spihid_ops - Ops provided to the core - * @power_up: do sequencing to power up the device - * @power_down: do sequencing to power down the device - * @assert_reset: do sequencing to assert the reset line - * @deassert_reset: do sequencing to deassert the reset line - * @sleep_minimal_reset_delay: minimal sleep delay during reset - */ -struct spihid_ops { - int (*power_up)(struct spihid_ops *ops); - int (*power_down)(struct spihid_ops *ops); - int (*assert_reset)(struct spihid_ops *ops); - int (*deassert_reset)(struct spihid_ops *ops); - void (*sleep_minimal_reset_delay)(struct spihid_ops *ops); -}; - static struct hid_ll_driver spi_hid_ll_driver; =20 static void spi_hid_populate_read_approvals(const struct spi_hid_conf *con= f, diff --git a/drivers/hid/spi-hid/spi-hid.h b/drivers/hid/spi-hid/spi-hid.h new file mode 100644 index 000000000000..f5a5f4d54beb --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Microsoft Corporation + * Copyright (c) 2026 Google LLC + */ + +#ifndef SPI_HID_H +#define SPI_HID_H + +#include +#include + +/* struct spi_hid_conf - Conf provided to the core */ +struct spi_hid_conf { + u32 input_report_header_address; + u32 input_report_body_address; + u32 output_report_address; + u8 read_opcode; + u8 write_opcode; +}; + +/** + * struct spihid_ops - Ops provided to the core + * @power_up: do sequencing to power up the device + * @power_down: do sequencing to power down the device + * @assert_reset: do sequencing to assert the reset line + * @deassert_reset: do sequencing to deassert the reset line + * @sleep_minimal_reset_delay: minimal sleep delay during reset + */ +struct spihid_ops { + int (*power_up)(struct spihid_ops *ops); + int (*power_down)(struct spihid_ops *ops); + int (*assert_reset)(struct spihid_ops *ops); + int (*deassert_reset)(struct spihid_ops *ops); + void (*sleep_minimal_reset_delay)(struct spihid_ops *ops); +}; + +int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops, + struct spi_hid_conf *conf); + +void spi_hid_core_remove(struct spi_device *spi); + +extern const struct attribute_group *spi_hid_groups[]; + +#endif /* SPI_HID_H */ --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f177.google.com (mail-pl1-f177.google.com [209.85.214.177]) (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 AFA553C279B for ; Tue, 24 Mar 2026 06:40:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334421; cv=none; b=PvX80VtLNKljYUo7+ze2iw/4oLcRoxCeCelY9jvyVSBEsLsA4g6LK+WBtJQvIaOf9hmnsYzwGcIQpDpsoe8S5WKI5uh++8GowkEPW6wGV70rVOT1MFBnx2pfJIFq3/0mmztOiW4p0ki7fzAfgJA4c80wpo1R9KeNS1n50yi5rs8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334421; c=relaxed/simple; bh=1LdXJ5jnOfI9O5hQ73wJ1dSLwCSR4SGiOqmQhpvEUSc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=j70UIWDnbY17wt1pKI4bCYBQOoDXSeF10Qfla4ILEjPv1KRLGBmJaYaBiw0Cr3F+P3522R+oktzxUVs0yMxYV+zV8cHWKQUin7O8et09t0ttaazeFkI1pvKlzs0Fjx1+bgVA0KYq73S2NMlouALvbJ0svO2Vnrcxwks8CXvIo8Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=UUYevHvz; arc=none smtp.client-ip=209.85.214.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="UUYevHvz" Received: by mail-pl1-f177.google.com with SMTP id d9443c01a7336-2aecefc7503so37343355ad.1 for ; Mon, 23 Mar 2026 23:40:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334410; x=1774939210; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=eQShHEmZaDqkpmvNKc42ulb0eSRkDXykwi+h4iXesqs=; b=UUYevHvzG4uqcOz4L5v6K0xOLUjrEq/AeuGx39jgmAbzRh6DZqJu2vYLou1pxdnHAo mQhkGOEBPFgvRqK9rdgzIAjqmMnBwXRqPNVuizQM9BNsIKtBwUXoDzkIrJm6TX0Q79U1 9DGGZywKHw/kf3BLcSr0JVXJ/TcH++Mg/wxAY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334410; x=1774939210; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=eQShHEmZaDqkpmvNKc42ulb0eSRkDXykwi+h4iXesqs=; b=OwlpQWBi3Kcf3B6MqHy2LaLpCGCCtdCLQRtCPYk91DOYsTn/FYnCz3S8U3F2/boVtI Znv5MBPdpk/EJdhOSczPDVbeZP3HX3eQ9Dm8Fo1T6gi3J5Xd0slo7kfsL7agrNI9BgR9 vh6CGYDWkwehxh0sHBnSoN7Z9duwT2SaLE/OUF8lKLCn0ziHrUGd3z5TExiVR6dMQ1SM Srt8/ps45f6dEZQBOm6VbImIaVidcPoiJYTvmS609xjFC/CXXLAZJDuJPepovi2CxIMp QkvChxVgtHkGym8h/nKVga6hMfdw6ES1KCMBHBebyYJwPdf0LOJlzBdB1Zv9eIWfftAc 5utw== X-Forwarded-Encrypted: i=1; AJvYcCUQbhDid9fp9ilZPlbnO3CgCj5vjlV2bRyWYyjTDIz5IAHdz2riIeh8bhikatsnBNHqTcPq4sP4qH+y65A=@vger.kernel.org X-Gm-Message-State: AOJu0YxX3c86E8DTrfT38Pm+J3VnkrrQ8A2iJq1/XNooUXDEm5pUPWCL +8wyb45jrudVzB9Bxpc0Ovq+sMzg4+D6Tb+OJtJYbN43RVSl746c+c0Ly9zikNoXpg== X-Gm-Gg: ATEYQzxk4DyRkdizXlFDTE8vjpzQAMVPsFmCWGmWkCdu4RmShgYme8FGsAocaVEdU39 InJ0zbBhJpExkbbLBQ66YJUWWcg+mKm/dZNPZRsAWw+MyBjbh/XPChiVqaS3vOA7Gniy+8GpHx8 IMcYLgXkgDu/xk5jxne1UQg5LEcjB+zJvugT/yE7GjSckcuqhE8BGE5jKEuP7C1DYIkDa4u6qtR VPwFMwINy65cFjuqwi5aqkTjEFBkf6yJy6QFVwHZKnn5TkdiOV56pHg6yJ+7ptLmyZc6tmzqwqj r5reOuX8zeo0mGTZFKf0nQA6794BftLqgiR3oKnZdoxawlCH9IAZuSHl0uAYfkiy9IuPeK6+HUf R6Q0Sx9p3kE2vag7Evfl87tG4RSDf1QIvrRgM/KQKgILLo5yIrOuJosDVH331DvyAYlh3mI4hwq XzkvWTubkwfnyfY7wH2t2CdLpajwemmSNlnZnAmVUT0AHq/qmpf76lcvzmyQ2hkLpICBcD/14ZZ tsPeDC1aXkmmzpIbLmX1ALPYCMrtoPigQ== X-Received: by 2002:a17:903:288:b0:2b0:4579:ae6 with SMTP id d9443c01a7336-2b08279f65cmr136502225ad.38.1774334409749; Mon, 23 Mar 2026 23:40:09 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:08 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:41 +0000 Subject: [PATCH v2 08/11] HID: spi_hid: add device tree support for SPI over HID Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-8-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Jarrett Schultz , Dmitry Antipov X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=8620; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=mkmXMpXTmrFUxfWJvm7MB0bvyfpEyxmpvE8pXakk9Dg=; b=EY1PsvT152HiRsQwPnxFaTeiDOxDUJPtg2Vd3VONn6Cp/IP7jBxSczW243Y7uWFrnT585x4/u EMUh75vdFzRB8r7sDt1ixzYVE6Tp3lcRgyP6vl4WiqjfftzAUXdntNg X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= From: Jarrett Schultz Signed-off-by: Dmitry Antipov Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/Kconfig | 15 +++ drivers/hid/spi-hid/Makefile | 1 + drivers/hid/spi-hid/spi-hid-of.c | 243 +++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 259 insertions(+) diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig index 114b1e00da39..76a2cd587a3e 100644 --- a/drivers/hid/spi-hid/Kconfig +++ b/drivers/hid/spi-hid/Kconfig @@ -25,6 +25,21 @@ config SPI_HID_ACPI will be called spi-hid-acpi. It will also build/depend on the module spi-hid. =20 +config SPI_HID_OF + tristate "HID over SPI transport layer Open Firmware driver" + depends on OF + select SPI_HID_CORE + help + Say Y here if you use a keyboard, a touchpad, a touchscreen, or any + other HID based devices which are connected to your computer via SPI. + This driver supports Open Firmware (Device Tree)-based systems. + + If unsure, say N. + + This support is also available as a module. If so, the module + will be called spi-hid-of. It will also build/depend on the + module spi-hid. + config SPI_HID_CORE tristate endif diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile index 753c7b7a7844..fe627fd378e3 100644 --- a/drivers/hid/spi-hid/Makefile +++ b/drivers/hid/spi-hid/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_SPI_HID_CORE) +=3D spi-hid.o spi-hid-objs =3D spi-hid-core.o obj-$(CONFIG_SPI_HID_ACPI) +=3D spi-hid-acpi.o +obj-$(CONFIG_SPI_HID_OF) +=3D spi-hid-of.o diff --git a/drivers/hid/spi-hid/spi-hid-of.c b/drivers/hid/spi-hid/spi-hid= -of.c new file mode 100644 index 000000000000..651456b6906d --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-of.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HID over SPI protocol, Open Firmware related code + * + * Copyright (c) 2021 Microsoft Corporation + * + * This code was forked out of the HID over SPI core code, which is partia= lly + * based on "HID over I2C protocol implementation: + * + * Copyright (c) 2012 Benjamin Tissoires + * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France + * Copyright (c) 2012 Red Hat, Inc + * + * which in turn is partially based on "USB HID support for Linux": + * + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2,= Inc + * Copyright (c) 2007-2008 Oliver Neukum + * Copyright (c) 2006-2010 Jiri Kosina + */ + +#include +#include +#include +#include +#include + +#include "spi-hid.h" + +struct spi_hid_timing_data { + u32 post_power_on_delay_ms; + u32 minimal_reset_delay_ms; +}; + +/* Config structure is filled with data from Device Tree */ +struct spi_hid_of_config { + struct spihid_ops ops; + + struct spi_hid_conf property_conf; + const struct spi_hid_timing_data *timing_data; + + struct gpio_desc *reset_gpio; + struct regulator *supply; + bool supply_enabled; + u16 hid_over_spi_flags; +}; + +static int spi_hid_of_populate_config(struct spi_hid_of_config *conf, + struct device *dev) +{ + int error; + u32 val; + + error =3D device_property_read_u32(dev, "input-report-header-address", + &val); + if (error) { + dev_err(dev, "Input report header address not provided."); + return -ENODEV; + } + conf->property_conf.input_report_header_address =3D val; + + error =3D device_property_read_u32(dev, "input-report-body-address", &val= ); + if (error) { + dev_err(dev, "Input report body address not provided."); + return -ENODEV; + } + conf->property_conf.input_report_body_address =3D val; + + error =3D device_property_read_u32(dev, "output-report-address", &val); + if (error) { + dev_err(dev, "Output report address not provided."); + return -ENODEV; + } + conf->property_conf.output_report_address =3D val; + + error =3D device_property_read_u32(dev, "read-opcode", &val); + if (error) { + dev_err(dev, "Read opcode not provided."); + return -ENODEV; + } + conf->property_conf.read_opcode =3D val; + + error =3D device_property_read_u32(dev, "write-opcode", &val); + if (error) { + dev_err(dev, "Write opcode not provided."); + return -ENODEV; + } + conf->property_conf.write_opcode =3D val; + + conf->supply =3D devm_regulator_get(dev, "vdd"); + if (IS_ERR(conf->supply)) { + if (PTR_ERR(conf->supply) !=3D -EPROBE_DEFER) + dev_err(dev, "Failed to get regulator: %ld.", + PTR_ERR(conf->supply)); + return PTR_ERR(conf->supply); + } + conf->supply_enabled =3D false; + + conf->reset_gpio =3D devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(conf->reset_gpio)) { + dev_err(dev, "%s: error getting reset GPIO.", __func__); + return PTR_ERR(conf->reset_gpio); + } + + return 0; +} + +static int spi_hid_of_power_down(struct spihid_ops *ops) +{ + struct spi_hid_of_config *conf =3D container_of(ops, + struct spi_hid_of_config, + ops); + int error; + + if (!conf->supply_enabled) + return 0; + + error =3D regulator_disable(conf->supply); + if (error =3D=3D 0) + conf->supply_enabled =3D false; + + return error; +} + +static int spi_hid_of_power_up(struct spihid_ops *ops) +{ + struct spi_hid_of_config *conf =3D container_of(ops, + struct spi_hid_of_config, + ops); + int error; + + if (conf->supply_enabled) + return 0; + + error =3D regulator_enable(conf->supply); + + if (error =3D=3D 0) { + conf->supply_enabled =3D true; + fsleep(1000 * conf->timing_data->post_power_on_delay_ms); + } + + return error; +} + +static int spi_hid_of_assert_reset(struct spihid_ops *ops) +{ + struct spi_hid_of_config *conf =3D container_of(ops, + struct spi_hid_of_config, + ops); + + gpiod_set_value(conf->reset_gpio, 1); + return 0; +} + +static int spi_hid_of_deassert_reset(struct spihid_ops *ops) +{ + struct spi_hid_of_config *conf =3D container_of(ops, + struct spi_hid_of_config, + ops); + + gpiod_set_value(conf->reset_gpio, 0); + return 0; +} + +static void spi_hid_of_sleep_minimal_reset_delay(struct spihid_ops *ops) +{ + struct spi_hid_of_config *conf =3D container_of(ops, + struct spi_hid_of_config, + ops); + fsleep(1000 * conf->timing_data->minimal_reset_delay_ms); +} + +static int spi_hid_of_probe(struct spi_device *spi) +{ + struct device *dev =3D &spi->dev; + struct spi_hid_of_config *config; + int error; + + config =3D devm_kzalloc(dev, sizeof(struct spi_hid_of_config), + GFP_KERNEL); + if (!config) + return -ENOMEM; + + config->ops.power_up =3D spi_hid_of_power_up; + config->ops.power_down =3D spi_hid_of_power_down; + config->ops.assert_reset =3D spi_hid_of_assert_reset; + config->ops.deassert_reset =3D spi_hid_of_deassert_reset; + config->ops.sleep_minimal_reset_delay =3D + spi_hid_of_sleep_minimal_reset_delay; + + config->timing_data =3D device_get_match_data(dev); + /* + * FIXME: hid_over_spi_flags could be retrieved from spi mode. + * It is always 0 because multi-SPI not supported. + */ + config->hid_over_spi_flags =3D 0; + + error =3D spi_hid_of_populate_config(config, dev); + if (error) { + dev_err(dev, "%s: unable to populate config data.", __func__); + return error; + } + + return spi_hid_core_probe(spi, &config->ops, &config->property_conf); +} + +const struct spi_hid_timing_data timing_data =3D { + .post_power_on_delay_ms =3D 10, + .minimal_reset_delay_ms =3D 100, +}; + +const struct of_device_id spi_hid_of_match[] =3D { + { .compatible =3D "hid-over-spi", .data =3D &timing_data }, + {} +}; +MODULE_DEVICE_TABLE(of, spi_hid_of_match); + +static const struct spi_device_id spi_hid_of_id_table[] =3D { + { "hid", 0 }, + { "hid-over-spi", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, spi_hid_of_id_table); + +static struct spi_driver spi_hid_of_driver =3D { + .driver =3D { + .name =3D "spi_hid_of", + .owner =3D THIS_MODULE, + .of_match_table =3D spi_hid_of_match, + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + .dev_groups =3D spi_hid_groups, + }, + .probe =3D spi_hid_of_probe, + .remove =3D spi_hid_core_remove, + .id_table =3D spi_hid_of_id_table, +}; + +module_spi_driver(spi_hid_of_driver); + +MODULE_DESCRIPTION("HID over SPI OF transport driver"); +MODULE_AUTHOR("Dmitry Antipov "); +MODULE_LICENSE("GPL"); --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f177.google.com (mail-pl1-f177.google.com [209.85.214.177]) (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 021D13C4574 for ; Tue, 24 Mar 2026 06:40:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334421; cv=none; b=cwY0SdtLEGZGv0B+35/PZiakDddCCCvaol9v4MjxmYMBBezuCal6QmPqTOB/LSbeIKLf5E3HFa/3dHPqttaihucSQ3mQhJyLCpt+8Ol2AMul/1vG5T0iU/F83as35hVwXqNenvp3XElkkbX5gIJ+XoBCIzyt7IVJOqeM5KvEk7o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334421; c=relaxed/simple; bh=I0x2CYlsya/0TkxpZg4isJbbaEKJvgNnG/ZcBXcqYso=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=dDNtB6nHl1gGjrDLdtBW+yhp0MBpSnSnRk7HkvjEppafU6C9gXZObvJ7q1xyySP0kia2Am4wyyjfB3/pMS7w3IzlFKd8zPsqDRd5H1jh2ZPTkjACyaOhjIJ01wpCaWJDpWKTytq6WfoiYMBzcICHBuM8m3mav5uWd67PLRuR6bU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=JPe6YGcf; arc=none smtp.client-ip=209.85.214.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="JPe6YGcf" Received: by mail-pl1-f177.google.com with SMTP id d9443c01a7336-2a8fba3f769so23101185ad.2 for ; Mon, 23 Mar 2026 23:40:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334411; x=1774939211; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=vmfN5FmsExdbcn4DHUdyn65cCMHIcyMfjFNYjbqU8ZE=; b=JPe6YGcfmma5p1cUw0WFcKwlKmvj8pYDCH9sodtY7xVmMA3Oqh8rBuk6Rfoe9cvwCC LAxHmZapOrOX67nhBrj77UOb/QWaGDsjjiBsoH3pmgsrP/0bp++l+eB2E+5DjmuJkvPl Tv9KT33Fo1kmQ+s9AHnqrONUAcQjAYRnKfT0A= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334411; x=1774939211; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=vmfN5FmsExdbcn4DHUdyn65cCMHIcyMfjFNYjbqU8ZE=; b=Bu/n2YnJB/8AXniupxkcdTncFK/n5lcMbmwG8OB6GReC/PKWOk47jre7P38fqL6uxM K0SOXasZNZY0MfeIcY+pbBLKuRkAZDQsneTfsHWwBgf55Dcx5A4R5/9c9VAmgeC7THw+ CYJ7z2elUno1Fdzfge1OdvdJ6ntnHyJlSEIJcEpRQlAImfeAZo8CZi+jZ79NLKFUbgy0 dk1TG0dDxhc+/vSuAWjhzimik+egQCbuJhFIgPnsep+cka9N1z/0LNdvIgwQ54EVSSFs PThgnkNES4XkzJs127PuLKKb2I3Qmdl9LOF0T4+Q+2TzoE3ls2SuE4AH2oGTHLmi7/wL QTFQ== X-Forwarded-Encrypted: i=1; AJvYcCXLi5mIjRxg7yTX2Jqg7r0ihHU79x92VZuhS+U7LkxDDKGKTZKttQ1K0AkAwDWYef16lljRIIPiGGuVMi4=@vger.kernel.org X-Gm-Message-State: AOJu0Yz4TOSs4uo751S3hY/1hXjkk+phhAKTmcddloI0lpTlN5e9tt0m Qyw9wjw/s+LCUEClkjwb8K42dlZ/N6PsrLf4E77/WirrLcAroK57GY2wv4odHlRW3g== X-Gm-Gg: ATEYQzzG4rB3aCCNDEzOSxycjvvbxLyoogZ2ONMOWRkzegotezgGAxjjDhjJjqGaZgR Ii2SGMvDL3bzxT1GYPMaCJgMZ67KiBUXN94P39TapR9CzoPRO3Z2G+HsE7ac5Hmd6E41eFa19sx uDwHEtfTFGyFDgzkt9B6CDBCI39k3ijAlYxsNPlqwWoVGJxW/U8XU7H4koKBrU6GEAGBE7EWPO2 FSpkxtFPnvTS2hzj9CqmR6gGs4N1KrJhQ9SHqBimKIYDC6k1Cp/lXCRfSzHE3OtDM7qnJFJKZXg G7YgO2RuO+ZLRe3lEw2Co388OUKmdCJVqiInM4XiS6teUEzuWDtDHkC/aZuCFqkHMyz2bebqvtw UQlskG8HtWXRJug8OiQMCjrUGKgHAknWhbK0FzQJgqhbMe361nFrU9x1uZxPFXC9WVSYZQC84uG nT5ecL62yOd2bTxKHDtEE5IHY7ciDoej0+OcsA0sCWZWVvdK9NOY3EW4CPAR3RejwCn8VvjHvXO gS3O9cA9aEUEtFRML3h8AtX/2c/B6a35/vTXUPHU+kd X-Received: by 2002:a17:902:f651:b0:2b0:4d7d:fbc8 with SMTP id d9443c01a7336-2b0825e0b04mr139394415ad.0.1774334410624; Mon, 23 Mar 2026 23:40:10 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:10 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:42 +0000 Subject: [PATCH v2 09/11] dt-bindings: input: Document hid-over-spi DT schema Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-9-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Dmitry Antipov , Jarrett Schultz X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=4683; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=I0x2CYlsya/0TkxpZg4isJbbaEKJvgNnG/ZcBXcqYso=; b=Wd4UHeJCa7yzoQMx8kpcrdTRh9ZPmUZ2TgcHdNkjZOoJrOu56ULT4r/WjLCmtbtGg/Lffo9HD mwgl9wmJhmvBIaFZfYBBF7eQ+TVVS8ocx+e4UQRW6+Wvcw0ow6HtA8N X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= Documentation describes the required and optional properties for implementing Device Tree for a Microsoft G6 Touch Digitizer that supports HID over SPI Protocol 1.0 specification. The properties are common to HID over SPI. Signed-off-by: Dmitry Antipov Signed-off-by: Jarrett Schultz Signed-off-by: Jingyuan Liang --- .../devicetree/bindings/input/hid-over-spi.yaml | 126 +++++++++++++++++= ++++ 1 file changed, 126 insertions(+) diff --git a/Documentation/devicetree/bindings/input/hid-over-spi.yaml b/Do= cumentation/devicetree/bindings/input/hid-over-spi.yaml new file mode 100644 index 000000000000..d1b0a2e26c32 --- /dev/null +++ b/Documentation/devicetree/bindings/input/hid-over-spi.yaml @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/hid-over-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HID over SPI Devices + +maintainers: + - Benjamin Tissoires + - Jiri Kosina + +description: |+ + HID over SPI provides support for various Human Interface Devices over t= he + SPI bus. These devices can be for example touchpads, keyboards, touch sc= reens + or sensors. + + The specification has been written by Microsoft and is currently availab= le + here: https://www.microsoft.com/en-us/download/details.aspx?id=3D103325 + + If this binding is used, the kernel module spi-hid will handle the + communication with the device and the generic hid core layer will handle= the + protocol. + +allOf: + - $ref: /schemas/input/touchscreen/touchscreen.yaml# + +properties: + compatible: + oneOf: + - items: + - enum: + - microsoft,g6-touch-digitizer + - const: hid-over-spi + - description: Just "hid-over-spi" alone is allowed, but not recomme= nded. + const: hid-over-spi + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + maxItems: 1 + description: + GPIO specifier for the digitizer's reset pin (active low). The line = must + be flagged with GPIO_ACTIVE_LOW. + + vdd-supply: + description: + Regulator for the VDD supply voltage. + + input-report-header-address: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0xffffff + description: + A value to be included in the Read Approval packet, listing an addre= ss of + the input report header to be put on the SPI bus. This address has 24 + bits. + + input-report-body-address: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0xffffff + description: + A value to be included in the Read Approval packet, listing an addre= ss of + the input report body to be put on the SPI bus. This address has 24 = bits. + + output-report-address: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0xffffff + description: + A value to be included in the Output Report sent by the host, listin= g an + address where the output report on the SPI bus is to be written to. = This + address has 24 bits. + + read-opcode: + $ref: /schemas/types.yaml#/definitions/uint8 + description: + Value to be used in Read Approval packets. 1 byte. + + write-opcode: + $ref: /schemas/types.yaml#/definitions/uint8 + description: + Value to be used in Write Approval packets. 1 byte. + +required: + - compatible + - interrupts + - reset-gpios + - vdd-supply + - input-report-header-address + - input-report-body-address + - output-report-address + - read-opcode + - write-opcode + +additionalProperties: false + +examples: + - | + #include + #include + + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + + hid@0 { + compatible =3D "microsoft,g6-touch-digitizer", "hid-over-spi"; + reg =3D <0x0>; + interrupts-extended =3D <&gpio 42 IRQ_TYPE_EDGE_FALLING>; + reset-gpios =3D <&gpio 27 GPIO_ACTIVE_LOW>; + vdd-supply =3D <&pm8350c_l3>; + pinctrl-names =3D "default"; + pinctrl-0 =3D <&ts_d6_int_bias>; + input-report-header-address =3D <0x1000>; + input-report-body-address =3D <0x1004>; + output-report-address =3D <0x2000>; + read-opcode =3D /bits/ 8 <0x0b>; + write-opcode =3D /bits/ 8 <0x02>; + }; + }; --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pj1-f42.google.com (mail-pj1-f42.google.com [209.85.216.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 11C9B3C9441 for ; Tue, 24 Mar 2026 06:40:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334423; cv=none; b=cKxT/Faf1RieGv3tOGg7afTr/zYHhKGQVXlNJ+Iu2J2Rvs6RvLrlPx6rPKbJrSwN3AGTu0+UeCGCNsiV+6F0dhajJ1pDtr6isGlRG2eUo3Y2xgMtR3dJvIORvD4aFe+qTNyh8Jf0EhRPcDT/ON5qiGXCGS9kAda+DhT+tNv5ze4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334423; c=relaxed/simple; bh=0ShLgUfw8xYUqYg8g1hkipQJ7Pim/IpYSDnBkx4kC3k=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=IT7dd3zwglUPW09F3xdLv4CeegPL8foFQDqjvYfFuh9feLDKd98xHxV6dGBu7IUoJRHMdyaIuj5mx/qclcFU279jjzJMlLiy+BvEGADzjQSvGx9YakgcACc/rlikrUDLc2YZPct8w9Nq32UfJbZo/d4xXJNHp5jvy1a4cTNuUqw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=d1juY+0o; arc=none smtp.client-ip=209.85.216.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="d1juY+0o" Received: by mail-pj1-f42.google.com with SMTP id 98e67ed59e1d1-354bc7c2c46so2356381a91.0 for ; Mon, 23 Mar 2026 23:40:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334412; x=1774939212; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=5fOl7nSumxiQQ22tuWWzFLmSP0tNH8zNV33iVHAJAWQ=; b=d1juY+0o8+OUEQVSCm9VK4dW8vZJZPLvBj11NQI+TotHtpnMCtB+1PTNW+0dRaGkP0 38yGy/QsDXGHvdkYD6AkpIuXrwpaQSy03ljNxXCKWrmUut8H8GPmPpsFpRPCAPBj3FSz /XNCrXrjXHZ6qHqO0ilqownO71qtYrIDBxvZA= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334412; x=1774939212; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=5fOl7nSumxiQQ22tuWWzFLmSP0tNH8zNV33iVHAJAWQ=; b=ezaOgfeg7Vy9/USXcRqMcHaOA7tVfdwFFbsYMpNeGw8pO2c5OFM9S8NmqHA5htALvF KeqPu+Jd4KX8LZLmz1wGmbSkjMhf09nwcY7dZekQqywyVDa00aDjWUWM9PdmTlZ4Nk0S /tHV6X1dJvahbTcL+EyVRaYlPhD2rk2oWYAvnJxBOGiIjJap/rUJOVKBOApah2Sc/yri sc5dIDaI4aXwFcOZv8zejFq9oaftX98UANJ1E7cijr28xYy++ueJGIwHD/cWCCLn0qKj 6ANHTxMVw0tFNDIy3nW2yu27CmoeNhT6CSqZwJ5gA1H+7DCO6eiczOu5+4HI0NieTsV6 nhkw== X-Forwarded-Encrypted: i=1; AJvYcCVH56NrVFiSQIMx/WctWYB2VGD6HgEI5eU1H0Fg5d5bG8yMfehP8RE4GBl8y7vHr4EtGgHFt+wJ5sH7ejI=@vger.kernel.org X-Gm-Message-State: AOJu0Yyq6xpXEhoZB3xTzo+shLHoaMj094p/mRSSJva+AqEFkXehldM+ v4ffQ+K1APMPL0FVsheUGl2K2M/0i73jrW7rPMi/MpkUe8yNCAZMzlIOQyrgTK1P5g== X-Gm-Gg: ATEYQzwyph73f5yI7lWsEYNmrx+9ySjfEt2QjDnN8E2e+zkBVuFFYO2z2klekVD7iRM gKnAWLYLHeWuzZUXZrqWSoNKnRr9amjLBP4NWftVvXciL7z7wc1AorFab0RK0oasJSfGHeQkzKP xdPklqL11mFvzhhoVPnCEJoG9O+wamaam6lgQQUJk/AURcZC82C4hy0RXgQtPf8VXKiee2fA8yY /ZAVPDY/sWIxwpwSRPPq/QZVAz6u3fUpfzTXFWuxqpIYCr7Fv+/6cd2hv90ffAwP/unfbhm2AD5 OCDBGYFdvfSomYLOl3KXFFKWDBnvhAgECDqlvbg52O7bbtLpaX1mbrP2ZY9xI0gxL/gEmlwYw88 DB0uAh2xBDeh5yuZY6n3lrC2HASnODZBqPppL6NIdT05YZyHHmfkGitFlwfh1G3l6aSYo8eLJym R3WR1oghLWuCuK8vBqLws/vw/OWqu+QklTZnE5JcPdxHBW192B/pvVF2OSJHh3WiRwN8QMCFdSG VuUBUPVGGdnjyiwNn2nbV23DSgSNoB66Q== X-Received: by 2002:a17:902:cece:b0:2b0:7e4d:f43f with SMTP id d9443c01a7336-2b0827a71b9mr150520295ad.41.1774334411667; Mon, 23 Mar 2026 23:40:11 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:10 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:43 +0000 Subject: [PATCH v2 10/11] HID: spi-hid: add power management implementation Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-10-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=5451; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=0ShLgUfw8xYUqYg8g1hkipQJ7Pim/IpYSDnBkx4kC3k=; b=MVoQai+OxPMiMzvQ10d7yPTDsI9Ruok69BFUlI95JNH3FHzmcW4FRyfC9Jd938bfaOIx0p1W2 5d2Kpd5KP5SCCJOM2s19xquT4clf+fe5Nf18semlX1hsL+80Lob20vV X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= Implement HID over SPI driver power management callbacks. Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/spi-hid-acpi.c | 1 + drivers/hid/spi-hid/spi-hid-core.c | 107 +++++++++++++++++++++++++++++++++= ++++ drivers/hid/spi-hid/spi-hid-of.c | 1 + drivers/hid/spi-hid/spi-hid.h | 1 + 4 files changed, 110 insertions(+) diff --git a/drivers/hid/spi-hid/spi-hid-acpi.c b/drivers/hid/spi-hid/spi-h= id-acpi.c index 298e3ba44d8a..15cfc4e6cc2f 100644 --- a/drivers/hid/spi-hid/spi-hid-acpi.c +++ b/drivers/hid/spi-hid/spi-hid-acpi.c @@ -238,6 +238,7 @@ static struct spi_driver spi_hid_acpi_driver =3D { .driver =3D { .name =3D "spi_hid_acpi", .owner =3D THIS_MODULE, + .pm =3D &spi_hid_core_pm, .acpi_match_table =3D spi_hid_acpi_match, .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, .dev_groups =3D spi_hid_groups, diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index 5411425710e2..ac46e2c7c8aa 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include #include @@ -236,6 +238,81 @@ static const char *spi_hid_power_mode_string(enum hids= pi_power_state power_state } } =20 +static void spi_hid_suspend(struct spi_hid *shid) +{ + int error; + struct device *dev =3D &shid->spi->dev; + + guard(mutex)(&shid->power_lock); + if (shid->power_state =3D=3D HIDSPI_OFF) + return; + + if (shid->hid) { + error =3D hid_driver_suspend(shid->hid, PMSG_SUSPEND); + if (error) { + dev_err(dev, "%s failed to suspend hid driver: %d", + __func__, error); + return; + } + } + + disable_irq(shid->spi->irq); + + clear_bit(SPI_HID_READY, &shid->flags); + + if (!device_may_wakeup(dev)) { + set_bit(SPI_HID_RESET_PENDING, &shid->flags); + + shid->ops->assert_reset(shid->ops); + + error =3D shid->ops->power_down(shid->ops); + if (error) { + dev_err(dev, "%s: could not power down.", __func__); + shid->regulator_error_count++; + shid->regulator_last_error =3D error; + return; + } + + shid->power_state =3D HIDSPI_OFF; + } +} + +static void spi_hid_resume(struct spi_hid *shid) +{ + int error; + struct device *dev =3D &shid->spi->dev; + + guard(mutex)(&shid->power_lock); + if (shid->power_state =3D=3D HIDSPI_ON) + return; + + enable_irq(shid->spi->irq); + + if (!device_may_wakeup(dev)) { + shid->ops->assert_reset(shid->ops); + + shid->ops->sleep_minimal_reset_delay(shid->ops); + + error =3D shid->ops->power_up(shid->ops); + if (error) { + dev_err(dev, "%s: could not power up.", __func__); + shid->regulator_error_count++; + shid->regulator_last_error =3D error; + return; + } + shid->power_state =3D HIDSPI_ON; + + shid->ops->deassert_reset(shid->ops); + } + + if (shid->hid) { + error =3D hid_driver_reset_resume(shid->hid); + if (error) + dev_err(dev, "%s: failed to reset resume hid driver: %d.", + __func__, error); + } +} + static void spi_hid_stop_hid(struct spi_hid *shid) { struct hid_device *hid =3D shid->hid; @@ -1161,6 +1238,13 @@ int spi_hid_core_probe(struct spi_device *spi, struc= t spihid_ops *ops, dev_err(dev, "%s: unable to request threaded IRQ.", __func__); return error; } + if (device_may_wakeup(dev)) { + error =3D dev_pm_set_wake_irq(dev, spi->irq); + if (error) { + dev_err(dev, "%s: failed to set wake IRQ.", __func__); + return error; + } + } =20 error =3D shid->ops->power_up(shid->ops); if (error) { @@ -1192,6 +1276,29 @@ void spi_hid_core_remove(struct spi_device *spi) } EXPORT_SYMBOL_GPL(spi_hid_core_remove); =20 +static int spi_hid_core_pm_suspend(struct device *dev) +{ + struct spi_hid *shid =3D dev_get_drvdata(dev); + + spi_hid_suspend(shid); + + return 0; +} + +static int spi_hid_core_pm_resume(struct device *dev) +{ + struct spi_hid *shid =3D dev_get_drvdata(dev); + + spi_hid_resume(shid); + + return 0; +} + +const struct dev_pm_ops spi_hid_core_pm =3D { + SYSTEM_SLEEP_PM_OPS(spi_hid_core_pm_suspend, spi_hid_core_pm_resume) +}; +EXPORT_SYMBOL_GPL(spi_hid_core_pm); + MODULE_DESCRIPTION("HID over SPI transport driver"); MODULE_AUTHOR("Dmitry Antipov "); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/spi-hid/spi-hid-of.c b/drivers/hid/spi-hid/spi-hid= -of.c index 651456b6906d..80c481b77149 100644 --- a/drivers/hid/spi-hid/spi-hid-of.c +++ b/drivers/hid/spi-hid/spi-hid-of.c @@ -227,6 +227,7 @@ static struct spi_driver spi_hid_of_driver =3D { .driver =3D { .name =3D "spi_hid_of", .owner =3D THIS_MODULE, + .pm =3D &spi_hid_core_pm, .of_match_table =3D spi_hid_of_match, .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, .dev_groups =3D spi_hid_groups, diff --git a/drivers/hid/spi-hid/spi-hid.h b/drivers/hid/spi-hid/spi-hid.h index f5a5f4d54beb..17b2fdf192ed 100644 --- a/drivers/hid/spi-hid/spi-hid.h +++ b/drivers/hid/spi-hid/spi-hid.h @@ -41,5 +41,6 @@ int spi_hid_core_probe(struct spi_device *spi, struct spi= hid_ops *ops, void spi_hid_core_remove(struct spi_device *spi); =20 extern const struct attribute_group *spi_hid_groups[]; +extern const struct dev_pm_ops spi_hid_core_pm; =20 #endif /* SPI_HID_H */ --=20 2.53.0.983.g0bb29b3bc5-goog From nobody Sun Apr 5 13:04:55 2026 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (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 732463C6610 for ; Tue, 24 Mar 2026 06:40:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334428; cv=none; b=UdGcnoC4QoFV8Bylc6D7r0Ju4EwT/FdxFOWddoRxJvrKZruECWdbYxeZ7t02HnzUHNl06Z2rlUyA+91gpRWTNnvvXwnbrj6cGXLMlmW7yTuO+W9dy+2Em9Mn9mTzN3BuAeeqwaC0qw02vLY4fX5iH7mvvF0bwmMsv+YfWmoxo7Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774334428; c=relaxed/simple; bh=IRrInqQtcOiaAEhp8wKGZA+QpgivXtu7wbojYzUNBKg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Gto7Jm9Kxr3z8vka+0EOwMmn4j01oDpw2Zr1gBhgjFu4yV+mWNqOUazp7XKXohFJ4ac1qg+7N419oMxtcNDJiKp3HM2S3idXW/h4VB8959F/ipRwaguKwm5ZwzU422sCHPqaA9YmlgRHc9G642zN8gXSdySvbbgjbNHtAThZ9Gs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=muwSloOR; arc=none smtp.client-ip=209.85.214.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="muwSloOR" Received: by mail-pl1-f176.google.com with SMTP id d9443c01a7336-2b06c43e6a7so18853385ad.2 for ; Mon, 23 Mar 2026 23:40:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1774334412; x=1774939212; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=vM+2YuV3a9NGW/p128SptnqAT8+vbtKMpK5c1NDgHRc=; b=muwSloOR8qqdwvXkJofLMq4NhHMTYx4qDe2W5oHVbYe+zY0FMRPuV+pIEXKVIj23QR 84gJR2BX3s86eCzXMzEglApgUUojGy6LK8m69FdxCaD/ksonXXbh8f4kSb4Mdpmbi+am SHtvohXKV7ZJTUzyup7la/Yu5WNUOn5Laf8M0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774334412; x=1774939212; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=vM+2YuV3a9NGW/p128SptnqAT8+vbtKMpK5c1NDgHRc=; b=l3fXCqirm8t3zHpZfpwm0srMFKnIqjSsIKVIDVzfGvmnBXD252GQhA4Q2szJrMQQuN yoQVnBnal0sYAjzKhkglkduwZDjkSIiqKerp+hVAZIQRUIh77GbeibyslQkyhAvfM7Ra pBs2/fX+sNQzTWLZ6l88Df4sj0VFDpgOpDGqC21SPKrVhcwQqO8NC/ykNrTJ6PpbIkru 0qvRrjJR2PEYJALh08rLVTjSpD8dA3W132MnHHr1WKZWupJuGhCKiUPYtcYpJ/JvNXhR ReVGrOjWTJPv21HoKt8DwmdobG2yAep/4YN1Axed1d8nHmpradwNoeVFb8HTtpDH0SLd X+QA== X-Forwarded-Encrypted: i=1; AJvYcCXtIhRqPALFCTMSwGUb7ZyKW3Za55obGOe/CIEhfA1aRRiiYFv7y9uDo33kIjNn19+J+GKPMZ+Io3+e4Ms=@vger.kernel.org X-Gm-Message-State: AOJu0YxaHbF8nFMopSa9tlz/XASQEKdzrbTxUlICSpSj3OSNUSDt/YEd CvZuxdNKXrNkKs7+o9jjTRj+s4f+qlPOP6MQEGe1BwCxDZzJDHI6BpqKOhZ3kLgYZw== X-Gm-Gg: ATEYQzwj4vt0SNte3rz3nDQMH3S0NEPg3godnSgiZl2row4W2Hqoe/GWf59/pYdqcuR glWKhURGwfuzLnAtFCxFkLrnquGNUKBeswwAiQug580NQX5iRA8jnSr7I0fMPK7CkH/TDg18G6D xZ/Ds3b/DYVgfRG9t6DRpOi3M4AcW82d0Wr75u6kpfe/uc/Q0hSc7HfN598OOt1aUSrwyJxuUfj k2jqrSyJGJ8vKQfQleZltLCNHVZNqmvWwpya+oaUOLtRpgf2NDEUedHItWOQaNugELA/pZmYm7k sVb0UxOuJFPwX2elUGHd7t5npkLTyRroACDGTbC6YMbS8QVHkGwJKpzOzKBvYE6i8BuG+SG4mxc FQLHYpVYzsyCgRXfz3VUJILo5cCd+nnfiHg8NZ9d7pdUa3bHwt8o9PH/ELBYjG7G21XEMtEIoz9 zNWh/5Fi6QvQd2w+m91zEa9X9qAlsTOZ7ItVWZI7ZDTTuqBezZC8YMFv3VMKzQZTarnVzdhjdHu ZiwM4bOfhLOl6aDSHn9ACQc6dYWxIjzVA== X-Received: by 2002:a17:903:1b0d:b0:2ae:cb0e:fd59 with SMTP id d9443c01a7336-2b0826ff371mr155178895ad.8.1774334412326; Mon, 23 Mar 2026 23:40:12 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b0835429afsm128416545ad.26.2026.03.23.23.40.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 23 Mar 2026 23:40:11 -0700 (PDT) From: Jingyuan Liang Date: Tue, 24 Mar 2026 06:39:44 +0000 Subject: [PATCH v2 11/11] HID: spi-hid: add panel follower support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260324-send-upstream-v2-11-521ce8afff86@chromium.org> References: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> In-Reply-To: <20260324-send-upstream-v2-0-521ce8afff86@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774334401; l=10413; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=IRrInqQtcOiaAEhp8wKGZA+QpgivXtu7wbojYzUNBKg=; b=KYyIMQQOWQlzy+ILzxiWkvkxM0fyixw5VqW/Ryyc5KMjXzptm3hopTSacuEtuEgYiLof/bSUy JJHMAjdU+95A+JlLTZFAdlXa00g574IAt2PWzRPGRzrcxwt/gpJUfig X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= Add support to spi-hid to be a panel follower. Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/spi-hid-core.c | 199 +++++++++++++++++++++++++++++----= ---- drivers/hid/spi-hid/spi-hid-core.h | 7 ++ 2 files changed, 163 insertions(+), 43 deletions(-) diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index ac46e2c7c8aa..6037496ad8a8 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -238,21 +238,21 @@ static const char *spi_hid_power_mode_string(enum hid= spi_power_state power_state } } =20 -static void spi_hid_suspend(struct spi_hid *shid) +static int spi_hid_suspend(struct spi_hid *shid) { int error; struct device *dev =3D &shid->spi->dev; =20 guard(mutex)(&shid->power_lock); if (shid->power_state =3D=3D HIDSPI_OFF) - return; + return 0; =20 if (shid->hid) { error =3D hid_driver_suspend(shid->hid, PMSG_SUSPEND); if (error) { dev_err(dev, "%s failed to suspend hid driver: %d", __func__, error); - return; + return error; } } =20 @@ -270,21 +270,22 @@ static void spi_hid_suspend(struct spi_hid *shid) dev_err(dev, "%s: could not power down.", __func__); shid->regulator_error_count++; shid->regulator_last_error =3D error; - return; + return error; } =20 shid->power_state =3D HIDSPI_OFF; } + return 0; } =20 -static void spi_hid_resume(struct spi_hid *shid) +static int spi_hid_resume(struct spi_hid *shid) { int error; struct device *dev =3D &shid->spi->dev; =20 guard(mutex)(&shid->power_lock); if (shid->power_state =3D=3D HIDSPI_ON) - return; + return 0; =20 enable_irq(shid->spi->irq); =20 @@ -298,7 +299,7 @@ static void spi_hid_resume(struct spi_hid *shid) dev_err(dev, "%s: could not power up.", __func__); shid->regulator_error_count++; shid->regulator_last_error =3D error; - return; + return error; } shid->power_state =3D HIDSPI_ON; =20 @@ -307,10 +308,13 @@ static void spi_hid_resume(struct spi_hid *shid) =20 if (shid->hid) { error =3D hid_driver_reset_resume(shid->hid); - if (error) + if (error) { dev_err(dev, "%s: failed to reset resume hid driver: %d.", __func__, error); + return error; + } } + return 0; } =20 static void spi_hid_stop_hid(struct spi_hid *shid) @@ -1177,6 +1181,132 @@ const struct attribute_group *spi_hid_groups[] =3D { }; EXPORT_SYMBOL_GPL(spi_hid_groups); =20 +/* + * At the end of probe we initialize the device: + * 0) assert reset, bias the interrupt line + * 1) sleep minimal reset delay + * 2) request IRQ + * 3) power up the device + * 4) deassert reset (high) + * After this we expect an IRQ with a reset response. + */ +static int spi_hid_dev_init(struct spi_hid *shid) +{ + struct spi_device *spi =3D shid->spi; + struct device *dev =3D &spi->dev; + int error; + + shid->ops->assert_reset(shid->ops); + + shid->ops->sleep_minimal_reset_delay(shid->ops); + + error =3D devm_request_threaded_irq(dev, spi->irq, NULL, spi_hid_dev_irq, + IRQF_ONESHOT, dev_name(&spi->dev), shid); + if (error) { + dev_err(dev, "%s: unable to request threaded IRQ.", __func__); + return error; + } + if (device_may_wakeup(dev)) { + error =3D dev_pm_set_wake_irq(dev, spi->irq); + if (error) { + dev_err(dev, "%s: failed to set wake IRQ.", __func__); + return error; + } + } + + error =3D shid->ops->power_up(shid->ops); + if (error) { + dev_err(dev, "%s: could not power up.", __func__); + shid->regulator_error_count++; + shid->regulator_last_error =3D error; + return error; + } + + shid->ops->deassert_reset(shid->ops); + + return 0; +} + +static void spi_hid_panel_follower_work(struct work_struct *work) +{ + struct spi_hid *shid =3D container_of(work, struct spi_hid, + panel_follower_work); + int error; + + if (!shid->desc.hid_version) + error =3D spi_hid_dev_init(shid); + else + error =3D spi_hid_resume(shid); + if (error) + dev_warn(&shid->spi->dev, "Power on failed: %d", error); + else + WRITE_ONCE(shid->panel_follower_work_finished, true); + + /* + * The work APIs provide a number of memory ordering guarantees + * including one that says that memory writes before schedule_work() + * are always visible to the work function, but they don't appear to + * guarantee that a write that happened in the work is visible after + * cancel_work_sync(). We'll add a write memory barrier here to match + * with spi_hid_panel_unpreparing() to ensure that our write to + * panel_follower_work_finished is visible there. + */ + smp_wmb(); +} + +static int spi_hid_panel_follower_resume(struct drm_panel_follower *follow= er) +{ + struct spi_hid *shid =3D container_of(follower, struct spi_hid, panel_fol= lower); + + /* + * Powering on a touchscreen can be a slow process. Queue the work to + * the system workqueue so we don't block the panel's power up. + */ + WRITE_ONCE(shid->panel_follower_work_finished, false); + schedule_work(&shid->panel_follower_work); + + return 0; +} + +static int spi_hid_panel_follower_suspend(struct drm_panel_follower *follo= wer) +{ + struct spi_hid *shid =3D container_of(follower, struct spi_hid, panel_fol= lower); + + cancel_work_sync(&shid->panel_follower_work); + + /* Match with shid_core_panel_follower_work() */ + smp_rmb(); + if (!READ_ONCE(shid->panel_follower_work_finished)) + return 0; + + return spi_hid_suspend(shid); +} + +static const struct drm_panel_follower_funcs + spi_hid_panel_follower_prepare_funcs =3D { + .panel_prepared =3D spi_hid_panel_follower_resume, + .panel_unpreparing =3D spi_hid_panel_follower_suspend, +}; + +static int spi_hid_register_panel_follower(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + + shid->panel_follower.funcs =3D &spi_hid_panel_follower_prepare_funcs; + + /* + * If we're not in control of our own power up/power down then we can't + * do the logic to manage wakeups. Give a warning if a user thought + * that was possible then force the capability off. + */ + if (device_can_wakeup(dev)) { + dev_warn(dev, "Can't wakeup if following panel\n"); + device_set_wakeup_capable(dev, false); + } + + return drm_panel_add_follower(dev, &shid->panel_follower); +} + int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops, struct spi_hid_conf *conf) { @@ -1196,6 +1326,7 @@ int spi_hid_core_probe(struct spi_device *spi, struct= spihid_ops *ops, shid->ops =3D ops; shid->conf =3D conf; set_bit(SPI_HID_RESET_PENDING, &shid->flags); + shid->is_panel_follower =3D drm_is_panel_follower(&spi->dev); =20 spi_set_drvdata(spi, shid); =20 @@ -1208,6 +1339,7 @@ int spi_hid_core_probe(struct spi_device *spi, struct= spihid_ops *ops, init_completion(&shid->output_done); =20 INIT_WORK(&shid->reset_work, spi_hid_reset_work); + INIT_WORK(&shid->panel_follower_work, spi_hid_panel_follower_work); =20 /* * We need to allocate the buffer without knowing the maximum @@ -1218,42 +1350,18 @@ int spi_hid_core_probe(struct spi_device *spi, stru= ct spihid_ops *ops, if (error) return error; =20 - /* - * At the end of probe we initialize the device: - * 0) assert reset, bias the interrupt line - * 1) sleep minimal reset delay - * 2) request IRQ - * 3) power up the device - * 4) deassert reset (high) - * After this we expect an IRQ with a reset response. - */ - - shid->ops->assert_reset(shid->ops); - - shid->ops->sleep_minimal_reset_delay(shid->ops); - - error =3D devm_request_threaded_irq(dev, spi->irq, NULL, spi_hid_dev_irq, - IRQF_ONESHOT, dev_name(&spi->dev), shid); - if (error) { - dev_err(dev, "%s: unable to request threaded IRQ.", __func__); - return error; - } - if (device_may_wakeup(dev)) { - error =3D dev_pm_set_wake_irq(dev, spi->irq); + if (shid->is_panel_follower) { + error =3D spi_hid_register_panel_follower(shid); if (error) { - dev_err(dev, "%s: failed to set wake IRQ.", __func__); + dev_err(dev, "%s: could not add panel follower.", __func__); return error; } + } else { + error =3D spi_hid_dev_init(shid); + if (error) + return error; } =20 - error =3D shid->ops->power_up(shid->ops); - if (error) { - dev_err(dev, "%s: could not power up.", __func__); - return error; - } - - shid->ops->deassert_reset(shid->ops); - dev_dbg(dev, "%s: d3 -> %s.", __func__, spi_hid_power_mode_string(shid->power_state)); =20 @@ -1267,6 +1375,9 @@ void spi_hid_core_remove(struct spi_device *spi) struct device *dev =3D &spi->dev; int error; =20 + if (shid->is_panel_follower) + drm_panel_remove_follower(&shid->panel_follower); + spi_hid_stop_hid(shid); =20 shid->ops->assert_reset(shid->ops); @@ -1280,18 +1391,20 @@ static int spi_hid_core_pm_suspend(struct device *d= ev) { struct spi_hid *shid =3D dev_get_drvdata(dev); =20 - spi_hid_suspend(shid); + if (shid->is_panel_follower) + return 0; =20 - return 0; + return spi_hid_suspend(shid); } =20 static int spi_hid_core_pm_resume(struct device *dev) { struct spi_hid *shid =3D dev_get_drvdata(dev); =20 - spi_hid_resume(shid); + if (shid->is_panel_follower) + return 0; =20 - return 0; + return spi_hid_resume(shid); } =20 const struct dev_pm_ops spi_hid_core_pm =3D { diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-h= id-core.h index 61b35a4a4180..72792304c69b 100644 --- a/drivers/hid/spi-hid/spi-hid-core.h +++ b/drivers/hid/spi-hid/spi-hid-core.h @@ -7,6 +7,8 @@ #include #include =20 +#include + /* Protocol message size constants */ #define SPI_HID_READ_APPROVAL_LEN 5 #define SPI_HID_OUTPUT_HEADER_LEN 8 @@ -53,6 +55,10 @@ struct spi_hid { struct spi_hid_input_buf *input; /* Input buffer. */ struct spi_hid_input_buf *response; /* Response buffer. */ =20 + struct drm_panel_follower panel_follower; + bool is_panel_follower; + bool panel_follower_work_finished; + u16 response_length; u16 bufsize; =20 @@ -63,6 +69,7 @@ struct spi_hid { unsigned long flags; /* device flags. */ =20 struct work_struct reset_work; + struct work_struct panel_follower_work; =20 /* Control lock to ensure complete output transaction. */ struct mutex output_lock; --=20 2.53.0.983.g0bb29b3bc5-goog