From nobody Sun Apr 5 13:04:35 2026 Received: from mail-pl1-f172.google.com (mail-pl1-f172.google.com [209.85.214.172]) (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 4472631716B for ; Thu, 2 Apr 2026 01:59:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095184; cv=none; b=NLtD+BqnzLxZE8fc2BYTfQZxVbQSEVk3+xrOLlGu2kpgXKnb8z/Fzac3vAYYJUsUTdcCQ4Zlqw7u0kJN6r4om87Du4E/Ezb32hnukaxbvuziqg0imLTHHYjfACvHMXzDUkdM/eTNrdnmscoJPvEpgTfinqWruuMb5vVvJ7NARvA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095184; c=relaxed/simple; bh=BDcoGzUEgpCGDfndnWpejoVBe/vKuqQ4SnpVtpHMd0E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uW6pfCf346VviUDiW6PZsptCjUDkWcEGFH1MoR3zFq3q/76O7jw7w9Sx4MUilIwwxGDt7u+i3CBaGzz9XGeh0VazbEIxP6tVJPIUhbbv2e/GhJIMAHB2+AAsB8d241nTtjI7uxnEBfsyNRjjASXuCYcTZ2YDWgJ8/RwlF4gYESk= 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=JyUMCfsE; arc=none smtp.client-ip=209.85.214.172 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="JyUMCfsE" Received: by mail-pl1-f172.google.com with SMTP id d9443c01a7336-2ab077e3f32so997025ad.3 for ; Wed, 01 Apr 2026 18:59:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095182; x=1775699982; 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=fzHrAn4/x3CwheBfg3e0ZTKZNKx+9iUkLN9KNaFrA10=; b=JyUMCfsEzJUvnAMUaH8pFk7JOJz5S6j4yc2RPRMEDvcR7+AHkR2RcjSaIaWqcS1bk1 pYXgyC2RR8ANzfyWQI2dZu03PBe2k9eQDah7UHIYUoXK+HKo+CrxeS0+HybIbbmEtud1 M3Tr2cQDeNwYp6bFGqBDUUyAFrSsM5AQoXgXo= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095182; x=1775699982; 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=fzHrAn4/x3CwheBfg3e0ZTKZNKx+9iUkLN9KNaFrA10=; b=Uu+vjl0tyEgFOCaeIOM56jc+5YQXJ2/NjXi/CmAf4CBKVgR4DcOTvbQSjx+uz2GdKp ct07K5W2ZyrUl62c5hV2PiRcmCuD7vQBjIYN0hqInzMpsVBNUNvFlqEU3vq+TBnq+56S FAxnTYbAn1Uyls7Tha9Xqg7MHt5tdHz7YFR6CzeNi2VuAN5bDqatmYiDaRKgQZzb0Ht+ GFtuG2RsTtC6dae01zlWHkhmsAUs0Q3Q+JjqM3/jYPiw0/Vuftlgax3wWLnSE6uORlJE 3eB6KndUeVKMIhnuQjl5HlICjdsajgGD6YtoObN2zj1oBpHJMSD1lADcmL2I4etpsgn5 11vw== X-Forwarded-Encrypted: i=1; AJvYcCU9gy9+U9L2kUPrX1ONqwgzcjkk06XC83Ld9x+s43cD83vXy+EXTG9Hha8sRSYyzZke7vv/ht/MoN5Ewtc=@vger.kernel.org X-Gm-Message-State: AOJu0YyxW81sFoJNjklslRq38dqNajN8SprF8wYQYMWsJJ4baezol7/Z Zb0mVMLwz/iWLPn/CjG72OyWuvUuq/avfwVBjNXFucCzT2Gk1Y2H94LTk3SfyNyeig== X-Gm-Gg: ATEYQzx532yg3tw5stQH1MDUm6VWPjS8P+X70EBP84knA47cY9aVnYvLYJEAbx+NoJi sY97M9RsJ2TAAD6pnrJ71nwFfRIZNpIzm+SacrEOVLUO0vVyU/b36qViNloveD0c62tNyhlgRGZ C0aFMhc7WmqrO5aZilNZZ1LBllfW5pncYgOwhjkEoOVubT67wlRUxvPNLVxy1R1swqrofCCBhvU EV25EDWknl8R9NWNHWA4Vk1EEwx2hbFZrToAIxjh7bxXrRWAw8FAI7pd8lnFGjG2n4BFTLByGgf dhZtrd2XTWTOJPX7dmmbVflvwbh0uMIMscbp4788wUGw/CHxispsE/iQ1R2PR+oif+FIXpkYKaa 1FbmYnaMitmZ/AfWz3UHn9puhQ9NO+0salsCe8LaXb/G5r6QcpQLmxSh1oLv7LZMuLcetG6hgUz dOu7rBMKxUye4s5JK9AOYDbep1GqfIzvSSm3AV/HrKMn0Mh321UtlcBoeRvYfZoJBL+V14kbNrf P2MeiLd/qDkChL7JEOKwYLLxjibPKdqSQ== X-Received: by 2002:a17:903:b8d:b0:2b2:65db:8c5f with SMTP id d9443c01a7336-2b269c9b80fmr50932815ad.27.1775095181703; Wed, 01 Apr 2026 18:59:41 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:41 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:38 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-1-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=1213; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=sHkk34xkCVNuYNvgvLM4yfgwiPvB0kDmMlwDb9gzQSI=; b=sWAbum2KtSMvIZHpotQFRl3qPX0w5lDPo0wVXGjdcQZ4Iy/Q1lBj1M1z1kObb2Say/DR3abDE qMXRcGAYSZjCwmC4SoKf0troiioLGh8ZBZYI6TTyyS5zWbVHFTQHQ7k 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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 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 011EF331A53 for ; Thu, 2 Apr 2026 01:59:42 +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=1775095184; cv=none; b=G7Wot/jGwU/GUWJ5Nx/cW7a3aUOqUXSHLTPNQUKMA+PZi6uSV148BhQlB8EMX/LvJcxWMsJB14PY2TKUQZ/xyze2Y6hCc4O+xGE57q1IzrjqPuCgiWb8piXg7cQX0xxsLawkbokW4OSTMyVKwE5oapPXIyNfBAIgWMJXtp7Vw0o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095184; c=relaxed/simple; bh=Tpi0yK/iaLtS44lW6KsA/vacKkKe39WvD034jzj5Q1k=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Fr/4bFYSbF3tlq2Fc3sq/VpSKW73BeVoFMG9i79u5jtcodbB9oUwyElAQDs78K8b2JBViFgzDg6ThiA4KHkyFre6LPO2Xo4rdqjwsIUT/R6Jdk5gkfxrOT95YTs3qY6o3okp427J1IdRJN1p4oydi4K9ItvGxhMZWWdNCvoZKG0= 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=l9wyNuW/; 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="l9wyNuW/" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2b0ba3bfe16so11933045ad.1 for ; Wed, 01 Apr 2026 18:59:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095182; x=1775699982; 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=uaSym8CXCOjXII696jSRn6vHWWlOmeUfwp714NDMznM=; b=l9wyNuW/X8GoaB8VBjCO8tdBYvh9Vd24nXum3SRwu6fQJxDlm/CtMGE4ey/u9w4gC2 lyhdGhqTpfrcZDtYcsz0v2C/zRcmt+KuCg3lOlEuMN+KSh1jnbq9M/JTPr89FuhnHFvP +ndaRXJ/LQIDNlyR1IsjvnKRHp8RLbNI1ahoI= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095182; x=1775699982; 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=uaSym8CXCOjXII696jSRn6vHWWlOmeUfwp714NDMznM=; b=kjY+28AC9/gJl2MTkSjdpxVQ9WsxvnZy8RE3CWQYTq9RQKLcDRU58JCv1/84MUGZcY FhvEADbnfl3XSpFEwxq8jtr3hKUR1sj2tGKwg4laNY6o3/bpGpIi5Xx0xak9zscB5fbP 3Gz8vyW8xevCS9oaljcg3K9PzOliAP/ubmKxc0goUSsweB9pTJLsYZd6KZDJwH1cdN+j Uvha31o7m0GYwhphxjP64aWq5FnIYQzSz80v3nCHP/u2Jol39r8LJN1cnsdBcnBrFh/N 22aAw0igoTYosZWzPIOOW+OorQ/00BosydDvcA8b81SWwAgNk3DNQhnT6at4Xhl2ucL5 Lc+g== X-Forwarded-Encrypted: i=1; AJvYcCXjujjTux5MwFRQ0oa/HsEDMmKA0otvwwHUXP/ZyeFUrYUu1ON8YJf3FgEIM4dc4B0735O1qx49132xOr0=@vger.kernel.org X-Gm-Message-State: AOJu0YxiY3TkFCkt/8DtYekv4lsJRLMw++dDXBRgezqrhG2rrqieZKA1 5EMgOaivC6SFZl021nnLouMPgALMWNhFJ2BrMvE+eKewgHo7PgChB8RFgOQygsRXbw== X-Gm-Gg: ATEYQzzvI765an8EwJzzHPQdeGAb5HN/mwv4V+PLqB2usAT8p0T3V2wqbd/BUVV0dSX +ZFuseyoCHcnXpeuqbIS4iQtM396DKFVuLTahAI87uEiWoGDXixHE/BdUjDjl8JhBhjhHC0S1YZ VcXKDDA6ijPeoQA16MEcLTlcRzBC1Ip4QWq0WNinRYD4baVpMsYL9lVW48Sw565VQfKHa6LT9t7 2UOEaoN6drrei57ce6h7B3w9qWBDB6wkgbpYo7JLtxT0Tn69xxIdfRBRqjSU/ny0/rUoAMT3MMC +4EF2ufZaLCvzCgXzVekIN19ZF7RxJMg+zl8syNbf+ZDdWGE0ltQKNsFVzlAsXuS3yk4dnvsqZi YvAOO6i2Av+vcCTbW/xQ1F1t9zYNUmK/BVRRWBvLYEEQX0DGQjX4+J3cmA1fppcC2oopX/LgaIn zbYT1AAbQpv/ntaDcG8VUZZi64KncGekII1hIA9ztkckIR+qo9ffeHfDfiCut+/EYlUnc5wgdQL PKUyZFjQJz5AxTAQ2oFVgvF5d3G2bOBJQ== X-Received: by 2002:a17:903:946:b0:2b2:5293:f415 with SMTP id d9443c01a7336-2b277d8ba21mr5711705ad.7.1775095182367; Wed, 01 Apr 2026 18:59:42 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:41 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:39 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-2-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=1402; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=FjzRhH7/ErE4TnsMI+7AvjihWBkT/xpCI98HMGNLH6s=; b=u4Lc+txBMvZJgNMm1YnjIU7DFBygkIoEJquBuEPB6zFCxqo5mXw0HAIQJAzTByC+gO06+zlwJ t0bqPyaLh0nD1I0sai05bo1y2ot0KK93bqPfmYAxaNKepG3FQdXBoLt 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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 2026 Received: from mail-pl1-f172.google.com (mail-pl1-f172.google.com [209.85.214.172]) (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 BF94D30BF68 for ; Thu, 2 Apr 2026 01:59:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095186; cv=none; b=Dz97C0rnn0wKhuc2B586vitKZwX9UknnN0YlmZzftG9jUpqRbn1V/0Ro+Uc00d4+38vavUXuZ0G/JbFXFLaDPAN195eWad8EmK79EMkS4yuFIEeMcyXYIXCUxYVdgglFNOTJqdFOUlj8ZCdiCuMocf7M9RSrJvWuG3u6J15WcqE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095186; c=relaxed/simple; bh=tYvqGQV800S2KgKjcqUob/5OYtcybaCsli/eTLii6dw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PJ/+hqa4UM1a/viataNVfR9XcLvUuw9li5706UkgmjmkbM4lU+T61wbUXpn0EtcXqU4N6IzCmoYpY0VgX2NCmQTKdQwbDKUVjR+0PLmIALzbm3TaVBn05LpdOjg7/MnA/WN4ConvnT91jFsxonHFI+zOxgcbnpcq0sROUkETPsQ= 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=bbrFV31A; arc=none smtp.client-ip=209.85.214.172 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="bbrFV31A" Received: by mail-pl1-f172.google.com with SMTP id d9443c01a7336-2b25cf1b5f0so1881915ad.3 for ; Wed, 01 Apr 2026 18:59:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095183; x=1775699983; 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=lmx5tAEeqHeylhKnot0cbS4zhiC5aaYm/HdQaLxFmFo=; b=bbrFV31AfDRhx9puLKyf4EbTpJy+Am67xDU+DbekBFiBmlQAj8ZVIZcxM0wN5zg6PR 8eMsudj9KWut8AWAot3bNusORoE1WN4ySrU9uSCXOIieVKDl0e84PLb+owiVYLV1L+Hd TZ0vnA82dnVaFikiuLee/OdQj3ovEfDIspOoY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095183; x=1775699983; 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=lmx5tAEeqHeylhKnot0cbS4zhiC5aaYm/HdQaLxFmFo=; b=bvEV6V35WXeRFSDmDqBi4oybZEsEYdJuwtN98q09gA0KAWChAu0B4NHIclj4YzfOJc AdHdDTJOLfipcwSSg+qih2UabMdgSyHrB4xKPfrSa65t/hgzA6ivJtMFETBYh1eGGc81 VEDftX3HY/CzsWIukdb2Tm+L675BGK9r1oxphyKRcgd5C3YaxlbtVSL9vhfj27Ae2cCZ RsuzZAvDA7nNpZRJzEhiv7O9GjK8xR0QAchW0Wz9Xslljka6vVQVdT2L3fwB+RATWJj2 6kn+emVTjl23MLnJ0rdEIA2ax2fVZR9I/lxXBjJdD7m2Wgw92PAlznOLlCgS1nj+aUd5 Qe3g== X-Forwarded-Encrypted: i=1; AJvYcCVsVz8AyL5s/SCMBuVG+4+ad6yNXhpBE/tJOJULLMvoextWOkORmNK5TWlTfmnIym7usWYhUK6oPDfUl6s=@vger.kernel.org X-Gm-Message-State: AOJu0Yz4KyFK8BWmeU/TP25FU2QciTOH/oJu5fwFzjYeWvehpR1S02Pg BqrJicBMwrvCsG++7PCzDeaP2DTMQxtE/GwFzPlNSUfnjDT0iIpNaMRqGGHvwKDBxg== X-Gm-Gg: ATEYQzxqfV4dPstg609QFcpIcNM+ueDlH/Wlc+GiyWlaIw6aS+zCk9LgC6ONtBzHG5L PervGm9fH+2X1KWl9pkYwpzG1CxT4V+is25f7aTSfn/fNvFepwNx7Nk+JmO3wAYR5GM6p8p0LCF nHHkcgsNZNMthu82rCOrb1H8UiRqRFxioL+A+SjEwYWl+txtsO05MspoSNk/mgcRudk6Fv1zwyY tlhU6MUkBMOKxGo0uEdHLo/bWAieWepqvZ2BjJ7kthqqgHC2Qu7S+rHIsCBSjCaEDkQr06x7K4N AjpwjoL9zPN1rPsxzQMcs7HNUB10wNoAGrsMxQ3zgwGtew5v3o6/KTJoB0ghzzjjs0UtPjnUkf1 Q38SOAcSfubDk9l4V2euzgm/Vm6T0d3kdDZRcPuwcB8+LlYqPW1ECNyw3g5Pqnpb+2RyC8b61Pu VKrrUO63mfBE2f6tjB5ujuHn6hAHI91+JMlUc8LCB1rYaSFuj1R4BAQs+in5WGy3Lfp9QM789la nRLOOxXks9qsf28q7PJ/rwZFlyYdkRnRg== X-Received: by 2002:a17:903:3c4c:b0:2ae:826f:2c50 with SMTP id d9443c01a7336-2b269aa6173mr57330235ad.12.1775095183221; Wed, 01 Apr 2026 18:59:43 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:42 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:40 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-3-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=8512; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=k2H3ie7YFtCK66VrpOXNpoULBztAuQv+M2HBUIlKwfo=; b=1nxLuZX4OuDDCMTgcILKZSnyc1P+vds8a7ZXwwb+4u9DhHv+kUv3LpJr7WJ4xoUsTb9xytxxY 3j07DLvTVJ6CRkW91979YiP4IX+D0CA3BV9AWdeCIn0lPeb25v69YjO 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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 2026 Received: from mail-pl1-f171.google.com (mail-pl1-f171.google.com [209.85.214.171]) (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 A112A33A9C4 for ; Thu, 2 Apr 2026 01:59:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095188; cv=none; b=KKNTk7tWePYOkwLk5UK6L9gIf81v+0gdPmbGsiXTUFaM2RLKe8Kpaad0deId6+2QWyJg73CgCXaXKqh0/aQYOgrfkYyVgmbLrgX7iot7akD/M1a7OohE9mifqxm5k6rupvuMTIuPKvs31TzGveA04PrpQ+QE6j8RA8HHG6Ou8EY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095188; c=relaxed/simple; bh=EnqLNJCTcvH0q+/xqzcK90S2pdO7Zg4AFKyEqEget/Q=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AuR7S6Sm7GErRW8taEGsopAlxHA8xHAcy+lkQ8x14Y35Ozl6B9zGW1Kx8uwPys1w2kL2oYVdlHHVLZ+CgQLeHJYRoGss1z4+WqyW37Q6/uowpv0N5ZcYD8bhZpLR6P/VgEKQ80X3qAKPXlOk74vkkcwAF1KQQTjM3TmWFhmISYY= 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=c8lW/ZjC; arc=none smtp.client-ip=209.85.214.171 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="c8lW/ZjC" Received: by mail-pl1-f171.google.com with SMTP id d9443c01a7336-2aaf43014d0so2170105ad.2 for ; Wed, 01 Apr 2026 18:59:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095184; x=1775699984; 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=O9lFXpOnmNivau0HIkBY2iJi8wwoteY30kbns1hbNbw=; b=c8lW/ZjChoqM5/pAl6z2en1cqs4ODyMR0PwUba9Cc7KvawqPTxFMsgzH9Z2OBsm3Ru X9YC23eFUCbT3w5l7KyiKT2kRaZd19h1PZ0mbndGyWeTF8Q9N7JenmQao53JGxpDUuH2 apeRvwJ45eWjssW74JnuCiBYORqfYGTxffgwQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095184; x=1775699984; 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=O9lFXpOnmNivau0HIkBY2iJi8wwoteY30kbns1hbNbw=; b=OU6e89BWxwSHMsKTuVY9Y+v4BwuWQnAxu718uj9kPbxoRS0TaCBAIxt0ex9XqNk6+q KqpXiTLy3SbTlLDl1gPfyzb4MaPPaLXZZXkvRkYb/QZfPDIWtG2IrcLdust2+erjkrPj t01AuWxqbEq+Nxh3B54dXvHVBIW7ighFeSzVm7F3gDIT5B6ZEBjKYLZkcwmGO8xPOG6p x/6WhjLn20AdwfdtThlHR//H7OBTVhqIBGGw3JSdKKgdC49dJdp3TmfPoPdknKnRDwYL xvCQMCokUSwXyMbkAN2FEWKWj4cukK4PmZhp9Z1ZZbXfFSISGE0+DT6vxlWgw64Ectac fb7w== X-Forwarded-Encrypted: i=1; AJvYcCXn0JFw/1iDntv+DIMLgzMBJ2xms7fgeq2qVNO2y5cbv8s57iUsdskE8k1I0UWF/2Sb+Q8aBeSIIdJCk3s=@vger.kernel.org X-Gm-Message-State: AOJu0YxZy11S/lGGfo4bLzzEJ1cJ2uOV0g97xTQILOi0Kxc+HSjpGmLT Vultu1wgDXaf0lX5UciwLVgp1tI3dSmWk5D7GG/ekyQBXeKYxq9ZLq+amn2y0YgqBQ== X-Gm-Gg: ATEYQzx2Hzct6foCtbI3OygOv+p4rDgThtov5onLkeGyL4mYecn8sql3XpqWclj0sau Ch4EHOzdhsmoLPbjKOtGYMiKuDl5cbcsf9gQaqXbtjgGyOj0/yRdiXtx4wAGzHOu0BOjy5H3rE+ bN80/bBqGJqzWe0OYe2p4B28a9MXjasfuhqZLRM4UVtQlFzdIW7hLDQhLNiPPl5dAMwxTiOhETS KgJ4w0tVrNdqtrhjHzs5S5f6goaOXZSFAOOelZd3t5exKZcJgFfKcMUA9aNa5zvbD7zsw4/nzJ5 T4AgdXjVQy2iikilo5YsryR54gl4L5SDhQhqNraEGeiw8dCAB5Qz8VoEPvSQFnASqfc22KbX06b thbmrMHMM8CKzfSkhy42RwQZIXq88XhstwqJ6eg+QaKVbrlPurx4E3pLAH9obDcBWP/7udENqT7 mkob2/G6sexbNuwVxsmpClxjubb+EtNuf+VktG02pBgSBFcUVZLiB0AY6H+3BPkm5JYmizpLZiN y7+s+v73OA/fFBegBea68t1LY0/zaBWjQ== X-Received: by 2002:a17:902:e851:b0:2ae:b9cd:d2ce with SMTP id d9443c01a7336-2b275b4a227mr15040575ad.27.1775095183891; Wed, 01 Apr 2026 18:59:43 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:43 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:41 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-4-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=16407; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=EnqLNJCTcvH0q+/xqzcK90S2pdO7Zg4AFKyEqEget/Q=; b=OP0zkCq23IY9fMPPmA1mIvs+C4YYM+3vU4P6rzGd4BPoFUAyIo1CqlScI5LFcrtDmj3CJFOhv RYlLuw3ZFX2BS1Ish9HjYsYS0YizCZxemcOuXXsCIRdVlQ7Ap5YBkrI 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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 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 78B7F33F377 for ; Thu, 2 Apr 2026 01:59:45 +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=1775095189; cv=none; b=QI8Kk0BjVYzkmVy18WKHBr3qqeCMX4BNCrQqKG92/+9Yk3Tug0AhVAp9ZKiV6gPhQS2OOZoOJfRfp8ENW+cqsm+PsBCb02oEYkxibyg2L966O4UkHhhfJ7pInQnN6QxIXz7OwIqmgUUOsNysZVctVuBtcvRKaXNz+stEIdzU1ec= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095189; c=relaxed/simple; bh=JIGaiWILWYd0ZoK0ikea+P2ixn29s5bt23bI5nqme0o=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UUhhA75GWnuymtaIbYre0pQSW0vpKqXXgKPi/3SAu2eK+aw0GyCm/iesd3UUVVmp19nw9z+PQACk5/rjwKI6QyCINpeiggu0XuSgqFZA0hDSOFKkGbJ9ssXMtYBxKcS84QGpdOafNqDtcFSyGK8L3QjUNUpDJ4EUYdminfkoNH8= 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=ZJCYjjej; 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="ZJCYjjej" Received: by mail-pj1-f42.google.com with SMTP id 98e67ed59e1d1-35c206f0481so293708a91.0 for ; Wed, 01 Apr 2026 18:59:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095185; x=1775699985; 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=xs3FKlG0KKJ6apTLSx2TatZL5Y4UXFT4/1d9n+29X9c=; b=ZJCYjjejDXvGwPRc74WTF1jzxQQc0BJjPcXfiMNyIEH0Ho2ki+q2LiWOKYXr1+n4+h xjcYJ6k4cNTQJKDRVR5TE2+xXhc+j5ozEidma4tUQ7V/O4/2zlGrGo9DnwnxdroOqryW KPCB9Uo0vF14/WKFnoIJBfXrcB/ENv+K9V0MY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095185; x=1775699985; 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=xs3FKlG0KKJ6apTLSx2TatZL5Y4UXFT4/1d9n+29X9c=; b=eZEcKXmotBTcYkFGHhCgckKlYUv/Ckfqr4kzS6RruclAmi1k7/gmwFqqxZWsa+8jLf 13Ko28LF45J1kYfIXvvEfIUygfQO1fI4ZAsOu9rqK3SqqLby4W8QNxMRnu7awmEPfEZV mITju1JxEwLcag/5+5GAzgsrVTVjw0bDQpS7cskf7NM3cTOt6prdV48A9oeiDLOI/RxP LdhYQmzXGK4ZqhxJtdpaOBD64DkGxZdTmdw7XwgQm7NWT5zDE3Vg8CuEOPyRDKiAbxTQ 5i7PMynjMyxq8AWDBE8vuHtz9k3cxrI4wQxYi9L8gp/A+ajD87g1QuKAUtyLU1N54Jfp zbKw== X-Forwarded-Encrypted: i=1; AJvYcCXwrXH7BDtJ/rPqmKDWFpIrr3AdQ6xw83q52CmVm2wPOVGlOcJTJjcWt0t+S8rCUX+ZwZot6uOhV+KSdNg=@vger.kernel.org X-Gm-Message-State: AOJu0YznmjhHxF0GPKlnNBDEVThvreg7MXQMoiVrL5NrtBKQ4q/f9Jfj jJng2/gciWnPGVCgUVyO+YmrLq9ZLNgwjSvGwzBfaFTnPvdioZpePr4aCkKEVrGYLw== X-Gm-Gg: ATEYQzz/U9VVxsqyKeC+pX4rMbchjTqIi3dyQJb/p3XK+G0ZG8OcTgTYJrk6RQfbvzZ +3o0Ytkwl+TtTc4athJDW3Ik5Kf/6qEJlTPgG3jdoHZoVESwQHWXId4DjqXHiPdJYmOaualpiZV spHZNVvmjnw11QQr6ddbSe0AhC7UsiNFLVc/AM+11pcw4ZcEuqA7v1O5OzI+Ey858faeDVRVG2G OmpXI3J7m7lP+ZHsuB+A2B7ZnETyvbYYPsoA8rItc8hWD2axRJ6eZ7sRGFvFLU8i2YKVCSVFcSA Dlqz2v5zassK1O/tZuwFeM5A4MYBXX2CF27Wbwa8RhwiVWRG0nAYw0Yh/jqZhNXg8pHhtCvLrqW wW9iy2pI+T6jlMbruNSMPI9C3465auM0knnrSa4rI8j1zpEhLXRUSoz4dIiPvw7yi8DIW2YUSJ5 9Iqrp3kYTwCSrPBOJaTLOm0PTktTK3vBjFPTmUpAyW6IdMiQkrEpuD3+sLfu1gJwUsLa+X1X3Zx KBWXAWqiNf/dIW9bycm8tgD26uWoJxjoA== X-Received: by 2002:a17:90b:2783:b0:35d:974d:8f7 with SMTP id 98e67ed59e1d1-35dd40208f3mr1231386a91.1.1775095184852; Wed, 01 Apr 2026 18:59:44 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:44 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:42 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-5-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=23224; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=JIGaiWILWYd0ZoK0ikea+P2ixn29s5bt23bI5nqme0o=; b=nZiAqmeHu4hv26Kd9OPkd+PhIzNo3aU2JQ+48z6nkx4mCCvATf3kMdHDwjTgHhCvU5n4crQWw h5kPZL1PliPAMqxM48ZAJKgNV8K223STr2rA0L4E/SJl7JAxcvXk/5a 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 | 582 +++++++++++++++++++++++++++++++++= +++- 1 file changed, 572 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..00b9718ba2c3 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,23 @@ 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); + mutex_init(&shid->io_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 +1270,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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 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 B1D9F341AC7 for ; Thu, 2 Apr 2026 01:59:46 +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=1775095190; cv=none; b=exE5+wNXQMOx/INp2/Rrg7oVpxAJbR91RMFWTTNUHOk24tFnVDVorIKYkCLvfZoCm0pHkGXRyrGgXoOpdxt41wgftO9qLgmnzAWMdIxQfyUm+eFWVIdJ6AdE/lZ6f8zYILSwkbDu4XGEOG7E0OTP0LkzgemRX1a2oGIup5yAU/g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095190; c=relaxed/simple; bh=vDHaoV/gYpkl6O82neaWLp853ITUnPEedYwKE5Cx7o8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Kduz13SyEu9xEQfdpzgTZlyDfkUNLF2QiJbNZxmF+GGecsb8arZ+v4hbrOmSC/0jXkrRhlVSc694RSZ+FJdu3z4dPuhhitW9wcUPCIrwxs8jdlPb8jmgs21MXGp4O0FqR7qD8idBMy4orR7CzY2BayhkJDZJtnRuPuA5BMKiNnM= 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=g8Yt8EA7; 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="g8Yt8EA7" Received: by mail-pl1-f173.google.com with SMTP id d9443c01a7336-2b2494440f3so1114375ad.2 for ; Wed, 01 Apr 2026 18:59:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095185; x=1775699985; 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=tRpz9hV8ktD4Npg0jOlXpgMkGgUebLGK5shW3+Bc4Yo=; b=g8Yt8EA7ARqTKPW9h7bODjHWG3oj3ipH6afrU3nKHsrWS7yTvhGv9l0crJv2L7L2yg 0wM87XXpP67UnQDF+56iPdq0g/vAo7GpYFUMmNI+degOg8H522pmM92YzcYmJvaxDlh6 SIN3a5/6rH0tJvqYDNIhlDB3em7A1SFT3thUM= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095185; x=1775699985; 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=tRpz9hV8ktD4Npg0jOlXpgMkGgUebLGK5shW3+Bc4Yo=; b=C4rPsO4xnFWYnfEHUz3oVAlLRMmVyBf9FVuNV60Z11NCYAbmjUFBlzC1x1UGd24A7Z IKnt2Es3YK8HnIsd55OcRD7OV4WladXpvyVvULvlGo2EmxQqXwvQmu3k7Vwe+un1x5hJ wjYMP/hAQ491AWpEhP8FgYTU7+XTXOX5TKsTHv56gkWEHuWGiGVxQJZUfeeRBymVQiLo QENMziSnLCMP1gzdd/X6bTW4yRe+LI6ial5vMuBu92GNQQ1ro0a+j5nVDSSSspbLSQpK DP+2n/P8CtyvmnAg5Ar2R5AMbBLLtPZ6e4PioGIv68w6G7AxyINvx6gH0PKtdVxttxA8 nLRg== X-Forwarded-Encrypted: i=1; AJvYcCWx8LIVIHND8bwTmq9AXjNqx6FR6jkabMNhatM4+jsbcu+vCzzHgfNOvfdnT8xTJo2nUeiiHnKuzHnyfvg=@vger.kernel.org X-Gm-Message-State: AOJu0YxQ4vGbFSDFzAd69AzJ6Y4RuSuPj+V5Ak2axUw+YxwkJILVwXh7 Fh1XvjHIdYfcUqe0CWQKFHfzghTy3tWZzO3CFLHBmmLW77TaO5qQGt2vrIypijjTlA== X-Gm-Gg: ATEYQzyTXDAjhrLG0mk0ycHP/6KnPqWM1fgjpW233gHGR9YBWyFQH2ojVrIVuKFUtJn pJ7XlE253uCmvAVZA4MCKeqkOGyl7AU7e9Zu9XWe2ExGGYM3Rh2A1bP8Bn7kxcYqifExb0cwIWU 3k48NO1+EOOylb2Us2SQf0rSHa09w2nJ0s+xzYm+KEtOthnaDLAudG5TGBhAQNkmnmhXLLodPy5 k8msUKqx+fdB2pbIEQQlIvEzNfO9huiX1HSOXdNusMVRf9R8WGeycWrndvGZAvFR9aMEUnqEQzr OgFLqqG2TXfp9TYoc2pSmCrXtyjVbvTp0Cvft9y0uvBAVM1zx1XfWUSbuVwP/JVmlAhZIENuEq4 WsfCwq/8PFsugSEJK8SMbNhybP7dHmoUfKf/jP3LY3Q+ssUnKcnVS2TtLWEm30NkcvGCQ0/P4p+ BQ0ZP2N+Rk3ItesWZyGdyjXIn/hPf5hGf/zPB7zqlsh/hF8nsUkSK4DQ+aumH5OX0XWSaNOetfz naLE2x8Br4KsHNH8YvFj+xRMvH6z00Alg== X-Received: by 2002:a17:903:22d2:b0:2b2:57ee:c047 with SMTP id d9443c01a7336-2b269cc2f42mr56517835ad.47.1775095185541; Wed, 01 Apr 2026 18:59:45 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:45 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:43 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-6-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=17241; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=vDHaoV/gYpkl6O82neaWLp853ITUnPEedYwKE5Cx7o8=; b=eglUqnsoIwIXYiY2KrrqMu9mlaIdxPZ6AZoIet1ltfoL/QP0sQmr/7p0GsxqE1gCOFUwEn/6U A/z6xAglRUQDxQ5AWAuxA35hPz7EDKSTfQbNorno8SBHyClYCtdXf2E 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/Makefile | 1 + drivers/hid/spi-hid/spi-hid-core.c | 118 +++++++++---------------- drivers/hid/spi-hid/spi-hid-core.h | 91 +++++++++++++++++++ drivers/hid/spi-hid/spi-hid-trace.h | 169 ++++++++++++++++++++++++++++++++= ++++ 4 files changed, 303 insertions(+), 76 deletions(-) diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile index 92e24cddbfc2..733e006df56e 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 +CFLAGS_spi-hid-core.o :=3D -I$(src) diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index 00b9718ba2c3..802615565541 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -43,6 +43,11 @@ #include #include =20 +#include "spi-hid-core.h" + +#define CREATE_TRACE_POINTS +#include "spi-hid-trace.h" + /* Protocol constants */ #define SPI_HID_READ_APPROVAL_CONSTANT 0xff #define SPI_HID_INPUT_HEADER_SYNC_BYTE 0x5a @@ -81,13 +86,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 +102,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 +110,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 +135,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, @@ -293,6 +224,11 @@ static int spi_hid_input_sync(struct spi_hid *shid, vo= id *buf, u16 length, spi_message_init_with_transfers(&shid->input_message, shid->input_transfer, 2); =20 + trace_spi_hid_input_sync(shid, shid->input_transfer[0].tx_buf, + shid->input_transfer[0].len, + shid->input_transfer[1].rx_buf, + shid->input_transfer[1].len, 0); + error =3D spi_sync(shid->spi, &shid->input_message); if (error) { dev_err(&shid->spi->dev, "Error starting sync transfer: %d.", error); @@ -343,11 +279,13 @@ static void spi_hid_stop_hid(struct spi_hid *shid) hid_destroy_device(hid); } =20 -static void spi_hid_error(struct spi_hid *shid) +static void spi_hid_error_handler(struct spi_hid *shid) { struct device *dev =3D &shid->spi->dev; int error; =20 + trace_spi_hid_error_handler(shid); + guard(mutex)(&shid->power_lock); if (shid->power_state =3D=3D HIDSPI_OFF) return; @@ -457,6 +395,8 @@ static void spi_hid_reset_response(struct spi_hid *shid) }; int error; =20 + trace_spi_hid_reset_response(shid); + if (test_bit(SPI_HID_READY, &shid->flags)) { dev_err(dev, "Spontaneous FW reset!"); clear_bit(SPI_HID_READY, &shid->flags); @@ -482,6 +422,8 @@ static int spi_hid_input_report_handler(struct spi_hid = *shid, struct spi_hid_input_report r; int error =3D 0; =20 + trace_spi_hid_input_report_handler(shid); + 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"); @@ -506,6 +448,8 @@ static int spi_hid_input_report_handler(struct spi_hid = *shid, static void spi_hid_response_handler(struct spi_hid *shid, struct input_report_body_header *body) { + trace_spi_hid_response_handler(shid); + shid->response_length =3D body->content_len; /* completion_done returns 0 if there are waiters, otherwise 1 */ if (completion_done(&shid->output_done)) { @@ -562,6 +506,8 @@ static int spi_hid_create_device(struct spi_hid *shid) struct device *dev =3D &shid->spi->dev; int error; =20 + trace_spi_hid_create_device(shid); + hid =3D hid_allocate_device(); error =3D PTR_ERR_OR_ZERO(hid); if (error) { @@ -603,6 +549,8 @@ static void spi_hid_refresh_device(struct spi_hid *shid) u32 new_crc32 =3D 0; int error =3D 0; =20 + trace_spi_hid_refresh_device(shid); + error =3D spi_hid_report_descriptor_request(shid); if (error < 0) { dev_err(dev, @@ -668,7 +616,7 @@ static void spi_hid_reset_work(struct work_struct *work) } =20 if (test_and_clear_bit(SPI_HID_ERROR, &shid->flags)) { - spi_hid_error(shid); + spi_hid_error_handler(shid); return; } } @@ -681,6 +629,8 @@ static int spi_hid_process_input_report(struct spi_hid = *shid, struct device *dev =3D &shid->spi->dev; struct hidspi_dev_descriptor *raw; =20 + trace_spi_hid_process_input_report(shid); + spi_hid_populate_input_header(buf->header, &header); spi_hid_populate_input_body(buf->body, &body); =20 @@ -840,6 +790,9 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid) struct spi_hid_input_header header; int error =3D 0; =20 + trace_spi_hid_dev_irq(shid, irq); + trace_spi_hid_header_transfer(shid); + scoped_guard(mutex, &shid->io_lock) { error =3D spi_hid_input_sync(shid, shid->input->header, sizeof(shid->input->header), true); @@ -853,6 +806,13 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shi= d) goto out; } =20 + trace_spi_hid_input_header_complete(shid, + shid->input_transfer[0].tx_buf, + shid->input_transfer[0].len, + shid->input_transfer[1].rx_buf, + shid->input_transfer[1].len, + shid->input_message.status); + if (shid->input_message.status < 0) { dev_warn(dev, "Error reading header: %d.", shid->input_message.status); @@ -889,6 +849,12 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shi= d) goto out; } =20 + trace_spi_hid_input_body_complete(shid, shid->input_transfer[0].tx_buf, + shid->input_transfer[0].len, + shid->input_transfer[1].rx_buf, + shid->input_transfer[1].len, + shid->input_message.status); + if (shid->input_message.status < 0) { dev_warn(dev, "Error reading body: %d.", shid->input_message.status); 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..293e2cfcfbf7 --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-core.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Microsoft Corporation + * Copyright (c) 2026 Google LLC + */ + +#ifndef SPI_HID_CORE_H +#define SPI_HID_CORE_H + +#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. */ +}; + +#endif /* SPI_HID_CORE_H */ diff --git a/drivers/hid/spi-hid/spi-hid-trace.h b/drivers/hid/spi-hid/spi-= hid-trace.h new file mode 100644 index 000000000000..cc13d71a14de --- /dev/null +++ b/drivers/hid/spi-hid/spi-hid-trace.h @@ -0,0 +1,169 @@ +/* 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 +#include "spi-hid-core.h" + +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 */ + +/* + * The following must be outside the protection of the above #if block. + */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . + +/* + * It is required that the TRACE_INCLUDE_FILE be the same + * as this file without the ".h". + */ +#define TRACE_INCLUDE_FILE spi-hid-trace +#include --=20 2.53.0.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 2026 Received: from mail-pl1-f175.google.com (mail-pl1-f175.google.com [209.85.214.175]) (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 BADD6347BC9 for ; Thu, 2 Apr 2026 01:59:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095193; cv=none; b=O6GdP8KhaRLeNScqK1Q2mgPPGjIsRa4lwLpSJG+Pvf7B9xIQxUYBWVB15zhZ8MnssPnWNjnheors4Um3DGfI/wnQxqH7U21W6CWxvSuLxlETMTgDbKCkl8TJEon49nfVWqhA6FMSTEA6otG1BnaXiQZ0tQz8RYBJ5wTYQVRhQ/Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095193; c=relaxed/simple; bh=kCXFWP6q7VRlod3hAJwQuCfya5h/Q/xKdqbSjbVDbVo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=NMjc8/+AGIzF91APCcJSR+SLDNLm3Ul/aNvcV3gB9CVf+/X74KPLNX0rl/VreVs3iE86r1Jh9pIgymWn3Bv6C+5ChXWBCJmQuBQW+SyK72SQrPh/mX97cm6I/aC80vuByjgq83buRZvTFtJbr1qpLiEWA2VhqPR01seRRbSwj0c= 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=PncTnOMO; arc=none smtp.client-ip=209.85.214.175 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="PncTnOMO" Received: by mail-pl1-f175.google.com with SMTP id d9443c01a7336-2a871daa98fso2429015ad.1 for ; Wed, 01 Apr 2026 18:59:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095187; x=1775699987; 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=ADOckTKec790d3noJR2HqtEZOwwgU6ZvDblTuQ8Ardw=; b=PncTnOMOUKIkvdgdWr9fDe6kn+Nf2Dp9u1Mp2G6uJ/Zks4sE4A6BLBr4zlzdnbCABU arsofUetTQkMyNuPRToQcY/qtTnM7BDOt5eXS/cB7pUlR0vAm2LmeZagDOWyQan5n+TY d7oLE9URN4EOErbGhh6b2luHD7b74G/BXJXtM= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095187; x=1775699987; 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=ADOckTKec790d3noJR2HqtEZOwwgU6ZvDblTuQ8Ardw=; b=ZFnXzOy6MQUlvMLUlwvXKtQdg2ZiXwkGVdc1dwPvdR6rezMI0yxsS5va2btbjUDlh2 FM00u+j9HMywNgtbUL+Nf38bYfOQVS3oeF+GhqczoETFRTjpRTkJxGSk8kvQ/jq+Us5W OFVopkI9TE5adLx3dj+uBB4lctOw+Zpy1Yo6yXwhtwoiyZr2Wqkc/WDEKw/Kcr1FpmK+ Vfyfw+vECD2WIVFVnGtN8og94GPEYlnHcgRFbr/opi8+G540db1tSiawAU4deI3Yu7hp WUxIjd6j+xQ/vNQXt2hhkMmBRW9q5uq16oqyHgvxM0JJk5ST9o09S3+YHCGKaBJoOq7p h3Og== X-Forwarded-Encrypted: i=1; AJvYcCVTniCTAu7EAgUOl0FAHmEmjW8R6euH0dwy7CT7JevDqWmGLXPx+Rv0lKP1y1KJcALUQNCSyYaRa+THLx4=@vger.kernel.org X-Gm-Message-State: AOJu0Yy4xFhujVcvulKgCCiDax+ym5rmfbRnubDvfsMxFjFpXgJxpIHE MI8wpsJcfg4rZUK7bbIGkPn5zLQNvZz3ZYVkoatkl6rbazpPs2Lmn9LDl59ftGBAmw== X-Gm-Gg: ATEYQzzC6bp3rnoWWgiRdG+G72zeRP6WmaAVWOy0/XbNgukQvLMCSD3q7DD+LP8jbj2 KRBxJIN1rg8px0GoCW1IHfpOtxDhOwUv/czfP9GMcm5Z+FDGTv/idyOO5uYUC/93+UaBmsX0KT7 8hHXY02QPb6Vd9h2J6sZ9CqODuav3G6nSkE30tXmxhcTgxFjyORE9rZ9oaB2+NJmdnVpDhzXGEj +EPylFtX0K6tMYtQ4uNi7y/VaxLdRv0mPdpZScSUsZOiFy700igzRFR7WxTIXHtZCKpEw/qg+F2 HiJcL7mQbfrwehqYTVbgPGmwbfRKTsMVH3sth4ZIwhFVMLpcF0dSykKVs0z0vl4nm3KX16qpGpI XIvJkisnr3srKiRFm2vjTsYFP0SaSvTYwEkIrPsAcPCXhUGqrzaeCZcdNy/J88r6PDk8Iy4LmRZ fAeUVCqndT3IwvrQjPTGeHbsliyfxMCTC6nJ8K393jjuRb3eUNj1G0+WLTbt3gir5LjhcVBkQet 2963vVk5u1+TU8ws3FJFLHtz1wuLE/0Gw== X-Received: by 2002:a17:903:3807:b0:2b2:50bd:83b3 with SMTP id d9443c01a7336-2b269a8c61cmr55995685ad.10.1775095186935; Wed, 01 Apr 2026 18:59:46 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:45 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:44 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-7-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=12288; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=+z3XgfP7gaQKNpPG0pzbt5WpGtYXleKwSkc9YuyC0Qc=; b=KyjFsleYgB2Cu7t86FhoA5eQDnIzDp4k4NwkHy0U3nEC4MbwZOFGuustLrlYFodv2xxOgoxvE SC1Ru1W/4cAAzCQdkv4Faag7TUIHvd86mLzRAn0PFMLptfTT4mkGvgs 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 | 26 +--- drivers/hid/spi-hid/spi-hid.h | 45 +++++++ 5 files changed, 315 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 733e006df56e..3ca326602643 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 CFLAGS_spi-hid-core.o :=3D -I$(src) +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 802615565541..d48175c764b9 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -43,6 +43,7 @@ #include #include =20 +#include "spi-hid.h" #include "spi-hid-core.h" =20 #define CREATE_TRACE_POINTS @@ -110,31 +111,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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 2026 Received: from mail-pj1-f49.google.com (mail-pj1-f49.google.com [209.85.216.49]) (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 0AA4134C141 for ; Thu, 2 Apr 2026 01:59:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095192; cv=none; b=Zm2QH7NWo+nFE2qWnAmbyAftareyuRAhtGg6iNZY3Trsjz+5/3HsB/XnlO5G77fKG/EQ3NB29U0rSTZl9ruWP9owfGriKBysSV8mLMP1ZIS2To1/X0et1Nhcncmn2RZRB86T/djvHr/OhO3a9sj/mfRbbcxGsAfMjxnM9jMy4CM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095192; c=relaxed/simple; bh=muYzCeJxxDVVoGN5ZRXonkUdkqtuysZ7Wys8uAcwP2Y=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qmdDn8UnxZOajKQhkOVV9X4eKm9BL/yTT8mfodvI3KFJOA+xUVySXvxAhtQDLfx9g0j7RiTbhj8EtecLZGdg4jljTk+/gcHkutuDHlad2zBQEq62Pi1XbYT3y2uRukcOuKBsfupUC1IAZswmlPhSHS1rBy0JTsk9+IImS9+aAAw= 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=Wk6O4AER; arc=none smtp.client-ip=209.85.216.49 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="Wk6O4AER" Received: by mail-pj1-f49.google.com with SMTP id 98e67ed59e1d1-35da2d35eccso190334a91.0 for ; Wed, 01 Apr 2026 18:59:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095188; x=1775699988; 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=ci9N603dJdfsrRIavDTc2BSxOYIaJmfIgiSDMr4kr0s=; b=Wk6O4AERIJ5LocFJPSwl5UwQVnSbB0kGTGOWilSjgNeLn/qqmesNJpQguLzpOgqW+a wYSd6tTptqhdbK+HDFbZttfTC9s3Tlprl0FuAHQhBbSYxjtwZjXI7WQl+lI0PvaiOmf/ DpOd2dywKfsU6bsjmoMhoxhDpl91uW4kRxvwE= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095188; x=1775699988; 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=ci9N603dJdfsrRIavDTc2BSxOYIaJmfIgiSDMr4kr0s=; b=mDY6FmEZi2IwaIvtF8OJ/fulgHfu3aiVdceifutkzaD1jdf7OfA9kJ+vZQr+yqOEpd QN1l9CLrkrzY1bvzcLnSUllJytjcP+FXLMmtY/BaBvgOGLWlPKAsbxJylamn/rn0fEJN gv9HH7ZAEQ8eVsjPpftIJqtVIkIGeP+O7kXtnGkM20ZQOPdbZ5X+JY3Z1twvROT7Qeiw 27aaO/fSForVzpza7o5KwouuX89i7NTmkvurvhhVhDxA7iT8UqC4DYlSMvotmATLhz3Y /ZGi9OlD9xgfmTF7AMDBj2GXHgiNHLLUxhYlVwsVxU//Pv0y2jtsKxQt6ZNSRX+VJgF1 VtKA== X-Forwarded-Encrypted: i=1; AJvYcCVSihwyhUkEDu6lqnJSPmwHPwhcvUvVdwIhsZopmK3dnXJu/Yl9FDVlYMxhHri8kS7UiXwCCd3az8hFAyU=@vger.kernel.org X-Gm-Message-State: AOJu0YweEOYiSSRQE3qjjDl2ehJlvGkuSUPr2Rij6+GLdMDhl2FCgATv 9MiAhhX0bxzUfvO4yJX3WJbUdfw2TIxFVyHZsDnCQWBhoRsnx75dVdNQBiGXTYP0ZQ== X-Gm-Gg: ATEYQzyknchdZNQEcEriQGJzzyULvqKy3G67ankd6T9oXABHp4vrC2bEeQYze9ogrgX XOPk4MdMpUlx1qEvlk7PcuYMUThgo1RhcvmP2RQB6NeWAbGXPDhD/1lXn03anFfFIUAxhadJvC5 v/pfDsvQueZ1NuATv3p3kqmyUUG8+tLKfrtf29brXhjGkuMpok0PgZwCb1tDEUS3mj2/YOpzMso L8Gxjd3Fdy4Qyve/hGyeyGNIn3wJr3Dwt4QFPbYaz6qBgelfff3GXTJquyCAISGgeJAl3Oclx20 foZDlGQuWGQFZTzusjk/BbUUuwWUGorg0AZGu/KSBOyn27vR0DpUl6dpEsWXIgyheWHzuBHme73 YVqb4aPRoKcHq3e3oWvq+ipJ/YxHh4xOfVVWy/t/icDHQuJUJRHQUu0RODMswN1Uv+3FD1wdkU6 abwtCHfrer6PTzT0a2KQnRGLah21T5MmqAXzm/H6RV0gC1JrmyNk/LzbPLA9o9eFR+ky86S5Ewi 83TYUcN4ZDpX/JSbBrPZQYgLEqY5ezcLA== X-Received: by 2002:a17:902:da87:b0:2b2:50f6:cdd9 with SMTP id d9443c01a7336-2b269aa3b73mr60215885ad.8.1775095187988; Wed, 01 Apr 2026 18:59:47 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:47 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:45 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-8-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=8657; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=dkIDzLBKdAqTbNwZchQpCk88/EtnlSaDxMhx8sAerwU=; b=vr2vdVnMrpKHC0DyZIyl9spKXeNl9V6J92Ju1O6Xne+YKaIYVQfFLm50hSetEbpNdQMM/Fi3o A1E3qmVS3usB8zqPhUD1RV40nH2TG/my1dQ9OzzC/1b8ghKU2fzVnbB 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 3ca326602643..31192e71edae 100644 --- a/drivers/hid/spi-hid/Makefile +++ b/drivers/hid/spi-hid/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_SPI_HID_CORE) +=3D spi-hid.o spi-hid-objs =3D spi-hid-core.o CFLAGS_spi-hid-core.o :=3D -I$(src) 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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 2026 Received: from mail-pj1-f51.google.com (mail-pj1-f51.google.com [209.85.216.51]) (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 05C8834D4E4 for ; Thu, 2 Apr 2026 01:59:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095194; cv=none; b=PjjTcxl106wcXhExm8vr1F9qijiFTGAn83e3erria1hXxMnwIGcv0Yw+ylSbtdhbOe26HgVUs3WhoHnXco6HBXLPio86j+v2urGsOkyqEevuyK+cjKooVuTnDVnjTtqUdTC5H2Bf7MhNEd+oPF7Lq8P9Q6btRJiJjaw4e0tHq94= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095194; c=relaxed/simple; bh=G9ErukyjtFQQONiLAGMEclYQ3lC/nZkKFqeuRk5+bII=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=m0+6MSbqxnL7LrLc2J9gkhJpXnkta7W0tYcm/p0ZKDBfdULmjE/1RqsnIhTtBJ7E8nsyy2Y8rtNO9yaKjqUgaMXwTCav8YDTPAq60OCfahOcJ6weJW7PPsih8L8sN//W9K2bKfQJ+I77ua0CPyeE1Ior2HcM9y23YZxGNpVSrGY= 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=jpZTNAKK; arc=none smtp.client-ip=209.85.216.51 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="jpZTNAKK" Received: by mail-pj1-f51.google.com with SMTP id 98e67ed59e1d1-358e3cc5e7eso136960a91.0 for ; Wed, 01 Apr 2026 18:59:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095189; x=1775699989; 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=1HRLPCzgG72vKngIOPMMcPHhtLpKEoLGj3vPRDEvTYE=; b=jpZTNAKKcsuq6+jSNZvtl3J9HxWg7l4lLK/mRVHB+SY40WA9ynaQUrLU8yflp3A/lY yOtxjy7s6rAmPmxJFZh7JwAHwZ7YW5El83xBgHBdM4Ul9mPWbxf/A6mcRmtcGF0Ui3Ue zw21cVYOkx6mh5qjwy5yyHkbqscThNNrSSLNo= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095189; x=1775699989; 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=1HRLPCzgG72vKngIOPMMcPHhtLpKEoLGj3vPRDEvTYE=; b=c/qq8JbyRhdHKu36Kv6LfpzToKr4K7IiMLo8lywWPBmgyzuRyNthDFxX1UbUcZjycf ZYwagknTmtszIf3MR9/A5ILk3T3LBfqAO6dk+BVCm92BTYfT9dW0ChwkNN+SW1faZqqS w8OTzhWqVU7rGdPIfNxVNsHQaFr7Izp9cjehMMnejPJLc7M8GvLEARrNstodkS/wz3T1 vb4YCWl8SIUFavow8oETbCWRwK+7d9u6WL4SqEDlHWv+qAjJRYOiVnA54Wu/Fl5OfuTw LsXpmaz+Dr3twufamaK9QZqbGOEunlBzExsiNxH/SSaNFDTlNvEPxGPD7uMGMsioUAJe Ysmg== X-Forwarded-Encrypted: i=1; AJvYcCU3bIVWI2bDG+m1h+ToqK2jlVwDZ47Xt/jeRJVEL2Q9U0DCfyVbDyQRh2JO2McHYQbEy8NX+rrE+Sok0nw=@vger.kernel.org X-Gm-Message-State: AOJu0YwcYwJjBMscCxHNJY8/Sas/1caKEhgDqhqhgYfUZAMoeYwmeEKF zq0lWPF2FiM0UBc+caVtvdPU1nGHC0WIq182fIQnkg+VijecLRDryOZf4KQdR3yHh+eMpRbAR10 XTl8= X-Gm-Gg: ATEYQzww9//EVeuK+MkrfymDUrGjl6KSenTvyHr70ZfO6gLxSpgtGVD6iepwkAoOpEK FOB7QJjhpMnzfka/GUtNfINhVeUShG4v4poEU+Z/6Us35OdaRbb/bt0DH3v+FSzF5zhxDdP5yZv 08t1iUEtVPPPoZ5O+BfTc9Wf3JKGiFo1QmudBunraLeVzi6q0GNMVWL4RRgBDAqUDw/pBMdCuhv CyNnwl34zMl69+khV5QrJYV65PkLTaNsnEnKiI7u3gccmJCvl13ghEN2FOCjS6Tc0Tcx4gGt2Va 2Y5vXEUpDq5AcOJBLeC7fmWTdRLLm+Z2oSeZsHG81mlHxc41B9UKNv1vtkPJ1O/BJ6nZPaQxtzb QdMRONSswr2f9RkSMfXAy7rlzTxy8op2V5T7uWAx24ZE4vrkskyzvY9kcDSTMqsUnWs/zGobIRs YclOUyIxXPU9oiRjXmq+0UFZSE86wLdBONk80UR2b7S2prToUFHC0VZ59130ENWQQEZKERXrOl5 R/8wpHrJ8wQ3HC749XGuAmkkmEPK4cU7Q== X-Received: by 2002:a17:903:22d1:b0:2ae:fc60:2650 with SMTP id d9443c01a7336-2b269c94d5dmr58282855ad.39.1775095188890; Wed, 01 Apr 2026 18:59:48 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:48 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:46 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-9-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=4684; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=G9ErukyjtFQQONiLAGMEclYQ3lC/nZkKFqeuRk5+bII=; b=ycKoinGQcCRHLiNXC/cPrBW/8Eb646sAYUjTLCBWfa8jeV0fetsq980x3F7Mj3e2GB9rJqrL3 pMQPugikmo/CIMzn+dRTxihOXhj4JvYuFrgOkwZyMmKNE9uU1LucVvv 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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 2026 Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) (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 0983A34F489 for ; Thu, 2 Apr 2026 01:59:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095193; cv=none; b=qUKkiWEomPltsUWwiP8uHHE60IT8Q0YTA7ZM+CE9XlQQPaNwHhg2t8E/mIysGdsax4A44616W3QXrN+L+y13xX71NIw22tm9ER5O1oJ2/elmvuOvDNVdN9gx5gbb48qZwdQNYYr4D75t/o74cSOuwldjjQ4kffmDmFKitnnz3QM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095193; c=relaxed/simple; bh=IsaZ710oxuBN4PiA+69dKS/EYofhjm1DhhAZbjz2WWI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PUM+s9WzWWkR5TSe6Pzm/hxf/6u2XumXpWk+Q72DcfIb06h10T7dXhxzeVGi703OiOLaU8ViixWe3l++zezMhoY9XI0bKujfnpmhOJAK7mk9YDiqZeb6SXsxL1ki4v9fq/+6eHmEHhatlsg3JfYk1DtOY/e7G1Lo58AtgB204cU= 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=M+Y59rIf; arc=none smtp.client-ip=209.85.216.53 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="M+Y59rIf" Received: by mail-pj1-f53.google.com with SMTP id 98e67ed59e1d1-35d99bae2ebso281188a91.3 for ; Wed, 01 Apr 2026 18:59:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095190; x=1775699990; 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=eD7F1u44Jio2+2ZFUsBLXdqQbokoRei5mkxn2sOyNEM=; b=M+Y59rIfmowLxJXl3UOfLRPIBM3hrzyEK650qtn2g2croO3QMu4QbVL2hVuD5rFkDN mqAHLihFGFyR4GulofsALlJl485BRhnQevdY9NT3JmikPPn8LxlSAtKQDDTqEv8TzYCW 1l+SVw57NOoI4PuRPha67Q+IhyRezvV0be71c= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095190; x=1775699990; 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=eD7F1u44Jio2+2ZFUsBLXdqQbokoRei5mkxn2sOyNEM=; b=o3aoDu3F/doxfKMjzWIL3WkA9CWVcIz12Ckkp6D6bFhWN0FQ4t1dgV0Le4SJ5gs+yP DIbXI+hBnNpEEmrt7X+nR9hfmOTiC0IGWgK6jvFotgmwAoCsToUjo8imAJCCUZRPGxZt 5FR2H2vPezQo7BZNAfdkYmqjZEp+CMB9afbzNPgjQuK/JJpMwRiCwP1EQ1pUCjf3sTJ+ HZ0spTY/34a8hAMDRjgi5Zi3X9dvLYPhELE6HW7NbJMNMBDEVkMmQeuN/1PQkMthHcyv wABZd7iHG1Slz8gWOGqlPHg7ZcbvlETB9VMVjgmLntNmBNdSZIdxzOL6Cyv3gEnIYDPz go7Q== X-Forwarded-Encrypted: i=1; AJvYcCX0z7qOrddIgXqoxKHjIm7t3WOacwF4O4uMI0ktppWh3U/7A5mtmDO2yIInr/mFx62bUFu6yofcAAW+4LE=@vger.kernel.org X-Gm-Message-State: AOJu0YygcaYnaLLFYi8QE651L1KS115x4SI4g554tyTn4w8UsXal3Xp2 X78DIjX2l9bmtNiw9slWx1F5ybcjvuhjxXjPulQZ/Vce4dpNEYnlc+0s1BVLV8sQRA== X-Gm-Gg: ATEYQzzD2ZVMQ71j4NKmF8FvuhndLd+Jn+YsmjqmE/QH0E0kLX+LP4i6DSdW9BSU7WU MzIgwA6xEzq+mLtSALhlkCe1tAmh9BGn4HyKKyAFNUymgJckiyt6NdNdYn1hYIRkVsMAD3Cn9z0 p8VyM4VhDTP0BqnS4G2y94d8pJdQ44DKJIZysehZrYihyDBtqkzaSHjI5lNpiekz+8H4o08Sbtj EIjOUg6cI84kGHwGHO+uIPeMpLrMBIAq4JKrjw75zarJn9Vkci/8nfw2q4nZcdYuOnXVtNaR8ZC dRjsd4uG+2hfkk/Ed/6IbKuj5lSD01/jk9QIBWfkh9QCBw0eXHuPkepe7dHj9b5QEyrEK4dmYf1 JwRwPlT1IEodnvljJoupWerIpuE7D8QUP0vIKasszYI8ASALhCh0EJfU/rjiKpzkYhRv6GzdD2F DIU/UpsNm6zVLiqweqR8Y6rkQTKVisya1LBZFYdPUtV55YEEuaFV+d/dOI3LzvrXInF3nSVO3AA boNTinXmHWrG6OVwioTm6hBEgYjBVUL9Q== X-Received: by 2002:a17:902:e54a:b0:2b0:cb96:9815 with SMTP id d9443c01a7336-2b275da7802mr13614315ad.43.1775095189867; Wed, 01 Apr 2026 18:59:49 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:49 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:47 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-10-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=5452; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=IsaZ710oxuBN4PiA+69dKS/EYofhjm1DhhAZbjz2WWI=; b=sBQZ5/1CaFm4bih4+ceHjvhV3qaKwRPf7JlWoD907mU3+jK+oEsLVs4ps/CdORmGFzJTYd57q Za+4vFrNs19BHkaem3YK//a6rCIcoj3XRCtafJnCAeYZzipz0Z1T3uK 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 d48175c764b9..5f7a5bb692d9 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 @@ -244,6 +246,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; @@ -1200,6 +1277,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) { @@ -1231,6 +1315,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.1185.g05d4b7b318-goog From nobody Sun Apr 5 13:04:35 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 3BABE35295C for ; Thu, 2 Apr 2026 01:59:51 +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=1775095195; cv=none; b=acKvOM7jDASpofco2YjsTzfp8EDXGZLrtkhLYShrV3EUQVt7/wZzX5HutsH4Mv8hERpFdi0GlymMWkxTlezErmcLv3ZuVZy6ujCzqjmBLsHqZdJc1Xb/Qecvv6aXr4np5+bW6oBuTn+UpypkWeH3owMtAFMPRD/Z9mzO5cYcZe8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095195; c=relaxed/simple; bh=GBCgfFdmNxX9+OgrVg4WaE5DIA07uRQnuGXMjbga6H4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=SHFUg3wvDjzWmir2yvY4dtve01huyaPkNJ0IPLnclylGMaKXPSyWgSs3BEk3MDHs+aPkpWjC8zM9NMCLXMsHa1yt2HP+MmWF3C8/LRrzgoYR9edlzEfUpk4i+zYORHtiwUG8+Qh2xPUoSY2eEn89J2DrWVuVzQl2X1tIhFFjQEM= 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=TifoRlnx; 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="TifoRlnx" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-2ab46931cf1so10498335ad.0 for ; Wed, 01 Apr 2026 18:59:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095191; x=1775699991; 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=NWRl/pdZN5I8n4vNv9o2RQjMPPH8QcZcS//VFK8KffE=; b=TifoRlnxA41757qDVJz0Fu17MMURDBQC0VPBin9YmQBqx1UiFSC7SnyicRqxDQUqKp C+5Zrypg4XA2ImPWVHdf4kPWYzQvNQdOBGK7f28vNhzv6idpbwlga7xIhgwC+C1y6g1E OGXrAeBMZl782vFqnUZLNWfxBYSj1Bhiwfj5Q= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095191; x=1775699991; 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=NWRl/pdZN5I8n4vNv9o2RQjMPPH8QcZcS//VFK8KffE=; b=LhpN9pIMESy6/+aWjKZwGrkl0bmsAkvRE7BcWJO+BiI+mbPStQ/K51XDOwJGqPjwYE xJxQUg0E+YnGFTauGOX8knJRsbAsw6bRvCNhoAqNBosYSW902xoSmjKqxwaYFR7uuong TVbmxGAvcUqKUZ/FUi7RcFSgOf5TjchUpgbOSDQlhH66PbReV+W7Uf5nMWtm0ju9jUtM YcZp3N0sitKJTcEVMZ2i53eXsNkTBqYaLYucsQVkdr1L8VI6fK18tFrLJCx2MS0ZPunY 6PKyZvvv5c+OU8JMkERL4+lFHIzmAhLakanpaltE7u/FpZW/vY4ZhEOLdxE5x9GRo9yw VwLQ== X-Forwarded-Encrypted: i=1; AJvYcCUYmtwqEhdN9odJQWWzcyYr23C8ctucvSMnIqjVcaMNF61Kz4prp5r0UCZmkJQPhwHLPDNdDlIlNWX2XS8=@vger.kernel.org X-Gm-Message-State: AOJu0YwevvlY4a04tHQQMjstGdqimF3DADkm8UcbhfMq7eKYtxeIp7D1 lqoUNSxQkDnwGPsB/68ki+FFxW2BSShA+hKz2nO6TVvRE7zMLCrJfHBvtsz/kKSRbQ== X-Gm-Gg: ATEYQzykjn1eQWyvZq7uEjoqHRk6ZmhjLqQqdOlYNtFJSBmbqwwWq5ZRb2dYviv5/oo QuMcfhV++Bdzv4KSVsGkh+uJ93DKubEU9ZyEMiNbO5Wf8LogPJ+XD9xGuFER3dgehDPOnPmRCiL fw516xOT2yty6JPcILwiowb7Isvd/RhuiKFNiK7KnIrN1QTnSfanBWu0j8B5FrBZ229Su7mwEOA 7ISzfvWq9KS6I1szAqcrGjT0AYEK5BJuE7gDXbVAjqW3OmufZGplWK19upNCZa+Hbk5FhHxSoJh gXQSsFNlBMVGwNClrqvY4nhdBQ6K9fJuIf83k3CcumeQHqDH8MbhnrHZ3L4WIKjz689lhLIjU+0 KEyjtJiSxkf0r6buTto6Xau4zO/I0l2R6uNafHOu3Tyuxp8smQ0qHDxicfltQkkRUnUuaLvgL51 dPoilTnRQHrUxA+Wwm705jgL2ntMxnNEa15CBr4RGOBJ8YVLyThAaH8uUtwPDTgf80lXddf7Yqw TVeLuOS+lqyrE14bDQTIEMmUwF15pUHMg== X-Received: by 2002:a17:903:1aee:b0:24b:1585:6350 with SMTP id d9443c01a7336-2b277df754bmr5416395ad.11.1775095190960; Wed, 01 Apr 2026 18:59:50 -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-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:50 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:48 +0000 Subject: [PATCH v3 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: <20260402-send-upstream-v3-11-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@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=1775095180; l=10416; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=GBCgfFdmNxX9+OgrVg4WaE5DIA07uRQnuGXMjbga6H4=; b=7f4b91lHmlf5TYFEkbbrgzi8PSgBr3/69XLfgY8PZh2sAcrCKQdt4dNTBD2zvy7mxEXouf84t zT2LB5hJEr2AJxS5Z1czURg1AYqDDJWcLBcQsD+NO2kypaT1TQEg8qG 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 5f7a5bb692d9..9eedd4f1cba7 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -246,21 +246,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 @@ -278,21 +278,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 @@ -306,7 +307,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 @@ -315,10 +316,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) @@ -1215,6 +1219,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) { @@ -1234,6 +1364,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 @@ -1247,6 +1378,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 @@ -1257,42 +1389,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 @@ -1306,6 +1414,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); @@ -1319,18 +1430,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 293e2cfcfbf7..261b2fd7f332 100644 --- a/drivers/hid/spi-hid/spi-hid-core.h +++ b/drivers/hid/spi-hid/spi-hid-core.h @@ -10,6 +10,8 @@ #include #include =20 +#include + /* Protocol message size constants */ #define SPI_HID_READ_APPROVAL_LEN 5 #define SPI_HID_OUTPUT_HEADER_LEN 8 @@ -56,6 +58,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 @@ -66,6 +72,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.1185.g05d4b7b318-goog