From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f179.google.com (mail-dy1-f179.google.com [74.125.82.179]) (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 F01BE199920 for ; Sat, 24 Jan 2026 01:49:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219355; cv=none; b=Kuekd7X2B2rVsmAUpEZ2c/hAHfMYByvj8GzFV9OYjhoX3TeJdHM1upY4Yhkn9BtHy4uwE6hkPK6HeDqyHz4/DvlgDrjPwvlgTcgA981Z6MqfW2U91xn97XVC69PUUKE4WOU9XodjfyHwtlRLcNzoIRk77KOIcrd67uWMb0MVqaw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219355; c=relaxed/simple; bh=9P2X6lBhLKEX370B2D3nfIFaS4rm86UkbX+g3x5l7u8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fVlLxBGfIlVzyMBHu6345XUmLuckSMQWdK0rzcz4NACIwPoav+V5oNs4m8ROobchtg6Q/7nsKf+TXoEiEMWzEW8bt6eJL4z62gWUy3Rb8nFcSSYg0ztu4I+utiAXeXO0jqZPyOfvaLQ/602ewQvHjX9ninvEVzMms6sqRwjk2FU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ONnHl3n1; arc=none smtp.client-ip=74.125.82.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ONnHl3n1" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2b72e49776eso4455640eec.1 for ; Fri, 23 Jan 2026 17:49:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219353; x=1769824153; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ZF99/uWCYPK4U9PbgOwY3pTx4yNCxW3AAGZV8/M/3yc=; b=ONnHl3n1R+KgAHPPumQ1QEM8Z0oAbLiL+loOTS5k3fPFHuy0bK3TWN/btHhbLDDaXW zQt65McIZo5Kp4RKr8Wng1SW5F80+V3V60LCn7ClxVwEbmb0dlWCUTaQfBFhmCabTOVn +jlYp30irZ9wcN7BiTXck1z3Awe2BW1x7BGx4nsIVNRZqgqhTLdvnQax3WrcMZE03OBw Dbm5UT30Rj41BLYchXi2aTp1qbZ/te0/JEriiz50UP/hN7DqFeVLG4TSWl1wnDR5rr3t RgIQTXVyv35/EJJ446u4mgJPEjo0uk0C++7LCHIWE1agg4ZS3b2yOV4+r5YDY2bxaW97 g1+g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219353; x=1769824153; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=ZF99/uWCYPK4U9PbgOwY3pTx4yNCxW3AAGZV8/M/3yc=; b=B5fWzX9heUdd18KeuFRgdpthY8MXoVKO+LCU3vVUfVDwkTNLNyzSlRXR9P8X4NV3zY ea3uoMbdpg6UPzq5n6QbXee350g19kyXch+y3vKkEO5Nn4olvu2N6jPdal6kkdq+wYXe 6rOyu9giB4cidW9w/7fjfyhZMYYKA0MDlwY5Pp07BqNnUW/iBObqhWjtyZCCaMnrpnim QBZ2DYnK+npK4OogxVUKUQeHiFCtd+ayzWwInv/Y8kRhC1VECRXF+w+gmIixVieVZWXb SmMd0WpV0ghaPJIDCHXHIhkqlp4M/Utv0pYIBRvXBhVds5I8sQcy7SYdKIVQHF6DgtKl 1PgQ== X-Forwarded-Encrypted: i=1; AJvYcCWF464NZdchK73VziiRJJp3M9GDsTzI3CmFJ2t3Z7JDZdk/ifr1NvWpR13tCnwVWJA37KEmmmfN4pk0QRI=@vger.kernel.org X-Gm-Message-State: AOJu0YzhO/YhkfqwCCQqkyhmrc3cFM9ZHoPc9kLf3yTEZBsXmLSjnMz/ LzV7ZRiFe5OZHvAC+6UVjXmP21FqN0Sgx1TkuJu0WGMpOHsvr615q6fK X-Gm-Gg: AZuq6aKX4uSdxKx0BrL8uVFxkOKeh/86cJoGBuLXSnVxmuG5BfsbTHBaLWpyR7lT+qg oH65wA7OeYYgnYSGG0xPfV4kChCt7njF/RrMtWyzvOlcAPn5Jky4XnVoqt62m7Puujn29TmOJ4D lnW78ApXZh1IjuOSkoIFsRRJratyexYExZcZsAhEAQ20df9ikRMRr8S+ppXJxigX5e97dwUyzHy vEW/V5m5t+WmuKkmKBCTrMm3cHR0BMZ/CPtd+ftKBNi/TffvgYT++phYfcDWOsLQ83FaAcT9WyW 1NubHNBig3opu4RRovnOPfDCfVXdijgBbK2FgYBDEjXzVtX17chY/ypQsA3RfThSeafrHI5uP1C bYkxq/S0PWcNb9bktC6llBHUbdae2abvgu0xK8aIxYwTvOMitNff4Zvz/bgb/GcKb9bzHCE9l35 ertBjII0Dr3FdSPAXjXx9pIvwJcA6Xagtq48pSwjpb+VZm8KJeOuXN3/xmFfmZNxFQR/hTp6PN3 Q== X-Received: by 2002:a05:693c:2296:b0:2b7:19f2:6b59 with SMTP id 5a478bee46e88-2b73995f8c3mr3558092eec.9.1769219352977; Fri, 23 Jan 2026 17:49:12 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:12 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 01/16] include: device.h: Add named device attributes Date: Sat, 24 Jan 2026 01:48:52 +0000 Message-ID: <20260124014907.991265-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds DEVICE_ATTR_[RW|RO|WO]_NAMED macros for adding attributes that reuse the same sysfs name in a driver under separate subdirectories. When dealing with some devices it can be useful to be able to reuse the same name for similar attributes under a different subdirectory. For example, a single logical HID endpoint may provide a configuration interface for multiple physical devices. In such a case it is useful to provide symmetrical attribute names under different subdirectories on the configuration device. The Lenovo Legion Go is one such device, providing configuration to a detachable left controller, detachable right controller, the wireless transmission dongle, and the MCU. It is therefore beneficial to treat each of these as individual devices in the driver, providing a subdirectory for each physical device in the sysfs. As some attributes are reused by each physical device, it provides a much cleaner interface if the same driver can reuse the same attribute name in sysfs while uniquely distinguishing the store/show functions in the driver, rather than repeat string portions. Example new WO attrs: ATTRS{left_handle/reset}=3D=3D"(not readable)" ATTRS{right_handle/reset}=3D=3D"(not readable)" ATTRS{tx_dongle/reset}=3D=3D"(not readable)" vs old WO attrs in a subdir: ATTRS{left_handle/left_handle_reset}=3D=3D"(not readable)" ATTRS{right_handle/right_handle_reset}=3D=3D"(not readable)" ATTRS{tx_dongle/tx_dongle_reset}=3D=3D"(not readable)" or old WO attrs with no subdir: ATTRS{left_handle_reset}=3D=3D"(not readable)" ATTRS{right_handle_reset}=3D=3D"(not readable)" ATTRS{tx_dongle_reset}=3D=3D"(not readable)" While the third option is usable, it doesn't logically break up the physical devices and creates a device directory with over 80 attributes once all attrs are defined. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- include/linux/device.h | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/include/linux/device.h b/include/linux/device.h index b031ff71a5bd..76f485e6b24c 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -189,6 +189,22 @@ ssize_t device_show_string(struct device *dev, struct = device_attribute *attr, #define DEVICE_ATTR_ADMIN_RW(_name) \ struct device_attribute dev_attr_##_name =3D __ATTR_RW_MODE(_name, 0600) =20 +/** + * DEVICE_ATTR_RW_NAMED - Define a read-write device attribute with a sysf= s name + * that differs from the function name. + * @_name: Attribute function preface + * @_attrname: Attribute name as it wil be exposed in the sysfs. + * + * Like DEVICE_ATTR_RW(), but allows for reusing names under separate path= s in + * the same driver. + */ +#define DEVICE_ATTR_RW_NAMED(_name, _attrname) \ + struct device_attribute dev_attr_##_name =3D { \ + .attr =3D { .name =3D _attrname, .mode =3D 0644 }, \ + .show =3D _name##_show, \ + .store =3D _name##_store, \ + } + /** * DEVICE_ATTR_RO - Define a readable device attribute. * @_name: Attribute name. @@ -207,6 +223,21 @@ ssize_t device_show_string(struct device *dev, struct = device_attribute *attr, #define DEVICE_ATTR_ADMIN_RO(_name) \ struct device_attribute dev_attr_##_name =3D __ATTR_RO_MODE(_name, 0400) =20 +/** + * DEVICE_ATTR_RO_NAMED - Define a read-only device attribute with a sysfs= name + * that differs from the function name. + * @_name: Attribute function preface + * @_attrname: Attribute name as it wil be exposed in the sysfs. + * + * Like DEVICE_ATTR_RO(), but allows for reusing names under separate path= s in + * the same driver. + */ +#define DEVICE_ATTR_RO_NAMED(_name, _attrname) \ + struct device_attribute dev_attr_##_name =3D { \ + .attr =3D { .name =3D _attrname, .mode =3D 0444 }, \ + .show =3D _name##_show, \ + } + /** * DEVICE_ATTR_WO - Define an admin-only writable device attribute. * @_name: Attribute name. @@ -216,6 +247,21 @@ ssize_t device_show_string(struct device *dev, struct = device_attribute *attr, #define DEVICE_ATTR_WO(_name) \ struct device_attribute dev_attr_##_name =3D __ATTR_WO(_name) =20 +/** + * DEVICE_ATTR_WO_NAMED - Define a read-only device attribute with a sysfs= name + * that differs from the function name. + * @_name: Attribute function preface + * @_attrname: Attribute name as it wil be exposed in the sysfs. + * + * Like DEVICE_ATTR_WO(), but allows for reusing names under separate path= s in + * the same driver. + */ +#define DEVICE_ATTR_WO_NAMED(_name, _attrname) \ + struct device_attribute dev_attr_##_name =3D { \ + .attr =3D { .name =3D _attrname, .mode =3D 0200 }, \ + .store =3D _name##_store, \ + } + /** * DEVICE_ULONG_ATTR - Define a device attribute backed by an unsigned lon= g. * @_name: Attribute name. --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f178.google.com (mail-dy1-f178.google.com [74.125.82.178]) (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 D6C531E2606 for ; Sat, 24 Jan 2026 01:49:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219357; cv=none; b=K2brEWfDOenQQ5j7EsTl9GYV6IHVUwoG6T7SeOxd0dXGla1lVMdO1mpx6OqwpSAPmCm3gmeTC1pOc+hqiqAXoF80MX6uGC4LoKBu2gOtsjOujvSlELqjWd6shJh21S41JTQmRQdTKbtjsEZ+GmZ14IcgziNRxeES8n8E0f0Yzrs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219357; c=relaxed/simple; bh=1Z7AlPZs4LWIDYHPdC2ffZHRX605q2r8UvSa+x59c7E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cdvKyjcJ0uJAo4WIe0tzy3d2ESUbOdEWzDr/YoLGseDT0eVF4H4S4YDJixnx+PJiiN6lgbXJOZheRtSV3W1LmCLsEFkfI/NyunNYCRPCgAAOj60flKPBOAIpZCZ5oeKz7vRay8XnkEPImtTBZqkLBMhN7+FgjDCPYwkLyDhMDb0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DxstezxL; arc=none smtp.client-ip=74.125.82.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DxstezxL" Received: by mail-dy1-f178.google.com with SMTP id 5a478bee46e88-2b6fd5bec41so6652581eec.1 for ; Fri, 23 Jan 2026 17:49:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219354; x=1769824154; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=XwTKRkRtSvTd9xMQGrbqbO5LAyzoJO85alpWp3Go1LI=; b=DxstezxL+rjsNBwYxOuAbsB5LJWEkrvr2d7jK4NIAh6ODVLwOEy67aJLd4tAOTZZPN pxWQdrUChTpAQl3fs+C7aEEn9Z1E2/piw9l7xIHv+evD8eSaxwbBz5zp/Y5H3bxgf08s zGYFBMPgTbt9/0cYDbZ3VzlKm0PE8wc/caeNu+/MYBlJQPwfe5xewAQXmYGwCCEcjgkj /dY020Yp9rAQsw7A3DzaLcvOGUDw46qLgjcDsjj8phYN4A/VzQUqbbC86mWzJuNUpNvQ i5iJeCERPV6vBY1cMY6+2NC7NJIc8mJXQNvext4qTcNrG+yTsacDLJMtNqp4FPesh0s0 4yWg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219354; x=1769824154; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=XwTKRkRtSvTd9xMQGrbqbO5LAyzoJO85alpWp3Go1LI=; b=jh1qjKhyOARl/I7FfnePzSmmJMmnKc5fU9rSSwEBrMWuxwsPjIlRAeqQB1+hI4mgvS RQkisthg/Yq9uPCuBhQe4PAHKYzNYcVsUa90/lG1DByQ8NvLSOdfYAl21hKMED0ZWb2G 8vb9x9AXaSmfNdSCxs1x8thCfU9phKkH6Jq9+G5XfMdLuSAwMWoSTxc6vzPSyOtKS4h5 Nes0jXtzgFUZQ1s2Au+wv8JIprQlFn/ebeRf/kIOFOitSFv2c4Wd3m9ScTUer76zC6KU xkzAnlN/DwRMwh56uhjepvXYBJxMTNJTVr7V1T2BAIlMZ/7PnViF4NhJKRz0fnXG9uBD spgw== X-Forwarded-Encrypted: i=1; AJvYcCWLUUa45XIJliVud2yKV0WZfWLYqUAqqgjSaNEYnvDCUcmZxUA7GfiFRgjSm/WNI0GknAkN3Z5BTX+5po4=@vger.kernel.org X-Gm-Message-State: AOJu0YwE+pO1Nd2IPCG66Si7ZktP7/BOezNDkT3I7pXipSs6wsO5MM+Y 6LfSrny0O+mfLfZPP5/P/TLL73EG0tYxdROmkBboQ/Pn39iBwzHUgt1T X-Gm-Gg: AZuq6aI+Je1YP17sQxZ/VWCANQYx8ssGUrnBqp18oNoDAoXMx91ygjjMDOcnSzv1Po0 etE9pj0DYl4jyq43zCL6ekruOaUCSiDqinglU/NcJLdqjIsp3Q11htkJme4yLZPN0OwEPzzCjiK 6gzZ0ZOhNv9YG4APA9e9YR3+r0hqeVGzgIYdt3Pw1A7bfp/9IJTy5UE0vmcBAvQ5oca7iSsoLAr 7DuTkpQO6/JqKHANgE7+rmcFTmyHXGJ6qFy/uUc6Z2ul+ihvF1G3SE9beE7LUSQOtVXwLxMsZ0L lOvt/G7IOUWoYgfgl8PLrwZLTPc/FBET4gT1D+Lv0M5eNxhsgVCk/+QMt3LzvJv3GMEvgUxs1RJ l27HTjEsi80w4NwJQrkVyq3KUEYp6drmznBM4r8idzZoRfmcgTOHHXUf2GeusjhnQs0THGNZE78 yNtYVw8rbwsS9b/uEjjWWNY7QaC7rWZJ9QB/x2xwCjyvzKc0BNl6rxiM8H7poALb0= X-Received: by 2002:a05:693c:3009:b0:2ae:5ffa:8da4 with SMTP id 5a478bee46e88-2b74274c9a4mr1906660eec.1.1769219353868; Fri, 23 Jan 2026 17:49:13 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:13 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 02/16] HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver Date: Sat, 24 Jan 2026 01:48:53 +0000 Message-ID: <20260124014907.991265-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds initial framework for a new HID driver, hid-lenovo-go, along with attributes that report the firmware and hardware version for each component of the HID device, of which there are 4 parts: The MCU, the transmission dongle, the left "handle" controller half, and the right "handle" controller half. Each of these devices are provided an attribute group to contain its device specific attributes. Additionally, the touchpad device attributes are logically separated from the other components in another attribute group. This driver primarily provides access to the configurable settings of the Lenovo Legion Go and Lenovo Legion Go 2 controllers running the latest firmware. As previously noted, the Legion Go controllers recently had a firmware update[1] which switched from the original "SepentiaUSB" protocol to a brand new protocol for the Go 2, primarily to ensure backwards and forwards compatibility between the Go and Go 2 devices. As part of that update the PIDs for the controllers were changed, so there is no risk of this driver attaching to controller firmware that it doesn't support. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- V3: - Add hid-lenovo.c and Mark Pearson to LENOVO HID DRIVERS entry in MAINTA= INERS --- MAINTAINERS | 8 + drivers/hid/Kconfig | 12 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-lenovo-go.c | 734 ++++++++++++++++++++++++++++++++++++ 5 files changed, 758 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go.c diff --git a/MAINTAINERS b/MAINTAINERS index 9ed6d11a7746..c6396be68ff1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14135,6 +14135,14 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c =20 +LENOVO HID drivers +M: Derek J. Clark +M: Mark Pearson +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-lenovo-go.c +F: drivers/hid/hid-lenovo.c + LETSKETCH HID TABLET DRIVER M: Hans de Goede L: linux-input@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 04420a713be0..74ac6793b29a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -621,6 +621,18 @@ config HID_LENOVO - ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys) - ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys) =20 +config HID_LENOVO_GO + tristate "HID Driver for Lenovo Legion Go Series Controllers" + depends on USB_HID + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + Support for Lenovo Legion Go devices with detachable controllers. + + Say Y here to include configuration interface support for the Lenovo Legi= on Go + and Legion Go 2 Handheld Console Controllers. Say M here to compile this + driver as a module. The module will be called hid-lenovo-go. + config HID_LETSKETCH tristate "Letsketch WP9620N tablets" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 361a7daedeb8..11435bce4e47 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_HID_KYE) +=3D hid-kye.o obj-$(CONFIG_HID_KYSONA) +=3D hid-kysona.o obj-$(CONFIG_HID_LCPOWER) +=3D hid-lcpower.o obj-$(CONFIG_HID_LENOVO) +=3D hid-lenovo.o +obj-$(CONFIG_HID_LENOVO_GO) +=3D hid-lenovo-go.o obj-$(CONFIG_HID_LETSKETCH) +=3D hid-letsketch.o obj-$(CONFIG_HID_LOGITECH) +=3D hid-logitech.o obj-$(CONFIG_HID_LOGITECH) +=3D hid-lg-g15.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index c4589075a5ed..309e15580a38 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -841,7 +841,10 @@ #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093 #define USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT 0x6184 +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT 0x61eb +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT 0x61ec #define USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT 0x61ed +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS 0x61ee =20 #define USB_VENDOR_ID_LETSKETCH 0x6161 #define USB_DEVICE_ID_WP9620N 0x4d15 diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c new file mode 100644 index 000000000000..6380434b2d89 --- /dev/null +++ b/drivers/hid/hid-lenovo-go.c @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go series gamepads. + * + * Copyright (c) 2025 Valve Corporation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define GO_GP_INTF_IN 0x83 +#define GO_OUTPUT_REPORT_ID 0x05 +#define GO_GP_RESET_SUCCESS 0x01 +#define GO_PACKET_SIZE 64 + +struct hid_go_cfg { + unsigned char *buf; + struct completion send_cmd_complete; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u32 gp_left_version_firmware; + u8 gp_left_version_gen; + u32 gp_left_version_hardware; + u32 gp_left_version_product; + u32 gp_left_version_protocol; + u32 gp_right_version_firmware; + u8 gp_right_version_gen; + u32 gp_right_version_hardware; + u32 gp_right_version_product; + u32 gp_right_version_protocol; + u32 mcu_version_firmware; + u8 mcu_version_gen; + u32 mcu_version_hardware; + u32 mcu_version_product; + u32 mcu_version_protocol; + u32 tx_dongle_version_firmware; + u8 tx_dongle_version_gen; + u32 tx_dongle_version_hardware; + u32 tx_dongle_version_product; + u32 tx_dongle_version_protocol; +} drvdata; + +struct go_cfg_attr { + u8 index; +}; + +struct command_report { + u8 report_id; + u8 id; + u8 cmd; + u8 sub_cmd; + u8 device_type; + u8 data[59]; +} __packed; + +enum command_id { + MCU_CONFIG_DATA =3D 0x00, + OS_MODE_DATA =3D 0x06, + GAMEPAD_DATA =3D 0x3c, +}; + +enum mcu_command_index { + GET_VERSION_DATA =3D 0x02, + GET_FEATURE_STATUS, + SET_FEATURE_STATUS, + GET_MOTOR_CFG, + SET_MOTOR_CFG, + GET_DPI_CFG, + SET_DPI_CFG, + SET_TRIGGER_CFG =3D 0x0a, + SET_JOYSTICK_CFG =3D 0x0c, + SET_GYRO_CFG =3D 0x0e, + GET_RGB_CFG, + SET_RGB_CFG, + GET_DEVICE_STATUS =3D 0xa0, + +}; + +enum dev_type { + UNSPECIFIED, + USB_MCU, + TX_DONGLE, + LEFT_CONTROLLER, + RIGHT_CONTROLLER, +}; + +enum version_data_index { + PRODUCT_VERSION =3D 0x02, + PROTOCOL_VERSION, + FIRMWARE_VERSION, + HARDWARE_VERSION, + HARDWARE_GENERATION, +}; + +static int hid_go_version_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case PRODUCT_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_product =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_product =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_product =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_product =3D + get_unaligned_le32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case PROTOCOL_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_protocol =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_protocol =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_protocol =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_protocol =3D + get_unaligned_le32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case FIRMWARE_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_firmware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_firmware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_firmware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_firmware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case HARDWARE_VERSION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_hardware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_hardware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_hardware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_hardware =3D + get_unaligned_le32(cmd_rep->data); + return 0; + default: + return -EINVAL; + } + case HARDWARE_GENERATION: + switch (cmd_rep->device_type) { + case USB_MCU: + drvdata.mcu_version_gen =3D cmd_rep->data[0]; + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_gen =3D cmd_rep->data[0]; + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_gen =3D cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_gen =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf =3D to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep; + + if (!intf) + return -ENODEV; + + ep =3D intf->cur_altsetting->endpoint; + if (!ep) + return -ENODEV; + + return ep->desc.bEndpointAddress; +} + +static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *re= port, + u8 *data, int size) +{ + struct command_report *cmd_rep; + int ep, ret; + + if (size !=3D GO_PACKET_SIZE) + goto passthrough; + + ep =3D get_endpoint_address(hdev); + if (ep !=3D GO_GP_INTF_IN) + goto passthrough; + + cmd_rep =3D (struct command_report *)data; + + switch (cmd_rep->id) { + case MCU_CONFIG_DATA: + switch (cmd_rep->cmd) { + case GET_VERSION_DATA: + ret =3D hid_go_version_event(cmd_rep); + break; + default: + ret =3D -EINVAL; + break; + }; + break; + default: + goto passthrough; + }; + dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", + GO_PACKET_SIZE, data); + + complete(&drvdata.send_cmd_complete); + return ret; + +passthrough: + /* Forward other HID reports so they generate events */ + hid_input_report(hdev, HID_INPUT_REPORT, data, size, 1); + return 0; +} + +static int mcu_property_out(struct hid_device *hdev, u8 id, u8 command, + u8 index, enum dev_type device, u8 *data, size_t len) +{ + u8 header[] =3D { GO_OUTPUT_REPORT_ID, id, command, index, device }; + size_t header_size =3D ARRAY_SIZE(header); + size_t total_size =3D header_size + len; + int timeout =3D 50; + int ret; + + guard(mutex)(&drvdata.cfg_mutex); + memcpy(drvdata.buf, header, header_size); + memcpy(drvdata.buf + header_size, data, len); + memset(drvdata.buf + total_size, 0, GO_PACKET_SIZE - total_size); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_PACKET_SIZE, drvdata.buf); + + ret =3D hid_hw_output_report(hdev, drvdata.buf, GO_PACKET_SIZE); + if (ret < 0) + return ret; + + ret =3D ret =3D=3D GO_PACKET_SIZE ? 0 : -EINVAL; + if (ret) + return ret; + + ret =3D wait_for_completion_interruptible_timeout(&drvdata.send_cmd_compl= ete, + msecs_to_jiffies(timeout)); + + if (ret =3D=3D 0) /* timeout occurred */ + ret =3D -EBUSY; + if (ret > 0) /* timeout/interrupt didn't occur */ + ret =3D 0; + + reinit_completion(&drvdata.send_cmd_complete); + return ret; +} + +static ssize_t version_show(struct device *dev, struct device_attribute *a= ttr, + char *buf, enum version_data_index index, + enum dev_type device_type) +{ + ssize_t count =3D 0; + int ret; + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + index, device_type, 0, 0); + if (ret) + return ret; + + switch (index) { + case PRODUCT_VERSION: + switch (device_type) { + case USB_MCU: + count =3D sysfs_emit(buf, "%u\n", + drvdata.mcu_version_product); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%u\n", + drvdata.tx_dongle_version_product); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_left_version_product); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_right_version_product); + break; + default: + return -EINVAL; + } + break; + case PROTOCOL_VERSION: + switch (device_type) { + case USB_MCU: + count =3D sysfs_emit(buf, "%u\n", + drvdata.mcu_version_protocol); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%u\n", + drvdata.tx_dongle_version_protocol); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_left_version_protocol); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_right_version_protocol); + break; + default: + return -EINVAL; + } + break; + case FIRMWARE_VERSION: + switch (device_type) { + case USB_MCU: + count =3D sysfs_emit(buf, "%u\n", + drvdata.mcu_version_firmware); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%u\n", + drvdata.tx_dongle_version_firmware); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_left_version_firmware); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_right_version_firmware); + break; + default: + return -EINVAL; + } + break; + case HARDWARE_VERSION: + switch (device_type) { + case USB_MCU: + count =3D sysfs_emit(buf, "%u\n", + drvdata.mcu_version_hardware); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%u\n", + drvdata.tx_dongle_version_hardware); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_left_version_hardware); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_right_version_hardware); + break; + default: + return -EINVAL; + } + break; + case HARDWARE_GENERATION: + switch (device_type) { + case USB_MCU: + count =3D sysfs_emit(buf, "%u\n", + drvdata.mcu_version_gen); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%u\n", + drvdata.tx_dongle_version_gen); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_left_version_gen); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%u\n", + drvdata.gp_right_version_gen); + break; + default: + return -EINVAL; + } + break; + } + + return count; +} + +#define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) = \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_store(dev, attr, buf, count, _name.index, \ + _dtype); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index, _dtype); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return _group##_options(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW_NAMED(_name, _attrname) + +#define LEGO_DEVICE_ATTR_WO(_name, _attrname, _dtype, _group) \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_store(dev, attr, buf, count, _name.index, \ + _dtype); \ + } \ + static DEVICE_ATTR_WO_NAMED(_name, _attrname) + +#define LEGO_DEVICE_ATTR_RO(_name, _attrname, _dtype, _group) = \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index, _dtype); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + +/* Gamepad - MCU */ +struct go_cfg_attr version_product_mcu =3D { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, versi= on); + +struct go_cfg_attr version_protocol_mcu =3D { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_mcu, "protocol_version", USB_MCU, ver= sion); + +struct go_cfg_attr version_firmware_mcu =3D { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_mcu, "firmware_version", USB_MCU, ver= sion); + +struct go_cfg_attr version_hardware_mcu =3D { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_version", USB_MCU, ver= sion); + +struct go_cfg_attr version_gen_mcu =3D { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_mcu, "hardware_generation", USB_MCU, versi= on); + +static struct attribute *mcu_attrs[] =3D { + &dev_attr_version_firmware_mcu.attr, + &dev_attr_version_gen_mcu.attr, + &dev_attr_version_hardware_mcu.attr, + &dev_attr_version_product_mcu.attr, + &dev_attr_version_protocol_mcu.attr, + NULL, +}; + +static const struct attribute_group mcu_attr_group =3D { + .attrs =3D mcu_attrs, +}; + +/* Gamepad - TX Dongle */ +struct go_cfg_attr version_product_tx_dongle =3D { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_tx_dongle, "product_version", TX_DONGL= E, version); + +struct go_cfg_attr version_protocol_tx_dongle =3D { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_tx_dongle, "protocol_version", TX_DON= GLE, version); + +struct go_cfg_attr version_firmware_tx_dongle =3D { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_tx_dongle, "firmware_version", TX_DON= GLE, version); + +struct go_cfg_attr version_hardware_tx_dongle =3D { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardware_version", TX_DON= GLE, version); + +struct go_cfg_attr version_gen_tx_dongle =3D { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_tx_dongle, "hardware_generation", TX_DONGL= E, version); + +static struct attribute *tx_dongle_attrs[] =3D { + &dev_attr_version_hardware_tx_dongle.attr, + &dev_attr_version_firmware_tx_dongle.attr, + &dev_attr_version_gen_tx_dongle.attr, + &dev_attr_version_product_tx_dongle.attr, + &dev_attr_version_protocol_tx_dongle.attr, + NULL, +}; + +static const struct attribute_group tx_dongle_attr_group =3D { + .name =3D "tx_dongle", + .attrs =3D tx_dongle_attrs, +}; + +/* Gamepad - Left */ +struct go_cfg_attr version_product_left =3D { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_left, "product_version", LEFT_CONTROLL= ER, version); + +struct go_cfg_attr version_protocol_left =3D { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_left, "protocol_version", LEFT_CONTRO= LLER, version); + +struct go_cfg_attr version_firmware_left =3D { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_left, "firmware_version", LEFT_CONTRO= LLER, version); + +struct go_cfg_attr version_hardware_left =3D { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_version", LEFT_CONTRO= LLER, version); + +struct go_cfg_attr version_gen_left =3D { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_left, "hardware_generation", LEFT_CONTROLL= ER, version); + +static struct attribute *left_gamepad_attrs[] =3D { + &dev_attr_version_hardware_left.attr, + &dev_attr_version_firmware_left.attr, + &dev_attr_version_gen_left.attr, + &dev_attr_version_product_left.attr, + &dev_attr_version_protocol_left.attr, + NULL, +}; + +static const struct attribute_group left_gamepad_attr_group =3D { + .name =3D "left_handle", + .attrs =3D left_gamepad_attrs, +}; + +/* Gamepad - Right */ +struct go_cfg_attr version_product_right =3D { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_right, "product_version", RIGHT_CONTRO= LLER, version); + +struct go_cfg_attr version_protocol_right =3D { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_right, "protocol_version", RIGHT_CONT= ROLLER, version); + +struct go_cfg_attr version_firmware_right =3D { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_right, "firmware_version", RIGHT_CONT= ROLLER, version); + +struct go_cfg_attr version_hardware_right =3D { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_version", RIGHT_CONT= ROLLER, version); + +struct go_cfg_attr version_gen_right =3D { HARDWARE_GENERATION }; +LEGO_DEVICE_ATTR_RO(version_gen_right, "hardware_generation", RIGHT_CONTRO= LLER, version); + +static struct attribute *right_gamepad_attrs[] =3D { + &dev_attr_version_hardware_right.attr, + &dev_attr_version_firmware_right.attr, + &dev_attr_version_gen_right.attr, + &dev_attr_version_product_right.attr, + &dev_attr_version_protocol_right.attr, + NULL, +}; + +static const struct attribute_group right_gamepad_attr_group =3D { + .name =3D "right_handle", + .attrs =3D right_gamepad_attrs, +}; + +/* Touchpad */ +static struct attribute *touchpad_attrs[] =3D { + NULL, +}; + +static const struct attribute_group touchpad_attr_group =3D { + .name =3D "touchpad", + .attrs =3D touchpad_attrs, +}; + +static const struct attribute_group *top_level_attr_groups[] =3D { + &mcu_attr_group, &tx_dongle_attr_group, + &left_gamepad_attr_group, &right_gamepad_attr_group, + &touchpad_attr_group, NULL, +}; + +static int hid_go_cfg_probe(struct hid_device *hdev, + const struct hid_device_id *_id) +{ + unsigned char *buf; + int ret; + + buf =3D devm_kzalloc(&hdev->dev, GO_PACKET_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + hid_set_drvdata(hdev, &drvdata); + drvdata.buf =3D buf; + drvdata.hdev =3D hdev; + mutex_init(&drvdata.cfg_mutex); + + ret =3D sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create gamepad configuration attributes\n"); + return ret; + } + + init_completion(&drvdata.send_cmd_complete); + + return 0; +} + +static void hid_go_cfg_remove(struct hid_device *hdev) +{ + guard(mutex)(&drvdata.cfg_mutex); + sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); + hid_hw_close(hdev); + hid_hw_stop(hdev); + hid_set_drvdata(hdev, NULL); +} + +static int hid_go_probe(struct hid_device *hdev, const struct hid_device_i= d *id) +{ + int ret, ep; + + hdev->quirks |=3D HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; + + ret =3D hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret =3D hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep =3D get_endpoint_address(hdev); + if (ep !=3D GO_GP_INTF_IN) { + dev_dbg(&hdev->dev, "Started interface %x as generic HID device\n", ep); + return 0; + } + + ret =3D hid_go_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, "Failed to start configuration interface\= n"); + + dev_dbg(&hdev->dev, "Started Legion Go HID Device: %x\n", ep); + + return ret; +} + +static void hid_go_remove(struct hid_device *hdev) +{ + int ep =3D get_endpoint_address(hdev); + + if (ep <=3D 0) + return; + + switch (ep) { + case GO_GP_INTF_IN: + hid_go_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + break; + } +} + +static const struct hid_device_id hid_go_devices[] =3D { + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS) }, + {} +}; +MODULE_DEVICE_TABLE(hid, hid_go_devices); + +static struct hid_driver hid_lenovo_go =3D { + .name =3D "hid-lenovo-go", + .id_table =3D hid_go_devices, + .probe =3D hid_go_probe, + .remove =3D hid_go_remove, + .raw_event =3D hid_go_raw_event, +}; +module_hid_driver(hid_lenovo_go); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go Series Gamepads."); +MODULE_LICENSE("GPL"); --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f180.google.com (mail-dy1-f180.google.com [74.125.82.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B3B8717A309 for ; Sat, 24 Jan 2026 01:49:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219358; cv=none; b=LapV/PnNoouvVwjGYU+d4+bFOBVHWWz2MaReNzpJeQh+P+pG3vSVDGKDefcn6JSn5mm2pgrB+VrU1Je5HcB8HBERkZd/eHiVtGMoJ06HRefhovCMPXQvThV+kHGTkeO2F9NddTC7bAxejPKtvdvmvkrAL+hFqqqccsIvCtUmI+M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219358; c=relaxed/simple; bh=LVEeVSHCulsWJP6rUr/2UWOMpGWB7O86x+Iobggm86Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=c5k6qjuX0KegVEA3qQyWOEJv7pGcALDQiSnnTI3z+VZk44pgykmkNfihiyxfERwBbzidq6OSdrEeGZDPmA4x3PNFqfq5lxn0vlHZIdwCklYMsYRO0/71G07zNDXlAhvXtbaLgUXIpxsYNFPKozqb7Cr68Lib8sgprKZTmzeJ1sA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=evF6Tz+E; arc=none smtp.client-ip=74.125.82.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="evF6Tz+E" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2b6b0500e06so3716074eec.1 for ; Fri, 23 Jan 2026 17:49:15 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219355; x=1769824155; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=TJ7+ao0VbS84Ef+sfxzPkFJS5ibSZVXwMmwKn+qG6xs=; b=evF6Tz+EPjvVa2CO2zotK+0ZQZM2GjXS+A5hG1EpI1TGyX63XHI5+0xkizoA/JPJur KNGaFsLlYe0PWAjcvb/DBxq6PW4mh22/VC52gH0wjjP0EcvuD9uAGvV46mKOacp2pc9E y2cGgkoCTNxS1M+Aw5g7iOPDnqdRa6XEWvM6/dEwTal+5G0sY6jxP68RIUXjcFjBfJG9 TmSe38Syb4EZJP/8GRN3ejXKMordiOxkwnJzvJ29K3SqJEYHUcIlBMGhfU5IUkevMAh5 xkc+traH7dLkTjcQTBfle02+FnHFUwlI6cPLg6Csawc588bVRW9L1IHr8eQtT/Ty0r9Q umrA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219355; x=1769824155; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=TJ7+ao0VbS84Ef+sfxzPkFJS5ibSZVXwMmwKn+qG6xs=; b=b946hISR4Ok5oVI7jPeuorarvGHFGl7cHIJWjMltaOXddZGNjoSeEZ/04ZaSHHZsYu +nxMYHpzayvGwbfA9eZT+jyTQZ02Zp1/4FBp+sF+jLHD5hp/Cl4FKBMkzJVBGzmrzo20 98qV0jxycPNR8K28IAoRuXsnzqIi81bJqtUPWckoGM0OlBjAwuuegW3zmqDI+BpCc2WV chpaLlVABWyvHoK5OiTZKmRfU+VvV+UDbYksIqJ7v/iRwMidilpfw19mqZvhA2HimYuY bkt7i9Qwz3R+EKDV0/8DUhOf432i9vOx4y1gvg2xSI+GkRgRrLyP+OtzMcpT/SSHaYcm 4SxQ== X-Forwarded-Encrypted: i=1; AJvYcCXKmleHFvl1DcgmvwKMdBCCj7/qZSXCN/XCwEVhjVjYJldTDGOTFC1MlYjv4B20bvg5V+V7SrT8jE5gnsc=@vger.kernel.org X-Gm-Message-State: AOJu0Yw6+QgzMz4YYJrJdvT3dcp5SFTVib0ZAGzRQmrEqa7xn14Fjvy5 dfHAmbGjc9stZSJM3FFayRlXmKrGP86rqVmfZcEre6wuCag3ZKJv9nWf X-Gm-Gg: AZuq6aJ/53sDUom+ijEY1fK0Lxna9pV4xQFrUHB1CD3fp6Z7jG+xiN0kVtORbPQ0fT0 ahFj754HCrrpoLtKBQptFLQDEBwggkn23km8GeMn9rxDyKjkg+ydnYc8PL9LdeSN2FIWefz21l+ aID2i5Fn4w8NtD+hHmUub/dcvdBGvocDe5JzAgk5f47lZ7vdHMmGD6WOpdkSFHLICKiP1aEZP5s 5SjmCAmnLRAG153BMkOgeS/aCsam9cIay+zeO+q6c9shHQrSpMNzQulTcy+c207WrVbG/ZwBu9h JMdBkpA/02IFryQLQjBW3BCsuypxVOs7QTt5i6d+DQywwfPaBU2fToUlUzKqpoIkezssAtcEE6O 6L8peDepytXmQYBR72taO+7QVfygME0nAN/0XnYDXr2Ri4dB2dHzWtFjpb4e+b9Hwxpj10h56hK zUY+laaj4j0zJSS7ju13DOot2NMefyeqeNHVvstOA36zsgzg7SbH1TgDTylJsIDko= X-Received: by 2002:a05:7300:c87:b0:2ae:59d3:46d3 with SMTP id 5a478bee46e88-2b739b6d06cmr2712819eec.25.1769219354616; Fri, 23 Jan 2026 17:49:14 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:14 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 03/16] HID: hid-lenovo-go: Add Feature Status Attributes Date: Sat, 24 Jan 2026 01:48:54 +0000 Message-ID: <20260124014907.991265-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds various feature status indicators and toggles to hid-lenovo-go, including the FPS mode switch setting, touchpad enable toggle, handle automatic sleep timer, etc. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go.c | 396 +++++++++++++++++++++++++++++++++++- 1 file changed, 395 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 6380434b2d89..681791f119d1 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -36,21 +36,31 @@ struct hid_go_cfg { struct completion send_cmd_complete; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 fps_mode; + u8 gp_left_auto_sleep_time; u32 gp_left_version_firmware; u8 gp_left_version_gen; u32 gp_left_version_hardware; u32 gp_left_version_product; u32 gp_left_version_protocol; + u8 gp_mode; + u8 gp_right_auto_sleep_time; u32 gp_right_version_firmware; u8 gp_right_version_gen; u32 gp_right_version_hardware; u32 gp_right_version_product; u32 gp_right_version_protocol; + u8 imu_left_bypass_en; + u8 imu_left_sensor_en; + u8 imu_right_bypass_en; + u8 imu_right_sensor_en; u32 mcu_version_firmware; u8 mcu_version_gen; u32 mcu_version_hardware; u32 mcu_version_product; u32 mcu_version_protocol; + u8 rgb_en; + u8 tp_en; u32 tx_dongle_version_firmware; u8 tx_dongle_version_gen; u32 tx_dongle_version_hardware; @@ -102,6 +112,18 @@ enum dev_type { RIGHT_CONTROLLER, }; =20 +enum enabled_status_index { + FEATURE_UNKNOWN, + FEATURE_ENABLED, + FEATURE_DISABLED, +}; + +static const char *const enabled_status_text[] =3D { + [FEATURE_UNKNOWN] =3D "unknown", + [FEATURE_ENABLED] =3D "true", + [FEATURE_DISABLED] =3D "false", +}; + enum version_data_index { PRODUCT_VERSION =3D 0x02, PROTOCOL_VERSION, @@ -110,6 +132,41 @@ enum version_data_index { HARDWARE_GENERATION, }; =20 +enum feature_status_index { + FEATURE_RESET_GAMEPAD =3D 0x02, + FEATURE_IMU_BYPASS, + FEATURE_IMU_ENABLE =3D 0x05, + FEATURE_TOUCHPAD_ENABLE =3D 0x07, + FEATURE_LIGHT_ENABLE, + FEATURE_AUTO_SLEEP_TIME, + FEATURE_FPS_SWITCH_STATUS =3D 0x0b, + FEATURE_GAMEPAD_MODE =3D 0x0e, +}; + +enum fps_switch_status_index { + FPS_STATUS_UNKNOWN, + GAMEPAD, + FPS, +}; + +static const char *const fps_switch_text[] =3D { + [FPS_STATUS_UNKNOWN] =3D "unknown", + [GAMEPAD] =3D "gamepad", + [FPS] =3D "fps", +}; + +enum gamepad_mode_index { + GAMEPAD_MODE_UNKNOWN, + XINPUT, + DINPUT, +}; + +static const char *const gamepad_mode_text[] =3D { + [GAMEPAD_MODE_UNKNOWN] =3D "unknown", + [XINPUT] =3D "xinput", + [DINPUT] =3D "dinput", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -219,6 +276,71 @@ static int hid_go_version_event(struct command_report = *cmd_rep) } } =20 +static int hid_go_feature_status_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case FEATURE_RESET_GAMEPAD: + return 0; + case FEATURE_IMU_ENABLE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.imu_left_sensor_en =3D cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.imu_right_sensor_en =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + case FEATURE_IMU_BYPASS: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.imu_left_bypass_en =3D cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.imu_right_bypass_en =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + break; + case FEATURE_LIGHT_ENABLE: + drvdata.rgb_en =3D cmd_rep->data[0]; + return 0; + case FEATURE_AUTO_SLEEP_TIME: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_auto_sleep_time =3D cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_auto_sleep_time =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + break; + case FEATURE_TOUCHPAD_ENABLE: + drvdata.tp_en =3D cmd_rep->data[0]; + return 0; + case FEATURE_GAMEPAD_MODE: + drvdata.gp_mode =3D cmd_rep->data[0]; + return 0; + case FEATURE_FPS_SWITCH_STATUS: + drvdata.fps_mode =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + } +} + +static int hid_go_set_event_return(struct command_report *cmd_rep) +{ + if (cmd_rep->data[0] !=3D 0) + return -EIO; + + return 0; +} + static int get_endpoint_address(struct hid_device *hdev) { struct usb_interface *intf =3D to_usb_interface(hdev->dev.parent); @@ -255,6 +377,12 @@ static int hid_go_raw_event(struct hid_device *hdev, s= truct hid_report *report, case GET_VERSION_DATA: ret =3D hid_go_version_event(cmd_rep); break; + case GET_FEATURE_STATUS: + ret =3D hid_go_feature_status_event(cmd_rep); + break; + case SET_FEATURE_STATUS: + ret =3D hid_go_set_event_return(cmd_rep); + break; default: ret =3D -EINVAL; break; @@ -440,6 +568,195 @@ static ssize_t version_show(struct device *dev, struc= t device_attribute *attr, return count; } =20 +static ssize_t feature_status_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum feature_status_index index, + enum dev_type device_type) +{ + size_t size =3D 1; + u8 val =3D 0; + int ret; + + switch (index) { + case FEATURE_IMU_ENABLE: + case FEATURE_IMU_BYPASS: + case FEATURE_LIGHT_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + ret =3D sysfs_match_string(enabled_status_text, buf); + val =3D ret; + break; + case FEATURE_AUTO_SLEEP_TIME: + ret =3D kstrtou8(buf, 10, &val); + break; + case FEATURE_RESET_GAMEPAD: + ret =3D kstrtou8(buf, 10, &val); + if (val !=3D GO_GP_RESET_SUCCESS) + return -EINVAL; + break; + case FEATURE_FPS_SWITCH_STATUS: + ret =3D sysfs_match_string(fps_switch_text, buf); + val =3D ret; + break; + case FEATURE_GAMEPAD_MODE: + ret =3D sysfs_match_string(gamepad_mode_text, buf); + val =3D ret; + break; + default: + return -EINVAL; + }; + + if (ret < 0) + return ret; + + if (!val) + size =3D 0; + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, + SET_FEATURE_STATUS, index, device_type, &val, + size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t feature_status_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index, + enum dev_type device_type) +{ + ssize_t count =3D 0; + int ret; + u8 i; + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, + GET_FEATURE_STATUS, index, device_type, 0, 0); + if (ret) + return ret; + + switch (index) { + case FEATURE_IMU_ENABLE: + switch (device_type) { + case LEFT_CONTROLLER: + i =3D drvdata.imu_left_sensor_en; + break; + case RIGHT_CONTROLLER: + i =3D drvdata.imu_right_sensor_en; + break; + default: + return -EINVAL; + } + if (i >=3D ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_IMU_BYPASS: + switch (device_type) { + case LEFT_CONTROLLER: + i =3D drvdata.imu_left_bypass_en; + break; + case RIGHT_CONTROLLER: + i =3D drvdata.imu_right_bypass_en; + break; + default: + return -EINVAL; + } + if (i >=3D ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_LIGHT_ENABLE: + i =3D drvdata.rgb_en; + if (i >=3D ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_TOUCHPAD_ENABLE: + i =3D drvdata.tp_en; + if (i >=3D ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case FEATURE_AUTO_SLEEP_TIME: + switch (device_type) { + case LEFT_CONTROLLER: + i =3D drvdata.gp_left_auto_sleep_time; + break; + case RIGHT_CONTROLLER: + i =3D drvdata.gp_right_auto_sleep_time; + break; + default: + return -EINVAL; + }; + count =3D sysfs_emit(buf, "%u\n", i); + break; + case FEATURE_FPS_SWITCH_STATUS: + i =3D drvdata.fps_mode; + if (i >=3D ARRAY_SIZE(fps_switch_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", fps_switch_text[i]); + break; + case FEATURE_GAMEPAD_MODE: + i =3D drvdata.gp_mode; + if (i >=3D ARRAY_SIZE(gamepad_mode_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", gamepad_mode_text[i]); + break; + default: + return -EINVAL; + }; + + return count; +} + +static ssize_t feature_status_options(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index) +{ + ssize_t count =3D 0; + unsigned int i; + + switch (index) { + case FEATURE_IMU_ENABLE: + case FEATURE_IMU_BYPASS: + case FEATURE_LIGHT_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + for (i =3D 1; i < ARRAY_SIZE(enabled_status_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + enabled_status_text[i]); + } + break; + case FEATURE_AUTO_SLEEP_TIME: + return sysfs_emit(buf, "0-255\n"); + case FEATURE_FPS_SWITCH_STATUS: + for (i =3D 1; i < ARRAY_SIZE(fps_switch_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + fps_switch_text[i]); + } + break; + case FEATURE_GAMEPAD_MODE: + for (i =3D 1; i < ARRAY_SIZE(gamepad_mode_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + gamepad_mode_text[i]); + } + break; + default: + return -EINVAL; + }; + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) = \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -494,7 +811,22 @@ LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_ve= rsion", USB_MCU, version); struct go_cfg_attr version_gen_mcu =3D { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_mcu, "hardware_generation", USB_MCU, versi= on); =20 +struct go_cfg_attr fps_switch_status =3D { FEATURE_FPS_SWITCH_STATUS }; +LEGO_DEVICE_ATTR_RO(fps_switch_status, "fps_switch_status", UNSPECIFIED, + feature_status); + +struct go_cfg_attr gamepad_mode =3D { FEATURE_GAMEPAD_MODE }; +LEGO_DEVICE_ATTR_RW(gamepad_mode, "mode", UNSPECIFIED, index, feature_stat= us); +static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); + +struct go_cfg_attr reset_mcu =3D { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status); + static struct attribute *mcu_attrs[] =3D { + &dev_attr_fps_switch_status.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_reset_mcu.attr, &dev_attr_version_firmware_mcu.attr, &dev_attr_version_gen_mcu.attr, &dev_attr_version_hardware_mcu.attr, @@ -523,7 +855,11 @@ LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardw= are_version", TX_DONGLE, v struct go_cfg_attr version_gen_tx_dongle =3D { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_tx_dongle, "hardware_generation", TX_DONGL= E, version); =20 +struct go_cfg_attr reset_tx_dongle =3D { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_RO(reset_tx_dongle, "reset", TX_DONGLE, feature_status); + static struct attribute *tx_dongle_attrs[] =3D { + &dev_attr_reset_tx_dongle.attr, &dev_attr_version_hardware_tx_dongle.attr, &dev_attr_version_firmware_tx_dongle.attr, &dev_attr_version_gen_tx_dongle.attr, @@ -553,7 +889,33 @@ LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_v= ersion", LEFT_CONTROLLER, struct go_cfg_attr version_gen_left =3D { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_left, "hardware_generation", LEFT_CONTROLL= ER, version); =20 +struct go_cfg_attr auto_sleep_time_left =3D { FEATURE_AUTO_SLEEP_TIME }; +LEGO_DEVICE_ATTR_RW(auto_sleep_time_left, "auto_sleep_time", LEFT_CONTROLL= ER, + range, feature_status); +static DEVICE_ATTR_RO_NAMED(auto_sleep_time_left_range, + "auto_sleep_time_range"); + +struct go_cfg_attr imu_bypass_left =3D { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_bypass_left, "imu_bypass_enabled", LEFT_CONTROLLER, + index, feature_status); +static DEVICE_ATTR_RO_NAMED(imu_bypass_left_index, "imu_bypass_enabled_ind= ex"); + +struct go_cfg_attr imu_enabled_left =3D { FEATURE_IMU_ENABLE }; +LEGO_DEVICE_ATTR_RW(imu_enabled_left, "imu_enabled", LEFT_CONTROLLER, inde= x, + feature_status); +static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "imu_enabled_index"); + +struct go_cfg_attr reset_left =3D { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); + static struct attribute *left_gamepad_attrs[] =3D { + &dev_attr_auto_sleep_time_left.attr, + &dev_attr_auto_sleep_time_left_range.attr, + &dev_attr_imu_bypass_left.attr, + &dev_attr_imu_bypass_left_index.attr, + &dev_attr_imu_enabled_left.attr, + &dev_attr_imu_enabled_left_index.attr, + &dev_attr_reset_left.attr, &dev_attr_version_hardware_left.attr, &dev_attr_version_firmware_left.attr, &dev_attr_version_gen_left.attr, @@ -583,7 +945,33 @@ LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_= version", RIGHT_CONTROLLER struct go_cfg_attr version_gen_right =3D { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_right, "hardware_generation", RIGHT_CONTRO= LLER, version); =20 +struct go_cfg_attr auto_sleep_time_right =3D { FEATURE_AUTO_SLEEP_TIME }; +LEGO_DEVICE_ATTR_RW(auto_sleep_time_right, "auto_sleep_time", RIGHT_CONTRO= LLER, + range, feature_status); +static DEVICE_ATTR_RO_NAMED(auto_sleep_time_right_range, + "auto_sleep_time_range"); + +struct go_cfg_attr imu_bypass_right =3D { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_bypass_right, "imu_bypass_enabled", RIGHT_CONTROLL= ER, + index, feature_status); +static DEVICE_ATTR_RO_NAMED(imu_bypass_right_index, "imu_bypass_enabled_in= dex"); + +struct go_cfg_attr imu_enabled_right =3D { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_enabled_right, "imu_enabled", RIGHT_CONTROLLER, in= dex, + feature_status); +static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, "imu_enabled_index"); + +struct go_cfg_attr reset_right =3D { FEATURE_RESET_GAMEPAD }; +LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); + static struct attribute *right_gamepad_attrs[] =3D { + &dev_attr_auto_sleep_time_right.attr, + &dev_attr_auto_sleep_time_right_range.attr, + &dev_attr_imu_bypass_right.attr, + &dev_attr_imu_bypass_right_index.attr, + &dev_attr_imu_enabled_right.attr, + &dev_attr_imu_enabled_right_index.attr, + &dev_attr_reset_right.attr, &dev_attr_version_hardware_right.attr, &dev_attr_version_firmware_right.attr, &dev_attr_version_gen_right.attr, @@ -598,8 +986,14 @@ static const struct attribute_group right_gamepad_attr= _group =3D { }; =20 /* Touchpad */ +struct go_cfg_attr touchpad_enabled =3D { FEATURE_TOUCHPAD_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSPECIFIED, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); + static struct attribute *touchpad_attrs[] =3D { - NULL, + &dev_attr_touchpad_enabled.attr, + &dev_attr_touchpad_enabled_index.attr, }; =20 static const struct attribute_group touchpad_attr_group =3D { --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f181.google.com (mail-dy1-f181.google.com [74.125.82.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 83CEC19E96D for ; Sat, 24 Jan 2026 01:49:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219359; cv=none; b=oVun5wGadinqOGb9N6NXXM+9OETFdkp5HOVTAKdRbKT7kXuVNNFvRhDPzrHGcIrr9q3bZT24tIeYAHYPWrJ9YMSdUgLTal3lwPKNuxawULo0yyjoYF52O/NS5rWDYfrByTNdNr9dduVOT23KFHYpBWpIqBWYWYxe66mds964BxM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219359; c=relaxed/simple; bh=yNRYzCUvw7h/24TV4k/FTIrRtyG1qVaH9Te99Pv3tzE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OuQ1eh5LEqmmSJ1WlHVeZYg8FFN+NLJvvbnjRbZ2VDXlH6Ny2ijUa47PlTuINlMvnnVgbZTwRcN5zbgCuStCwzWaoAx+ulFCMwsBCL0/tdZrHEod16iaki4ouLheUoS/J1u5NWwmw44/1AkzaX/10josjo7IxxEhZqR8EL0A8WA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=mZg/TZj4; arc=none smtp.client-ip=74.125.82.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mZg/TZj4" Received: by mail-dy1-f181.google.com with SMTP id 5a478bee46e88-2b72e49776eso4455670eec.1 for ; Fri, 23 Jan 2026 17:49:16 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219355; x=1769824155; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=deL+GFbShaY/PU1BGtjyGIzsYhM8+/qKKNNQ78FbRiE=; b=mZg/TZj4opmfwZHLRxtXtCZ/HiNpzFRspZmDRkbCLJGTmDnWEQjN6/CDyxU/1xXblm WCYzuNxDMs2SfaMHC7oNg/qGchb621xq5AJT50wNHW4SPUe4gO8L/juq3YU/p26+h+84 ecGe+HZyIbazdleA3ksKdfG8GmgbBDd41GeQYENhUT3HwxrgzZsu7GAacf2JMjflDLnH 1O53BK/R96B7/OeBz+IE47R8pu2AnshhrJDQy+3zWfHsoF0paHtUJXUbth5yyrskLJvK kRekwHThD+4T18zC1vLgQTtsMKBOG01/Z7Mz9vN5Icw1m5IyInhqWlrpER968vbzvEmS gAVw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219355; x=1769824155; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=deL+GFbShaY/PU1BGtjyGIzsYhM8+/qKKNNQ78FbRiE=; b=pforDxHRalFuebWRdJXbfCtoPoENVbk63lLROBTv/qnO2OMVXdp3+ulFtSEk9v/sOQ teZSXX1qsr/+g61nyZ6mMULZTgcOCb3WYA6MYug/BcMu7VOCDAmVS6Cn8vxsEX97jY1d 3U2iUx7vfZPulNekmIl6tbDGyx8OXbGDRhoSoYtso0vkeWj0cvodEwfrrJDTiPBUytnN pHvikotawCI0IQDAlAEzX1Ssp4+yZqBo+F/7gBZjrwtj4UcZK4ivwPLqICsd/L9Bzgic v3w/6nAe6CouU3KlN8kSKDMXTIysHHtt7uDVRwJU+FFKBkeRf36dgvQK+CGJV8+s73lT Jfew== X-Forwarded-Encrypted: i=1; AJvYcCWDqc/z8z5pq6p3kkolxqPAq8MMqZvXM7pCl9Q2yRNEP+GSLhjqCnZuesWhlH9nbKI/rjIrfg3ZiU1NhHg=@vger.kernel.org X-Gm-Message-State: AOJu0YzldqnMBD3SIAKMjOz5VhzGDC/HrIi4seDaohRzIGBkx1wEJJIJ BCmuTHpPMKyakqqHNaqGMeXXYmWWpjgw9OUJvtC4aPCa2e9D34AgFMe2 X-Gm-Gg: AZuq6aIuUlJbZIDPm9ZBBdGweAEu6j7NFcPfIUIAgryXzI8NOlfiy2N7py5DtDJVqeU xKCfM50Iva+xUlYeZvFsNJHP1Bv3BmfxBHJq6XKCaN+rzIhN1XL0bl8z8omw5/OTU/lhxIWRmy/ chExcfz9/X3N3lNODP3CZBlZ/U6PO3N3Z/bW5m0ijFuvVDcKUGckg2uIBzcarpwoBCi/Zbdnrj/ H01alHmHltBRlKOjkSM/+QypYbYMJy8ACv5j88IqvK6NauWFsbhcw5RjwUs2SKwtj/UC2/LG6QF IMZOL0chro7VV9GVjsmHVVOnDdNHAHzjxFXNbj011pwZQCesWH+F0J3yN1uNZDlV6ppda8bYrFj z8uGaS/fHPs8NwLnDHOAWU2tdqhjl8slNhEk4EV58g24n2KHgFyG4JH834pvQODRLmZyvy9FpjK 4FVfzkQ0+gVMMT2fClAQdueWBSsdBWd8bRV8WunqUG90EbYjN64KDuy1sZ7JOTsDs= X-Received: by 2002:a05:7300:f10a:b0:2b7:359b:188f with SMTP id 5a478bee46e88-2b739b8d1e9mr3245418eec.25.1769219355351; Fri, 23 Jan 2026 17:49:15 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:15 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 04/16] HID: hid-lenovo-go: Add Rumble and Haptic Settings Date: Sat, 24 Jan 2026 01:48:55 +0000 Message-ID: <20260124014907.991265-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds attributes that control the handles rumble mode and intensity, as well as touchpad haptic feedback settings. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- V3: - Remove erroneous renaming of enabled -> enable for some left & right handle attributes. --- drivers/hid/hid-lenovo-go.c | 312 ++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 681791f119d1..fe39a2b38f38 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -38,6 +38,8 @@ struct hid_go_cfg { struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 fps_mode; u8 gp_left_auto_sleep_time; + u8 gp_left_notify_en; + u8 gp_left_rumble_mode; u32 gp_left_version_firmware; u8 gp_left_version_gen; u32 gp_left_version_hardware; @@ -45,11 +47,14 @@ struct hid_go_cfg { u32 gp_left_version_protocol; u8 gp_mode; u8 gp_right_auto_sleep_time; + u8 gp_right_notify_en; + u8 gp_right_rumble_mode; u32 gp_right_version_firmware; u8 gp_right_version_gen; u32 gp_right_version_hardware; u32 gp_right_version_product; u32 gp_right_version_protocol; + u8 gp_rumble_intensity; u8 imu_left_bypass_en; u8 imu_left_sensor_en; u8 imu_right_bypass_en; @@ -61,6 +66,8 @@ struct hid_go_cfg { u32 mcu_version_protocol; u8 rgb_en; u8 tp_en; + u8 tp_vibration_en; + u8 tp_vibration_intensity; u32 tx_dongle_version_firmware; u8 tx_dongle_version_gen; u32 tx_dongle_version_hardware; @@ -167,6 +174,49 @@ static const char *const gamepad_mode_text[] =3D { [DINPUT] =3D "dinput", }; =20 +enum motor_cfg_index { + MOTOR_CFG_ALL =3D 0x01, + MOTOR_INTENSITY, + VIBRATION_NOTIFY_ENABLE, + RUMBLE_MODE, + TP_VIBRATION_ENABLE, + TP_VIBRATION_INTENSITY, +}; + +enum intensity_index { + INTENSITY_UNKNOWN, + INTENSITY_OFF, + INTENSITY_LOW, + INTENSITY_MEDIUM, + INTENSITY_HIGH, +}; + +static const char *const intensity_text[] =3D { + [INTENSITY_UNKNOWN] =3D "unknown", + [INTENSITY_OFF] =3D "off", + [INTENSITY_LOW] =3D "low", + [INTENSITY_MEDIUM] =3D "medium", + [INTENSITY_HIGH] =3D "high", +}; + +enum rumble_mode_index { + RUMBLE_MODE_UNKNOWN, + RUMBLE_MODE_FPS, + RUMBLE_MODE_RACE, + RUMBLE_MODE_AVERAGE, + RUMBLE_MODE_SPG, + RUMBLE_MODE_RPG, +}; + +static const char *const rumble_mode_text[] =3D { + [RUMBLE_MODE_UNKNOWN] =3D "unknown", + [RUMBLE_MODE_FPS] =3D "fps", + [RUMBLE_MODE_RACE] =3D "racing", + [RUMBLE_MODE_AVERAGE] =3D "standard", + [RUMBLE_MODE_SPG] =3D "spg", + [RUMBLE_MODE_RPG] =3D "rpg", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -333,6 +383,47 @@ static int hid_go_feature_status_event(struct command_= report *cmd_rep) } } =20 +static int hid_go_motor_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + drvdata.gp_rumble_intensity =3D cmd_rep->data[0]; + return 0; + case VIBRATION_NOTIFY_ENABLE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_notify_en =3D cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_notify_en =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + break; + case RUMBLE_MODE: + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + drvdata.gp_left_rumble_mode =3D cmd_rep->data[0]; + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_rumble_mode =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; + case TP_VIBRATION_ENABLE: + drvdata.tp_vibration_en =3D cmd_rep->data[0]; + return 0; + case TP_VIBRATION_INTENSITY: + drvdata.tp_vibration_intensity =3D cmd_rep->data[0]; + return 0; + } + return -EINVAL; +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -380,7 +471,11 @@ static int hid_go_raw_event(struct hid_device *hdev, s= truct hid_report *report, case GET_FEATURE_STATUS: ret =3D hid_go_feature_status_event(cmd_rep); break; + case GET_MOTOR_CFG: + ret =3D hid_go_motor_event(cmd_rep); + break; case SET_FEATURE_STATUS: + case SET_MOTOR_CFG: ret =3D hid_go_set_event_return(cmd_rep); break; default: @@ -757,6 +852,168 @@ static ssize_t feature_status_options(struct device *= dev, return count; } =20 +static ssize_t motor_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum motor_cfg_index index, + enum dev_type device_type) +{ + size_t size =3D 1; + u8 val =3D 0; + int ret; + + switch (index) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + ret =3D sysfs_match_string(intensity_text, buf); + val =3D ret; + break; + case VIBRATION_NOTIFY_ENABLE: + ret =3D sysfs_match_string(enabled_status_text, buf); + val =3D ret; + break; + case RUMBLE_MODE: + ret =3D sysfs_match_string(rumble_mode_text, buf); + val =3D ret; + break; + case TP_VIBRATION_ENABLE: + ret =3D sysfs_match_string(enabled_status_text, buf); + val =3D ret; + break; + case TP_VIBRATION_INTENSITY: + ret =3D sysfs_match_string(intensity_text, buf); + val =3D ret; + break; + }; + + if (ret < 0) + return ret; + + if (!val) + size =3D 0; + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_MOTOR_CFG, + index, device_type, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t motor_config_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum motor_cfg_index index, + enum dev_type device_type) +{ + ssize_t count =3D 0; + int ret; + u8 i; + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_MOTOR_CFG, + index, device_type, 0, 0); + if (ret) + return ret; + + switch (index) { + case MOTOR_CFG_ALL: + return -EINVAL; + case MOTOR_INTENSITY: + i =3D drvdata.gp_rumble_intensity; + if (i >=3D ARRAY_SIZE(intensity_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", intensity_text[i]); + break; + case VIBRATION_NOTIFY_ENABLE: + switch (device_type) { + case LEFT_CONTROLLER: + i =3D drvdata.gp_left_notify_en; + break; + case RIGHT_CONTROLLER: + i =3D drvdata.gp_right_notify_en; + break; + default: + return -EINVAL; + }; + if (i >=3D ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case RUMBLE_MODE: + switch (device_type) { + case LEFT_CONTROLLER: + i =3D drvdata.gp_left_rumble_mode; + break; + case RIGHT_CONTROLLER: + i =3D drvdata.gp_right_rumble_mode; + break; + default: + return -EINVAL; + }; + if (i >=3D ARRAY_SIZE(rumble_mode_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", rumble_mode_text[i]); + break; + case TP_VIBRATION_ENABLE: + i =3D drvdata.tp_vibration_en; + if (i >=3D ARRAY_SIZE(enabled_status_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", enabled_status_text[i]); + break; + case TP_VIBRATION_INTENSITY: + i =3D drvdata.tp_vibration_intensity; + if (i >=3D ARRAY_SIZE(intensity_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", intensity_text[i]); + break; + }; + + return count; +} + +static ssize_t motor_config_options(struct device *dev, + struct device_attribute *attr, char *buf, + enum motor_cfg_index index) +{ + ssize_t count =3D 0; + unsigned int i; + + switch (index) { + case MOTOR_CFG_ALL: + break; + case RUMBLE_MODE: + for (i =3D 1; i < ARRAY_SIZE(rumble_mode_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + rumble_mode_text[i]); + } + break; + case MOTOR_INTENSITY: + case TP_VIBRATION_INTENSITY: + for (i =3D 1; i < ARRAY_SIZE(intensity_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + intensity_text[i]); + } + break; + case VIBRATION_NOTIFY_ENABLE: + case TP_VIBRATION_ENABLE: + for (i =3D 1; i < ARRAY_SIZE(enabled_status_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + enabled_status_text[i]); + } + break; + }; + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) = \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -822,10 +1079,18 @@ static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mod= e_index"); struct go_cfg_attr reset_mcu =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status); =20 +struct go_cfg_attr gamepad_rumble_intensity =3D { MOTOR_INTENSITY }; +LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumble_intensity", UNSPECIF= IED, + index, motor_config); +static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, + "rumble_intensity_index"); + static struct attribute *mcu_attrs[] =3D { &dev_attr_fps_switch_status.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, + &dev_attr_gamepad_rumble_intensity.attr, + &dev_attr_gamepad_rumble_intensity_index.attr, &dev_attr_reset_mcu.attr, &dev_attr_version_firmware_mcu.attr, &dev_attr_version_gen_mcu.attr, @@ -908,6 +1173,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "= imu_enabled_index"); struct go_cfg_attr reset_left =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); =20 +struct go_cfg_attr rumble_mode_left =3D { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_left, "rumble_mode", LEFT_CONTROLLER, inde= x, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_left_index, "rumble_mode_index"); + +struct go_cfg_attr rumble_notification_left =3D { VIBRATION_NOTIFY_ENABLE = }; +LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification", + LEFT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, + "rumble_notification_index"); + static struct attribute *left_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_left.attr, &dev_attr_auto_sleep_time_left_range.attr, @@ -916,6 +1192,10 @@ static struct attribute *left_gamepad_attrs[] =3D { &dev_attr_imu_enabled_left.attr, &dev_attr_imu_enabled_left_index.attr, &dev_attr_reset_left.attr, + &dev_attr_rumble_mode_left.attr, + &dev_attr_rumble_mode_left_index.attr, + &dev_attr_rumble_notification_left.attr, + &dev_attr_rumble_notification_left_index.attr, &dev_attr_version_hardware_left.attr, &dev_attr_version_firmware_left.attr, &dev_attr_version_gen_left.attr, @@ -964,6 +1244,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, = "imu_enabled_index"); struct go_cfg_attr reset_right =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); =20 +struct go_cfg_attr rumble_mode_right =3D { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_right, "rumble_mode", RIGHT_CONTROLLER, in= dex, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_right_index, "rumble_mode_index"); + +struct go_cfg_attr rumble_notification_right =3D { VIBRATION_NOTIFY_ENABLE= }; +LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification", + RIGHT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, + "rumble_notification_index"); + static struct attribute *right_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_right.attr, &dev_attr_auto_sleep_time_right_range.attr, @@ -972,6 +1263,10 @@ static struct attribute *right_gamepad_attrs[] =3D { &dev_attr_imu_enabled_right.attr, &dev_attr_imu_enabled_right_index.attr, &dev_attr_reset_right.attr, + &dev_attr_rumble_mode_right.attr, + &dev_attr_rumble_mode_right_index.attr, + &dev_attr_rumble_notification_right.attr, + &dev_attr_rumble_notification_right_index.attr, &dev_attr_version_hardware_right.attr, &dev_attr_version_firmware_right.attr, &dev_attr_version_gen_right.attr, @@ -991,9 +1286,26 @@ LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSP= ECIFIED, index, feature_status); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); =20 +struct go_cfg_attr touchpad_vibration_enabled =3D { TP_VIBRATION_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_enabled, "vibration_enabled", UNSPE= CIFIED, + index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_enabled_index, + "vibration_enabled_index"); + +struct go_cfg_attr touchpad_vibration_intensity =3D { TP_VIBRATION_INTENSI= TY }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_intensity, "vibration_intensity", + UNSPECIFIED, index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_intensity_index, + "vibration_intensity_index"); + static struct attribute *touchpad_attrs[] =3D { &dev_attr_touchpad_enabled.attr, &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_vibration_enabled.attr, + &dev_attr_touchpad_vibration_enabled_index.attr, + &dev_attr_touchpad_vibration_intensity.attr, + &dev_attr_touchpad_vibration_intensity_index.attr, + NULL, }; =20 static const struct attribute_group touchpad_attr_group =3D { --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f169.google.com (mail-dy1-f169.google.com [74.125.82.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 27C081D130E for ; Sat, 24 Jan 2026 01:49:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219359; cv=none; b=RNjwttefzYhbyNmMONg91u3Nl7be1TYfuYQ3mjkdEHNGtyrgfANPdSZTTgA4VNRQvNWKagLSvPUIZUUR2jmoPhlzl/YM++kabbibBxCTHenmqEIMgyc4BcgyaGGYsKeuJ+Yx6+1qCEzM7UuM/58Yhp9YLuGW4GAKg9mwf7Hdo/I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219359; c=relaxed/simple; bh=Nb3asoNUaPuKXIaITYKDr5TQ+47i7AoGAnc2tZshDts=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VWn/VKI8OK2t3KB9CcRM/ML4Ahasj1vm7ljcw86qEJ0nWp9ru0BjtYOn4ASS1T2S2KQLPt/XflH0wsaNkIoRHURWOcstMsC8B2jouFhIGQRpQPmhIyWpdF0XY/zCCMnYmahS774Mvn60OROmQSn1CKmpCcAHTBLtLVac+LyQHQ8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=RL8bufQE; arc=none smtp.client-ip=74.125.82.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="RL8bufQE" Received: by mail-dy1-f169.google.com with SMTP id 5a478bee46e88-2b729f4c154so4789985eec.0 for ; Fri, 23 Jan 2026 17:49:16 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219356; x=1769824156; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Y5leeh6zDjWLKpeuC/+j5CV0yQoA+GAmQ8ZEpaDqT6Y=; b=RL8bufQEOBJIYapviFfUitMy/HZvkaNzQ08uZunaJDvqMLnJRf1nyh2iyaXa+xea7O kZsB1Rty/vK67czZ+We1bGHpL3voMOiyqFfoYREg0m3MDH566Jx8PY2276YJdzQK+9VI du/Iyoiu8l1+XG7aUvpFjh7LW0zxwR2TVO1aZSjBGjhX9V/mRpMz7yMfUtuNck0MwWzU yQCHSb3hVnL2HQqOW+QMlggrTcTRmHlN+nqy3EuYP8P7RZOhCiKTAAnZlUR4vXORihQS wmHS6pg3Qs3QNUH+kZ2QV9mCmNsuTjLeJ8ZYSoVZLaXaPRO72eRm++RSWHMV3h3MALuo /lgQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219356; x=1769824156; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Y5leeh6zDjWLKpeuC/+j5CV0yQoA+GAmQ8ZEpaDqT6Y=; b=FQ6FqDglsXoqJqpmBS/aOGZFdkk+GFCgZrZ4ppzpeMgc9VCbNlQ3jq8Q2yfrK7OT4v qMEejG7SWaFRJlCf9tTyJ7Pf6YsY4y9A8Kwi1dLiKaihsO58xsJvZo7uZR09Nf15b/T5 Oli09eJUBKD2pfkNOS8xcCLDRoDGms2ErGfAmi2gHRw1wO8bFBmnEpiUvT6zu18cNBOh B2hQvzyNgGkf389rZBpMFiBBcX8BBqQpbAFdCK0oakpasFfeNYB2DXqOlYPJKiVj/PLI F1LGC09zRYaiOqqyPsarL5fORFI4oFu1OfTDMRgHv3bALQTHNaS8iadFFMRdPRV3HTae 3hQA== X-Forwarded-Encrypted: i=1; AJvYcCXTg5qHCBqBemBP7YsF3qNA+RPBONL8wMFk2c11OMfEbX9xxUNy8EpZGRIr0Gk9ML/Zt+wAzZb5jSuCPOM=@vger.kernel.org X-Gm-Message-State: AOJu0YxAV0iPGhhh1gw7xHhUU5iN+6i0jWFxHY4mXURmt5unLD0RhfeX 4aa6goIYOwqjRetf5WXZG+4fNijn+Ed4TLTakecoblNDzpB0/+MDaPWzxGbHaQ== X-Gm-Gg: AZuq6aJYv/PoYDp7blrw4NoaEeRsroMSblFXpt59KwPdaUY44JDWZKh3hjPmeKHGYog YqlC2463DatiJuYIjkTy8Bdy+77nbDxwkl8SPjTxuLRSEnhm/2z7bsC7OJba9oJ1J38WBC8X2/w fG28XJoHGUDgR39AFH5GNL2wvxvLbnidb8EaQZZyqlyz/G3kNS0mxdwbWfgoBTATPbaefPLOoSs sLiQEscro8bJzCwLz4mq7SdJ374Iv/Kpo27zyy/qMJ8lVV+QG4NSj0dGDuhMMl3TU9Doq5Tn+gs LffJC7+W4XraBOa8McxbRxbSaO3yH2EmvWee5K5JbATrrs92uGN5X/Dc8b/MYkXfeQLLr5YCE3N HGtzGoZuscfvQUFDVsgtR9Ss2qX7X75HxuHexVkVmKqn+ycRMGWDefHqdirojZoj4qcUIoc3rpI 3VF/8ulyuNwuxKOg/YeVHYeln9A6K21vz/2t0AY6ZEEcy1y66ewD26VD21krwdFkM= X-Received: by 2002:a05:7301:1016:b0:2b7:2723:c227 with SMTP id 5a478bee46e88-2b739bab766mr3019643eec.31.1769219356181; Fri, 23 Jan 2026 17:49:16 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:15 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 05/16] HID: hid-lenovo-go: Add FPS Mode DPI settings Date: Sat, 24 Jan 2026 01:48:56 +0000 Message-ID: <20260124014907.991265-6-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds attribute that enables selection of the DPI of the optical sensor when the right handle toggle is set to FPS mode. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go.c | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index fe39a2b38f38..5d454cd2cdae 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -64,6 +64,7 @@ struct hid_go_cfg { u32 mcu_version_hardware; u32 mcu_version_product; u32 mcu_version_protocol; + u32 mouse_dpi; u8 rgb_en; u8 tp_en; u8 tp_vibration_en; @@ -217,6 +218,8 @@ static const char *const rumble_mode_text[] =3D { [RUMBLE_MODE_RPG] =3D "rpg", }; =20 +#define FPS_MODE_DPI 0x02 + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -424,6 +427,16 @@ static int hid_go_motor_event(struct command_report *c= md_rep) return -EINVAL; } =20 +static int hid_go_fps_dpi_event(struct command_report *cmd_rep) +{ + if (cmd_rep->sub_cmd !=3D FPS_MODE_DPI) + return -EINVAL; + + drvdata.mouse_dpi =3D get_unaligned_le32(cmd_rep->data); + + return 0; +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -474,8 +487,12 @@ static int hid_go_raw_event(struct hid_device *hdev, s= truct hid_report *report, case GET_MOTOR_CFG: ret =3D hid_go_motor_event(cmd_rep); break; + case GET_DPI_CFG: + ret =3D hid_go_fps_dpi_event(cmd_rep); + break; case SET_FEATURE_STATUS: case SET_MOTOR_CFG: + case SET_DPI_CFG: ret =3D hid_go_set_event_return(cmd_rep); break; default: @@ -1014,6 +1031,52 @@ static ssize_t motor_config_options(struct device *d= ev, return count; } =20 +static ssize_t fps_mode_dpi_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) + +{ + size_t size =3D 4; + u32 value; + u8 val[4]; + int ret; + + ret =3D kstrtou32(buf, 10, &value); + if (ret) + return ret; + + if (value !=3D 500 && value !=3D 800 && value !=3D 1200 && value !=3D 180= 0) + return -EINVAL; + + put_unaligned_le32(value, val); + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_DPI_CFG, + FPS_MODE_DPI, UNSPECIFIED, val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t fps_mode_dpi_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_DPI_CFG, + FPS_MODE_DPI, UNSPECIFIED, 0, 0); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%u\n", drvdata.mouse_dpi); +} + +static ssize_t fps_mode_dpi_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "500 800 1200 1800\n"); +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) = \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -1085,7 +1148,12 @@ LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumbl= e_intensity", UNSPECIFIED, static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, "rumble_intensity_index"); =20 +static DEVICE_ATTR_RW(fps_mode_dpi); +static DEVICE_ATTR_RO(fps_mode_dpi_index); + static struct attribute *mcu_attrs[] =3D { + &dev_attr_fps_mode_dpi.attr, + &dev_attr_fps_mode_dpi_index.attr, &dev_attr_fps_switch_status.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f175.google.com (mail-dy1-f175.google.com [74.125.82.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 F26151FCF41 for ; Sat, 24 Jan 2026 01:49:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219361; cv=none; b=kGxa7gcMHnKNWK0rqaawEUI2oe7vh53s04xHAte/V2cf7ALG43Ac7h1CfsHJdGZBtaAPaE8likV4t+2p9CBU8NTo1j2eEMSixJAGCJaD/X7sDhTMDaCeVsLJZb6Cuw75vwaNWrElE+LEsb5p1bWgU7jAaRF8z3brZezqlwOyJUk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219361; c=relaxed/simple; bh=54h2ZD6ERLfG5uQrF4ZQMbWKIWD4Jp6Lb/w83rKlAAc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UQJxBWtDpYaboltGVodxRU2QUZqhLwPrc1o1F5YDTFJ+LEhi/COOp02Tj+Y0pgpMn63eDa8sB61wRvVq11kdle58NA2JtmXc/uCPBMm4vHWCtpTTQTFOwIKsIS3gYr2DNRH9irNAB31ErlDENaKMgSrvmScIubgqyD/GWKgYbIg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MjrqeHwx; arc=none smtp.client-ip=74.125.82.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MjrqeHwx" Received: by mail-dy1-f175.google.com with SMTP id 5a478bee46e88-2b7070acfdcso3094338eec.0 for ; Fri, 23 Jan 2026 17:49:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219357; x=1769824157; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=8sPu0k/HdGfAXuimtmNIyvKYNOTKDWTwAhrJ0MS9oKA=; b=MjrqeHwxI+urleJ7QzGrtcHCqshDqMIPdvXq3LZPBO1mMYFEdPUH+1vuCM5ykxtUyG E4C8az4Jc8W946Iwm3oGRIdzsLSYIlvWgBBq525Fj9s6n15JDoUVkQpT0nIiZiMlAyhw TCVi/5b0SaFmtyyPzI3eQP4+oeEjet8L0v2y5mgOXEASjH9Ud45WBZFviNBjdU/lZj3t F85nTJ74rSfzylKL8THInECMYVEvbH4QT5cjqiOOq1SIV2zyV0cd6y5UNiL6VPYTINHz w08qYBIjoWdHxAcKiK1a1NRIxlJnlPcvCTiLqhjB07Sm7AK+zGCHGxC5ZW+/5euJLrwM AFfw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219357; x=1769824157; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=8sPu0k/HdGfAXuimtmNIyvKYNOTKDWTwAhrJ0MS9oKA=; b=pzQ/OPlEG684s+Zz3htcSaZktdk394p8mKA9dCRPrki1SB8wNGHho/UkZMHMMkIt7Y RqG7to8zzBLTbFqxWDnCEfJvqNcMG0Qx8O30hdGvmQcUvlstq0mu3lrrQ0m4uoT6ZlT5 7F6t1ffHCORyiI9NXbFmfCv7tiINlRzaRHePHVfOSB6+PrEA7CNcTpFGRMZpwFOnuVdM gdDhFNpLW4taFithOzo550oBeOjoZS/+tydjrUuZViGU8h8LJZ7rwl9QjtEtug9Nk+H9 yYD5/Q4CPCiTBDCq2ojTpmiLpC0B8/nFnRfoNIXtK8w1cQzszbqoXjeBrG+khTamjHch 2QkA== X-Forwarded-Encrypted: i=1; AJvYcCVxBxO7gbIfzQpZBaEmFpIPANuXO0B6kvEVmovQ5b4kF110o382t3tkJqe20pAhanEBHKP/tHOQRoIUpVM=@vger.kernel.org X-Gm-Message-State: AOJu0YzqaonHpmtSvCnIxPy/HYvoh8RmllA1s/73IAR3FucTB8IGn+Ml 1bNYIK2394pCp5MBrFzXgoGwGt8ZtyGesiWp7BsNSeawsd7IRaMACOnA X-Gm-Gg: AZuq6aK/jUiSRyskgV0lrAn1725YaAL02eBfHqFta7RrHysS2zjZrYNpmrjmwXdhiPa RfoS82LAeGN3rBCKdPArzMgxULjTRfkQupteviZvSbfrQnP0SQ+GH9vkH9WLSGpwhtEHWSqAioC k0jjiJxEJKtAOjnX9SHxdLwQ7+IOtgq2c1nI+0wpQe8vkeIklo4gqD6oLLltT6FI3Qu4d+Dxsz8 +BrU45MQbLzUMetBWNIxCotQIciTgUXj/A39sE9HSuYKHCc3elg/65Aozk1Rg3govftiT21XFNw iAoMurk+DBKPN6GyMmuKvShj+Oaxq1ureYJHfgwiGtIgFmYRJIk9MX5tnn/raZoFywo36kUzg9Y vz4YN0tUBEjlNBT0muNyxfnUNqmF8KpMglKVVqe7lzEB0Gzv2iDtJKmi/Yz54AyHr9SaWBxkHFJ 8q9MC94Mkqdzq+HN3NGHzqlt5d45nzjQG+Af8l5+fcaMSbLgSUTF9shtkxGlIiFAA= X-Received: by 2002:a05:7301:9f18:b0:2b7:3678:2d17 with SMTP id 5a478bee46e88-2b739c21b5fmr2429576eec.41.1769219357028; Fri, 23 Jan 2026 17:49:17 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:16 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 06/16] HID: hid-lenovo-go: Add RGB LED control interface Date: Sat, 24 Jan 2026 01:48:57 +0000 Message-ID: <20260124014907.991265-7-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds an LED multicolor class device and attribute group for controlling the RGB of the Left and right handles. In addition to the standard led_cdev attributes, additional attributes that allow for the control of the effect (monocolor, breathe, rainbow, and chroma), speed of the effect change, an enable toggle, and profile. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go.c | 472 ++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 5d454cd2cdae..91eb61037a2c 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -17,12 +17,15 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include =20 #include "hid-ids.h" =20 @@ -33,7 +36,9 @@ =20 struct hid_go_cfg { unsigned char *buf; + struct delayed_work go_cfg_setup; struct completion send_cmd_complete; + struct led_classdev *led_cdev; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 fps_mode; @@ -65,7 +70,11 @@ struct hid_go_cfg { u32 mcu_version_product; u32 mcu_version_protocol; u32 mouse_dpi; + u8 rgb_effect; u8 rgb_en; + u8 rgb_mode; + u8 rgb_profile; + u8 rgb_speed; u8 tp_en; u8 tp_vibration_en; u8 tp_vibration_intensity; @@ -220,6 +229,41 @@ static const char *const rumble_mode_text[] =3D { =20 #define FPS_MODE_DPI 0x02 =20 +enum rgb_config_index { + LIGHT_CFG_ALL =3D 0x01, + LIGHT_MODE_SEL, + LIGHT_PROFILE_SEL, + USR_LIGHT_PROFILE_1, + USR_LIGHT_PROFILE_2, + USR_LIGHT_PROFILE_3, +}; + +enum rgb_mode_index { + RGB_MODE_UNKNOWN, + RGB_MODE_DYNAMIC, + RGB_MODE_CUSTOM, +}; + +static const char *const rgb_mode_text[] =3D { + [RGB_MODE_UNKNOWN] =3D "unknown", + [RGB_MODE_DYNAMIC] =3D "dynamic", + [RGB_MODE_CUSTOM] =3D "custom", +}; + +enum rgb_effect_index { + RGB_EFFECT_MONO, + RGB_EFFECT_BREATHE, + RGB_EFFECT_CHROMA, + RGB_EFFECT_RAINBOW, +}; + +static const char *const rgb_effect_text[] =3D { + [RGB_EFFECT_MONO] =3D "monocolor", + [RGB_EFFECT_BREATHE] =3D "breathe", + [RGB_EFFECT_CHROMA] =3D "chroma", + [RGB_EFFECT_RAINBOW] =3D "rainbow", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -437,6 +481,33 @@ static int hid_go_fps_dpi_event(struct command_report = *cmd_rep) return 0; } =20 +static int hid_go_light_event(struct command_report *cmd_rep) +{ + struct led_classdev_mc *mc_cdev; + + switch (cmd_rep->sub_cmd) { + case LIGHT_MODE_SEL: + drvdata.rgb_mode =3D cmd_rep->data[0]; + return 0; + case LIGHT_PROFILE_SEL: + drvdata.rgb_profile =3D cmd_rep->data[0]; + return 0; + case USR_LIGHT_PROFILE_1: + case USR_LIGHT_PROFILE_2: + case USR_LIGHT_PROFILE_3: + mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + drvdata.rgb_effect =3D cmd_rep->data[0]; + mc_cdev->subled_info[0].intensity =3D cmd_rep->data[1]; + mc_cdev->subled_info[1].intensity =3D cmd_rep->data[2]; + mc_cdev->subled_info[2].intensity =3D cmd_rep->data[3]; + drvdata.led_cdev->brightness =3D cmd_rep->data[4]; + drvdata.rgb_speed =3D cmd_rep->data[5]; + return 0; + default: + return -EINVAL; + } +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -490,9 +561,13 @@ static int hid_go_raw_event(struct hid_device *hdev, s= truct hid_report *report, case GET_DPI_CFG: ret =3D hid_go_fps_dpi_event(cmd_rep); break; + case GET_RGB_CFG: + ret =3D hid_go_light_event(cmd_rep); + break; case SET_FEATURE_STATUS: case SET_MOTOR_CFG: case SET_DPI_CFG: + case SET_RGB_CFG: ret =3D hid_go_set_event_return(cmd_rep); break; default: @@ -1077,6 +1152,274 @@ static ssize_t fps_mode_dpi_index_show(struct devic= e *dev, return sysfs_emit(buf, "500 800 1200 1800\n"); } =20 +static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cm= d, + enum rgb_config_index index, u8 *val, size_t size) +{ + if (cmd !=3D SET_RGB_CFG && cmd !=3D GET_RGB_CFG) + return -EINVAL; + + if (index < LIGHT_CFG_ALL || index > USR_LIGHT_PROFILE_3) + return -EINVAL; + + return mcu_property_out(hdev, MCU_CONFIG_DATA, cmd, index, UNSPECIFIED, + val, size); +} + +static int rgb_attr_show(void) +{ + enum rgb_config_index index; + + index =3D drvdata.rgb_profile + 3; + + return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, 0, 0); +}; + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + u8 effect; + int ret; + + ret =3D sysfs_match_string(rgb_effect_text, buf); + if (ret < 0) + return ret; + + effect =3D ret; + index =3D drvdata.rgb_profile + 3; + u8 rgb_profile[6] =3D { effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + drvdata.rgb_speed }; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_effect =3D effect; + return count; +}; + +static ssize_t rgb_effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret =3D rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >=3D ARRAY_SIZE(rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]); +} + +static ssize_t rgb_effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(rgb_effect_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int val =3D 0; + int ret; + + ret =3D kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 100) + return -EINVAL; + + index =3D drvdata.rgb_profile + 3; + u8 rgb_profile[6] =3D { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + val }; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_speed =3D val; + + return count; +}; + +static ssize_t rgb_speed_show(struct device *dev, struct device_attribute = *attr, + char *buf) +{ + int ret; + + ret =3D rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 100) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); +} + +static ssize_t rgb_speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} + +static ssize_t rgb_mode_store(struct device *dev, struct device_attribute = *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret =3D sysfs_match_string(rgb_mode_text, buf); + if (ret <=3D 0) + return ret; + + val =3D ret; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val, 1); + if (ret) + return ret; + + drvdata.rgb_mode =3D val; + + return count; +}; + +static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + int ret; + + ret =3D rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, 0, 0); + if (ret) + return ret; + + if (drvdata.rgb_mode >=3D ARRAY_SIZE(rgb_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); +}; + +static ssize_t rgb_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 1; i < ARRAY_SIZE(rgb_mode_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + +static ssize_t rgb_profile_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + size_t size =3D 1; + int ret; + u8 val; + + ret =3D kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + + if (val < 1 || val > 3) + return -EINVAL; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val, + size); + if (ret) + return ret; + + drvdata.rgb_profile =3D val; + + return count; +}; + +static ssize_t rgb_profile_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret =3D rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, 0, + 0); + if (ret) + return ret; + + if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); +}; + +static ssize_t rgb_profile_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1-3\n"); +} + +static void hid_go_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int ret; + + if (brightness > led_cdev->max_brightness) { + dev_err(led_cdev->dev, "Invalid argument\n"); + return; + } + + index =3D drvdata.rgb_profile + 3; + u8 rgb_profile[6] =3D { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + brightness, + drvdata.rgb_speed }; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + switch (ret) { + case 0: + led_cdev->brightness =3D brightness; + break; + case -ENODEV: /* during switch to IAP -ENODEV is expected */ + case -ENOSYS: /* during rmmod -ENOSYS is expected */ + dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); + break; + default: + dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); + }; +} + #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) = \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -1387,6 +1730,109 @@ static const struct attribute_group *top_level_attr= _groups[] =3D { &touchpad_attr_group, NULL, }; =20 +/* RGB */ +struct go_cfg_attr rgb_enabled =3D { FEATURE_LIGHT_ENABLE }; + +LEGO_DEVICE_ATTR_RW(rgb_enabled, "enabled", UNSPECIFIED, index, feature_st= atus); +static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index"); +static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index"); +static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index"); +static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range"); +static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range"); +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); +static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode"); +static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile"); +static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); + +static struct attribute *go_rgb_attrs[] =3D { + &dev_attr_rgb_effect.attr, + &dev_attr_rgb_effect_index.attr, + &dev_attr_rgb_enabled.attr, + &dev_attr_rgb_enabled_index.attr, + &dev_attr_rgb_mode.attr, + &dev_attr_rgb_mode_index.attr, + &dev_attr_rgb_profile.attr, + &dev_attr_rgb_profile_range.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_speed_range.attr, + NULL, +}; + +static struct attribute_group rgb_attr_group =3D { + .attrs =3D go_rgb_attrs, +}; + +struct mc_subled go_rgb_subled_info[] =3D { + { + .color_index =3D LED_COLOR_ID_RED, + .brightness =3D 0x50, + .intensity =3D 0x24, + .channel =3D 0x1, + }, + { + .color_index =3D LED_COLOR_ID_GREEN, + .brightness =3D 0x50, + .intensity =3D 0x22, + .channel =3D 0x2, + }, + { + .color_index =3D LED_COLOR_ID_BLUE, + .brightness =3D 0x50, + .intensity =3D 0x99, + .channel =3D 0x3, + }, +}; + +struct led_classdev_mc go_cdev_rgb =3D { + .led_cdev =3D { + .name =3D "go:rgb:joystick_rings", + .color =3D LED_COLOR_ID_RGB, + .brightness =3D 0x50, + .max_brightness =3D 0x64, + .brightness_set =3D hid_go_brightness_set, + }, + .num_colors =3D ARRAY_SIZE(go_rgb_subled_info), + .subled_info =3D go_rgb_subled_info, +}; + +static void cfg_setup(struct work_struct *work) +{ + int ret; + + /* RGB */ + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, + GET_FEATURE_STATUS, FEATURE_LIGHT_ENABLE, + UNSPECIFIED, 0, 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB enabled: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_RGB_CFG, + LIGHT_MODE_SEL, UNSPECIFIED, 0, 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Mode: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_RGB_CFG, + LIGHT_PROFILE_SEL, UNSPECIFIED, 0, 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Profile: %i\n", ret); + return; + } + + ret =3D rgb_attr_show(); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Profile Data: %i\n", ret); + return; + } +} + static int hid_go_cfg_probe(struct hid_device *hdev, const struct hid_device_id *_id) { @@ -1409,14 +1855,40 @@ static int hid_go_cfg_probe(struct hid_device *hdev, return ret; } =20 + ret =3D devm_led_classdev_multicolor_register(&hdev->dev, &go_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret =3D devm_device_add_group(go_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuration attributes\n"); + return ret; + } + + drvdata.led_cdev =3D &go_cdev_rgb.led_cdev; + init_completion(&drvdata.send_cmd_complete); =20 + /* Executing calls prior to returning from probe will lock the MCU. Sched= ule + * initial data call after probe has completed and MCU can accept calls. + */ + INIT_DELAYED_WORK(&drvdata.go_cfg_setup, &cfg_setup); + ret =3D schedule_delayed_work(&drvdata.go_cfg_setup, msecs_to_jiffies(2)); + if (!ret) { + dev_err(&hdev->dev, + "Failed to schedule startup delayed work\n"); + return -ENODEV; + } return 0; } =20 static void hid_go_cfg_remove(struct hid_device *hdev) { guard(mutex)(&drvdata.cfg_mutex); + cancel_delayed_work_sync(&drvdata.go_cfg_setup); sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); hid_hw_stop(hdev); --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f177.google.com (mail-dy1-f177.google.com [74.125.82.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F22FDEED8 for ; Sat, 24 Jan 2026 01:49:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219361; cv=none; b=bcL/GEdt0umsHXLMhAp1rFGcu+y9J8Bo/vemTJn5WFbsmr5ni6zlnTxw9dnIvecaDS/BAc6IcKTyCitvRjAEY7AaPOk8B91Rn+XbhQWqUHYLMewBDRiovKXQ7JswrDOBETKz04kkALFTY2PSfL+ss0ILqACJq9uBDctGOmwfFMI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219361; c=relaxed/simple; bh=Wali2wUCu0Y7HgALDpORP+ztHcLB1gdHLlhLhjF4bkE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rmEA4uHvZuitkdWsBWjRtKiX4Eh+JbRt7ArFlfJqNuo+h7VRUoeRpLt6KmQOaB5Mei0w73oCth+qBPbdANtWUbJgJWQXeDfmrf1xyvXDYSYh5gLo+CBImsWU7XvdiSnlipq/BXsxhRhLVlgf6TbMR3pBJdzOUDYZhl9PKqxm7Y8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Bz+/RZxv; arc=none smtp.client-ip=74.125.82.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Bz+/RZxv" Received: by mail-dy1-f177.google.com with SMTP id 5a478bee46e88-2b6b0500e06so3716129eec.1 for ; Fri, 23 Jan 2026 17:49:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219358; x=1769824158; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=fB+UXdbJZv/kqKsMjEjhslIic6Ng4MiMJztJEz9HC5M=; b=Bz+/RZxvHzLDCZ2By3WcP0Q6LdzpXS6msIMiCAv26g39f361OTWyKjMaiILuFPPTqe x2x9JAUXof6FBSvzwrQ3bPB6aze97HgG2iwKcyyYKmhpFNlk2WAy7YJf/qtfaJGlLEUC KGh3qJpDVCvAM4slsSvZuBJ3kz53cscGAkxzjshaf+VHApN8OwQRXhZpfTaJeEavdfpF yJWUzgiWabImQFbb6fwZtFMqNILvGMzH5Kil3dOkF/OAkyvso5OYpLd0FRnm2UNX6ROK zcZyD4d3RUyJ2dvjnr+mrf/tv7+KPLLPl+k15mtgNVPEiWlgj8GN7+KWjKEavCjrIcyB e6pw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219358; x=1769824158; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=fB+UXdbJZv/kqKsMjEjhslIic6Ng4MiMJztJEz9HC5M=; b=q5l7Xc/aQSmnnKitoo587GOxLAhXwdnQQb8EKxvRSdTp27vm87yOLsozev8uPnJul9 oW7FylqqChVdzRsaJkYmOeFdouc+VF3yXhrPv1O7Z8e8SLSg2Q8T6n6pRKErpi0san1W GaAdJQGj0mCUJa1XaAsxrcdHF1k/OLY2dR6zfnQ6uVFVFTwPvxtqirMYcUHSkZ1jQsd4 eFxyQiqzdT+zOtbzPIVyYVGwRlSuHAwp2OD5qbyn8wqFxBzb6vG9QoRcx0Xh4b+mzabZ nFkSidjYqJotL7980FZOwJii+Xsx3zttbmEaglUNK0XhMbsw0Hav/nezacSSjCvQn44T V9og== X-Forwarded-Encrypted: i=1; AJvYcCXPezKuBTz/1NDKWHIQnGC4yU8Mnx9SmvYHEVWZ4JITJ/ynMnM2Y/bugxTMp1D76jZZKL1+X7S88Oh4IOo=@vger.kernel.org X-Gm-Message-State: AOJu0YylzitOQgF2xIF2cnR8p1xu1pqk/W6L8toOQE0PLC0shBy706ob cjoRqeoqjra+EThIn5N+NbdfkxsjtzgO34M/dKS8w1KQDUBHb5YEF6rh X-Gm-Gg: AZuq6aKFycxEscnd/uHwDDdTpbF38xfgJMmvO67R3euPSapMsmIylRHVZ1xTiqRGI// rZaNE6H8d7c0Sxj30uuSbA9CiQbripTG6AB2FFbiHB7CnO61ZfID68R+jv+kS4/1+apcGXOI3Up rZGOyePAPZnAWnLnQO8Gz5/AKF9V9S8LVF5FEzrbY+njQ6F4XdaGNujoSlPSk9QOw36vIlcaNgQ 2TBwoBgQypy/4c9BVCfEVlSDWq7gCpue5cx7Ank6+cfkyKSjrScYUkyP9b5GlR6TuppX5x3bStY pOWRAyUE6N1ssDciN8Yt2k1CiMIXgTH4vy82qoCON/zAj0Yxd/TE3zr0nY/1XFXi0Ijz1GGoIcg /QKkBLMnvFMnGeFaoUTGOrXrjkx2HdyIjl1Mr5BeqIveeqUCei7JlUI4GaiB3nV3IG5zkb7OnNj VJIidiIuA+NBpTn2B3zEPvKcWhPfX/pZ05sU81RxAvJz7gxZ4emmsf2n4H+wit3kM= X-Received: by 2002:a05:7301:4e0b:b0:2b7:1253:3d4f with SMTP id 5a478bee46e88-2b739957211mr2193612eec.8.1769219357800; Fri, 23 Jan 2026 17:49:17 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:17 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 07/16] HID: hid-lenovo-go: Add Calibration Settings Date: Sat, 24 Jan 2026 01:48:58 +0000 Message-ID: <20260124014907.991265-8-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds calibration enable and last calibration status indicators for the triggers, joysticks, and handle gyros. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go.c | 284 +++++++++++++++++++++++++++++++++++- 1 file changed, 283 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 91eb61037a2c..6326a559fa73 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -43,8 +43,11 @@ struct hid_go_cfg { struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 fps_mode; u8 gp_left_auto_sleep_time; + u8 gp_left_gyro_cal_status; + u8 gp_left_joy_cal_status; u8 gp_left_notify_en; u8 gp_left_rumble_mode; + u8 gp_left_trigg_cal_status; u32 gp_left_version_firmware; u8 gp_left_version_gen; u32 gp_left_version_hardware; @@ -52,8 +55,11 @@ struct hid_go_cfg { u32 gp_left_version_protocol; u8 gp_mode; u8 gp_right_auto_sleep_time; + u8 gp_right_gyro_cal_status; + u8 gp_right_joy_cal_status; u8 gp_right_notify_en; u8 gp_right_rumble_mode; + u8 gp_right_trigg_cal_status; u32 gp_right_version_firmware; u8 gp_right_version_gen; u32 gp_right_version_hardware; @@ -227,7 +233,41 @@ static const char *const rumble_mode_text[] =3D { [RUMBLE_MODE_RPG] =3D "rpg", }; =20 -#define FPS_MODE_DPI 0x02 +#define FPS_MODE_DPI 0x02 +#define TRIGGER_CALIBRATE 0x04 +#define JOYSTICK_CALIBRATE 0x04 +#define GYRO_CALIBRATE 0x06 + +enum cal_device_type { + CALDEV_GYROSCOPE =3D 0x01, + CALDEV_JOYSTICK, + CALDEV_TRIGGER, + CALDEV_JOY_TRIGGER, +}; + +enum cal_enable { + CAL_UNKNOWN, + CAL_START, + CAL_STOP, +}; + +static const char *const cal_enabled_text[] =3D { + [CAL_UNKNOWN] =3D "unknown", + [CAL_START] =3D "start", + [CAL_STOP] =3D "stop", +}; + +enum cal_status_index { + CAL_STAT_UNKNOWN, + CAL_STAT_SUCCESS, + CAL_STAT_FAILURE, +}; + +static const char *const cal_status_text[] =3D { + [CAL_STAT_UNKNOWN] =3D "unknown", + [CAL_STAT_SUCCESS] =3D "success", + [CAL_STAT_FAILURE] =3D "failure", +}; =20 enum rgb_config_index { LIGHT_CFG_ALL =3D 0x01, @@ -264,6 +304,13 @@ static const char *const rgb_effect_text[] =3D { [RGB_EFFECT_RAINBOW] =3D "rainbow", }; =20 +enum device_status_index { + GET_CAL_STATUS =3D 0x02, + GET_UPGRADE_STATUS, + GET_MACRO_REC_STATUS, + GET_HOTKEY_TRIGG_STATUS, +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -508,6 +555,44 @@ static int hid_go_light_event(struct command_report *c= md_rep) } } =20 +static int hid_go_device_status_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->device_type) { + case LEFT_CONTROLLER: + switch (cmd_rep->data[0]) { + case CALDEV_GYROSCOPE: + drvdata.gp_left_gyro_cal_status =3D cmd_rep->data[1]; + return 0; + case CALDEV_JOYSTICK: + drvdata.gp_left_joy_cal_status =3D cmd_rep->data[1]; + return 0; + case CALDEV_TRIGGER: + drvdata.gp_left_trigg_cal_status =3D cmd_rep->data[1]; + return 0; + default: + return -EINVAL; + } + break; + case RIGHT_CONTROLLER: + switch (cmd_rep->data[0]) { + case CALDEV_GYROSCOPE: + drvdata.gp_right_gyro_cal_status =3D cmd_rep->data[1]; + return 0; + case CALDEV_JOYSTICK: + drvdata.gp_right_joy_cal_status =3D cmd_rep->data[1]; + return 0; + case CALDEV_TRIGGER: + drvdata.gp_right_trigg_cal_status =3D cmd_rep->data[1]; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -564,10 +649,16 @@ static int hid_go_raw_event(struct hid_device *hdev, = struct hid_report *report, case GET_RGB_CFG: ret =3D hid_go_light_event(cmd_rep); break; + case GET_DEVICE_STATUS: + ret =3D hid_go_device_status_event(cmd_rep); + break; case SET_FEATURE_STATUS: case SET_MOTOR_CFG: case SET_DPI_CFG: case SET_RGB_CFG: + case SET_TRIGGER_CFG: + case SET_JOYSTICK_CFG: + case SET_GYRO_CFG: ret =3D hid_go_set_event_return(cmd_rep); break; default: @@ -1152,6 +1243,101 @@ static ssize_t fps_mode_dpi_index_show(struct devic= e *dev, return sysfs_emit(buf, "500 800 1200 1800\n"); } =20 +static ssize_t device_status_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum device_status_index index, + enum dev_type device_type, + enum cal_device_type cal_type) +{ + u8 i; + + switch (index) { + case GET_CAL_STATUS: + switch (device_type) { + case LEFT_CONTROLLER: + switch (cal_type) { + case CALDEV_GYROSCOPE: + i =3D drvdata.gp_left_gyro_cal_status; + break; + case CALDEV_JOYSTICK: + i =3D drvdata.gp_left_joy_cal_status; + break; + case CALDEV_TRIGGER: + i =3D drvdata.gp_left_trigg_cal_status; + break; + default: + return -EINVAL; + } + break; + case RIGHT_CONTROLLER: + switch (cal_type) { + case CALDEV_GYROSCOPE: + i =3D drvdata.gp_right_gyro_cal_status; + break; + case CALDEV_JOYSTICK: + i =3D drvdata.gp_right_joy_cal_status; + break; + case CALDEV_TRIGGER: + i =3D drvdata.gp_right_trigg_cal_status; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + }; + + if (i >=3D ARRAY_SIZE(cal_status_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", cal_status_text[i]); +} + +static ssize_t calibrate_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, u8 cmd, u8 sub_cmd, + size_t count, enum dev_type device_type) +{ + size_t size =3D 1; + u8 val =3D 0; + int ret; + + ret =3D sysfs_match_string(cal_enabled_text, buf); + if (ret < 0) + return ret; + + val =3D ret; + if (!val) + size =3D 0; + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, cmd, sub_cmd, + device_type, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t calibrate_config_options(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 1; i < ARRAY_SIZE(cal_enabled_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", cal_enabled_text[i]); + + buf[count - 1] =3D '\n'; + + return count; +} + static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cm= d, enum rgb_config_index index, u8 *val, size_t size) { @@ -1458,6 +1644,30 @@ static void hid_go_brightness_set(struct led_classde= v *led_cdev, } \ static DEVICE_ATTR_RO_NAMED(_name, _attrname) =20 +#define LEGO_CAL_DEVICE_ATTR(_name, _attrname, _scmd, _dtype, _rtype) = \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return calibrate_config_store(dev, attr, buf, _name.index, \ + _scmd, count, _dtype); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return calibrate_config_options(dev, attr, buf); \ + } \ + static DEVICE_ATTR_WO_NAMED(_name, _attrname) + +#define LEGO_DEVICE_STATUS_ATTR(_name, _attrname, _scmd, _dtype) = \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return device_status_show(dev, attr, buf, _name.index, _scmd, \ + _dtype); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + /* Gamepad - MCU */ struct go_cfg_attr version_product_mcu =3D { PRODUCT_VERSION }; LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, versi= on); @@ -1595,9 +1805,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumbl= e_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, "rumble_notification_index"); =20 +struct go_cfg_attr cal_trigg_left =3D { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_left, "calibrate_trigger", SET_TRIGGER_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_left_index, "calibrate_trigger_index= "); + +struct go_cfg_attr cal_joy_left =3D { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_left, "calibrate_joystick", SET_JOYSTICK_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_left_index, "calibrate_joystick_index"= ); + +struct go_cfg_attr cal_gyro_left =3D { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_left, "calibrate_gyro", SET_GYRO_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_left_index, "calibrate_gyro_index"); + +struct go_cfg_attr cal_trigg_left_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_left_status, "calibrate_trigger_status", + LEFT_CONTROLLER, CALDEV_TRIGGER); + +struct go_cfg_attr cal_joy_left_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_left_status, "calibrate_joystick_status", + LEFT_CONTROLLER, CALDEV_JOYSTICK); + +struct go_cfg_attr cal_gyro_left_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_left_status, "calibrate_gyro_status", + LEFT_CONTROLLER, CALDEV_GYROSCOPE); + static struct attribute *left_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_left.attr, &dev_attr_auto_sleep_time_left_range.attr, + &dev_attr_cal_gyro_left.attr, + &dev_attr_cal_gyro_left_index.attr, + &dev_attr_cal_gyro_left_status.attr, + &dev_attr_cal_joy_left.attr, + &dev_attr_cal_joy_left_index.attr, + &dev_attr_cal_joy_left_status.attr, + &dev_attr_cal_trigg_left.attr, + &dev_attr_cal_trigg_left_index.attr, + &dev_attr_cal_trigg_left_status.attr, &dev_attr_imu_bypass_left.attr, &dev_attr_imu_bypass_left_index.attr, &dev_attr_imu_enabled_left.attr, @@ -1666,9 +1912,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumb= le_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, "rumble_notification_index"); =20 +struct go_cfg_attr cal_trigg_right =3D { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_right, "calibrate_trigger", SET_TRIGGER_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_right_index, "calibrate_trigger_inde= x"); + +struct go_cfg_attr cal_joy_right =3D { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_right, "calibrate_joystick", SET_JOYSTICK_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_right_index, "calibrate_joystick_index= "); + +struct go_cfg_attr cal_gyro_right =3D { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_right, "calibrate_gyro", SET_GYRO_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_right_index, "calibrate_gyro_index"); + +struct go_cfg_attr cal_trigg_right_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_right_status, "calibrate_trigger_status", + RIGHT_CONTROLLER, CALDEV_TRIGGER); + +struct go_cfg_attr cal_joy_right_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_right_status, "calibrate_joystick_status", + RIGHT_CONTROLLER, CALDEV_JOYSTICK); + +struct go_cfg_attr cal_gyro_right_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_right_status, "calibrate_gyro_status", + RIGHT_CONTROLLER, CALDEV_GYROSCOPE); + static struct attribute *right_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_right.attr, &dev_attr_auto_sleep_time_right_range.attr, + &dev_attr_cal_gyro_right.attr, + &dev_attr_cal_gyro_right_index.attr, + &dev_attr_cal_gyro_right_status.attr, + &dev_attr_cal_joy_right.attr, + &dev_attr_cal_joy_right_index.attr, + &dev_attr_cal_joy_right_status.attr, + &dev_attr_cal_trigg_right.attr, + &dev_attr_cal_trigg_right_index.attr, + &dev_attr_cal_trigg_right_status.attr, &dev_attr_imu_bypass_right.attr, &dev_attr_imu_bypass_right_index.attr, &dev_attr_imu_enabled_right.attr, --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f170.google.com (mail-dy1-f170.google.com [74.125.82.170]) (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 907E5212564 for ; Sat, 24 Jan 2026 01:49:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219362; cv=none; b=C51d0WsPLjhzAWT1aiuA2DH4+lRCsuB4EEVXiydGF6i9azqK8Pzly9FTdgnncoz074vTHtoRkxDnxLBU/3o6ll9qrbCs8ALVSBI1wWCULL8tjCegoQxH+vxFYKcRXVGmib6GlpQWHBbWUufRv3pM7dTc5Iq/GlxA/rqtCiXwh6g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219362; c=relaxed/simple; bh=qJuj4Ia+10TBltpyLI+g8S5XrF+MJ8/PTrYWRa/oqwM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AoA0cElOZwZKI6e8I4MyUKEYX5xKamvv4Rz6L7IcyZB8gY+97+BUJ8nXTp0sMJAFaRBKv7kgJLs2CSkeEcWyYyDzAkAUVY9+lzsHHUR9AeEJ1U9DVDaYY7uG/zgaDtbMxL46WxKwtKNgFzg1EaAoemLMczyw92jq0bqm5Dh/Xzw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=F5S716y2; arc=none smtp.client-ip=74.125.82.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="F5S716y2" Received: by mail-dy1-f170.google.com with SMTP id 5a478bee46e88-2b1981ca515so3234828eec.1 for ; Fri, 23 Jan 2026 17:49:19 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219359; x=1769824159; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=0aZEaY+55AD/U4p/NVSCgdZ6HlJBX4UUvXfJ8P13h3Y=; b=F5S716y27XQ1v1g5YICN8DN0huiNAK7NM+DtdtSxC/IGyku+UfWHFHmFJ/roOLskvU jU0iRNK5WMc+gXk3qglwLy7URcpsdpCqV/BcUdxgCiaGIgTEHxWFPnoBkNl5Nvq2V3az aD21A3j4bhcnbBBUl/v0qzhqOeJkHFEHrmk19AdafkQuOH02saAUZ5LY618vLYYvg/v1 ezzC8qUh0p48cLvDrhA+Rm9oAE2N3WkP/D/ewvO2btAT2NpPKA/yT8f47IZ8KQQCiAQq vw44vwavRnGs/YXJdbJyb/csgSBCV2L50GI3lI3+T/gWBvSeYdS++VvJI/7jmMIQTFOn vFvQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219359; x=1769824159; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=0aZEaY+55AD/U4p/NVSCgdZ6HlJBX4UUvXfJ8P13h3Y=; b=SZzijI/VayFIp0DnatLPsD7G3YJGu+NfbwjCH7M9T/Ed0PcXcugbPJthuzMykHHgxv h/2+VbSHNHO5DUmDjQHifCJvh4U3BK1boQySKXHBIOyozi+0FFchwg1ygJkItYaeTgdy 1eaxRLU6DdtCGU7YidWrp+PMitSR40LgYRDQRiZgr/dkERNcaSAfCHgQxnTHUesrFJId /gWOsJQBA+sPOu5KzAFxG8iHYkS3z4QdD38OC7IWEXW5j4nobV0gypbSsGKXqGJyQv3Z zaCy+RzuaWQV63oilk7WgfASyrcmVozKk1siyvFIBwIw/XpeBXuPthuUkDfHeZRvwMjb q+hg== X-Forwarded-Encrypted: i=1; AJvYcCWhiZr9955+14bgz2RKJ2FSXdeUOVakQ9mt18X86h5zVwTctGB+R8xXPgc6O4/aFr2KCsaeZHRVHl6miPg=@vger.kernel.org X-Gm-Message-State: AOJu0Yz/W0nO4NCbTN63ystuhLpEWJJgVXnQBZuY4IolqVCudxzLWluL RCYcxm/yLDZO3LKrBauVPi8DGb/F9F/hvwjW0hHpCJ/n1UEWx1e1Gdbj X-Gm-Gg: AZuq6aJ1tmi2v/YTjUMJYeyf/ffPF/ge0oDShgrG7n++S8LBe6ncXOf24XuETwGlTNs 5/uqo/VN/H9DUuASzaHWd3BBMaY0XnRwEMz7W9aD66q9S+6XoiYhMeXKK8iBMke44jUjku9tT71 m/hPkUQOQ5Mwq4TKZdyi/ksEa8uR/Oq1r3Z4ef9Kq9lqmU0go4aSzjc7oiv3uk05DwNBW88Lqrk GwIJkTM2EoBIqNep4egi9HYbgfAEZZmmX1EtopyZnpxDUeecufafa3PhSMLXhHOOs2l32tlDT0+ Ms/Kp24vn/jpT/thirD0QVJXDnK+EiL9+xTTFZc70UU6/BDU/rOo2fE7epKisNPLg2txL3HDKqs nRlMiiA/Cv+9tDOnCPx7u03MMK2b5agmmORUuZvrNzZtm4ZBJ4cJ+zr/lgbdtHsWC4EItWe0ryd gH2RGgWfl+RoLYVbwtmSYLqa7j2vlLSFcpQYJwX3Ey0cYVQh91G9n8SCB5AIrKIlc= X-Received: by 2002:a05:7301:4586:b0:2b7:2fff:ed30 with SMTP id 5a478bee46e88-2b739b6cb22mr2614300eec.20.1769219358624; Fri, 23 Jan 2026 17:49:18 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:18 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 08/16] HID: hid-lenovo-go: Add OS Mode Toggle Date: Sat, 24 Jan 2026 01:48:59 +0000 Message-ID: <20260124014907.991265-9-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds OS Mode toggle, who's primary function is to change the built-in functional chords to use the right handle legion button instead of the left handle legion button as the mode shift key. This setting needs to be restored after resume, so a reset-resume hook is added. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- V3: - Fix collision with os_mode_index attribute and os_mode_index enum. --- drivers/hid/hid-lenovo-go.c | 137 ++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 6326a559fa73..a09950874add 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -76,6 +76,7 @@ struct hid_go_cfg { u32 mcu_version_product; u32 mcu_version_protocol; u32 mouse_dpi; + u8 os_mode; u8 rgb_effect; u8 rgb_en; u8 rgb_mode; @@ -166,6 +167,8 @@ enum feature_status_index { FEATURE_GAMEPAD_MODE =3D 0x0e, }; =20 +#define FEATURE_OS_MODE 0x69 + enum fps_switch_status_index { FPS_STATUS_UNKNOWN, GAMEPAD, @@ -311,6 +314,23 @@ enum device_status_index { GET_HOTKEY_TRIGG_STATUS, }; =20 +enum os_mode_cfg_index { + SET_OS_MODE =3D 0x09, + GET_OS_MODE, +}; + +enum os_mode_type_index { + OS_UNKNOWN, + WINDOWS, + LINUX, +}; + +static const char *const os_mode_text[] =3D { + [OS_UNKNOWN] =3D "unknown", + [WINDOWS] =3D "windows", + [LINUX] =3D "linux", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -593,6 +613,21 @@ static int hid_go_device_status_event(struct command_r= eport *cmd_rep) } } =20 +static int hid_go_os_mode_cfg_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case SET_OS_MODE: + if (cmd_rep->data[0] !=3D 1) + return -EIO; + return 0; + case GET_OS_MODE: + drvdata.os_mode =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -666,6 +701,9 @@ static int hid_go_raw_event(struct hid_device *hdev, st= ruct hid_report *report, break; }; break; + case OS_MODE_DATA: + ret =3D hid_go_os_mode_cfg_event(cmd_rep); + break; default: goto passthrough; }; @@ -1338,6 +1376,64 @@ static ssize_t calibrate_config_options(struct devic= e *dev, return count; } =20 +static ssize_t os_mode_store(struct device *dev, struct device_attribute *= attr, + const char *buf, size_t count) +{ + size_t size =3D 1; + int ret; + u8 val; + + ret =3D sysfs_match_string(os_mode_text, buf); + if (ret <=3D 0) + return ret; + + val =3D ret; + ret =3D mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + SET_OS_MODE, USB_MCU, &val, size); + if (ret < 0) + return ret; + + drvdata.os_mode =3D val; + + return count; +} + +static ssize_t os_mode_show(struct device *dev, struct device_attribute *a= ttr, + char *buf) +{ + ssize_t count =3D 0; + int ret; + u8 i; + + ret =3D mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + GET_OS_MODE, USB_MCU, 0, 0); + if (ret) + return ret; + + i =3D drvdata.os_mode; + if (i >=3D ARRAY_SIZE(os_mode_text)) + return -EINVAL; + + count =3D sysfs_emit(buf, "%s\n", os_mode_text[i]); + + return count; +} + +static ssize_t os_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 1; i < ARRAY_SIZE(os_mode_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", os_mode_text[i]); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cm= d, enum rgb_config_index index, u8 *val, size_t size) { @@ -1704,6 +1800,9 @@ static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_= index, static DEVICE_ATTR_RW(fps_mode_dpi); static DEVICE_ATTR_RO(fps_mode_dpi_index); =20 +static DEVICE_ATTR_RW(os_mode); +static DEVICE_ATTR_RO(os_mode_index); + static struct attribute *mcu_attrs[] =3D { &dev_attr_fps_mode_dpi.attr, &dev_attr_fps_mode_dpi_index.attr, @@ -1712,6 +1811,8 @@ static struct attribute *mcu_attrs[] =3D { &dev_attr_gamepad_mode_index.attr, &dev_attr_gamepad_rumble_intensity.attr, &dev_attr_gamepad_rumble_intensity_index.attr, + &dev_attr_os_mode.attr, + &dev_attr_os_mode_index.attr, &dev_attr_reset_mcu.attr, &dev_attr_version_firmware_mcu.attr, &dev_attr_version_gen_mcu.attr, @@ -2177,6 +2278,27 @@ static void hid_go_cfg_remove(struct hid_device *hde= v) hid_set_drvdata(hdev, NULL); } =20 +static int hid_go_cfg_reset_resume(struct hid_device *hdev) +{ + u8 os_mode =3D drvdata.os_mode; + int ret; + + ret =3D mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + SET_OS_MODE, USB_MCU, &os_mode, 1); + if (ret < 0) + return ret; + + ret =3D mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE, + GET_OS_MODE, USB_MCU, 0, 0); + if (ret < 0) + return ret; + + if (drvdata.os_mode !=3D os_mode) + return -ENODEV; + + return 0; +} + static int hid_go_probe(struct hid_device *hdev, const struct hid_device_i= d *id) { int ret, ep; @@ -2235,6 +2357,20 @@ static void hid_go_remove(struct hid_device *hdev) } } =20 +static int hid_go_reset_resume(struct hid_device *hdev) +{ + int ep =3D get_endpoint_address(hdev); + + switch (ep) { + case GO_GP_INTF_IN: + return hid_go_cfg_reset_resume(hdev); + default: + break; + } + + return 0; +} + static const struct hid_device_id hid_go_devices[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT) }, @@ -2254,6 +2390,7 @@ static struct hid_driver hid_lenovo_go =3D { .probe =3D hid_go_probe, .remove =3D hid_go_remove, .raw_event =3D hid_go_raw_event, + .reset_resume =3D hid_go_reset_resume, }; module_hid_driver(hid_lenovo_go); =20 --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f178.google.com (mail-dy1-f178.google.com [74.125.82.178]) (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 57739223336 for ; Sat, 24 Jan 2026 01:49:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219362; cv=none; b=XUZwj1MePve1m2BpQsGJ9XSEpjjX1/WlsV8ckBgkQuADhIjemZZ1rKvj111Kw1QhFztkhk9mlnUGoww+z3PVEZkjSiVWytyYxIvAuHupm/C0TRcsN+nkEMPmlyRIq5oRXP5xKYUGU/GzRxXk1W4Dy+EFT315PDTpZh0AFU+1+W8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219362; c=relaxed/simple; bh=yyHWHTZ3q3DotbU1mpLzfWDbwKx97IS2+wB4iEtV00w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MlpiXldmEhMe3jeommWho0VMS/cmDJcGGDg5y8XlXxz2mIPln6ZOrC/BdFhlE5rXplwVI0By/ig0+bCGJU/QabZup9nLQzv2kCLSBHCiqGK56QwDQkZBoDz5aA1sc7HDhyiOdiGJG0v809ED/oRPWv0xVhBP4P3D71e9gAC9cDA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=moF0QcdZ; arc=none smtp.client-ip=74.125.82.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="moF0QcdZ" Received: by mail-dy1-f178.google.com with SMTP id 5a478bee46e88-2b720e4dcb4so2863791eec.0 for ; Fri, 23 Jan 2026 17:49:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219359; x=1769824159; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=LwReS+VyqVAE69wkUgUOvkftIiDVyESdfXyVkGzdV9k=; b=moF0QcdZe++W5ptyLNjzpFOLk+uc6uV2JpNxY1fKjb5POXB7wnP9rF4PLr+lyylmqA +yDzgpER8Gif2IhL/FtFs6b8aIBbSty2zhBaKKDXoh2MK9oYpTO5/tVSb2JpV1NVuvS2 kQ17rDspaHSTnGETXnBBp6cWaMjYv3U83656aBNZre4Y9HQ4fNWknkoJVAG+LYYqyY0K 7onJlFnRabP+4Rv7ogTs/asgZgHXsM5xqhpr5VUiTTxvzTbRgMF34Yq03kE9+aZQNg16 vDUMxouA48XitdzRXnEvt38vXxFTlEPYWp4id+aUe99quA0L6R8IIvChmAXHuyyVmibv JAzg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219359; x=1769824159; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=LwReS+VyqVAE69wkUgUOvkftIiDVyESdfXyVkGzdV9k=; b=Jx2NECyaOqrjs6V9CpLSJ8fL2XWpawHnAvdL6bbKBwTtUCvMu+JqA+HRk/PBZP5V2W g1TWQOcagFiyUZWsTz1i8vf39iDne47yATRJhGBphGotwVdPyLtJ3mtO56jSb0hbHsge WaqCTiaAH2NCCE5A1JE3NRRzXVvKbvCgtq2uG1kWwN6XlxZP46qLlee6ctkAGyKkhfmE 6Cx7Yu6Y4RWRF4GyiYmhLGmufwWFV82zu+cMJyF4XRZlFdW9gGiD68853540GVKFG8nV XWxsW0YTOLN6Uiaf1ok9QK2OCvkbfrcDaJBsQ8V58blio/ms1jGkmon609ZAUvjUUIRt Y59Q== X-Forwarded-Encrypted: i=1; AJvYcCV8zQU5gQ+C+BuMwXWEscsdQ1Xvd3frt3bu0d5+x4YHPu6pAUJgJG27QenjCmnjAlxVTmJNdmHB/BESK8M=@vger.kernel.org X-Gm-Message-State: AOJu0Yyxon6i0J6AxLyt0+TLHDbEDh5rqpwy3aTccwZy7KxTrQhrEDWB aKq3qk/L911sGuVNPnZISix4Sjgq0FpsUKpPhMYwm0+7g5t55jilljsI X-Gm-Gg: AZuq6aLRsRCQqKgpxibOOIsQqbwxpR6v7x9dWZuvwinU5gGLMXqdJkUz7fTfjU7nQBN YEEFS5e7IgPztqwqDcF1Y+HIX+cotZ2QwDPvpbmq0LKhCk5cqicMbEJFlQWZicwxB/HnE95dCIa rQlt5Hfm29qH8V7DIa/x+HpvyYnLcfIq/KXQEw3+mV0npyKrFZ4FNqqGqh1gPLaEyPDJ2ngemWF fMt7f+SBSzqMp4uIx9jug4pMc7FdyX3orpvx9/szPO60WscVZ1BuS56YQORoPkzWR8BjnCSSbz0 Mk0187qDknrieeIzE7TgcnskQ5iN/zRW+OBvwE/ckGwNqmwDKSNBVPfLrY7RDjATgsDg5ZMgbW/ 8MXIhfUl2VYXMRJATnKAdNR/c36gaZu7JIrQqORd9GfY1pMtFLu2UdH8MlKMHBm4MLguxE88jYD RkOGZBQsB2yKZzbkXFyubJ+9IDwtRrO18P1/KfL5Zo7ZiaVyvxd00QEmLx9SgYjTQ= X-Received: by 2002:a05:7300:ed0e:b0:2a4:3592:c60e with SMTP id 5a478bee46e88-2b739b961cfmr2913536eec.31.1769219359370; Fri, 23 Jan 2026 17:49:19 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:19 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, Richard Hughes Subject: [PATCH v3 09/16] HID: Include firmware version in the uevent Date: Sat, 24 Jan 2026 01:49:00 +0000 Message-ID: <20260124014907.991265-10-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Mario Limonciello Userspace software fwupd probes some HID devices when the daemon starts up to determine the current firmware version in order to be able to offer updated firmware if the manufacturer has made it available. In order to do this fwupd will detach the existing kernel driver if one is present, send a HID command and then reattach the kernel driver. This can be problematic if the user is using the HID device at the time that fwupd probes the hardware and can cause a few frames of input to be dropped. In some cases HID drivers already have a command to look up the firmware version, and so if that is exported to userspace fwupd can discover it and avoid needing to detach the kernel driver until it's time to update the device. Introduce a new member in the struct hid_device for the version and export a new uevent variable HID_FIRMWARE_VERSION that will display the version that HID drivers obtained. Cc: Richard Hughes Signed-off-by: Mario Limonciello Reviewed-by: Mark Pearson --- drivers/hid/hid-core.c | 5 +++++ include/linux/hid.h | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index a5b3a8ca2fcb..524f2b9ed512 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2887,6 +2887,11 @@ static int hid_uevent(const struct device *dev, stru= ct kobj_uevent_env *env) if (add_uevent_var(env, "MODALIAS=3Dhid:b%04Xg%04Xv%08Xp%08X", hdev->bus, hdev->group, hdev->vendor, hdev->product)) return -ENOMEM; + if (hdev->firmware_version) { + if (add_uevent_var(env, "HID_FIRMWARE_VERSION=3D0x%04llX", + hdev->firmware_version)) + return -ENOMEM; + } =20 return 0; } diff --git a/include/linux/hid.h b/include/linux/hid.h index a4ddb94e3ee5..8249da55c9ba 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -698,6 +698,7 @@ struct hid_device { char name[128]; /* Device name */ char phys[64]; /* Device physical location */ char uniq[64]; /* Device unique identifier (serial #) */ + u64 firmware_version; /* Firmware version */ =20 void *driver_data; =20 --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f174.google.com (mail-dy1-f174.google.com [74.125.82.174]) (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 379FC226863 for ; Sat, 24 Jan 2026 01:49:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219365; cv=none; b=VclHKiJVBAr7jQmx2mvrmDlfqPXtPxnFQdOtIjixPgykjy1wxn7cxZpupKu6P817cBHXkExxJCWCI/W9yKAvNaFJpXcQDsvOPMKGwIPDnKlZhjv2Ro9nVq8g4FjDlQq1NIprhToatm0gaFUd032CIKPJZfKkntxwnhpW9KYPpQ0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219365; c=relaxed/simple; bh=YZAV2fCv+zHtZckQV8so+7ZE7tatCYtf4LTOqJNJgHk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=j6aeburmv6gugwmWY2wpGxfOKNfwNLbTq14gafeKNXr0fBeNXjm6ZGRLFTtpTMxUxhonWs8+lkqwFhWdrLvysjB+HRnx6/XcNvPNDB5YKloHT72IrF59Xt1f+0JBuCBw91bWWj+6Bkg+ErkAkUvIrwt5AvwZ+Gw4g2tyb6gobdk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DoysAizb; arc=none smtp.client-ip=74.125.82.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DoysAizb" Received: by mail-dy1-f174.google.com with SMTP id 5a478bee46e88-2b4520f6b32so4540802eec.0 for ; Fri, 23 Jan 2026 17:49:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219360; x=1769824160; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=hklQKX6M0DwYqoKUXsL3kGgABpcCY86KWl+klqFSMUQ=; b=DoysAizbk+4hjS0zH5KJbhwD7mjOPu+FZTNxzuNrBsXBUiLLIvTCwYqfIb1AL3Opu9 WE9IJxlQOJ3+ciwtC8tcFc+4HWwT2zR0os5YZ0hpxB+NraKi8WI7ItTkuNVNpcWBbjGM aqeHkJ4nHfg7MdoXLniTAqOHeyLalc7asylsNmHVxmnGyf0z4JdpWHfbcIfKwGi8nnFV tFL4GJ0szJxM8jZZ5mmZ+fpKeAT7xIRZmGBaCVeu3Mgdb2Unis1GqyfFC4kBjc0BMGN0 lTudGPSkZcAkcp/Acz9wdkW9wxACRNkP2ASsjqaPQQbCDWXrDlenZIpM0NFolAsYTnRR 31fg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219360; x=1769824160; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=hklQKX6M0DwYqoKUXsL3kGgABpcCY86KWl+klqFSMUQ=; b=mAA7QOb0cmoz46fT2AnXY54CU/XWweSn2k4srXLF4fLA86XOoLeooejFkgnc0NJ6HB IZk2VdptlCf+LupptgxhsQxgrMnbiyElwbGcqlXHVrSGSg7C4kRQad2xNiVBlSs6sDF9 CGgw4h29jK6N+nBRwGodKPXXpABU0D+WJHXbVuYvvZaq3UDdCWFuzqryPru9cUhdxdY3 nSCIspNp164mK6NEkXfNdrsyK/5ngYFTVK2BtzpS8/H4B44dQ529ptsp+qeBlEzTLOtI ThKJ3TO8RlJ1rkU1jM0CR9GxCOgwcC0yoZ9u7MAoq06uBAhywLbfJKSMPUbBzbuF5msg sgdw== X-Forwarded-Encrypted: i=1; AJvYcCWDXsc/cZh1K42He62sgksBHErDp5YiEoSOr97JI4krhVOl1gamU3qLoW6LQl29JzMdDQU46GRvsVRFGkU=@vger.kernel.org X-Gm-Message-State: AOJu0YzTByIeh4AJfpCGvxN6XXODaEA26RVwzqbe7gcGbr13XDFdiDby bTCCv6S+xK+GMdArbR1OCnwPFOyqADTIIzUNNy84DmYx85C9S4WkqcLA X-Gm-Gg: AZuq6aLWR66OXJF2gqZtRZQPDIODbYXZ/cbXXtX7A/+8b8vYK2TdO/GESotLJ8jABkE BCVOlsgEnfawHHi1HbEOcKRBnleIHBsJiSar6uTYs/aDGRycDGIQgg5I0RFdVSOUMhu+UfBO5Gl RSWSZt/NzSgPC4F+L1TkdXL9XWAln9W98SPGBril/qsmYi91R2MfdXU/Ez5nAg6833Wi/KKthEW luPZMsQiQ60LtGAYdMjcENAn6auoFuiURpquAjWgpwTYxqpwgNQ5idzd1TtpyPVLYFoWbJaNrO9 39lPV3ts5mSBe/8MJWVKe6vapRHt8gBB43rKGSGNiazZYHMeBdyAjt23AbK6n0r8Ds2wNZzXOIy A8sqFc7e5qpwy+Lkd3mkzj9DAx47gnTILmLAvWrj9+Jh7t82gMmk3S9kIl5QBGOjFYGc2DjDYFM EVX/TXRTzYBifQNte82l+8ZjmYKRJmTO7rDTjgTcF4O21xL89gMsGFe1DI+27qXNo= X-Received: by 2002:a05:7300:2310:b0:2b7:1e86:35ee with SMTP id 5a478bee46e88-2b739b71179mr2222242eec.28.1769219360136; Fri, 23 Jan 2026 17:49:20 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:19 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 10/16] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver Date: Sat, 24 Jan 2026 01:49:01 +0000 Message-ID: <20260124014907.991265-11-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds initial framework for a new HID driver, hid-lenovo-go-s, along with a uevent to report the firmware version for the MCU. This driver primarily provides access to the configurable settings of the Lenovo Legion Go S controller. It will attach if the controller is in xinput or dinput mode. Non-configuration raw reports are forwarded to ensure the other endpoints continue to function as normal. Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- V3: - Include Mario's SOB tag --- MAINTAINERS | 1 + drivers/hid/Kconfig | 12 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 4 + drivers/hid/hid-lenovo-go-s.c | 293 ++++++++++++++++++++++++++++++++++ 5 files changed, 311 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go-s.c diff --git a/MAINTAINERS b/MAINTAINERS index c6396be68ff1..723598010189 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14140,6 +14140,7 @@ M: Derek J. Clark M: Mark Pearson L: linux-input@vger.kernel.org S: Maintained +F: drivers/hid/hid-lenovo-go-s.c F: drivers/hid/hid-lenovo-go.c F: drivers/hid/hid-lenovo.c =20 diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 74ac6793b29a..2b1a9fa93758 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -633,6 +633,18 @@ config HID_LENOVO_GO and Legion Go 2 Handheld Console Controllers. Say M here to compile this driver as a module. The module will be called hid-lenovo-go. =20 +config HID_LENOVO_GO_S + tristate "HID Driver for Lenovo Legion Go S Controller" + depends on USB_HID + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + Support for Lenovo Legion Go S Handheld Console Controller. + + Say Y here to include configuration interface support for the Lenovo Legi= on Go + S. Say M here to compile this driver as a module. The module will be call= ed + hid-lenovo-go-s. + config HID_LETSKETCH tristate "Letsketch WP9620N tablets" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 11435bce4e47..ef9169974bf0 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_HID_KYSONA) +=3D hid-kysona.o obj-$(CONFIG_HID_LCPOWER) +=3D hid-lcpower.o obj-$(CONFIG_HID_LENOVO) +=3D hid-lenovo.o obj-$(CONFIG_HID_LENOVO_GO) +=3D hid-lenovo-go.o +obj-$(CONFIG_HID_LENOVO_GO_S) +=3D hid-lenovo-go-s.o obj-$(CONFIG_HID_LETSKETCH) +=3D hid-letsketch.o obj-$(CONFIG_HID_LOGITECH) +=3D hid-logitech.o obj-$(CONFIG_HID_LOGITECH) +=3D hid-lg-g15.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 309e15580a38..eee1091125ca 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -723,6 +723,10 @@ #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 =20 +#define USB_VENDOR_ID_QHE 0x1a86 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT 0xe310 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT 0xe311 + #define USB_VENDOR_ID_JABRA 0x0b0e #define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412 #define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420 diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c new file mode 100644 index 000000000000..3e9041e746df --- /dev/null +++ b/drivers/hid/hid-lenovo-go-s.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go S devices. + * + * Copyright (c) 2025 Derek J. Clark + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define GO_S_CFG_INTF_IN 0x84 +#define GO_S_PACKET_SIZE 64 + +struct hid_gos_cfg { + unsigned char *buf; + struct delayed_work gos_cfg_setup; + struct completion send_cmd_complete; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ +} drvdata; + +struct command_report { + u8 cmd; + u8 sub_cmd; + u8 data[63]; +} __packed; + +struct version_report { + u8 cmd; + u32 version; + u8 reserved[59]; +} __packed; + +enum mcu_command_index { + GET_VERSION =3D 0x01, + GET_MCU_ID, + GET_GAMEPAD_CFG, + SET_GAMEPAD_CFG, + GET_TP_PARAM, + SET_TP_PARAM, + GET_RGB_CFG =3D 0x0f, + SET_RGB_CFG, + GET_PL_TEST =3D 0xdf, +}; + +#define FEATURE_NONE 0x00 + +static int hid_gos_version_event(u8 *data) +{ + struct version_report *ver_rep =3D (struct version_report *)data; + + drvdata.hdev->firmware_version =3D get_unaligned_le32(&ver_rep->version); + return 0; +} + +static u8 get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf =3D to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep; + + if (intf) { + ep =3D intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; + } + + return -ENODEV; +} + +static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *r= eport, + u8 *data, int size) +{ + struct command_report *cmd_rep; + int ep, ret; + + if (size !=3D GO_S_PACKET_SIZE) + goto passthrough; + + ep =3D get_endpoint_address(hdev); + if (ep !=3D GO_S_CFG_INTF_IN) + goto passthrough; + + cmd_rep =3D (struct command_report *)data; + + switch (cmd_rep->cmd) { + case GET_VERSION: + ret =3D hid_gos_version_event(data); + break; + default: + ret =3D -EINVAL; + break; + } + dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", + GO_S_PACKET_SIZE, data); + + complete(&drvdata.send_cmd_complete); + return ret; + +passthrough: + /* Forward other HID reports so they generate events */ + hid_input_report(hdev, HID_INPUT_REPORT, data, size, 1); + return 0; +} + +static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index, + u8 *data, size_t len) +{ + u8 header[] =3D { command, index }; + size_t header_size =3D ARRAY_SIZE(header); + size_t total_size =3D header_size + len; + int timeout =3D 5; + int ret; + + /* PL_TEST commands can take longer because they go out to another device= */ + if (command =3D=3D GET_PL_TEST) + timeout =3D 200; + + guard(mutex)(&drvdata.cfg_mutex); + memcpy(drvdata.buf, header, header_size); + memcpy(drvdata.buf + header_size, data, len); + memset(drvdata.buf + total_size, 0, GO_S_PACKET_SIZE - total_size); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_S_PACKET_SIZE, drvdata.buf); + + ret =3D hid_hw_output_report(hdev, drvdata.buf, GO_S_PACKET_SIZE); + if (ret < 0) + return ret; + + ret =3D ret =3D=3D GO_S_PACKET_SIZE ? 0 : -EINVAL; + if (ret) + return ret; + + ret =3D wait_for_completion_interruptible_timeout(&drvdata.send_cmd_compl= ete, + msecs_to_jiffies(timeout)); + + if (ret =3D=3D 0) /* timeout occurred */ + ret =3D -EBUSY; + if (ret > 0) /* timeout/interrupt didn't occur */ + ret =3D 0; + + reinit_completion(&drvdata.send_cmd_complete); + return ret; +} + +static void cfg_setup(struct work_struct *work) +{ + int ret; + + ret =3D mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve MCU Version: %i\n", ret); + return; + } +} + +static int hid_gos_cfg_probe(struct hid_device *hdev, + const struct hid_device_id *_id) +{ + unsigned char *buf; + int ret; + + buf =3D devm_kzalloc(&hdev->dev, GO_S_PACKET_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + hid_set_drvdata(hdev, &drvdata); + drvdata.buf =3D buf; + drvdata.hdev =3D hdev; + mutex_init(&drvdata.cfg_mutex); + + init_completion(&drvdata.send_cmd_complete); + + /* Executing calls prior to returning from probe will lock the MCU. Sched= ule + * initial data call after probe has completed and MCU can accept calls. + */ + INIT_DELAYED_WORK(&drvdata.gos_cfg_setup, &cfg_setup); + ret =3D schedule_delayed_work(&drvdata.gos_cfg_setup, + msecs_to_jiffies(2)); + if (!ret) { + dev_err(&hdev->dev, + "Failed to schedule startup delayed work\n"); + return -ENODEV; + } + return 0; +} + +static void hid_gos_cfg_remove(struct hid_device *hdev) +{ + guard(mutex)(&drvdata.cfg_mutex); + cancel_delayed_work_sync(&drvdata.gos_cfg_setup); + hid_hw_close(hdev); + hid_hw_stop(hdev); + hid_set_drvdata(hdev, NULL); +} + +static int hid_gos_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret, ep; + + hdev->quirks |=3D HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; + + ret =3D hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret =3D hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep =3D get_endpoint_address(hdev); + if (ep !=3D GO_S_CFG_INTF_IN) { + dev_dbg(&hdev->dev, + "Started interface %x as generic HID device.\n", ep); + return 0; + } + + ret =3D hid_gos_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, + "Failed to start configuration interface"); + + dev_dbg(&hdev->dev, "Started Legion Go S HID Device: %x\n", ep); + return ret; +} + +static void hid_gos_remove(struct hid_device *hdev) +{ + int ep =3D get_endpoint_address(hdev); + + switch (ep) { + case GO_S_CFG_INTF_IN: + hid_gos_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + + break; + } +} + +static const struct hid_device_id hid_gos_devices[] =3D { + { HID_USB_DEVICE(USB_VENDOR_ID_QHE, + USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QHE, + USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, hid_gos_devices); +static struct hid_driver hid_lenovo_go_s =3D { + .name =3D "hid-lenovo-go-s", + .id_table =3D hid_gos_devices, + .probe =3D hid_gos_probe, + .remove =3D hid_gos_remove, + .raw_event =3D hid_gos_raw_event, +}; +module_hid_driver(hid_lenovo_go_s); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go S Series gamepad."); +MODULE_LICENSE("GPL"); --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f169.google.com (mail-dy1-f169.google.com [74.125.82.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF2A8218596 for ; Sat, 24 Jan 2026 01:49:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219364; cv=none; b=OcxTa+TnO1piuSB/2xYLbMEhlVPbnDoeUUrfq5hl/ZSZV5+5Glg/haPfCB7zRXIxXBimYr1XeNZx6dE1TcnjPRTJvcWXW61W47NtfeDeRb4L6Q5Fjr9BK/t3Eb3/EsttA+Xf8ct/Sy114eUeMqXlJdXBTirabew8hXffkbGspus= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219364; c=relaxed/simple; bh=GPHNlP+i5kClaBF8b/aV82UQxUbAdS3uGeajNgNkvow=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pvbJTZJbcK/ggPX2Ccmw7D/w1rQlSpCRzA3N4SsTXTxvzbrisrsl40QOd0h2WeD4Dom6xtLEqS2xRW61E7Y33t5wJNr42Op0GEazahXPe/F6fbpfxFDPa5oMElLL7EkD+bTI++3p8EFqF9oZMSQ69tBzxG64zvce9v7fk5+Xqmc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=UCMosbFC; arc=none smtp.client-ip=74.125.82.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="UCMosbFC" Received: by mail-dy1-f169.google.com with SMTP id 5a478bee46e88-2b4520f6b32so4540815eec.0 for ; Fri, 23 Jan 2026 17:49:21 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219361; x=1769824161; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=AwMqD/JxDTChJj/0URSAhzNpbthbnAQ4R2LI3Lm7E3I=; b=UCMosbFC3PJrkCtmElMkJgYUuPeqOXE9jvyFlGzhrhWDU4Fb5u4jNITGSfUnfiEpcG qSFoADjj/eAG46BEfsQhzBuGAmrSEEgu3OY/AJdBUOAv2PDUncaLgfFCoptutvXr8P4f +W1p/SvQG5K4xsh+kNIfXOjTjq6AZsNL5UuOXOjWgN2qOGLTI3YiD5y7gp+gq4nbtPob 7kz9J+ORu+L/ApobSEYAc63LJsOveEQ4h5FawavEzEfzrB2/ZUx1RLZkvnIXFlHvYX1I e3wNXVqSKf8kbeZb7FlU5HI8oMRLQ1o7vGwedxaXGOi7UKYtch8pMMBZ7SXh0HJmkQPv AyPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219361; x=1769824161; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=AwMqD/JxDTChJj/0URSAhzNpbthbnAQ4R2LI3Lm7E3I=; b=FSSau6SvU2gU7PnajE9oFieoFy1An2c9uvMWqCephOJtoJHO8XmCZHg5d5zOCzxu7G zvkyUjBfAqVH1tCGfQ5OHPOrlRrkSMEPQtX0acI+Y+jjNjSJqXKbZNoZr0NLJpLJJgTt cZwvyxDgsMQndpCalImENku3ZDgS7ZvhLgVM7g7oicxSze5RAd2YS6JZQJ3eJNXRYGOG ZsYxRMsc1eB3XTlyDzlH046N3Ns0ZmN1OKuaAMc0ohSUvRvNf398elPQDhu7XZLTHHZu oEJdtavQJJ4L8Q9+8f7RvzBWeVrjHOnqUbPoUG3Jg/ctXj32uCnHSWxUQCrmIAP3Xopd UsPg== X-Forwarded-Encrypted: i=1; AJvYcCWzuAoDhx6j40eZ1LWLFHpaPWwvI4COKYFDL2+N85J08jL9fdPv1vOwQO1w5ITtVRIHJlpKcZN3hixoW00=@vger.kernel.org X-Gm-Message-State: AOJu0YzE9gAUm4Us9a2kRqHj+/vDQWX7FXyhdhvAJgPqdmjYbDE9fc9l 2KeqYat6PUjk1a4KCQi+qq/K07MyzRxxz209Z4KznP5n8AYY2lOhRal3 X-Gm-Gg: AZuq6aJEsqGsblcXXcCC9yE7LKK7/L4HhkGW6JmjfVJ9Y0Bn1fWF/MCQStZx2zsrYe0 sm1Jqjn0eTaIc9RapYweroI0Ly9TO8hcBaEDFVv6qct4fkz+uA8TplbYO8J6S4aYWkJzEI6ZPTU eDrGSWwCmmOKEDST1JdRcGsAlGxxZO/E+PUXQy7ChGYqzlJuNrBqws8nsiFA50x4hg28A48MYCh hqBrjA4suFes8q9/cHFm+23dQIVO66V+fF9gPBvR09iifDsfl5Hr2XFztKAz2UKGOTPlFuUucCO int9D3ukGfBMeXLdz6jlqijhBasF7duJrm037JsLLQkJeIb6U6zOl6VV38UNTEuvtdEVLKyR40R ObufMxAZ5nQIjTxSn5CY9i0VlA7P3qEjYq+t8vP9/crF8AwVHz9Ug0pHuR5e05aIoj3AavFrdkC /j8GVqXnnCa6dm2FPRT9fjVZlGrWZxdVzkAjhGPPmr8MGih/df014+bOomZKbvOUc= X-Received: by 2002:a05:7300:80c2:b0:2b4:706d:4e23 with SMTP id 5a478bee46e88-2b7397aa1c7mr2039019eec.0.1769219360957; Fri, 23 Jan 2026 17:49:20 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:20 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 11/16] HID: hid-lenovo-go-s: Add MCU ID Attribute Date: Sat, 24 Jan 2026 01:49:02 +0000 Message-ID: <20260124014907.991265-12-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds command to probe for the MCU ID of the Lenovo Legion Go S Controller and assign it to a device attribute. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go-s.c | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 3e9041e746df..36cee6c3d4cc 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -34,8 +35,13 @@ struct hid_gos_cfg { struct completion send_cmd_complete; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 mcu_id[12]; } drvdata; =20 +struct gos_cfg_attr { + u8 index; +}; + struct command_report { u8 cmd; u8 sub_cmd; @@ -70,6 +76,14 @@ static int hid_gos_version_event(u8 *data) return 0; } =20 +static int hid_gos_mcu_id_event(struct command_report *cmd_rep) +{ + drvdata.mcu_id[0] =3D cmd_rep->sub_cmd; + memcpy(&drvdata.mcu_id[1], cmd_rep->data, 11); + + return 0; +} + static u8 get_endpoint_address(struct hid_device *hdev) { struct usb_interface *intf =3D to_usb_interface(hdev->dev.parent); @@ -103,6 +117,9 @@ static int hid_gos_raw_event(struct hid_device *hdev, s= truct hid_report *report, case GET_VERSION: ret =3D hid_gos_version_event(data); break; + case GET_MCU_ID: + ret =3D hid_gos_mcu_id_event(cmd_rep); + break; default: ret =3D -EINVAL; break; @@ -160,10 +177,41 @@ static int mcu_property_out(struct hid_device *hdev, = u8 command, u8 index, return ret; } =20 +static ssize_t mcu_id_show(struct device *dev, struct device_attribute *at= tr, + char *buf) +{ + return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id); +} + +/* MCU */ +static DEVICE_ATTR_RO(mcu_id); + +static struct attribute *legos_mcu_attrs[] =3D { + &dev_attr_mcu_id.attr, + NULL, +}; + +static const struct attribute_group mcu_attr_group =3D { + .attrs =3D legos_mcu_attrs, +}; + +static const struct attribute_group *top_level_attr_groups[] =3D { + &mcu_attr_group, + NULL, +}; + static void cfg_setup(struct work_struct *work) { int ret; =20 + /* MCU */ + ret =3D mcu_property_out(drvdata.hdev, GET_MCU_ID, FEATURE_NONE, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU ID: %i\n", + ret); + return; + } + ret =3D mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, 0, 0); if (ret) { dev_err(&drvdata.hdev->dev, @@ -187,6 +235,13 @@ static int hid_gos_cfg_probe(struct hid_device *hdev, drvdata.hdev =3D hdev; mutex_init(&drvdata.cfg_mutex); =20 + ret =3D sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create gamepad configuration attributes\n"); + return ret; + } + init_completion(&drvdata.send_cmd_complete); =20 /* Executing calls prior to returning from probe will lock the MCU. Sched= ule @@ -207,6 +262,7 @@ static void hid_gos_cfg_remove(struct hid_device *hdev) { guard(mutex)(&drvdata.cfg_mutex); cancel_delayed_work_sync(&drvdata.gos_cfg_setup); + sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); hid_hw_stop(hdev); hid_set_drvdata(hdev, NULL); --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f171.google.com (mail-dy1-f171.google.com [74.125.82.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 F0CF922E3E7 for ; Sat, 24 Jan 2026 01:49:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219366; cv=none; b=JI8ozPMomctcDL89kpUYc9h6pacoB6aOsyOkedb1WhoNuEEiegdbV82/gLRcYBmhXlCCzakRTlozOKWbfiuM91rqYe0hTYdqrkGgvQ3f4xxlf3m1oG4xzuY8tF+nts/5pnA7uspaEMO2OoojFBVArXYTuQBvsJKOn0XqFEvZbj4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219366; c=relaxed/simple; bh=QL9vc8e/YZBsymktlWZAmFCbHpEawvytpOUcj2Dei2w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=j9BnFsbbKd5kbWhlQ5BkE80gQeQNlpugqmlIM8rrsg4iOLZfzv91ZXmMcsaSssctYLI/x6rSYXwjWttU94zzeU8hILiXHt9nZiosIAAMEInkD76hU2VlPSCElGjR8Vch7uh7fyRH36/mVlHgD57Z1uRbsKpllktNcSQ74U5KxTE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OwFgWQiI; arc=none smtp.client-ip=74.125.82.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OwFgWQiI" Received: by mail-dy1-f171.google.com with SMTP id 5a478bee46e88-2b73112ab62so2050798eec.1 for ; Fri, 23 Jan 2026 17:49:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219362; x=1769824162; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=AmrXoMrk2AbEQp2wwyDd0rnL7z2sF3WkcSu+YIR47ZQ=; b=OwFgWQiI775LhsLdEHAQvq+wnuKR4S3IWIiovGfTx8WiZKooj9e3KnMK5344XgqJB8 kVx+GXFQc2GyLL6PyuF3FzBIIN4S9qkgINd5XXzeO7hv60je7dnF7yF9NNmuTS9Q63ig FzArzn8EFL3QO/zdunZamsPrMYtSN2wMlPzQ+z6ctXVtxyaZHGKj6xs82p4Gaq4ln9KA Uf9DZPx5H2U1ihldXYsq5zNl8/1jhiEfIDklna33to+nOsJy216mQSaZwKOWT5JbuJoR zndcptv+DVBfoBgdq/xsUkNvY+ijbetIxeO6YJV+WlintGIbbg0vXlfQjJgfhfaLKYP5 kopg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219362; x=1769824162; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=AmrXoMrk2AbEQp2wwyDd0rnL7z2sF3WkcSu+YIR47ZQ=; b=k8taajLo/CGWZt7UwfO/ppIaspxLGRvHiCN27eDKnqw5ENHmQsrDjumqMEM4we4NUS JzOa3BsuApFEusJu3Rb3CHLZEaT7srdz/IQNW9BIoSJVSRkuOmN9Ovy6W2+k2l3XZF5K QcsV6FJan0QhZnb1SVn6yCnTUkY6ywnAyH1q5Z4b+EDZJLYiXLHP699B3NDE+LDjf6bf qb5D5HR27DTqMAAVudypVPRPJpke21dq/ZDlyYwhsC4u5uVzS6KlzXFF9L0sdShuwEpq iI44irSGU4QezXPHLRO1IRVqRGr9F92PfZ73kCrS4wVdL4MEdqeJvROB+pC5BJJiEKDj wIPQ== X-Forwarded-Encrypted: i=1; AJvYcCVpCRCG9LR/4jvWqdoqm2c5YEkOPrfhUhuK7dVB52/WLYrpXCMnqUgesLbdP+8l8fYdBjT9jmjaS/3J2TA=@vger.kernel.org X-Gm-Message-State: AOJu0YwEACh2hQIgbTB33qZjtTsHUb8sBDlZHcRLo+GNpZ9aqJCrTjlj laB1IkhjIhEQBFyiiYxdlHDivK5Sq8OuQcqqyBZuK3kRmuw3QgEavHtZ X-Gm-Gg: AZuq6aKShyeeHwcoKDsy76bKEkHgVeQVcRX0cm04VBeibI0TlD0EM+mzpTPARAHD9L8 AoeLb+4mJGBNBu2qGhZha7hJj055IHvaJzhB0+5jQITfu7nmzV7Q5o7A/8WZ5dF2LODr3D552Yc UALo2D2P3PlQbSGfn6aspPLFSo7TjxWTL7pXOrue4cQ31XXKZsK0nJkWMm/ejI9WAm+ocD8dUEw eoszKXS/aKDmFSpNm5oAXIBMq2K9CDk4ma1jK3M5t5Ub8jHAkyfJOzpqu9zHc/pE1AyAvkGRYKY GtT4k/ffQ3rMjCzCeLqy9ivvCLz9dy8QdiFWpuJ96tBVwlV1KVnpRRNrfOvrUdkfsCNfvy0YhIx iepft6EInG/UfGQTNAhj+PrbJ0y+Zpnhld/KIVQjH7UOSEA+dCXMJw4W3khzddjbw9HNTl3HpMd /yLFPVM3FFCyn/YxI3FN4+7mUhHD+Cg0JhtTAxlwA3WnXpUe5GL1/XvdkBaGMZNpOgY2ooo4niY A== X-Received: by 2002:a05:7301:578f:b0:2ac:21b5:f43c with SMTP id 5a478bee46e88-2b739b748c0mr2725549eec.20.1769219361808; Fri, 23 Jan 2026 17:49:21 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:21 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 12/16] HID: hid-lenovo-go-s: Add Feature Status Attributes Date: Sat, 24 Jan 2026 01:49:03 +0000 Message-ID: <20260124014907.991265-13-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds features status attributes for the gamepad, MCU, touchpad/mouse, and IMU devices. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go-s.c | 524 +++++++++++++++++++++++++++++++++- 1 file changed, 523 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 36cee6c3d4cc..076afdaa9628 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,17 @@ struct hid_gos_cfg { struct completion send_cmd_complete; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 gp_auto_sleep_time; + u8 gp_dpad_mode; + u8 gp_mode; + u8 gp_poll_rate; + u8 imu_bypass_en; + u8 imu_sensor_en; u8 mcu_id[12]; + u8 mouse_step; + u8 os_mode; + u8 rgb_en; + u8 tp_en; } drvdata; =20 struct gos_cfg_attr { @@ -66,7 +77,73 @@ enum mcu_command_index { GET_PL_TEST =3D 0xdf, }; =20 -#define FEATURE_NONE 0x00 +enum feature_enabled_index { + FEATURE_DISABLED, + FEATURE_ENABLED, +}; + +static const char *const feature_enabled_text[] =3D { + [FEATURE_DISABLED] =3D "false", + [FEATURE_ENABLED] =3D "true", +}; + +enum feature_status_index { + FEATURE_NONE =3D 0x00, + FEATURE_GAMEPAD_MODE =3D 0x01, + FEATURE_AUTO_SLEEP_TIME =3D 0x04, + FEATURE_IMU_BYPASS, + FEATURE_RGB_ENABLE, + FEATURE_IMU_ENABLE, + FEATURE_TOUCHPAD_ENABLE, + FEATURE_OS_MODE =3D 0x0A, + FEATURE_POLL_RATE =3D 0x10, + FEATURE_DPAD_MODE, + FEATURE_MOUSE_WHEEL_STEP, +}; + +enum gamepad_mode_index { + XINPUT, + DINPUT, +}; + +static const char *const gamepad_mode_text[] =3D { + [XINPUT] =3D "xinput", + [DINPUT] =3D "dinput", +}; + +enum os_type_index { + WINDOWS, + LINUX, +}; + +static const char *const os_type_text[] =3D { + [WINDOWS] =3D "windows", + [LINUX] =3D "linux", +}; + +enum poll_rate_index { + HZ125, + HZ250, + HZ500, + HZ1000, +}; + +static const char *const poll_rate_text[] =3D { + [HZ125] =3D "125", + [HZ250] =3D "250", + [HZ500] =3D "500", + [HZ1000] =3D "1000", +}; + +enum dpad_mode_index { + DIR8, + DIR4, +}; + +static const char *const dpad_mode_text[] =3D { + [DIR8] =3D "8-way", + [DIR4] =3D "4-way", +}; =20 static int hid_gos_version_event(u8 *data) { @@ -84,6 +161,57 @@ static int hid_gos_mcu_id_event(struct command_report *= cmd_rep) return 0; } =20 +static int hid_gos_gamepad_cfg_event(struct command_report *cmd_rep) +{ + int ret =3D 0; + + switch (cmd_rep->sub_cmd) { + case FEATURE_GAMEPAD_MODE: + drvdata.gp_mode =3D cmd_rep->data[0]; + break; + case FEATURE_AUTO_SLEEP_TIME: + drvdata.gp_auto_sleep_time =3D cmd_rep->data[0]; + break; + case FEATURE_IMU_BYPASS: + drvdata.imu_bypass_en =3D cmd_rep->data[0]; + break; + case FEATURE_RGB_ENABLE: + drvdata.rgb_en =3D cmd_rep->data[0]; + break; + case FEATURE_IMU_ENABLE: + drvdata.imu_sensor_en =3D cmd_rep->data[0]; + break; + case FEATURE_TOUCHPAD_ENABLE: + drvdata.tp_en =3D cmd_rep->data[0]; + break; + case FEATURE_OS_MODE: + drvdata.os_mode =3D cmd_rep->data[0]; + break; + case FEATURE_POLL_RATE: + drvdata.gp_poll_rate =3D cmd_rep->data[0]; + break; + case FEATURE_DPAD_MODE: + drvdata.gp_dpad_mode =3D cmd_rep->data[0]; + break; + case FEATURE_MOUSE_WHEEL_STEP: + drvdata.mouse_step =3D cmd_rep->data[0]; + break; + default: + ret =3D -EINVAL; + break; + } + + return ret; +} + +static int hid_gos_set_event_return(struct command_report *cmd_rep) +{ + if (cmd_rep->data[0] !=3D 0) + return -EIO; + + return 0; +} + static u8 get_endpoint_address(struct hid_device *hdev) { struct usb_interface *intf =3D to_usb_interface(hdev->dev.parent); @@ -120,6 +248,12 @@ static int hid_gos_raw_event(struct hid_device *hdev, = struct hid_report *report, case GET_MCU_ID: ret =3D hid_gos_mcu_id_event(cmd_rep); break; + case GET_GAMEPAD_CFG: + ret =3D hid_gos_gamepad_cfg_event(cmd_rep); + break; + case SET_GAMEPAD_CFG: + ret =3D hid_gos_set_event_return(cmd_rep); + break; default: ret =3D -EINVAL; break; @@ -177,17 +311,333 @@ static int mcu_property_out(struct hid_device *hdev,= u8 command, u8 index, return ret; } =20 +static ssize_t gamepad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum feature_status_index index) +{ + size_t size =3D 1; + u8 val =3D 0; + int ret; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + ret =3D sysfs_match_string(gamepad_mode_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case FEATURE_AUTO_SLEEP_TIME: + ret =3D kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 255) + return -EINVAL; + break; + case FEATURE_IMU_ENABLE: + ret =3D sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case FEATURE_IMU_BYPASS: + ret =3D sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case FEATURE_RGB_ENABLE: + ret =3D sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case FEATURE_TOUCHPAD_ENABLE: + ret =3D sysfs_match_string(feature_enabled_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case FEATURE_OS_MODE: + ret =3D sysfs_match_string(os_type_text, buf); + if (ret < 0) + return ret; + val =3D ret; + drvdata.os_mode =3D val; + break; + case FEATURE_POLL_RATE: + ret =3D sysfs_match_string(poll_rate_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case FEATURE_DPAD_MODE: + ret =3D sysfs_match_string(dpad_mode_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case FEATURE_MOUSE_WHEEL_STEP: + ret =3D kstrtou8(buf, 10, &val); + if (ret) + return ret; + if (val < 1 || val > 127) + return -EINVAL; + break; + default: + return -EINVAL; + } + + if (!val) + size =3D 0; + + ret =3D mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, index, &val, + size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t gamepad_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum feature_status_index index) +{ + size_t count =3D 0; + u8 i; + + count =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, 0, 0); + if (count < 0) + return count; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + i =3D drvdata.gp_mode; + if (i >=3D ARRAY_SIZE(gamepad_mode_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", gamepad_mode_text[i]); + break; + case FEATURE_AUTO_SLEEP_TIME: + count =3D sysfs_emit(buf, "%u\n", drvdata.gp_auto_sleep_time); + break; + case FEATURE_IMU_ENABLE: + i =3D drvdata.imu_sensor_en; + if (i >=3D ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_IMU_BYPASS: + i =3D drvdata.imu_bypass_en; + if (i >=3D ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_RGB_ENABLE: + i =3D drvdata.rgb_en; + if (i >=3D ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_TOUCHPAD_ENABLE: + i =3D drvdata.tp_en; + if (i >=3D ARRAY_SIZE(feature_enabled_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", feature_enabled_text[i]); + break; + case FEATURE_OS_MODE: + i =3D drvdata.os_mode; + if (i >=3D ARRAY_SIZE(os_type_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", os_type_text[i]); + break; + case FEATURE_POLL_RATE: + i =3D drvdata.gp_poll_rate; + if (i >=3D ARRAY_SIZE(poll_rate_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", poll_rate_text[i]); + break; + case FEATURE_DPAD_MODE: + i =3D drvdata.gp_dpad_mode; + if (i >=3D ARRAY_SIZE(dpad_mode_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", dpad_mode_text[i]); + break; + case FEATURE_MOUSE_WHEEL_STEP: + i =3D drvdata.mouse_step; + if (i < 1 || i > 127) + return -EINVAL; + count =3D sysfs_emit(buf, "%u\n", i); + break; + default: + return -EINVAL; + } + + return count; +} + +static ssize_t gamepad_property_options(struct device *dev, + struct device_attribute *attr, + char *buf, + enum feature_status_index index) +{ + size_t count =3D 0; + unsigned int i; + + switch (index) { + case FEATURE_GAMEPAD_MODE: + for (i =3D 0; i < ARRAY_SIZE(gamepad_mode_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + gamepad_mode_text[i]); + } + break; + case FEATURE_AUTO_SLEEP_TIME: + return sysfs_emit(buf, "0-255\n"); + case FEATURE_IMU_ENABLE: + for (i =3D 0; i < ARRAY_SIZE(feature_enabled_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + feature_enabled_text[i]); + } + break; + case FEATURE_IMU_BYPASS: + case FEATURE_RGB_ENABLE: + case FEATURE_TOUCHPAD_ENABLE: + for (i =3D 0; i < ARRAY_SIZE(feature_enabled_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + feature_enabled_text[i]); + } + break; + case FEATURE_OS_MODE: + for (i =3D 0; i < ARRAY_SIZE(os_type_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + os_type_text[i]); + } + break; + case FEATURE_POLL_RATE: + for (i =3D 0; i < ARRAY_SIZE(poll_rate_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + poll_rate_text[i]); + } + break; + case FEATURE_DPAD_MODE: + for (i =3D 0; i < ARRAY_SIZE(dpad_mode_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + dpad_mode_text[i]); + } + break; + case FEATURE_MOUSE_WHEEL_STEP: + return sysfs_emit(buf, "1-127\n"); + default: + return count; + } + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + static ssize_t mcu_id_show(struct device *dev, struct device_attribute *at= tr, char *buf) { return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id); } =20 +#define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) = \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_property_store(dev, attr, buf, count, \ + _name.index); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_show(dev, attr, buf, _name.index); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_options(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW_NAMED(_name, _attrname) + +#define LEGOS_DEVICE_ATTR_RO(_name, _attrname, _group) = \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_property_show(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RO_NAMED(_name, _attrname) + +/* Gamepad */ +struct gos_cfg_attr auto_sleep_time =3D { FEATURE_AUTO_SLEEP_TIME }; +LEGOS_DEVICE_ATTR_RW(auto_sleep_time, "auto_sleep_time", range, gamepad); +static DEVICE_ATTR_RO(auto_sleep_time_range); + +struct gos_cfg_attr dpad_mode =3D { FEATURE_DPAD_MODE }; +LEGOS_DEVICE_ATTR_RW(dpad_mode, "dpad_mode", index, gamepad); +static DEVICE_ATTR_RO(dpad_mode_index); + +struct gos_cfg_attr gamepad_mode =3D { FEATURE_GAMEPAD_MODE }; +LEGOS_DEVICE_ATTR_RW(gamepad_mode, "mode", index, gamepad); +static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); + +struct gos_cfg_attr gamepad_poll_rate =3D { FEATURE_POLL_RATE }; +LEGOS_DEVICE_ATTR_RW(gamepad_poll_rate, "poll_rate", index, gamepad); +static DEVICE_ATTR_RO_NAMED(gamepad_poll_rate_index, "poll_rate_index"); + +static struct attribute *legos_gamepad_attrs[] =3D { + &dev_attr_auto_sleep_time.attr, + &dev_attr_auto_sleep_time_range.attr, + &dev_attr_dpad_mode.attr, + &dev_attr_dpad_mode_index.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_gamepad_poll_rate.attr, + &dev_attr_gamepad_poll_rate_index.attr, + NULL, +}; + +static const struct attribute_group gamepad_attr_group =3D { + .name =3D "gamepad", + .attrs =3D legos_gamepad_attrs, +}; + +/* IMU */ +struct gos_cfg_attr imu_bypass_enabled =3D { FEATURE_IMU_BYPASS }; +LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_inde= x"); + +struct gos_cfg_attr imu_sensor_enabled =3D { FEATURE_IMU_ENABLE }; +LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_inde= x"); + +static struct attribute *legos_imu_attrs[] =3D { + &dev_attr_imu_bypass_enabled.attr, + &dev_attr_imu_bypass_enabled_index.attr, + &dev_attr_imu_sensor_enabled.attr, + &dev_attr_imu_sensor_enabled_index.attr, + NULL, +}; + +static const struct attribute_group imu_attr_group =3D { + .name =3D "imu", + .attrs =3D legos_imu_attrs, +}; + /* MCU */ static DEVICE_ATTR_RO(mcu_id); =20 +struct gos_cfg_attr os_mode =3D { FEATURE_OS_MODE }; +LEGOS_DEVICE_ATTR_RW(os_mode, "os_mode", index, gamepad); +static DEVICE_ATTR_RO(os_mode_index); + static struct attribute *legos_mcu_attrs[] =3D { &dev_attr_mcu_id.attr, + &dev_attr_os_mode.attr, + &dev_attr_os_mode_index.attr, NULL, }; =20 @@ -195,8 +645,44 @@ static const struct attribute_group mcu_attr_group =3D= { .attrs =3D legos_mcu_attrs, }; =20 +/* Mouse */ +struct gos_cfg_attr mouse_wheel_step =3D { FEATURE_MOUSE_WHEEL_STEP }; +LEGOS_DEVICE_ATTR_RW(mouse_wheel_step, "step", range, gamepad); +static DEVICE_ATTR_RO_NAMED(mouse_wheel_step_range, "step_range"); + +static struct attribute *legos_mouse_attrs[] =3D { + &dev_attr_mouse_wheel_step.attr, + &dev_attr_mouse_wheel_step_range.attr, + NULL, +}; + +static const struct attribute_group mouse_attr_group =3D { + .name =3D "mouse", + .attrs =3D legos_mouse_attrs, +}; + +/* Touchpad */ +struct gos_cfg_attr touchpad_enabled =3D { FEATURE_TOUCHPAD_ENABLE }; +LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); + +static struct attribute *legos_touchpad_attrs[] =3D { + &dev_attr_touchpad_enabled.attr, + &dev_attr_touchpad_enabled_index.attr, + NULL, +}; + +static const struct attribute_group touchpad_attr_group =3D { + .name =3D "touchpad", + .attrs =3D legos_touchpad_attrs, +}; + static const struct attribute_group *top_level_attr_groups[] =3D { + &gamepad_attr_group, + &imu_attr_group, &mcu_attr_group, + &mouse_attr_group, + &touchpad_attr_group, NULL, }; =20 @@ -268,6 +754,27 @@ static void hid_gos_cfg_remove(struct hid_device *hdev) hid_set_drvdata(hdev, NULL); } =20 +static int hid_gos_cfg_reset_resume(struct hid_device *hdev) +{ + u8 os_mode =3D drvdata.os_mode; + int ret; + + ret =3D mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, FEATURE_OS_MODE, + &os_mode, 1); + if (ret < 0) + return ret; + + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_OS_MODE, = 0, + 0); + if (ret < 0) + return ret; + + if (drvdata.os_mode !=3D os_mode) + return -ENODEV; + + return 0; +} + static int hid_gos_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -326,6 +833,20 @@ static void hid_gos_remove(struct hid_device *hdev) } } =20 +static int hid_gos_reset_resume(struct hid_device *hdev) +{ + int ep =3D get_endpoint_address(hdev); + + switch (ep) { + case GO_S_CFG_INTF_IN: + return hid_gos_cfg_reset_resume(hdev); + default: + break; + } + + return 0; +} + static const struct hid_device_id hid_gos_devices[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_QHE, USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) }, @@ -340,6 +861,7 @@ static struct hid_driver hid_lenovo_go_s =3D { .id_table =3D hid_gos_devices, .probe =3D hid_gos_probe, .remove =3D hid_gos_remove, + .reset_resume =3D hid_gos_reset_resume, .raw_event =3D hid_gos_raw_event, }; module_hid_driver(hid_lenovo_go_s); --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f179.google.com (mail-dy1-f179.google.com [74.125.82.179]) (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 CF300237A4F for ; Sat, 24 Jan 2026 01:49:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219366; cv=none; b=XcqGvJVroiDxOOmmkfXWraKG26oBvuN3UMB+WcemMaklJIDweb9ozLpI5LVUX1IX0YcAhVEfP3f/Ym0Nju7gCR1YqWTt9Y8Fy6TGqjiV6kDt2Y5uM23QlLI0JB/iLq48imY31wStosgQFwQHTF+IPm+UXhIUp1oSikqbzhJu0BM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219366; c=relaxed/simple; bh=jQsLso/xojyFI3Qt+bgJOnTQ9btQK1wLsfz5szkgT3s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XFvMR7Mh3SR5ibPbGRuSPLheWYAouESCJZ9ByTikxTQAN3C6vBokqdfRPL5wtnIrXsnoJ3iprh+s6EHDIpVnbicax0H7+eO+ZhE9TZRFTi43lQdqdP5aFr4roIlz55i2efIwVUgl/1l5lZsNMR0pkCUx//clGr109QMp7Zuo1ks= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Y/mNibAS; arc=none smtp.client-ip=74.125.82.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Y/mNibAS" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2b714f30461so2645047eec.0 for ; Fri, 23 Jan 2026 17:49:23 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219363; x=1769824163; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=pbc+jDHPV6g8FKHvn7of3JFw4LTLv5qp1ch2s8G5+AE=; b=Y/mNibASmy+fjhQdcYif+wRIJB+Y2wF/lW79sk6ZOme1NkIQVLmkPOBnpQRS5aH90s iFe6Mm4Xp7C0kpPJ1qAl+lxdqGOrCUrH6SnIqDHANykAqSaMMvgWWKl05IAi/mtIB+A4 Z/KBf5T+WKaiN1e91TKukTuSZ77L+oOH4Xrv1UUjLAJ5/IkXLaKF2unwOrFrJ8jkJc0A DJq5vLLy53w5fEcbkkspJdeqbPOxjkVOMp9v6NTpho6WFE4f4ypNETNT0Odjp6tQPP1k WiKAO6HCp0Oblreq9runUbXRmpXvExKVDNYy7W4loZGDsDDC15McdzDAqgM+7+ZFgboX Jh0Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219363; x=1769824163; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=pbc+jDHPV6g8FKHvn7of3JFw4LTLv5qp1ch2s8G5+AE=; b=sbe0Jm47990lExFKbHsrCDhxx1zIVt3dnXi5dBHQD3qG7ybOyvPK4uZ8tisEOyUaWN yLO2rvNLPbtIRSGATQP7WOvelWN9im9Wm3UoQihGsKQZrpRHfMbXFg5jEEGKBBTPrmK6 dvQN7S5fDWcO0TczYMwGH4p3gDZIPWy3xburznbYiikvP+e6O0nPivXF02bsMxh5jJzH H/J89RaPaUaZ3FtMcCVTjiu/vfOvdUsSBztjgEiWs0AxXwb4XJZCHV5yPpqy0OK5b+Xi ACIVBz+nnns4nnNj5sT37isxX+jYlldAQmWBOaX8HIZeOZ65x6WOTxeWHkLSjy4eknUm ADpA== X-Forwarded-Encrypted: i=1; AJvYcCWyHfhp5NXl3oPEKTZlAb5HINFtAB+MZvK42jKzb4YE2yPmUBIqyGzyPBczx9nJlcgTghDv7ZIZKqR61M0=@vger.kernel.org X-Gm-Message-State: AOJu0Yw/KVNPQYFPrOk59sUE9oPTxfMB2GFQpohLUtGpqATd8uYqwt++ 6jmvGom+Gzt1eFMpKy8+GARfdyvP+qLPIEbbqE+T0A+TFKqrn8X8IQhX X-Gm-Gg: AZuq6aJw+2jtgJMm8dQKeO4pRtQBWT9GdOGvyi8kLSpyNGXkB8HZ7aTR7FEbDc23an7 ehGkX4JEa9B551dUkGnWc+bCCuteSed5opromhUlg0x50RrjLeKd3PGK0C6mKnQDD9U0jCIVQlf 9jrYpqzwAaoxdETnX797skPI8kno6A2kptEy+OR/AR9s/iNmsl3rNa0jbLHsAvl/07O8TfYN2pN QM8B6nVnj/S616tyeb6rbzHnDiELn0l+OsvMLG2rAE2soQOj/il2SwWa+TQZecMQIUVcTHL5GAv elNk96Q4gajKK9cauhCvc5R16TkEiMc9s/nieJ6ZDp/S4tB/EicEYijVCgzc04kceS+Ud/TYdRO BXC3BJQcCQo60k96Lbhrv3PPiRavAGAqc5S3gRpCEobZ2vr+1yYoC7iXLc0Mbg/TkHNM/ZvKuRE VBafzuNmovlLVjrs/ORYgd5WBCZiPH6iWusBc/pkpWROcaHk3Qf+i6QtiLCYUwDbU= X-Received: by 2002:a05:7301:4e0b:b0:2b7:1253:3d4f with SMTP id 5a478bee46e88-2b739957211mr2193694eec.8.1769219362617; Fri, 23 Jan 2026 17:49:22 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:22 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 13/16] HID: hid-lenovo-go-s: Add Touchpad Mode Attributes Date: Sat, 24 Jan 2026 01:49:04 +0000 Message-ID: <20260124014907.991265-14-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds attributes for managing the touchpad operating modes. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go-s.c | 142 ++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 076afdaa9628..38425e3d6cb2 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -47,6 +47,8 @@ struct hid_gos_cfg { u8 os_mode; u8 rgb_en; u8 tp_en; + u8 tp_linux_mode; + u8 tp_windows_mode; } drvdata; =20 struct gos_cfg_attr { @@ -145,6 +147,22 @@ static const char *const dpad_mode_text[] =3D { [DIR4] =3D "4-way", }; =20 +enum touchpad_mode_index { + TP_REL, + TP_ABS, +}; + +static const char *const touchpad_mode_text[] =3D { + [TP_REL] =3D "relative", + [TP_ABS] =3D "absolute", +}; + +enum touchpad_config_index { + CFG_WINDOWS_MODE =3D 0x03, + CFG_LINUX_MODE, + +}; + static int hid_gos_version_event(u8 *data) { struct version_report *ver_rep =3D (struct version_report *)data; @@ -204,6 +222,25 @@ static int hid_gos_gamepad_cfg_event(struct command_re= port *cmd_rep) return ret; } =20 +static int hid_gos_touchpad_event(struct command_report *cmd_rep) +{ + int ret =3D 0; + + switch (cmd_rep->sub_cmd) { + case CFG_LINUX_MODE: + drvdata.tp_linux_mode =3D cmd_rep->data[0]; + break; + case CFG_WINDOWS_MODE: + drvdata.tp_windows_mode =3D cmd_rep->data[0]; + break; + default: + ret =3D -EINVAL; + break; + } + + return ret; +} + static int hid_gos_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -251,7 +288,11 @@ static int hid_gos_raw_event(struct hid_device *hdev, = struct hid_report *report, case GET_GAMEPAD_CFG: ret =3D hid_gos_gamepad_cfg_event(cmd_rep); break; + case GET_TP_PARAM: + ret =3D hid_gos_touchpad_event(cmd_rep); + break; case SET_GAMEPAD_CFG: + case SET_TP_PARAM: ret =3D hid_gos_set_event_return(cmd_rep); break; default: @@ -537,6 +578,95 @@ static ssize_t gamepad_property_options(struct device = *dev, return count; } =20 +static ssize_t touchpad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum touchpad_config_index index) +{ + size_t size =3D 1; + u8 val =3D 0; + int ret; + + switch (index) { + case CFG_WINDOWS_MODE: + ret =3D sysfs_match_string(touchpad_mode_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + case CFG_LINUX_MODE: + ret =3D sysfs_match_string(touchpad_mode_text, buf); + if (ret < 0) + return ret; + val =3D ret; + break; + default: + return -EINVAL; + } + if (!val) + size =3D 0; + + ret =3D mcu_property_out(drvdata.hdev, SET_TP_PARAM, index, &val, size); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t touchpad_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum touchpad_config_index index) +{ + int ret =3D 0; + u8 i; + + ret =3D mcu_property_out(drvdata.hdev, GET_TP_PARAM, index, 0, 0); + if (ret < 0) + return ret; + + switch (index) { + case CFG_WINDOWS_MODE: + i =3D drvdata.tp_windows_mode; + break; + case CFG_LINUX_MODE: + i =3D drvdata.tp_linux_mode; + break; + default: + return -EINVAL; + } + + if (i >=3D ARRAY_SIZE(touchpad_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", touchpad_mode_text[i]); +} + +static ssize_t touchpad_property_options(struct device *dev, + struct device_attribute *attr, + char *buf, + enum touchpad_config_index index) +{ + size_t count =3D 0; + unsigned int i; + + switch (index) { + case CFG_WINDOWS_MODE: + case CFG_LINUX_MODE: + for (i =3D 0; i < ARRAY_SIZE(touchpad_mode_text); i++) { + count +=3D sysfs_emit_at(buf, count, "%s ", + touchpad_mode_text[i]); + } + break; + default: + return count; + } + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + static ssize_t mcu_id_show(struct device *dev, struct device_attribute *at= tr, char *buf) { @@ -666,9 +796,21 @@ struct gos_cfg_attr touchpad_enabled =3D { FEATURE_TOU= CHPAD_ENABLE }; LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); =20 +struct gos_cfg_attr touchpad_linux_mode =3D { CFG_LINUX_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad); +static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); + +struct gos_cfg_attr touchpad_windows_mode =3D { CFG_WINDOWS_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpa= d); +static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_ind= ex"); + static struct attribute *legos_touchpad_attrs[] =3D { &dev_attr_touchpad_enabled.attr, &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_linux_mode.attr, + &dev_attr_touchpad_linux_mode_index.attr, + &dev_attr_touchpad_windows_mode.attr, + &dev_attr_touchpad_windows_mode_index.attr, NULL, }; =20 --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f176.google.com (mail-dy1-f176.google.com [74.125.82.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 628DF24468C for ; Sat, 24 Jan 2026 01:49:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219368; cv=none; b=gW2Kbi3KDTz7XSUfLJZbWMew59e207WBspnuns7V3irFdbMfMZdPrDihprMgBOGNETSl+80whwmql5ianIrShZdCnb76fPCqvYeu5dTV9TNMq/CyixtZHLiCD5NBWRqDd66lD/PZ1D3UeHUD47RRNIXJAizPi8z/RRtBkGSXvTg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219368; c=relaxed/simple; bh=akfu/gqz275Z2VtjZlBAejfEYa0YzbKf7Gn0R+UJx+s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dGobhPK+VVmQGNnXQOYnJlLywsR9q5QKogeRuhoXchXXpoMFruYqpmxb07YnZPn9JdDzwNcRSQ4N/q6e9XtpKSGzrnfEq0xbHiExcIMTNOB95LVdgDkVjCNv4DfAyaUFMTL8mxAHlY7jhsmLvMkF0baItF6iWnTCZf1r64/VA90= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=K2VDDvU9; arc=none smtp.client-ip=74.125.82.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="K2VDDvU9" Received: by mail-dy1-f176.google.com with SMTP id 5a478bee46e88-2b71557299dso4025251eec.1 for ; Fri, 23 Jan 2026 17:49:25 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219364; x=1769824164; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=PluhxiDPPhMGcw1dhjJulXPC1agxFWmfzUD6W8pTh4g=; b=K2VDDvU9W79JidVEvAQasMeGuuL9626Svj1Bi6gz//LZ35+YinDKf2M1hI8Ei77d/T X3VLgs5CWL5oB7aQyqdIEQdIao+bSs9uK7o8c2trtLZjvUIkRZhUmgTjInYidXWHF1R7 c+L405LQGgNzFg5ApNZ6V92WhXtkor6jH+H6k8muxRXkou2bR1d7lIrnd7VEXMB/GL2I 5kRxPFAjL+bunolKFOE8JKd1Nr7l3fqHm322GSpRH9nn37Q5Fa5L6K4RmnYMcGDoad0e woLDB1yv3QHGTXPXKbE47F8oK1XKsMX+2Z4y8G6IwB57VJAyu7jiQoUncOhlsLCzHwPC iM+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219364; x=1769824164; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=PluhxiDPPhMGcw1dhjJulXPC1agxFWmfzUD6W8pTh4g=; b=LWvPwYoLbZrwjhQskkzcxW/vdOkmf6h06c2nrDLXjY1lE3YJ1gy+TyDKYjWPYwCQya 6zsYF2WyL2ujnUu2h/IS8+JuVhUMkbgUr29hK/f/27Tv84p9OnFxxtExwlbhOkInyJao g3YTswE/ngUUr2PGfNR/G89V+cWWQdYO3V9xVqJMWoqKbrXqeZYc2uRB51mnH/PAtdnW cXhNOaB/T5IbGwgbRfk8vAOukzpcr34TNJyv8hA/GxCzoauZNhw7ulfT5ZHiOeGg6nLy EnvmbVI4DZsOpTxz0nszgJkWPOHKEEHtPkrcAWmiIfNy2cBMTy2YeYcGr/B+NqBiK6PJ Ot6g== X-Forwarded-Encrypted: i=1; AJvYcCWLdfkP+wgKOSUQXtQHeOzT6hQgfMxY0Al9eM3VK+Hr0iLVRHy7EEU7Z7B9FMniBv5bgf8NeBsVvbcmlNI=@vger.kernel.org X-Gm-Message-State: AOJu0YwZUTQNJ0lTrWZ0pHH9cIpzcmnhLz9tFVE7c+JccU4P+uO6MqBb AQ+P43qIqe3ChMqmy8q2wHGIWJ7+vvX2yT+wzfrPiQEksX9yiGBQb/e8 X-Gm-Gg: AZuq6aI1N/9tlQ2vMzZuFSXYxif2ZIMKyaq3QiRf9Ux0YmLvFH1+ya35vR52JGv7d3W p8vJXzdWnlZZqw1OChIBHK1BzfZDO4C8WZ2SOeCt3XXg8dzZNdCKsud86xMSOsGDI0roqE6vJI+ rSsDCiR6iPguu2JWt7Ojb6tq51Y8RrIL5qihn1bnO6nvtsJEbKPinBtsz8jx3ivg5bzj4v3+E8u 4UVBQIP99KzTAyI0aydZZ+T20T+WM/WULVni1hZM7AgWxpZMKvxG+G0Cl6D7Kb0crRxNLWUMfWu oYOSTAoY+7qLOydbQRSqWwWBuB0bLDaPkZki7PcwXp/hgXN6TbQc6kFcO7hRIkMas75cuS2eGjk ajGo9cZnY9ME+2ht6NcFysqtSdubl6dt85bk3yST2g8UK62/FRZhT3prTJCmfBqxyFEOuDds/PW uYAYjaThU0S/9ViDhoQ9BUUlOPXpX7ylW4u691fkv6If4HSJ0kkIsdU5PBhd6FcBw= X-Received: by 2002:a05:7300:cb0a:b0:2ab:ca55:89ab with SMTP id 5a478bee46e88-2b739c2d8aemr2325977eec.42.1769219363435; Fri, 23 Jan 2026 17:49:23 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:23 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 14/16] HID: hid-lenovo-go-s: Add RGB LED control interface Date: Sat, 24 Jan 2026 01:49:05 +0000 Message-ID: <20260124014907.991265-15-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds an LED multicolor class device and attribute group for controlling the RGB of the Left and right joystick rings. In addition to the standard led_cdev attributes, additional attributes that allow for the control of the effect (monocolor, breathe, rainbow, and chroma), speed of the effect change, an enable toggle, and profile. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- drivers/hid/hid-lenovo-go-s.c | 456 ++++++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 38425e3d6cb2..e422f3977517 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ struct hid_gos_cfg { unsigned char *buf; struct delayed_work gos_cfg_setup; struct completion send_cmd_complete; + struct led_classdev *led_cdev; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 gp_auto_sleep_time; @@ -45,7 +47,11 @@ struct hid_gos_cfg { u8 mcu_id[12]; u8 mouse_step; u8 os_mode; + u8 rgb_effect; u8 rgb_en; + u8 rgb_mode; + u8 rgb_profile; + u8 rgb_speed; u8 tp_en; u8 tp_linux_mode; u8 tp_windows_mode; @@ -163,6 +169,38 @@ enum touchpad_config_index { =20 }; =20 +enum rgb_mode_index { + RGB_MODE_DYNAMIC, + RGB_MODE_CUSTOM, +}; + +static const char *const rgb_mode_text[] =3D { + [RGB_MODE_DYNAMIC] =3D "dynamic", + [RGB_MODE_CUSTOM] =3D "custom", +}; + +enum rgb_effect_index { + RGB_EFFECT_MONO, + RGB_EFFECT_BREATHE, + RGB_EFFECT_CHROMA, + RGB_EFFECT_RAINBOW, +}; + +static const char *const rgb_effect_text[] =3D { + [RGB_EFFECT_MONO] =3D "monocolor", + [RGB_EFFECT_BREATHE] =3D "breathe", + [RGB_EFFECT_CHROMA] =3D "chroma", + [RGB_EFFECT_RAINBOW] =3D "rainbow", +}; + +enum rgb_config_index { + LIGHT_MODE_SEL =3D 0x01, + LIGHT_PROFILE_SEL, + USR_LIGHT_PROFILE_1, + USR_LIGHT_PROFILE_2, + USR_LIGHT_PROFILE_3, +}; + static int hid_gos_version_event(u8 *data) { struct version_report *ver_rep =3D (struct version_report *)data; @@ -241,6 +279,39 @@ static int hid_gos_touchpad_event(struct command_repor= t *cmd_rep) return ret; } =20 +static int hid_gos_light_event(struct command_report *cmd_rep) +{ + struct led_classdev_mc *mc_cdev; + int ret =3D 0; + + switch (cmd_rep->sub_cmd) { + case LIGHT_MODE_SEL: + drvdata.rgb_mode =3D cmd_rep->data[0]; + ret =3D 0; + break; + case LIGHT_PROFILE_SEL: + drvdata.rgb_profile =3D cmd_rep->data[0]; + ret =3D 0; + break; + case USR_LIGHT_PROFILE_1: + case USR_LIGHT_PROFILE_2: + case USR_LIGHT_PROFILE_3: + mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + drvdata.rgb_effect =3D cmd_rep->data[0]; + mc_cdev->subled_info[0].intensity =3D cmd_rep->data[1]; + mc_cdev->subled_info[1].intensity =3D cmd_rep->data[2]; + mc_cdev->subled_info[2].intensity =3D cmd_rep->data[3]; + drvdata.led_cdev->brightness =3D cmd_rep->data[4]; + drvdata.rgb_speed =3D cmd_rep->data[5]; + ret =3D 0; + break; + default: + ret =3D -EINVAL; + break; + } + return ret; +} + static int hid_gos_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -291,7 +362,11 @@ static int hid_gos_raw_event(struct hid_device *hdev, = struct hid_report *report, case GET_TP_PARAM: ret =3D hid_gos_touchpad_event(cmd_rep); break; + case GET_RGB_CFG: + ret =3D hid_gos_light_event(cmd_rep); + break; case SET_GAMEPAD_CFG: + case SET_RGB_CFG: case SET_TP_PARAM: ret =3D hid_gos_set_event_return(cmd_rep); break; @@ -673,6 +748,276 @@ static ssize_t mcu_id_show(struct device *dev, struct= device_attribute *attr, return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id); } =20 +static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cm= d, + enum rgb_config_index index, u8 *val, size_t size) +{ + if (cmd !=3D SET_RGB_CFG && cmd !=3D GET_RGB_CFG) + return -EINVAL; + + if (index < LIGHT_MODE_SEL || index > USR_LIGHT_PROFILE_3) + return -EINVAL; + + return mcu_property_out(hdev, cmd, index, val, size); +} + +static int rgb_attr_show(void) +{ + enum rgb_config_index index; + + index =3D drvdata.rgb_profile + 2; + + return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, 0, 0); +}; + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + u8 effect; + int ret; + + ret =3D sysfs_match_string(rgb_effect_text, buf); + if (ret < 0) + return ret; + + effect =3D ret; + index =3D drvdata.rgb_profile + 2; + u8 rgb_profile[6] =3D { effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + drvdata.rgb_speed }; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_effect =3D effect; + return count; +}; + +static ssize_t rgb_effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret =3D rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >=3D ARRAY_SIZE(rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]); +} + +static ssize_t rgb_effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(rgb_effect_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct led_classdev_mc *mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int val =3D 0; + int ret; + + ret =3D kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 100) + return -EINVAL; + + index =3D drvdata.rgb_profile + 2; + u8 rgb_profile[6] =3D { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + drvdata.led_cdev->brightness, + val }; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + if (ret) + return ret; + + drvdata.rgb_speed =3D val; + + return count; +}; + +static ssize_t rgb_speed_show(struct device *dev, struct device_attribute = *attr, + char *buf) +{ + int ret; + + ret =3D rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 100) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); +} + +static ssize_t rgb_speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} + +static ssize_t rgb_mode_store(struct device *dev, struct device_attribute = *attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret =3D sysfs_match_string(rgb_mode_text, buf); + if (ret <=3D 0) + return ret; + + val =3D ret; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val, + 1); + if (ret) + return ret; + + drvdata.rgb_mode =3D val; + + return count; +}; + +static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + int ret; + + ret =3D rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, 0, 0); + if (ret) + return ret; + + if (drvdata.rgb_mode >=3D ARRAY_SIZE(rgb_mode_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); +}; + +static ssize_t rgb_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 1; i < ARRAY_SIZE(rgb_mode_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + +static ssize_t rgb_profile_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + size_t size =3D 1; + int ret; + u8 val; + + ret =3D kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + + if (val < 1 || val > 3) + return -EINVAL; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val, + size); + if (ret) + return ret; + + drvdata.rgb_profile =3D val; + + return count; +}; + +static ssize_t rgb_profile_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret =3D rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, 0, + 0); + if (ret) + return ret; + + if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); +}; + +static ssize_t rgb_profile_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1-3\n"); +} + +static void hid_gos_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + enum rgb_config_index index; + int ret; + + if (brightness > led_cdev->max_brightness) { + dev_err(led_cdev->dev, "Invalid argument\n"); + return; + } + + index =3D drvdata.rgb_profile + 2; + u8 rgb_profile[6] =3D { drvdata.rgb_effect, + mc_cdev->subled_info[0].intensity, + mc_cdev->subled_info[1].intensity, + mc_cdev->subled_info[2].intensity, + brightness, + drvdata.rgb_speed }; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6); + switch (ret) { + case 0: + led_cdev->brightness =3D brightness; + break; + case -ENODEV: /* during switch to IAP -ENODEV is expected */ + case -ENOSYS: /* during rmmod -ENOSYS is expected */ + dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n", + ret); + break; + default: + dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", + ret); + }; +} + #define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) = \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -828,6 +1173,70 @@ static const struct attribute_group *top_level_attr_g= roups[] =3D { NULL, }; =20 +/* RGB */ +struct gos_cfg_attr rgb_enabled =3D { FEATURE_RGB_ENABLE }; +LEGOS_DEVICE_ATTR_RW(rgb_enabled, "enabled", index, gamepad); +static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index"); + +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); +static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index"); +static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode"); +static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index"); +static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile"); +static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range"); +static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); +static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range"); + +static struct attribute *gos_rgb_attrs[] =3D { + &dev_attr_rgb_enabled.attr, + &dev_attr_rgb_enabled_index.attr, + &dev_attr_rgb_effect.attr, + &dev_attr_rgb_effect_index.attr, + &dev_attr_rgb_mode.attr, + &dev_attr_rgb_mode_index.attr, + &dev_attr_rgb_profile.attr, + &dev_attr_rgb_profile_range.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_speed_range.attr, + NULL, +}; + +static struct attribute_group rgb_attr_group =3D { + .attrs =3D gos_rgb_attrs, +}; + +struct mc_subled gos_rgb_subled_info[] =3D { + { + .color_index =3D LED_COLOR_ID_RED, + .brightness =3D 0x50, + .intensity =3D 0x24, + .channel =3D 0x1, + }, + { + .color_index =3D LED_COLOR_ID_GREEN, + .brightness =3D 0x50, + .intensity =3D 0x22, + .channel =3D 0x2, + }, + { + .color_index =3D LED_COLOR_ID_BLUE, + .brightness =3D 0x50, + .intensity =3D 0x99, + .channel =3D 0x3, + }, +}; + +struct led_classdev_mc gos_cdev_rgb =3D { + .led_cdev =3D { + .name =3D "go_s:rgb:joystick_rings", + .brightness =3D 0x50, + .max_brightness =3D 0x64, + .brightness_set =3D hid_gos_brightness_set, + }, + .num_colors =3D ARRAY_SIZE(gos_rgb_subled_info), + .subled_info =3D gos_rgb_subled_info, +}; + static void cfg_setup(struct work_struct *work) { int ret; @@ -846,6 +1255,38 @@ static void cfg_setup(struct work_struct *work) "Failed to retrieve MCU Version: %i\n", ret); return; } + + /* RGB */ + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_RGB_ENABL= E, + 0, 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB enabled: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, 0, + 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Mode: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, + 0, 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Profile: %i\n", ret); + return; + } + + ret =3D rgb_attr_show(); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Profile Data: %i\n", ret); + return; + } } =20 static int hid_gos_cfg_probe(struct hid_device *hdev, @@ -870,6 +1311,21 @@ static int hid_gos_cfg_probe(struct hid_device *hdev, return ret; } =20 + ret =3D devm_led_classdev_multicolor_register(&hdev->dev, &gos_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret =3D devm_device_add_group(gos_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuratiion attributes\n"); + return ret; + } + + drvdata.led_cdev =3D &gos_cdev_rgb.led_cdev; + init_completion(&drvdata.send_cmd_complete); =20 /* Executing calls prior to returning from probe will lock the MCU. Sched= ule --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f176.google.com (mail-dy1-f176.google.com [74.125.82.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 15FFD248880 for ; Sat, 24 Jan 2026 01:49:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219368; cv=none; b=X5z/LkNG/7ks23ZFv/20wmJeOOtnjjhtYSt+cv9/OX79ZJc15KjQONSnPZxKSk1cf/O0+QIo4gqUar0K7HTgyabPaV1xDaOUutlrQrHUBwroYr+vMTXm3Jrv5oOe7aYxHvkX9KHqzxvWqSuXU1uk6Vdgw2yvcbpQewmEj5uvlew= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219368; c=relaxed/simple; bh=Uq1flWJME9yZBIe+pK57YoX7PNsp53RCp/QQ+B2TBXo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=C/QXZxY5RFRMf0pEHu/ZNifd3SIWuIHNjKwh9Srxi3G+Om+XVG7w/ZSSmDlbhXJ+pGwGPkPwuw/N1HeaAKQcvEh/IIzbZag2SkLekVPvSdEGikt+215d+GRPxl2+6EvF4Ryyww9jOsZ/4S2qGwm1Z/SiK7+mS4VVKQNWECxlC/E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=P4Qr2eG3; arc=none smtp.client-ip=74.125.82.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="P4Qr2eG3" Received: by mail-dy1-f176.google.com with SMTP id 5a478bee46e88-2b6b0500e06so3716193eec.1 for ; Fri, 23 Jan 2026 17:49:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219365; x=1769824165; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=shcSpW9YVM231NFCSd2R8Nn6vQf/8t1cDlRE1ZGCnPo=; b=P4Qr2eG3mnYUDJNURHDGjaNBaIyFHSNKS7DxssfL4lJ28gg/bLO4hBPgjhNbI0YC13 VUmlvjm6Ey/bYoXaOcyVeT6vfu93zr6W7vOGI3i6XlJkvCfHcPMjaRTpbfhL/4gFb1EW /LFYRk5g3DqgwIt3xa1c+khF34iUBfhf1jr8hNN/Xf+f+kbUjXVc6VMFl8m4Cy6chh7Z ubBCuEJJwwasJiT4EGH3A/Vpd1t02nL/ZAM+HhHUE3Iis48ts4Mvyu7xvTxzH1ClA0LP pi5NvadbP9diRk3CWgZ9lHluS8gvl91ArBELOYY876vs8vy1K3XjJ66YSe+0jrlxeJNW 9XDQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219365; x=1769824165; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=shcSpW9YVM231NFCSd2R8Nn6vQf/8t1cDlRE1ZGCnPo=; b=DusRK/piN6nGHAR7Ir3cpmsG+Zv9lpGwrJpiH1KL3MqUGC/wbnV0hS4hNquF9Z0Okb jYJOgb8hw3brOelAydKsYbvnIWtgSQxew5zAc+sQL5GJNw8rUyReyBvIrR370qwvUZ3+ X7HKkJbIc9S5yBuEk3iw8cTxjnmtxI6Q87un1dwS2zxyoGcgQWTB85CnnXxX39XoTVyL siE0MFwPk6mEUU9gT0+xEUzqQQqyKB8rqyHB3CRdcMINH7gvm6IhTec/YOlItidkOmiZ S68EopG23F/V7OU0ODvO4FfEX/0TwGMH/4LlF6CYMcFKzy+nobw8ZF6vIyfn+bwD3FN5 eOoQ== X-Forwarded-Encrypted: i=1; AJvYcCXk8wFxpVEP7EIP202CRFK9/xe5dXsjAExqQg1RJuK8PDZiNTanzld7Ro9vPiJwtj/ODNy5uT/3bvSNXUs=@vger.kernel.org X-Gm-Message-State: AOJu0YzF5SktW0EC89UkHQMWisQ1CFU4mlv9OTnc9s+t+4zpHjSZbwpS xV8YtdxFImHMj0kRcfbCdH+L7fbrz+SvGJQf8EbcbwP6JICDuJhuxDos X-Gm-Gg: AZuq6aJihs5Dklk2OcqzSJjLGg13tyQrLIW/Ux3hxE/Uuh0zfQDmdENHXbpFtd0s2mZ OnuLLg13o9nw6uD6d9kBl4HezVtOCbmo6CkgNdBeUkggNmVudJI/F5/yCugIRPnJQrAr8Iv7h8T iergZXmg5FnjX8/XwzBPke5VhxHmN5TI8Kbz13pe2ucCeFlK407RIWcLxV8gv49WBtbIcCF5Opg wPDdPP1xxH8MS/AZDXlm4P0djTdINij5NRe6wnGwOuX9boy0Q8uPeSdB9KaFlKQzqH+wXBnKOdE h5ZgPw0W0ur1+qv8eGhpgRUyn2Kk9ldq96ln8n9Lw5PoIv4lk/GSNj7y9LYddVi7bUwpNZrVNo6 XmrvZwwENFhlqzlrYkiFUFn4r0h3LgRvy0KYs4seXyN12SNizLDI7j2XSYBOoP1cB7lBKDRq8dQ xUMpr28ntlOHY2nBq5SnfNBCtd8jLC+L5WSO2NMGPmnfdF4XHMy6+vj9zYR6h4PHM= X-Received: by 2002:a05:7301:fa84:b0:2b7:98d:7b54 with SMTP id 5a478bee46e88-2b739992976mr2783751eec.14.1769219365156; Fri, 23 Jan 2026 17:49:25 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:24 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 15/16] HID: hid-lenovo-go-s: Add IMU and Touchpad RO Attributes Date: Sat, 24 Jan 2026 01:49:06 +0000 Message-ID: <20260124014907.991265-16-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds attributes for reporting the touchpad manufacturer, version, and IMU manufacturer. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- V3: - Fix bug where the touchpad attributes were assigned to the touchpad _show function instead of the test _show function. --- drivers/hid/hid-lenovo-go-s.c | 108 ++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index e422f3977517..d74a37c1980f 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -43,6 +43,7 @@ struct hid_gos_cfg { u8 gp_mode; u8 gp_poll_rate; u8 imu_bypass_en; + u8 imu_manufacturer; u8 imu_sensor_en; u8 mcu_id[12]; u8 mouse_step; @@ -54,6 +55,8 @@ struct hid_gos_cfg { u8 rgb_speed; u8 tp_en; u8 tp_linux_mode; + u8 tp_manufacturer; + u8 tp_version; u8 tp_windows_mode; } drvdata; =20 @@ -201,6 +204,36 @@ enum rgb_config_index { USR_LIGHT_PROFILE_3, }; =20 +enum test_command_index { + TEST_TP_MFR =3D 0x02, + TEST_IMU_MFR, + TEST_TP_VER, +}; + +enum tp_mfr_index { + TP_NONE, + TP_BETTERLIFE, + TP_SIPO, +}; + +static const char *const touchpad_manufacturer_text[] =3D { + [TP_NONE] =3D "none", + [TP_BETTERLIFE] =3D "BetterLife", + [TP_SIPO] =3D "SIPO", +}; + +enum imu_mfr_index { + IMU_NONE, + IMU_BOSCH, + IMU_ST, +}; + +static const char *const imu_manufacturer_text[] =3D { + [IMU_NONE] =3D "none", + [IMU_BOSCH] =3D "Bosch", + [IMU_ST] =3D "ST", +}; + static int hid_gos_version_event(u8 *data) { struct version_report *ver_rep =3D (struct version_report *)data; @@ -279,6 +312,30 @@ static int hid_gos_touchpad_event(struct command_repor= t *cmd_rep) return ret; } =20 +static int hid_gos_pl_test_event(struct command_report *cmd_rep) +{ + int ret =3D 0; + + switch (cmd_rep->sub_cmd) { + case TEST_TP_MFR: + drvdata.tp_manufacturer =3D cmd_rep->data[0]; + ret =3D 0; + break; + case TEST_IMU_MFR: + drvdata.imu_manufacturer =3D cmd_rep->data[0]; + ret =3D 0; + break; + case TEST_TP_VER: + drvdata.tp_version =3D cmd_rep->data[0]; + ret =3D 0; + break; + default: + ret =3D -EINVAL; + break; + } + return ret; +} + static int hid_gos_light_event(struct command_report *cmd_rep) { struct led_classdev_mc *mc_cdev; @@ -362,6 +419,9 @@ static int hid_gos_raw_event(struct hid_device *hdev, s= truct hid_report *report, case GET_TP_PARAM: ret =3D hid_gos_touchpad_event(cmd_rep); break; + case GET_PL_TEST: + ret =3D hid_gos_pl_test_event(cmd_rep); + break; case GET_RGB_CFG: ret =3D hid_gos_light_event(cmd_rep); break; @@ -742,6 +802,42 @@ static ssize_t touchpad_property_options(struct device= *dev, return count; } =20 +static ssize_t test_property_show(struct device *dev, + struct device_attribute *attr, char *buf, + enum test_command_index index) +{ + size_t count =3D 0; + int ret; + u8 i; + + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, index, 0, 0); + if (ret) + return ret; + + switch (index) { + case TEST_TP_MFR: + i =3D drvdata.tp_manufacturer; + if (i >=3D ARRAY_SIZE(touchpad_manufacturer_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", touchpad_manufacturer_text[i]); + break; + case TEST_IMU_MFR: + i =3D drvdata.imu_manufacturer; + if (i >=3D ARRAY_SIZE(imu_manufacturer_text)) + return -EINVAL; + count =3D sysfs_emit(buf, "%s\n", imu_manufacturer_text[i]); + break; + case TEST_TP_VER: + count =3D sysfs_emit(buf, "%u\n", drvdata.tp_version); + break; + default: + count =3D -EINVAL; + break; + } + + return count; +} + static ssize_t mcu_id_show(struct device *dev, struct device_attribute *at= tr, char *buf) { @@ -1085,6 +1181,9 @@ struct gos_cfg_attr imu_bypass_enabled =3D { FEATURE_= IMU_BYPASS }; LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_inde= x"); =20 +struct gos_cfg_attr imu_manufacturer =3D { TEST_IMU_MFR }; +LEGOS_DEVICE_ATTR_RO(imu_manufacturer, "manufacturer", test); + struct gos_cfg_attr imu_sensor_enabled =3D { FEATURE_IMU_ENABLE }; LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_inde= x"); @@ -1092,6 +1191,7 @@ static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index,= "sensor_enabled_index"); static struct attribute *legos_imu_attrs[] =3D { &dev_attr_imu_bypass_enabled.attr, &dev_attr_imu_bypass_enabled_index.attr, + &dev_attr_imu_manufacturer.attr, &dev_attr_imu_sensor_enabled.attr, &dev_attr_imu_sensor_enabled_index.attr, NULL, @@ -1145,6 +1245,12 @@ struct gos_cfg_attr touchpad_linux_mode =3D { CFG_LI= NUX_MODE }; LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad); static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); =20 +struct gos_cfg_attr touchpad_manufacturer =3D { TEST_TP_MFR }; +LEGOS_DEVICE_ATTR_RO(touchpad_manufacturer, "manufacturer", test); + +struct gos_cfg_attr touchpad_version =3D { TEST_TP_VER }; +LEGOS_DEVICE_ATTR_RO(touchpad_version, "version", test); + struct gos_cfg_attr touchpad_windows_mode =3D { CFG_WINDOWS_MODE }; LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpa= d); static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_ind= ex"); @@ -1154,6 +1260,8 @@ static struct attribute *legos_touchpad_attrs[] =3D { &dev_attr_touchpad_enabled_index.attr, &dev_attr_touchpad_linux_mode.attr, &dev_attr_touchpad_linux_mode_index.attr, + &dev_attr_touchpad_manufacturer.attr, + &dev_attr_touchpad_version.attr, &dev_attr_touchpad_windows_mode.attr, &dev_attr_touchpad_windows_mode_index.attr, NULL, --=20 2.52.0 From nobody Sat Feb 7 18:15:11 2026 Received: from mail-dy1-f195.google.com (mail-dy1-f195.google.com [74.125.82.195]) (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 3319723F42D for ; Sat, 24 Jan 2026 01:49:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.195 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219373; cv=none; b=nihdNuY2uo5IBvgr20UOoDdiC4epyqxPiaTLuYLWGIvDHa+TDJ/baKjTddIGC8PE4zMz+kpiWirvqjvgmEFsH5+e4gILIVFe4+wKgG3AdfZpdeuaFbQE2Jfk5hD9hbKNTlJSkXZGpRyBSSrIyKSnjYSwy3BWxyRMKE+x1eosEOE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769219373; c=relaxed/simple; bh=V8oowEftJkNhkKHu/WfqaGxUPjCRUuqpH/Ki19Zz5Xw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=u3USUeHzjXA6EZnUS0GyWWHO/TdeOve6eBeHFFWo/cdJpzh853CGJUYFeJ3QNFW9/BINYyP2aiPcVeOj/3ddmQY/ZXJgme3ph+bjCCdGgQvlDCu5ToW28WIP0C6rveJ4Ze/jk1vHvQTfItYYmgysmHEvxRLMcWwckRFsMpf7OpM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MUh63YYu; arc=none smtp.client-ip=74.125.82.195 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MUh63YYu" Received: by mail-dy1-f195.google.com with SMTP id 5a478bee46e88-2b70abe3417so6623930eec.0 for ; Fri, 23 Jan 2026 17:49:27 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769219366; x=1769824166; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=dpRhPjmopp8WdNWVrQHMEm+6TuZHm/P6RO8FyT4Z3j4=; b=MUh63YYuSyY122L47iN3fW3kKsX0XFx0dKWa+33u8gqJH726r+DfwDFdh8Ft+v+AvZ DPAxmXfDvd0Umg8W1XkfuPrcKCWH7BQEPS/9m2NesgTej4OxSfOARCNj/LaUyW5gGZsx xVKLSSmof4DZKonVwosiFPt4qvxkrEmdlwit2CHMYrXrgK/yehEZb6ZpPnc2mo8bKVNb 131a0LQaCflOGLzjbHYBJklzIHckAqbd7dKVfNoDPEANoDeemkEjx1cDmw4D+XRCXE4w JyFgOBf5njHBcWLjN2XxZbEZ/RK4mANOQMKpfcfj1UBrYd03mz+O0Swbn7s1j8tZZybJ jAYQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769219366; x=1769824166; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=dpRhPjmopp8WdNWVrQHMEm+6TuZHm/P6RO8FyT4Z3j4=; b=QCMHbAGnNhgFF9QWBWJ4EJCBexDpgR+O78uUZtpTdKz+l+GX5ZKszR/xJyqvu62wJ1 wvz3T1VbM+F7w0sUvvT3buX6N5sQEU/qv865y5uwlvsH26p233cupJmGsBxWUbvpdgzn jA0zetSCvfPO32rSOCPbBeorCmqKB43ZVCbqFjkZuKa8hZGEMs2zbrJ5Rg5t/OTaCQLN vfiqUZlOoXwRjH0/5QcQsmfvsQWkg/y69YNQRQMA+r8rU37pRWx98691XoxRsXofSOIJ sPBGkonrJ2qQLLe7ggUD6D+X2sdCV91Zfs1sJZGaJ9rz6qhuDKOk5ANbLoma8tFcB75B 26CA== X-Forwarded-Encrypted: i=1; AJvYcCWHsCRPdrdtpeNQGxLOdE29QS7GQLyiLWaDNy36p+3lXW88XpRNHx6uakqYLP99yK0tKDfUwa+66+dTvP8=@vger.kernel.org X-Gm-Message-State: AOJu0Yx8VpGm7gehqAscAUTt6OXR9JEt2Sv+WP6He2xhvEUUC62fxCJ7 uWM8FTQ+i5jzgKfMrO4mRCET1U3TM+xSCjGRVew87xZqHoShtx9uiduS X-Gm-Gg: AZuq6aJdOZV+Rg9oNIcwqzZMTJtUcftQS7KeHbJV43sQl8olBlTwC63RI3FU16wOoby rOXQwqsZy+jMN5Grb/Za38ED5S0ycGWm8aAiDleELKtbgd+EEy97k/6hXvxW4ioLrA8im+y79/H 3VSK6GqvfO+3mnZPHc1yrs1yy6AZwCB946GhqVKAuhqJj4gGMNdGvV7UTqHn51lWs6vpcRWFuop f2C4YP2D7756PvsjZmv8NCHrccP/pxQ60zFr+xqQy0LRBYjspQYj5M4qGjbcqhEZ35+6V2MTN++ BZOYea6eZul0NErGD93+Zyxxoa1376hHp5D3Cy5ZAvAlEKJbCq5+1D80Q1cnCAyzEXDdHEyMBLZ ttRfuV75iAhfZN3OaAdxOElMHgv+JejJBTjqUzxdzEi1iDMJA1wgY8S0LQV1h1kJ1lYJp91SU4G +erzoryRcw/CqlUWcRZBqEF4rrQFjHYojrebWhCCc6W/vX/gxb3JsysR74ze1PUVw= X-Received: by 2002:a05:7300:748c:b0:2b0:50b1:d963 with SMTP id 5a478bee46e88-2b74274c8dfmr1455162eec.3.1769219365967; Fri, 23 Jan 2026 17:49:25 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2b73aa22707sm5160014eec.29.2026.01.23.17.49.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 23 Jan 2026 17:49:25 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 16/16] HID: Add documentation for Lenovo Legion Go drivers Date: Sat, 24 Jan 2026 01:49:07 +0000 Message-ID: <20260124014907.991265-17-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260124014907.991265-1-derekjohn.clark@gmail.com> References: <20260124014907.991265-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds ABI documentation for the hid-lenovo-go-s and hid-lenovo-go drivers. Signed-off-by: Derek J. Clark Reviewed-by: Mark Pearson --- V3: - Remove excess + from every line of patch. --- .../ABI/testing/sysfs-driver-hid-lenovo-go | 724 ++++++++++++++++++ .../ABI/testing/sysfs-driver-hid-lenovo-go-s | 304 ++++++++ MAINTAINERS | 2 + 3 files changed, 1030 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-lenovo-go create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go b/Documen= tation/ABI/testing/sysfs-driver-hid-lenovo-go new file mode 100644 index 000000000000..c8221373ef76 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go @@ -0,0 +1,724 @@ +What: /sys/class/leds/go:rgb:joystick_rings/effect +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the display effect of the RGB interface. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/effect_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the effect attribute. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the RGB interface. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the RGB interface. + + Values are dynamic or custom. Custom allows setting the RGB effect and c= olor. + Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curren= tly + supported under Linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are dynamic or custom. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/profile +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls selecting the configured RGB profile. + + Values are 1-3. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/profile_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the profile attribute. + + Values are 1-3. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/speed +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the change rate for the breathe, chroma, and ra= inbow effects. + + Values are 0-100. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/speed_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the speed attribute. + + Values are 0-100. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the firmware version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./fps_mode_dpi +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the DPI of the right handle when the FPS mode s= witch is on. + + Values are 500, 800, 1200, and 1800. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./fps_mode_dpi_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the fps_mode_dpi attr= ibute. + + Values are 500, 800, 1200, and 1800. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the internal MCU. + + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the left = removable controller. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/auto_= sleep_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's IMU. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_gyro attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_status +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's IMU. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's joystick. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick_in= dex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_jotstick attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick_st= atus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's joystick. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_tirgger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's trigger. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_trigger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_trigger attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_trigger_sta= tus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's trigger. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the left removable controller's firmware versio= n. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the left removable c= ontroller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the left removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function o= f the left removable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_bypass_enabled_in= dex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/imu_b= ypass_enabled attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU of the left remov= able controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/imu_e= nabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the left removable contr= oller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the left removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/reset +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the left removable controller to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the response behavior for rumble events= for the left removable controller. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/rumbl= e_mode attribute. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_notification +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the left remo= vable controller. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_notification_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/rumbl= e_notification attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controller. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the behavior of built in chord combinations. + + Values are windows or linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the os_mode attribute. + + Values are windows or linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./reset_mcu +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the internal MCU to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the right= removable controller. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/auto= _sleep_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's IMU. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_gyro attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_status +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's IMU. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's joystick. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_jotstick attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick_s= tatus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's joystick. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_tirgger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's trigger. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_trigg= er +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_trigger attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_trigger_st= atus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's trigger. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the right removable controller's firmware versi= on. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the right removable = controller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the right removable con= troller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function o= f the right removable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_bypass_enabled_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/imu_= bypass_enabled attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU of the right remo= vable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/imu_= enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the right removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the right removable con= troller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/reset +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the right removable controller to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the response behavior for rumble events= for the right removable controller. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/rumb= le_mode attribute. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_notification +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the right rem= ovable controller. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_notification_= index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/rumb= le_notification attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./rumble_intensity +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the rumble intensity for both removable= controllers. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./rumble_intensity_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the rumble_intensity = attribute. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the touchpad. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/enabled = attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the touchpad. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/vibratio= n_enabled attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_intensity +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the intensity of the touchpad haptics. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_intensity_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/vibratio= n_intensity attribute. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the firmware version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the internal wireles= s transmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the internal wireless tr= ansmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s b/Docum= entation/ABI/testing/sysfs-driver-hid-lenovo-go-s new file mode 100644 index 000000000000..4d317074bb7e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s @@ -0,0 +1,304 @@ +What: /sys/class/leds/go_s:rgb:joystick_rings/effect +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the display effect of the RGB interface. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/effect_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the effect attribute. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the RGB interface. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the RGB interface. + + Values are dynamic or custom. Custom allows setting the RGB effect and c= olor. + Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curren= tly + supported under Linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are dynamic or custom. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/profile +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls selecting the configured RGB profile. + + Values are 1-3. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/profile_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the profile attribute. + + Values are 1-3. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/speed +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the change rate for the breathe, chroma, and ra= inbow effects. + + Values are 0-100. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/speed_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the speed attribute. + + Values are 0-100. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the built= -in controller. + + Values are 0-255. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/auto_slee= p_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/dpad_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controllers = D-pad. + + Values are 4-way or 8-way. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/dpad_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/dpad_mode= attribute. + + Values are 4-way or 8-way. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controller. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/mode attr= ibute. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/poll_rate +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the poll rate in Hz of the built-in controller. + + Values are 125, 250, 500, or 1000. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/poll_rate_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/poll_rate= attribute. + + Values are 125, 250, 500, or 1000. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function. = When enabled the IMU data is directly reported to the OS through +an HIDRAW interface. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/bypass_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu/bypass_enable= d attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./imu/manufacturer +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the manufacturer of the intertial measurment un= it. + + Values are Bosch or ST. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/sensor_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU. + + Values are true, false, or wake-2s. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/sensor_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu/sensor_enable= d attribute. + + Values are true, false, or wake-2s. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mcu_id +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the MCU Identification Number + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mouse/step +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls which value is used for the mouse sensitivity. + + Values are 1-127. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mouse/step_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mouse/step attrib= ute. + + Values are 1-127. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls which value is used for the touchpads operating= mode. + + Values are windows or linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the os_mode attribute. + + Values are windows or linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the built-in touchpad. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/enabled = attribute. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/linux_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls behavior of the touchpad events when os_mode is= set to linux. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/linux_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/linux_mo= de attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/windows_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls behavior of the touchpad events when os_mode is= set to windows. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/windows_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/windows_= mode attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. diff --git a/MAINTAINERS b/MAINTAINERS index 723598010189..c812d79e3b28 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14140,6 +14140,8 @@ M: Derek J. Clark M: Mark Pearson L: linux-input@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go +F: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s F: drivers/hid/hid-lenovo-go-s.c F: drivers/hid/hid-lenovo-go.c F: drivers/hid/hid-lenovo.c --=20 2.52.0