From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pl1-f179.google.com (mail-pl1-f179.google.com [209.85.214.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 8B5A22494FE for ; Mon, 29 Dec 2025 03:17:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978281; cv=none; b=XDtsBpuGHMyPXgFScAmU4QerzNsp+Bg0byE1CnyuDrl9agsC6DGweDzbTSmpzXXLgFehMnjKARd+FHboan9fL56fSfT91Iuzi24pHgoIP1fQdTq0fKpp479mNqtMHOCfqepWPAde4TjpAIukKtyItUe2A81rRHgc+7/DWFprb70= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978281; c=relaxed/simple; bh=JYwujQFMXSGgCntgSP08tb0z8gWWluEo30iDiljW/dU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=a4xDt5k9Va/ldE9f/H5GhSZM7eY68emHNnrAVEMRM8Ok1O4nzDlxWYZq6BDHwCTUnna5R5MsGB5fKUmOvaLeTLfQlZlehUs65GGSuOv54AD+nVIOdOnOXL1Wnw0q4eRxE3l/blKkh3ZMi3lbORJ4XfwDsG6WGT8xY6L2tktujjc= 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=m8pYdAwB; arc=none smtp.client-ip=209.85.214.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="m8pYdAwB" Received: by mail-pl1-f179.google.com with SMTP id d9443c01a7336-29efd139227so114971715ad.1 for ; Sun, 28 Dec 2025 19:17:59 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978279; x=1767583079; 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=Jny3euFMoUvIIQc8PkVqUx2UEsum5WbafcB5Jo0OEm4=; b=m8pYdAwBYj/E9TBk4zT2/LZH91X5e1BrntbTrXBOvaCVHyxs8BigWfzTi2YnYcdFtq gva4Vor3jVOke0CogBm8eVgTcjKyJH1Edak+QNeSzfoHJiXPJDRINUcz+6MJ3eAqbOcI /F58yort6NG/RzofxcOCyjJjoDRFSqWI8W9yPrkk22drhTDKIHubCr4QxHAemif4o7oE 5Tfn2EU+wae2g7fKcj0QIHjAuGmk2DfiiODQyy3lleb6SFRLq/p75B7uInZa3+9pctfQ QbFTJtNnkl8Jr1kSiQp09hme0ce0ebDxry6PPlRPn5YoHntj02aoJjR8TDxLeOnVY6Q3 DcKQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978279; x=1767583079; 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=Jny3euFMoUvIIQc8PkVqUx2UEsum5WbafcB5Jo0OEm4=; b=dzNdXVsYkI4Ptf7l85DjuOm1kBzWQQ0eXz8eliG0ePcZA0eCQKDYguceE600FaHBhl 1GR5nTWH7nTyJT4aZbOHLwObzcRSg7+Jer9fMRYxQMYjihWdfNrtHYZD3WW+8SfFP8L0 80n7IukFuDpjk5cXsQIK6/M4kMQbRytg5YtnUSODZHCHS3Mp3fZnTJ+CPJFDgCKPACp/ WwjxbqEZVSTZ0VdlHID0vdU8Bo4R8m5skztyPMyePD76Jj0z/Ai98tR/bJYcMa2AmB4K IaTraCn6FWcVMG3vlAKLs33LS5GTgwKuS5iMLYR9CXli8eOMTqhZzduW3wKeAUXQ6Ls+ BrLw== X-Forwarded-Encrypted: i=1; AJvYcCVa24V3qmcbvgh1S/v49mTKWKFMIepFg2J3BppU0Oby4Sxj9VypJIeqwDiEVJwOANFxDwjwnC2LBhxCIVc=@vger.kernel.org X-Gm-Message-State: AOJu0YxSHpplOuJkQkEqbtJ8nQK3nPkQDnmcsweRgepZhOB95WzC2S8L Vkvj1miCXQ7xWmQqCfyLwrQU5GC3kFlFWAQg47J1L9NLCJAaXMUGN6hj X-Gm-Gg: AY/fxX6mgxKTByjuEjAP7x1oJ1k5a3vZ/jA+lAUF0XF3JDha5YDUeZhCEfIu8IyWY40 jK0oGKL3+psHNfA56HQUsWDya72dbJPx1rrV6DEi1sbbLmaPuyrgHQSX1I6sKTGAEvJu0WIxTEi VS8dlYUFri8ygsPiXeL4rrSHOMH1rkR2E2rA98Bg2Q/3OOXC2AkC26CjTIo9JjYCZJ21Cujpvdg ZrHWb9r0UaKpC4YAdLyIPnKX1g5LbudSVTCw7QBE4dQ1sjFswoqHIcf4lYqKTxI+SJ4gPy6Npq2 OVmuVVXNDXCU0V5zfdm7hZ5lPg9NgSEdubxltzNXqNfiC2CMlyc555yXOtSbuoJehFfK4+E/UqR eu9xyhq7oqEq0zt54+l/MYoy9dTUynejUGNFjTzrRoVOswd4RuefY1fGVp0Z2WkjYrAgaoXObaY tW1AyLGrMWRxeqeMJH5OBm54GVhECH5WLVHhbapbMTA4hDfa1bNwGlT9u5YiFUNfw= X-Google-Smtp-Source: AGHT+IEpEdjauXjggn5SR7U1Q+y64OlZqNg4Yg894GTeht+XbUtbyA5M9bniZWJzFw9VSOjbvaSz6g== X-Received: by 2002:a05:7022:6199:b0:11f:1e59:4c2d with SMTP id a92af1059eb24-121721acb6dmr26336934c88.7.1766978278720; Sun, 28 Dec 2025 19:17:58 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.17.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:17:58 -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 v2 01/16] include: device.h: Add named device attributes Date: Mon, 29 Dec 2025 03:17:38 +0000 Message-ID: <20251229031753.581664-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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.51.2 From nobody Sat Feb 7 22:55:10 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 D865F25A65B for ; Mon, 29 Dec 2025 03:18:00 +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=1766978283; cv=none; b=GEp/r2zU1l0RFm7kc4/doOFC9gTmdMT/BvliWUS0Ca1474Cg8MB2a3/CSioHjOpArz7tXhqNb8J1iJD8J8UCzse0DzKdOMh+nxyl+xpja6IRTleWpRw56NZ83Mq4MFJ3HYLBQiPNOvcWDHMK9BPh3t8AHmmp6+MmX0zf+332slw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978283; c=relaxed/simple; bh=VJRHLoH/I2SgffmsYbYbeS8A0U0iuJzaGjBajIKJnf4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Oj7g+tpWzqo/WaotX4kMKhLnOeRtT2bI0DN/CSgpB5RDa0Jn3LEk3wwHbAtxmLNtiwDidyXMAyx657VIJ1tuarTpWwwJBLDRMppn7BemTfjPXAnmdCMluTdZNQ3bwIb5H+CpANVGbXwam7PnQO8h+TwHrsB2F9UyBFqULESD1aE= 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=aK4zOQXm; 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="aK4zOQXm" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2ae29ddaed9so8459826eec.0 for ; Sun, 28 Dec 2025 19:18:00 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978280; x=1767583080; 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=qfbbtlHikLQOWZ/xeRNJiGTjXM9W91zKXMxinu1OeII=; b=aK4zOQXmdfUimdm4CWLJTWdA2T9ql/zb+WKo/n36nItl9dRX1jXh9xWuv/czXOZzTf 2Bgw/gI1LcTIjTYpE+BMfY9v5bcH7ki/X6Zs5oekkJ541OXGvEZ3bK11qVqwb8TJ7eIa c7yVVj0PGladNADbx8L8Y6KiijVWM21YajaNOGXq5If+Qlkb77HKqEMrBLNKMY9Pxhmc IcPJgNjCzv/k55K8NdXiDKZLax6mYBioxqCCmX7Jk6ldjAm17v8uM8udsj6FPBHSyBHx 8AidkBl6BMA79hkcJ1mb2J+QaUfWy5m8dY5wCChbVr0XcqhelJ1AetoCNQo/sfJx2yEg CdAA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978280; x=1767583080; 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=qfbbtlHikLQOWZ/xeRNJiGTjXM9W91zKXMxinu1OeII=; b=E/06m9A5ybNsBm1TeTZ1YMFCM92/AukgwSbRHyp2MCwFx2gkYbFpSkadUuH04X9eZk vITFL4x1WW0imuUG29go4BkDGUHmOcLKNdqODaIhsv6q2ak/oksPBpc/Y8DsWGTxNa0r 0I9DbR+5Eg9IPIH7pUqizPlldPyBM09VlQe29hoGDEtolywtTtBVz/L22LyOqqy0Pwi3 GxuZdXactupi5FyLFfEHZG0xtsXBBp7Q8vKgW9D6ttk5CJ2jD8+KHSJa66aOkexwJLlN IPMWgSq05FGVNl0/bT3dZV06eNeZr2kHFITEMNoMW4xFVYY/ggjMimDZJCRI9MsaNhqW M6vg== X-Forwarded-Encrypted: i=1; AJvYcCUEw77EmYPjIpLPRhVTMkRmvYmsS6DkynH9h09epPHhpuVSiytebkuJfh3uYi/FSiZb+5n3HJ7STEaTFJE=@vger.kernel.org X-Gm-Message-State: AOJu0YwPcPxKdh8ekl7/itED38rjdRBY4pJGlcpSQe0NJwv0TkO8nEQg F4CjExZPNpEBtQT6xaXOGL3ryTBoIbMAebY5IH272wJ8GITicN4Z9zyw X-Gm-Gg: AY/fxX4bqracdkVtkxcXYuuk0OzDavXTHNltYcnzrXK77ZF/MDPQZ2BIcFnl6StfzLA XtPgWTe4zCr/G7bmmbNIr5kEOXt1ReKSltJrZAT/LOK2Sc9+jMYiMbmyqmbTHe7/h0lrtTFF4kg 5B9LHodoZtAIAN5gv6nuiqQNvnOsn7Lv5pOXeR1yt2QNuD4796X/BLOUH7c3h4aF4L9igfNgZvV TwzXLjqZvvycZaSTGk3pn7X1SONks3AClVO7t51hKH/hIUlgqDpvDUc614VZS442UEVEg3hnvZv uw4CfIgoowROK4RCcHjMQjHd04GWx8mQa8eoMPdPEp7+sypAiXfdexIblwg5IhWWh2qQ4R+4Q9U 1T0g+PwdpMl6CAUDCCjylP9BIi7p3zay1drCIchQM0+FPv0n0XpzzYDtlRXS6MP6zbwU47dvvhS 9Dl6cl/NjhWK8hp3kjk3B3l2oN35YKtUPTEjemmu5GwOzL+10bWcxugiSKMOMrVi8= X-Google-Smtp-Source: AGHT+IHP/FuPIGvu5ySQpe2cB4z5xiMGdSG5/JRvpjcmeXIO5NMQBSlQA6GVAalZgjzeldq4cYQT4A== X-Received: by 2002:a05:7022:f401:b0:11f:2c69:3b with SMTP id a92af1059eb24-12061942691mr32013078c88.10.1766978279571; Sun, 28 Dec 2025 19:17:59 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.17.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:17:59 -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 v2 02/16] HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver Date: Mon, 29 Dec 2025 03:17:39 +0000 Message-ID: <20251229031753.581664-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- MAINTAINERS | 6 + drivers/hid/Kconfig | 12 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-lenovo-go.c | 734 ++++++++++++++++++++++++++++++++++++ 5 files changed, 756 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go.c diff --git a/MAINTAINERS b/MAINTAINERS index 9ed6d11a7746..b5ad29d24e3e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14135,6 +14135,12 @@ 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 +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-lenovo-go.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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-dy1-f182.google.com (mail-dy1-f182.google.com [74.125.82.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 82B1526E17F for ; Mon, 29 Dec 2025 03:18:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978284; cv=none; b=DGFsNCTsRypLKf3onhJI1+AMgSxpluwM1cR09ppZcDonxBA4AF9Q/FK39g6LCprbrwOLnrCeyG6WAaE5UYCjbkJ6cjAGTwYcgyu/yAEbdQ95u4zlkg2lnNcdr5WKBgiB17Nqg8iS9qVMZ9KRZV3s2lXwuwpTKN1ikA4KHNBYvtk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978284; c=relaxed/simple; bh=JIdPWlInQYLwoIAFoEs5e0xuStwTa44g3R7fLjrW+BE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZKr+0wLBJAMTDMirPDrJYBWzI5slTvdM1oUSm20MHDQRhKwbsDSj+u4AaNONXoGfnlXj1/rEoHmcuNuB9gluGBpo0h3dy7UUR9cUFoO3VPU/VDFj+coWpD0QGc5Ez2Ydh4d86JJNQBQ4HmBxtDtNX50DFimp/fr4QB9UuCCEYt4= 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=F05oVCOU; arc=none smtp.client-ip=74.125.82.182 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="F05oVCOU" Received: by mail-dy1-f182.google.com with SMTP id 5a478bee46e88-2ae24015dc0so8426994eec.1 for ; Sun, 28 Dec 2025 19:18:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978280; x=1767583080; 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=rcy7USvnuEW73zJsFOG5kiObsX7sFhbRaBYF5VQJH5w=; b=F05oVCOUIWElkfRzVbzehHAC9VcpxHMBqQ/fDO5KvCugQipymYoLl+IZcKyK9oSOy/ zRz/OYGwSa8xQ10aL5QmAmZ0K461MmAyEAAsrjOHcjqd/k4VeIQPEGTc1NVF3UUKYOJ8 ngpbHX4m/Ept+GxD5RVYJWdQl1BKjgZVCNgUXzXWoHjXeEVFfCKQelIMwTO94SxwmtoA WKv7IvZmZxEfdsM8u1HXLWC+nDLCzqqYa5l0mBlPUgh6G1WOcv/l4saLiqqcZBHCxS0r fo014e0oC+s74C2KpuzF2e5YsyW2ESRbkiKrkCTDsWgF1u14T1Idt5KyGV0/Axicp3aC oubA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978280; x=1767583080; 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=rcy7USvnuEW73zJsFOG5kiObsX7sFhbRaBYF5VQJH5w=; b=Dm6uw8MLPRTAR2Qmozv793CkuV56G1rG+GuBGdxEKki+T7qxHrWiKqCqtH2+dZbcJ/ FE/7KKvc958i9/Za75ymwmVCElLrhZH5tk8IfIN8owhL81Bkq/TpWIaLCLXApz0wr9TJ 6pASQIsRPeBmSF04NhEWnZen65W8tWYyc+Spx00jroncMuZ/R0r3s6NyfJVCFVqLGjaW E2C+5sMXH8335d4BXSF9QMYLOj9IZ/9HyuUQm1HGmzZLVSnRkoklIX+LmveEQ0oHTNGm 68MVIvBA0m4RUVn2ll2CXKEFqHOYUXkkQ4cI5by6xJdXK/UMP6Wzjerv/Dv8T72dALfV 1KAg== X-Forwarded-Encrypted: i=1; AJvYcCUMf0X7BndJBmCTj2cGylKMgXCueRHtSTo2CaHLp0bYfBeMUQ6zD43PARv3OvKbt9tNoCKDGt1i01vLtd4=@vger.kernel.org X-Gm-Message-State: AOJu0YzDsB3fo1ff9y4f/QTKH+2h95GTWQuRO0jS3HEs3WAadEraTegi 7c3OD/2ANDYg7WZV+QeQNeujBK183SA3OsLibfx7rX4eb2knnYvsJYDt X-Gm-Gg: AY/fxX5wu/L+y4eJrEshYrm8jnCGf+RcAiRYQdi1SaI24EDa2oVQj/oFZHmp8uWOq0f wwk93Q1w5RszM2P9l/zBRGeaHoD8gFB8FkBNttWv2X5YVm2YAlZaJ80M6xtQEk1hlG65yn8HsPW 10nJKW1LNP43C3Vrrk1O0ODxe24+JNbEnpz+bbAkYWWF+g7Cx2iXaZiaKPa6MTMweJ8LcWGdUat UUocJxDH4jgrCOz0VoT8uTLtgas849c7OA8P8L7deCQwDXfu6rB2KxCpsq5cNqyyp0TbihO4tgA EIyud7ko8ZPTd8r6Iyn3adHmQbeBkc1sZryRe3sS8F5oxqoqGzbzN9l5Rekgz3KdQ9Xhd2ZlPRf xKti3ecbvy/9zeRszS9qo2ZJ5ajVPUSNQig7EqfzjSlY6TCy1lyImEGSpG2qlY7Y59Xb+Vp7TVi qLDXb/Va3yYxZ0e74Io3+K56vN/GA4M6r1a8dDXvARHf9YXIWcAoH4ck9AqQTV2E3+CUBMesv4+ Q== X-Google-Smtp-Source: AGHT+IHa7TxHmJePs4nW9fSgYvK97FG5h3PwyoBtbZGEmglxWMiUDbGhglisuR4sQG2uH/bd9VbTtg== X-Received: by 2002:a05:7022:6883:b0:119:e569:f86d with SMTP id a92af1059eb24-12171a75912mr32428528c88.10.1766978280429; Sun, 28 Dec 2025 19:18:00 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.17.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:00 -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 v2 03/16] HID: hid-lenovo-go: Add Feature Status Attributes Date: Mon, 29 Dec 2025 03:17:40 +0000 Message-ID: <20251229031753.581664-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pf1-f174.google.com (mail-pf1-f174.google.com [209.85.210.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 3CA73273803 for ; Mon, 29 Dec 2025 03:18:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978285; cv=none; b=EZJCOcrKXT+R2h414j4oSWGRvzJd3jwrmLpX+tRuhVXOC5bAOK3wEtJTRRgmOO4Qy90TOstL7oPrc30xwfQ5+V/hVXulYRZ9AnZXOJOIUbaxQBy+TfEqAlR5sjBPk5BP/zAtAJw17mOwMWpiteNEUlJ/IhadCSMAJ4l2lG17xVs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978285; c=relaxed/simple; bh=LXmgTZe1DTlhBfOcVlbAaBHy8ZKaDHEjHmzF1BOSMdg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dLjM/fL/AvCKj0IeubmCaHAvRjqya17tzZ710JxUbBxxDmUU0urngBIXUuLN115kjlOplt8/OVrFOPy+6wccjrCOPfpPJ3fSPiD+FmZmtwkiYmUxPeGRg7f1qPqpSHbnxcRatqBzX10c47W0vT0U4NMxBdeFkbrredjjOefe1TY= 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=g5HSFf45; arc=none smtp.client-ip=209.85.210.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="g5HSFf45" Received: by mail-pf1-f174.google.com with SMTP id d2e1a72fcca58-7bab7c997eeso10167557b3a.0 for ; Sun, 28 Dec 2025 19:18:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978281; x=1767583081; 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=3JwaiSohIuuXkqze/ZakjDyPJj0LsG+i+X6xuEUq81s=; b=g5HSFf45gOY/i4N5x3QWdj5pLjSOXEvAX4f+SxdxgN2mpK0AQ9x9MYZkKczsHjXhiL QXR+d/RSnHeTwTYXEVWoBqIIG0cUJtcxvA8lDP7M5UliGxN18h9VMLZkxzeibjyZ5JUT bf2e4eAbazzNDZW+550pbK0qeqYvdHRUqZ/b9OKbSkqXx+QRu4ksEdFTHwI6la/kRSm1 BZfOFeACmtP1aWpk0/X3nDGWNVUQa8Gdl0A1TW2yO2VrwyIzc6Zbk564PfQ284sC27Dz MjiS96mnKxeZEnW7MMng5k5SWDbsieyf4H2U/B4OsTe+GkI3chA+rW0Hvb+cfSSvUYnN aPFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978281; x=1767583081; 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=3JwaiSohIuuXkqze/ZakjDyPJj0LsG+i+X6xuEUq81s=; b=rJJQtUD1VF1XCuHajsAXpvSN3xYdv3/YT8/OIMZfEyeNJMNk0nXw+/idBlJxSSHFuE ICk0qspG6FccRomPUuFQLY4mBKZNC2dHTxkMfQONILBsLJV7boif4xOqMI6YvLcrzVtI cHsk9F9vNrtG96bKNCLnT9f7fYPnI69wsUzyxnp/ATYhapVwYqZ5oITSY435RpAHcYiA RcgYNP+ZbnmeBDWBCPWaPFz9ijiZJuxKCpL5ccRWEdx4VHr02riEWkXXeWhep5SlIHcQ qlzBo1ZGaTd5X9Zo6/tsUM9u26OyfXUhtRUn0YxtNsCjg+nu+naGP2v9ty4oS2rQMLO0 2xpw== X-Forwarded-Encrypted: i=1; AJvYcCUQ+AT1RgL6ZeO/VFaDc/yri+H5B20Qus5y0tiSFQbpnKse7OYRivlGknrdjRGh/9EGR+azV3dUmDzLKLU=@vger.kernel.org X-Gm-Message-State: AOJu0YysY2g204wqCz2Z0FKivvrtFw2cyhaDBBwXu021FH7QkdaH6xht tDTLXe3HeZbFxhcpi++2o+WGZlnldB7dsENNgduW5uKKyY+prse2ibqS X-Gm-Gg: AY/fxX4dwG7CbNeD3sSys4fAwtuPtL56DkNVagGvsKbY/M2NtDRsQ7TfaFcDNeu9i8E /3McPDVY5UvmPpLHHMcQBbzIbjL5bc+Fb9bcxLQ7pGdAj1C66yizFtX4jBvznZ+WmLDyGZ94VMo yaP3NTBUe6/HX/ro6FauCuYZh6Y3xITydKT0HzKQ6y2YnVPHRFrS0CIBqfkQmzvGboiIPF20W8S IBLcADlkdISkrezhali5wiVD3kYhtlQG26EYIRJE5NaSYghiGsHHQ6QXKevzelCOnY+gCAVCukD +Y+fsBtYHxG6epOBtbtxTI88BbbPnubgzk7A2v/7rUS5xyKwPWg4mBx9KB6DphxHNbwmNk1/OZ+ LvB0PTVAleO91XzgjVshUDjX1cpOAZpxcJxPW9brwKn/UmTL76dJDrVaXHWFebAG2axc+ipBASU 9KuPSmTWQRaNAvi4cRxItWVQN4oLa2iOziJ4J/bIE9ux+XezQOSb5HrZ7RvknOHgk= X-Google-Smtp-Source: AGHT+IE0oxTw33lXF0gPNZqSMH5W8WMr0A1jHiugdw67TZB5txq6InB6ctwd1MVVDSWaebt5Yahqaw== X-Received: by 2002:a05:7022:6199:b0:11f:1e59:4c2d with SMTP id a92af1059eb24-121721acb6dmr26336978c88.7.1766978281186; Sun, 28 Dec 2025 19:18:01 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:00 -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 v2 04/16] HID: hid-lenovo-go: Add Rumble and Haptic Settings Date: Mon, 29 Dec 2025 03:17:41 +0000 Message-ID: <20251229031753.581664-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- drivers/hid/hid-lenovo-go.c | 340 ++++++++++++++++++++++++++++++++++-- 1 file changed, 326 insertions(+), 14 deletions(-) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 681791f119d1..7d8bd1dba7ce 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, @@ -896,26 +1161,41 @@ static DEVICE_ATTR_RO_NAMED(auto_sleep_time_left_ran= ge, "auto_sleep_time_range"); =20 struct go_cfg_attr imu_bypass_left =3D { FEATURE_IMU_BYPASS }; -LEGO_DEVICE_ATTR_RW(imu_bypass_left, "imu_bypass_enabled", LEFT_CONTROLLER, +LEGO_DEVICE_ATTR_RW(imu_bypass_left, "imu_bypass_enable", LEFT_CONTROLLER, index, feature_status); -static DEVICE_ATTR_RO_NAMED(imu_bypass_left_index, "imu_bypass_enabled_ind= ex"); +static DEVICE_ATTR_RO_NAMED(imu_bypass_left_index, "imu_bypass_enable_inde= x"); =20 -struct go_cfg_attr imu_enabled_left =3D { FEATURE_IMU_ENABLE }; -LEGO_DEVICE_ATTR_RW(imu_enabled_left, "imu_enabled", LEFT_CONTROLLER, inde= x, +struct go_cfg_attr imu_enable_left =3D { FEATURE_IMU_ENABLE }; +LEGO_DEVICE_ATTR_RW(imu_enable_left, "imu_enable", LEFT_CONTROLLER, index, feature_status); -static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "imu_enabled_index"); +static DEVICE_ATTR_RO_NAMED(imu_enable_left_index, "imu_enable_index"); =20 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, &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_imu_enable_left.attr, + &dev_attr_imu_enable_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, @@ -952,26 +1232,41 @@ static DEVICE_ATTR_RO_NAMED(auto_sleep_time_right_ra= nge, "auto_sleep_time_range"); =20 struct go_cfg_attr imu_bypass_right =3D { FEATURE_IMU_BYPASS }; -LEGO_DEVICE_ATTR_RW(imu_bypass_right, "imu_bypass_enabled", RIGHT_CONTROLL= ER, +LEGO_DEVICE_ATTR_RW(imu_bypass_right, "imu_bypass_enable", RIGHT_CONTROLLE= R, index, feature_status); -static DEVICE_ATTR_RO_NAMED(imu_bypass_right_index, "imu_bypass_enabled_in= dex"); +static DEVICE_ATTR_RO_NAMED(imu_bypass_right_index, "imu_bypass_enable_ind= ex"); =20 -struct go_cfg_attr imu_enabled_right =3D { FEATURE_IMU_BYPASS }; -LEGO_DEVICE_ATTR_RW(imu_enabled_right, "imu_enabled", RIGHT_CONTROLLER, in= dex, +struct go_cfg_attr imu_enable_right =3D { FEATURE_IMU_BYPASS }; +LEGO_DEVICE_ATTR_RW(imu_enable_right, "imu_enable", RIGHT_CONTROLLER, inde= x, feature_status); -static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, "imu_enabled_index"); +static DEVICE_ATTR_RO_NAMED(imu_enable_right_index, "imu_enable_index"); =20 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, &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_imu_enable_right.attr, + &dev_attr_imu_enable_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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.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 A8045263F4A for ; Mon, 29 Dec 2025 03:18:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978285; cv=none; b=mZAnd5IVFTRJjS60iki9hMEFh4ctD/SaS6EtydgIa3fhQHIDPMf91IXtaB9zhFZsFQVwYFSxi8LNH6/kzBRYYlqiB3MWSMxWNJbMJGiDnqM7Kra/VOK723qkWSjWzuaVLz8VP4DXcp4H7MqUPKgC6Z21TtJfwiaVyQiquimEHVw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978285; c=relaxed/simple; bh=jyYAuYoqThQ9IaQhCI0DxLxP8eJMA3q2OZLB0+BZ01U=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=S2x7jKuFRs/KwP4GgXDjDkIVINfaXHTP4+8zhl+ywtPfvLt0lcooEDTG3Z54DskwXP6sxeR7OzxL9S3JUiK05lVsAmeQRNo/LOAg4+dA3E3UC4fo58oCODEFd6mpe/cr6gg9GTodAERzdNAuqwvCJRnd0ceAhJ9aiJOnm57E39k= 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=d5pCa1VH; arc=none smtp.client-ip=209.85.215.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="d5pCa1VH" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-bc2abdcfc6fso4929523a12.2 for ; Sun, 28 Dec 2025 19:18:02 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978282; x=1767583082; 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=MKgim0dSrRrM4iK8owH+2zFzPvmukRMS6uSeUVSPKuE=; b=d5pCa1VHFZz62R22BxR0NlOKtSWIA7w9ZQLirVUgg6ITzSz2E0gubI0aOGg5wTu891 3x5BpTLiZvMB7B0G+vlOFq7kRnYDeXaLEnri7vST9Aj8FJagwhrqtllZnulmX5SCMmZL tBfTbppNLkgW8hk6yLaDz0V1Ygy6nopgA2VEFOPwYN3vUU18WxVF6iP541XZyWIaNFr5 W4DPoMYt2r+cmGyuhbFTBa8XT4ahv9N+eJjaG8b1yfORwn2gNPWVJ8aAnsZQ/yXp191g pUmhGu7Y7f7NRftJvB9jK2U/4Aeh04kKFtB4rDAQFHlM172UPUFhZSYrZ4+F7SvbCxa2 gJTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978282; x=1767583082; 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=MKgim0dSrRrM4iK8owH+2zFzPvmukRMS6uSeUVSPKuE=; b=ZEAjeOM/dKuBdm4xsozzfdjdT2yMDFp3oXuc7V/Ma0K9GXYL+8YH0+rgvlhKe3NYwC dNtQUtH2lvFZTJ73AWYqAcytm3MkDi1L5T3tHRhkbOum6gsHBFAsMSj8e+CTTCQVlxiG 3oqZTe3UuWXAsDu1SGjKJRaA+DoGgI6/xDTceOdJU/3d9d8BZpt/Bo4LmX9zKM62o071 jAxl1uQBvtO0Io/8cuqAPxHElvAywPZ5so6cqn/jP6ZquJYneuuXw4WNncuyX0slY9jn h37TPQ4rF2EswZQDQmXgwEuv1To8kmkAgW4EskZ6AtozjgrPayRHLp2VeW++ouNn8Rie +tfA== X-Forwarded-Encrypted: i=1; AJvYcCXggynlASCk91diWvWgwa5YcbdArjIDEsNWK++vBioxTUaCycb4tteVIBKTJd1uRfQ6hznyDnSXzUvxJnU=@vger.kernel.org X-Gm-Message-State: AOJu0YyWDpZNF4nWls+F0/IwGMKJRPwCyH+zWpqcNoyDarECTsHuMcYn e8FMqmxKrDtiU9FT7puUZDJ2zuejOE1iKHWhFyRZl3MBJF3nG4yHJ2xv X-Gm-Gg: AY/fxX4TEItVFMC/95L8AG1xp2VMlw2qSpsWrwFnrS2PppmfFmid8bRIhVI+tcIdBLz f+uaZgRcrdMNWkzHA235a15nQ11ogEOSG6rHP9LLwclu+dqnWY8Ahh18H0mTsOJ09gS9ANmdD9F ++5som+RjPmY/iGWt93SpeQm7Ww7Io8QPnnxAfFpcgFlSq6ig8CZkKx8hY9cOBAfO57ZPTtBgUT eUDCe/Vg60RrMw09M9BJoOouknFOgp7qGXFoH18OvxjDGfJNCo5fkjiHtToG662r3eFiTenmsw4 621VZsZK2DZLQskVJqFLRTWSyeOzkFges0O/mBC8+/NsS0tiyRzGwkZZeiQw8HWnoiZnt3pbrV2 6RYMGbaNj3iyNZBR1iWbXx7ERXSPS6IGyn4BHL1d2YQHu6yUZWkGc0cw6QxM7W5MA4e59QCz7jM YVMpWIWBGeI2ziQ/yxLmRml+ffhsSrhK4Wm92HeWGsx2/BG9GUBSMSZd6fjx4Z2wo= X-Google-Smtp-Source: AGHT+IHVhOYhQDnqD/Ev9sZ5WdVWs7wFjrnerdhiVF/Dq4HNgfa6258s0mR5YjktP8ulmxxOSTmzLA== X-Received: by 2002:a05:7022:924:b0:11b:923d:7753 with SMTP id a92af1059eb24-121721aab49mr35160631c88.3.1766978281939; Sun, 28 Dec 2025 19:18:01 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:01 -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 v2 05/16] HID: hid-lenovo-go: Add FPS Mode DPI settings Date: Mon, 29 Dec 2025 03:17:42 +0000 Message-ID: <20251229031753.581664-6-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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 7d8bd1dba7ce..96f16ba5526c 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9F5812727E0 for ; Mon, 29 Dec 2025 03:18:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978286; cv=none; b=jMaHIhrSwWuUA0qmc58ncJ9MFaq4O5ANH3Bqf/crPiyzzzt2v+Vbm1CA8xUSlJciQWc/LSJ1/VWRGOEqvIaAHheOy6TuHfpoEtIe1nJOMZ2D0TD562eggDFdXa0i9iboF1yy+KlVrL1+tddfTw2Be/Uj1Y6QD/sA9QpnB7cdJL0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978286; c=relaxed/simple; bh=k7cB8bimuZ6oT1v9Gj2n603wXmKa2cfulwV2tmsbxI8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RlgtDMF09eIgmrYY7CoJlj9ShJTfU3xYw0pLU9+lP/4s5Y+G2grCWOp+4qx9mWo35H64UySurM50A2hXwEXztxNngow18VcW1HAllfywqcT/FYOO4Wv6jPZa/fYYzwTeue0/EToMnjPgk48UclkoqjCPlky/IOfr/ZRI4exMvnc= 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=OR6guHCr; arc=none smtp.client-ip=209.85.214.182 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="OR6guHCr" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2a2ea96930cso100470705ad.2 for ; Sun, 28 Dec 2025 19:18:03 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978283; x=1767583083; 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=+x3dVzic0jLrP+IFVUqMXKs9oATFGZqyVhG2bgRl5N0=; b=OR6guHCr2Hn/W/MgC0uMadrPWdkLEPk5oEN2p+fXiccP+j3UAxAxGpEQeHgtimYQtZ xIpGc8k76uNl60yhp8/P2Cc/MBZabrgSdtmCeThC16VCuD7sfCbBj+1I5W7ygsoZTp9r e8XZgUYWV928VvTUyG47efRimYKmGU5+y85SQRiupfqVrfsmbxkDluYk3/ksmSKd3hy7 GIrfJmpkaJy4TptO+/uVuLrStMHLjJepGftlkJJApa/eriIoklyEjlHcO0Vh4m6M4Nit JaZfPzawNk4W1f568aNcBJd9v/I89wpN3Xr8UAT7GSOQ70VbcRJBM/a5nMhga+wxfGHn T3dw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978283; x=1767583083; 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=+x3dVzic0jLrP+IFVUqMXKs9oATFGZqyVhG2bgRl5N0=; b=qX5UUo7l6wV4QK6hfFMwzrbTUeBDQTXBUmftCDSV11TPIRzhnj7jf8Voxv9u9xZYqI B9H1YJ6eRioD3gGRJshVVSFGvlnHU5/5WwrFVGazr08/p9F5pzjtaorDtr4sfG7IueCi d6BfBTbHsH6GrqFBCVbzVdf4P67UqgeOL7XV6VBeGKko6rLpZwladZGPhqdeWRCVNexS Fs8bwl5DIxpCn9XGirjQh3ox8UrWes5xm6O8UX3QT14cjpm56PhM2cH/vYWCMtxAF3l/ ZXyAD69fnrdIxVRERLT/vVgauCPjMFE1zymowyOtDVHYk9aRhzDkdp9jEmEo2A3UQo63 mzow== X-Forwarded-Encrypted: i=1; AJvYcCVN03FeLSspSZwAAV8VWnKn8bEAgBx+nd5Sn0JM00u/IQsV/f/whqfM9ZAwaahO1XCS/EVsm0PkoRqSluo=@vger.kernel.org X-Gm-Message-State: AOJu0YykqjapbfTE7HykuKBzWWorJYiZm8L1QBP9rBxbXikMDbWlh9Jf bm3b8eg2vo35JVyqQwirCtCMzNQhoqCM1BlLJ8v5cbRWK1xAgk6eVI4E X-Gm-Gg: AY/fxX4eB09QRSUWKMK5LRn7Xdu7o72P/rhKkp4tpzwwQBJi9s37JROVqDVUM1DpuWc pA4Kc1wELyLhltaZgejNrjm0SHIyokVyzGyrXt39sdtnaN9L0umROVm6SE//TCtVPVZk+xZ07cI dWVV0cfMixwVlAiLNYbC7xbK+L5X1a0o6Pbw4G829ZYs8ZDxgXJ77AhFuBDWoDq5fg0wt3Mxt8K n8NWy2CDTmwSt2CHhHczC9KkronbNWkF61r0DB6Cu/mz+hmMve/KRDAOQjGrJBZ1Id/2r0Z1Zvs c0dmFin57j0jkaCjUPD4tGsxZQIG8mpczSqE5RiCQHr0RtOO1OwdUZUUYjsDYP9XrFbkYru5wwT kMSoEw+zr3SlnSN1ldW06AjOZRYSnLWVn7+/ZuhQZOmM+ZSvSMdBiOQHq86m4fot1nTeU7/yGRm MQlD3D6wo5u4Y3n14bl0ea9fcNI+LHGMqL8M+rv6w5OMpfozNVfX6oxTUkoMwr6Io= X-Google-Smtp-Source: AGHT+IHDA1RLeHSMiZ4j5C3eUkycXt81kF/BBdHJnJmIUy0kuAFrODRuG8IJvvrYQBK5lSQSh85yuQ== X-Received: by 2002:a05:7022:90a:b0:119:e56b:9590 with SMTP id a92af1059eb24-121722c3c9fmr26435727c88.21.1766978282811; Sun, 28 Dec 2025 19:18:02 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:02 -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 v2 06/16] HID: hid-lenovo-go: Add RGB LED control interface Date: Mon, 29 Dec 2025 03:17:43 +0000 Message-ID: <20251229031753.581664-7-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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 96f16ba5526c..c8761297f57b 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pj1-f54.google.com (mail-pj1-f54.google.com [209.85.216.54]) (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 837BC2741B5 for ; Mon, 29 Dec 2025 03:18:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978287; cv=none; b=EgTLLyd7Xo4cjo9T+lyYyYmmAZaPfFaYGoOIKXtL78PlQldqTBnUuTFFlI9Y3bg6wCi4uGrITA0bqol0CNgHfSFqyfOW3F6JkBQRH/h2F8ownj2i2cfbZ7dmUoOfR6AVAxepWV7iRbheCxVMo1t4nqbsXLwMgK4l4EGywrjSgVw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978287; c=relaxed/simple; bh=tNZs0S8Zijac4ZW6CpaZWlugpcI/4pA16X1La22rX5U=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Lp7QgW1FyiP27FC5bMBSW3c0wCIIpVkVTk+2JQsPtCM9JJKE/jwMz8JUE8/43BXFtrQv8ovtBkaT3qria0yuQmb4eUZt3xJhQ2T7R98HmfzRdyM4qmrQDZZqqO52H/oJk2Dporqq5lwVtPGzrVEMLLe3Kcw7t7+0uk5v2SE1xa8= 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=eMGVAVsE; arc=none smtp.client-ip=209.85.216.54 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="eMGVAVsE" Received: by mail-pj1-f54.google.com with SMTP id 98e67ed59e1d1-34e730f5fefso9561228a91.0 for ; Sun, 28 Dec 2025 19:18:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978284; x=1767583084; 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=9IQfKNQR1YN2qCXVPY7PVCVQVuK91xQgxlzV8ELl4us=; b=eMGVAVsEGObGZ8UU0uw6QPSSMuWVY17g9H0eVAqWuZ8C5bGRe8oxJ/G/WCeHDHnR07 29xvyFt1IN7hDEKr64p6K3DpWCif8KiuJAl7esZs/Z/btYuNkxTmkPE7SahasU+T+rhE PtYSBUx7nMqj198Szcne5aOW65psju62o8nvdNOPvUoaAvT6FmJqRQletK6IqQff7AQV SeyJzD26Wx9vmjcij6wMWFbQT0WnXb95EyEN//zwmajcPZ67tQNWOpyflirRswjh/nmW rAQEPhEBagJ3750qmHiJZFdncEccxRK5hegqs3MK/kvpT3Y0kkz/Reqt8mNrYPXdB3QO EQdQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978284; x=1767583084; 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=9IQfKNQR1YN2qCXVPY7PVCVQVuK91xQgxlzV8ELl4us=; b=iMjbuBv2cXo2dzqbr69DsTrAoWInIg5aYa57HxuYL1ahDv4Vpr8lf9JQZz3HDlTSPh 3cEgcBRraqX4VM4qzduHsk+LofIWsdPK/pQMd+vegbZH3lH3MnbGg/RKKd6GwaHgohIs ryhrubXbSx2NaDARQDFm5qMCId2BUjsukxIp1H/o2XG5rR2KckodhBykMJEQ9LnTg8c+ 9yurHG0cT6KY7yJzefKuTIF7Gsan+CuUFj77XJro5bzm2wts17+9VvRiAbzOaQo3G7ef W2MuhJeqtUbWXAJGJf0zkDYLi4/2a/CybIBaq+EsYTL5SIoCPJLwYOzKH9cY15IMB+gI 4L/Q== X-Forwarded-Encrypted: i=1; AJvYcCVPneqKSDmUEVY07XTVf/kUDjfU314Gqrt+peCTQd8vDkf6OzoU0Yf0eFtH35I/V5Tap6dnmydkS8wa4b8=@vger.kernel.org X-Gm-Message-State: AOJu0YwqR7og9VI8VgZ0g7tGiOEOBXAp+CFenPFrAhYQYPokVjVL/Ujd HK16kOyk4FDErNQNBLA116j3A0iFjD9v+VGz3HKFSJ0XWWcRhAYofxHx X-Gm-Gg: AY/fxX5LVlc3wi2ctyrqGhzGdCwTjTQGDT+8TIEmziH66LmQl0cMnTP4+y49Mti2VDi MjxbSUqeJuLvYM8Iq3n+kwg6q8KH8VI0GRzDdn9wxKjmFZi9v/fVU/5gXyyAIRNdzLxLo5egXir Mbxcub64kDINH/QxE7uqi9GoYiXzV6wFcvCuxWEDhMwzEQG/0TECZbgyLrnYTNSOh4cIAI1jwZs O6eRDUpghAnf3Kcoa4pC3h5Fc5bmPmPLtLpM1XfUayhC2uuD47ro6MfOUMpPshELl26mZVmbJaY WrIqUDzpMZDRSVs9nmqhKgwW0pDEGM89ZmfoG7HNmti3lIQzpg0ykcB/XjxxBFdC1SRvbjpVus6 64X7W39HxHZFMLq5upLAA8aq9cCO6tH9t0AuqwxFxBpRt/zFmit/cGv+kZb2njGf7l+CwXSe5fC LGUL01wXnMkEN7UmqmVSvDyIX1WUQ6XuleN50UwWL2tHRM9BLbWtr/6mIi75GrX9w= X-Google-Smtp-Source: AGHT+IHQnnT7tExR1ci3zRSoO4izwrF2dBzRsUyZ1Y15a0DYrgefSPPfviRg9+LlIOHVKTE/hNjuHA== X-Received: by 2002:a05:7022:4583:b0:119:e56b:c762 with SMTP id a92af1059eb24-121722f837fmr26769856c88.39.1766978283505; Sun, 28 Dec 2025 19:18:03 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:03 -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 v2 07/16] HID: hid-lenovo-go: Add Calibration Settings Date: Mon, 29 Dec 2025 03:17:44 +0000 Message-ID: <20251229031753.581664-8-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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 c8761297f57b..470878b51e97 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_enable_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_enable_right.attr, --=20 2.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pg1-f174.google.com (mail-pg1-f174.google.com [209.85.215.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 50C55275864 for ; Mon, 29 Dec 2025 03:18:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978288; cv=none; b=o1zzlm/Njr3eyOXYqZA0GkdorowiP+k5FU4aMHFIeX0NG2y63eVFIrweF9cXIyjFTVqx157SU9FoZ142YpHPJGOvDO4HnzcC1hxN/jrZIZ1HstXP1DuCzjaJF1pUmlT2xzjUi+fgDKkDRyAIZzn2lHkzlACLMwllvQO6CNRzcyM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978288; c=relaxed/simple; bh=H1DevgY7ldlq+LgJ4KTfx5nobMC0edHeeHRaeArfCzA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dmAZZI94WnAWDpYY+H2g31fGuTUMNSX9TT86Nzg4FSnlNFJ3LcmZ4cV0ucYeyD9PGGJpjx4gaXudQ+PK6q5+AosHYi86wZ1cr32AGgBtC3FxISJF4u62beMFPA/yw9cDWeIAg1hvN1f0czlZq9M2pyyVQkFw6vpZ2M2pEM2JRcI= 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=WRfOVsAh; arc=none smtp.client-ip=209.85.215.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="WRfOVsAh" Received: by mail-pg1-f174.google.com with SMTP id 41be03b00d2f7-c13771b2cf9so7305364a12.1 for ; Sun, 28 Dec 2025 19:18:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978284; x=1767583084; 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=8ApPxIyni3cWMMjHk1TPgt3W0M/pYt17LWc/dDcwFVI=; b=WRfOVsAhOadQVKLgdJcbPMnt/KYajV14OEpypXF2KLPIdA/N+M4dmRdGNUww34OJLL vTsDCpAEUMhowJLCREEfk55cNqdfktW9n3iE/efMxhymSdSzHRFv0hYXmwKpmr2aZuYV s7HwhEVaHmT+9Npu8MApCvUs5Tm6pKfgfx9U0rwSEPiZaa6dN04pavEClOiXd2Izrd5l XRE4Ut+Emm8I4mrNFbbAppFb8YpBnhhNtwo/z9ykRwxRbOpQhwSgw1EB6XQDHqG3m4ag iTL9Dz5/RuxXw4z7KIm/1xvSYa+4Z2x/TTEK4QOIpwyCqRqZdZwVNx5taW1mHL73ibvU 06sA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978284; x=1767583084; 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=8ApPxIyni3cWMMjHk1TPgt3W0M/pYt17LWc/dDcwFVI=; b=E4REhCPi7UMvRajMFQ4JXju7Ml5k6xqWWjkPoUBQZeGsJWnzsi0T4WLAJsG4L2ptUY EOnElzN/a73XHOy0McZHXt9Zzz2yDFTXlgL2twq0gz27aAw22UA0vunpJk2dmSo+ekp/ 89j0MbO4wQJmdxVbCRwYnUxal94Ti3IbRuAC5Z7SKkCLHzsvc29NGx1lx/dO35oiPdKu P7QqCVYMo3PiZ49zHKsv5FgRUFJ4vOFHREAetYVnusdB86nks20EsHvlZxgxap2zY71v SYhtfpI5KiZKE42pM0zY9TuvgPbAIlzZ6sq+vWR/V8xzFxYAtfto4ZIy0nddrZFmGTMJ O8WQ== X-Forwarded-Encrypted: i=1; AJvYcCVVT0D7z+htAYfEeHs9gjtiDDQXt8w+eAegWoq/v/iU9y2FvSQ4qV23vs5EJEIRNiCO56fzk+AEvasHhoc=@vger.kernel.org X-Gm-Message-State: AOJu0YzeWq1KYCPE81gezIVQvCd5pV20YHjKFHAR58NAscysOAApQQoJ +fw+kxOcq/af5d0FWjbs3vVtYHLXoT+bx2SOD3Gp4JQDt8ADquj1NMgd X-Gm-Gg: AY/fxX4ybNLHSbX8rV8bflN7kb366ieY5ysmMrDOa8x4gmX49WftWuyXcPmw28nuQ1E y8jyM1sOPzGf7IlHCGoWjgmE7zr9LSHVyfz4vPpreLjkflD4KNM1XQKColUTCyoLddQOeqodzLX raWdelVDmZaFHeCT6kza310THeB4UHSpnrsJ2IcMX/KzaZxL1O9DXTC3pl4DA5LsPZI/8vog+rv 6CL4IeC7L20wnZsVi/Qj1gvzfUakTMEd8BzU3lUHTnA3jXYamG9k6qD/sba1XUO/HgpSPFa8KyN HPUWZ6+YxNRMHWG+LGYIBo7Y2b7O50mR2zg3UyHFQiAS5nk1HyBEl1HYVz/1I5/07BmD0vetl7N dS6GQkUYbR4kROb02cHsofCzP2ibxjz7DLeStHqDuL8RSukc3pPIGPmJ0DdQOKtUNAEAmHEk7Dc eRfeN9XtcJ6SA2DO1pOa91D6XuKnSnc28h8qq9I/SB0LPyMrPth7j9K/HBEnB5hBk= X-Google-Smtp-Source: AGHT+IGkT8wJ9FLo7iQAbw/26b3x0f0Nf3C0m7EhEbIkxwCRLJwYybdVJicektb4lrjeCjQzYVpxeQ== X-Received: by 2002:a05:7022:4287:b0:11b:9386:8268 with SMTP id a92af1059eb24-12172302173mr27788731c88.45.1766978284316; Sun, 28 Dec 2025 19:18:04 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:04 -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 v2 08/16] HID: hid-lenovo-go: Add OS Mode Toggle Date: Mon, 29 Dec 2025 03:17:45 +0000 Message-ID: <20251229031753.581664-9-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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 470878b51e97..f3623871edbf 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_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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1E787279DAB for ; Mon, 29 Dec 2025 03:18:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978290; cv=none; b=Y4ewwuP+NOz1DqdEGBzbQfh1jhyG0iFmJAWQ53up+5CkFgXOVvAeLdBGaOfELxMjdkE7HHX+09QIotiBek61uHqNTROAr/MzquSM4UtKO14g5YsVX6q770Kyxttl3r/vco+R1WaeJ/YsZL92SYF2bNgn9ykMfSLSqkVbgUgaPWY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978290; c=relaxed/simple; bh=9+pJd3NBP++1JTk1sqOowLELYEPW5+pC2QloAQLjOjs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SZQHX+3ICJQMkvefsJsbcCzcs1Ha2pzUQL/kHrVk9IpXUtRu/lNmjlrmV1D3+ovVVcpr3KYoxXDHAtbIRpGVPau1DRmAi41Y5k3fn/F7D66dbD4SCuKz56g1oKSgJx38jFr79uG4wLpr9MjiSGWghGZvmze8IdQPT/QetgaZ9I8= 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=iC0hM66p; arc=none smtp.client-ip=209.85.216.53 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="iC0hM66p" Received: by mail-pj1-f53.google.com with SMTP id 98e67ed59e1d1-34b75fba315so9663734a91.3 for ; Sun, 28 Dec 2025 19:18:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978285; x=1767583085; 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=b5fLauwF03o87S7cIGBwSfbxsvXnHNtEz/FCDWnd8ng=; b=iC0hM66pQzv/0cNJ5dAl8AcF8iVP/J7Sf0QmZxmmg0mK4EiqEWkVpziuEBKWKxotmH 69wnxE6KX+bwc1vAJZ4gSboRmGQE7n7pq1XnB7ACy6ehOAd/rfdipBu1dp1JpmumQ52l pteyhl6eUVBfC4XYFOrpc05p654ffF7EXj6tSf8WDs6unIsDi3TJN0czoVEnmY+naV04 YGnt66g9tzCaGISXoDvVMNGa3Wp/TLiEZosXhN2tcFntuXi/imHkK0X3rqFtWYYyXoyc Xzv6dSp4NG091bVz9CoMc1jQWjO0ouHuaHKb5hNjgPEbbhGVv+BUPkikSkqaThXbko6E uT5g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978285; x=1767583085; 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=b5fLauwF03o87S7cIGBwSfbxsvXnHNtEz/FCDWnd8ng=; b=bdKRt1uj6e7V5dk3xsutKLM/FdpNPRrLsNGMVE/L2LJxu1l/z3fNgRN8bwJJX6aPRP p8/ZKcpyAVDs68iPrPLyhQY5Fajod6/TcB3SaGqsDejejPOVxrGPg3YzCh64xD4oZeAl oUfoVS8mxku/+L5HSXbCjiDqRcc0GOrUk3EaKv9tHZmimv/XPJl3+MbWsfCdRp0jL50G J85IVTfMbCvqvIAVH0LlGdLTcFCPJ4TuC9NikTLTFdEdCGJUrsrdLV2utvAfaST6TL7i XmbzCHz2TuKbX9Ab5TBUzGpG1lkBOUR6ve60PrK8rpBKC340y+extUxw5DdWB88wO34M VUEA== X-Forwarded-Encrypted: i=1; AJvYcCUot7qfOxbVnB+vcgw6Al/ntFA4lkForTXmmE0iJXucXY8ol/eRdW6gMX/F95fTihIg6U4Wqr7DAX44FRA=@vger.kernel.org X-Gm-Message-State: AOJu0YxdFGgXnjZ1Vpx4rvZBgLs/OGdmc6pLBpZM3cU+QTqGuYrTaTg4 97FrJeRX1SahzcqbRlCwLnJF/V5wezbq4f5rKanILWS8AYOhOcF7km9I X-Gm-Gg: AY/fxX78ke6s1D8Rxc24Teq+AkPiL7bzpHIhl7lPqR9q8h2yKPWLhlPZDzNx7Bs478k qokQGIESI8EbbH+j1/a+XQ4+CqY9Akk87rfAydAsQMP0rcK4qUIrqgJJmGTxahdSc3oX8ki/0q2 cbNoyQM6YL8/xD7HPa5fJ6l6t2EiqcEGKVq/EPO+/UF/ukebQrskg6X0/Mkci39OeNZgKUMVmO3 ZwrLP/EKv/IXerl7kzC5OFuhwp+kzx+SONFC7CASOM/2aar2r6Rm0Jwr/NohMzBxobdNlVAIj9f CMKxJPiFHIEkf/OJYzOXgXRMv8qe6731kwFSRft2IZSaC3R9crHEmmP9EjjDd/H6wc4Ai3bq/Ml e0kCiwxGkBM1VAhHhwUwnrJSJQFWq4rRHX1lVMBAddls+Ute61IjZXtkCqzmTh10RF5TJcytp8X wnWD9I7yaDi+zTu/kniAXuYwx1sKDQDIJw6Ve+m5pabbUx1VaWW2K2LYf4CB1ojkg= X-Google-Smtp-Source: AGHT+IHxlvbsjIIiGCdaZR2kNkfJhhczYREzl56thhmxe0C7ocWaa7i+0Rinex+uCUd+z7fHP0XHDg== X-Received: by 2002:a05:7022:7e8e:b0:11b:bf3f:5208 with SMTP id a92af1059eb24-121722ac21bmr29615714c88.1.1766978285028; Sun, 28 Dec 2025 19:18:05 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:04 -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 v2 09/16] HID: Include firmware version in the uevent Date: Mon, 29 Dec 2025 03:17:46 +0000 Message-ID: <20251229031753.581664-10-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pg1-f174.google.com (mail-pg1-f174.google.com [209.85.215.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 E29DF274671 for ; Mon, 29 Dec 2025 03:18:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978289; cv=none; b=SwmHrJIAbyabUGFo3m7ErEqf4esZVdOkROifnHVt6puXYoqDonUjIGce4HO3Peo0/nn53gvbv/aAZVH9b0ed1DN8OhnN1UMWHTaAtUz0u29UeCyc0hqgpAvaOYzpiXtswgdkuTUPHS05+GrhsGDdGnbMV9Z8RxhXkk6t7guqx4g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978289; c=relaxed/simple; bh=CyPGy/3mvq/hebcHmXe+Y5WXBqt8Pn7GqB3rJzc5ekA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kdL5FbUdD4JvYF394fzEUBdZfd9BV8FxxtMrWariW6JFSFkNxkeYdGY9jYQFoidaP2SsMMyCGEydpXtbRkSPT3nITMOAcVwFfrlo1PmGzMx5T8/DcCV+hYtAJSFDZqu8jVDmCxG0lVroWzOINP/FWN3ZtEq4vprSgpxs+rqQRAA= 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=k8HREMtC; arc=none smtp.client-ip=209.85.215.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="k8HREMtC" Received: by mail-pg1-f174.google.com with SMTP id 41be03b00d2f7-b4755f37c3eso5723568a12.3 for ; Sun, 28 Dec 2025 19:18:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978286; x=1767583086; 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=jp3GPajsk+7B+eOxl1G0u93YrGBbnZb9DccHb6UPn/o=; b=k8HREMtC4VlqSjELfnHEokobEdciOuiuzsdYfVLCJz9RgJ5eEhvvhPE14PmCSPdxyQ bvuWQY+PFZNACn7p8CIsjc3ZRHlaiCOJdFFVhxd/fPaWE1MdSfNheVH3rIvtgo7hTmjq 1V4Q/YAlEY9C/RnnYnrLWg0MdUbaJl/7JR60ez7dUiQbwv7XDNNkHhl0jkg3w+xbLKkY FPAk2TM8z1EB5Wgu2YW5mrm8jeL5j/M51MsYphyudvKuWAXyK2Vf6hq4iholOpbNXYN6 GBy9yNYSWNWaaKLVN+eHGhG7iC80TiYVVVnLMdYt2yvpprFi9R/C8KqHBgo+6UKvAodZ gyWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978286; x=1767583086; 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=jp3GPajsk+7B+eOxl1G0u93YrGBbnZb9DccHb6UPn/o=; b=TDyZFNauZdS/Qy/oDKPHV+puNteS6kcPShUyhBAEOXjLWvoPLh/NHG86vEDYdUpjcH GXJ14dtPmgFhBbeM8kx6O5k10Rp2r+b4j8e5+qEOQ28gW1E4zBEntcUYFVs7GMR8by8t cR57rsTWw1oGPCt/iTGHTLscd2CciB1envJsn9N7Ao25JP01HT7ijtX+SLxFuvC/Te3p s5jsGorkryP0JPriqOrmZsfKgVI8e/G1LRKuSC+baaA1QyBeknUxHJLV9yM18AvN0qkQ O/StaoUacCb5MNiyAlvjfTZ/ZOX/xvOpMOAsNNCMga1Rw7aFW7amjzH93Nwc4gBY30EU GGcA== X-Forwarded-Encrypted: i=1; AJvYcCX1m9xQ8MpyOcLduA/sBbQ4XVbWpAggDps7mKVLcsEZOAP+wud5GDDczYU/1CZIzDTxfO9v80PZzqJ00oc=@vger.kernel.org X-Gm-Message-State: AOJu0YxhrITgSTWGxiZII+SBpTCAPJDVrGZtHi/5div/Pru+K+ElJNS1 pL8WhQqymY5GL3j3+PyAD8Hb9W752GVvDs5kwxx3pcm/ekWnl04zBesY X-Gm-Gg: AY/fxX4fkAmcwhjZ4L+2m1HbR7yueHoc776U0bo0xfHBbz4KATroN5q3v6IHYT2zsyJ Q9S8Usrwky0/vHy5HFoRNMNjwBBtItiZ7Hn+nxPWiAtaDtD6g2bnQ15zDzx31S9uhMyurXMrKf4 h58PvzqYyfhp29pRLW/f5+dQTJTVX8FH9ilYfS0GkHBvR1hE6fOwx+DmuJmpiH0ly/gOCpJS6ms LwIekaj4TV7YWrxYp96lqUPwNyFLR8P/ZZCZy6dIxJR2J5kBdg06kmQKUPCC7go7qDji40ZYbCc b6rg33XWR4TgEtcsd2hwFMXLX1Dzg3CYGfbj29ReQSg9sGl/OxxXbR2IQ7GvFKYT/LNUxXzuTSo ifzXQrmWEcCPtMdpwQWZIpEqKt3+AZHYpTxEUicEeIKz9X7RjWvUa1XzPBUlnlfykNruW77Ks5J fTUjaBgaPoqZfFvt7rBBjnVRw53w1Lkdi8Vz1SfnsuqNzuz9p9y6cMOOzedzr4iJ0= X-Google-Smtp-Source: AGHT+IFmVYvJCI9uzal+zlMjMpGlkXHsXeM2CNpL6gbTnHRrwMqhaHFwJoeBWAyLu43SPth6kg4/Cw== X-Received: by 2002:a05:7301:4885:b0:2ae:582b:db80 with SMTP id 5a478bee46e88-2b05eb7d4cfmr22118922eec.9.1766978285711; Sun, 28 Dec 2025 19:18:05 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:05 -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 v2 10/16] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver Date: Mon, 29 Dec 2025 03:17:47 +0000 Message-ID: <20251229031753.581664-11-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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: Derek J. Clark --- 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 b5ad29d24e3e..be4a0fcf23dd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14139,6 +14139,7 @@ LENOVO HID drivers M: Derek J. Clark L: linux-input@vger.kernel.org S: Maintained +F: drivers/hid/hid-lenovo-go-s.c F: drivers/hid/hid-lenovo-go.c =20 LETSKETCH HID TABLET DRIVER 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 986C82494FE for ; Mon, 29 Dec 2025 03:18:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978291; cv=none; b=KHMyJzWzZUfbSnnUi9h/z8zNaWtrzE9RYYodj03JSxkfq3R9E/Zc8DTrMR78Y8JZDa3eW4S42oJ7QeCQRcQoJMOwhqmMcuPC1+DgY38ESf/4NICdqV03d/PQrFNz4suPkyVMNHiATUOz3X3nGZ5YMfzanHybuQzVKdYEqFiUwWU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978291; c=relaxed/simple; bh=X263MocZ1Dz5lSyiIa/ApKNLtn8LzJpAs2PJ0aYdw1E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gRqPPlynqur1yDig++5t34bIGECqVUvAHQcGgnmGZJ3d3523O1vkyaJsFxr638T29OA8iFR7ulFcOl2pnjBDzV84LklcgQd7pp6R71xIzUHFQwGdXMjAcdWdU1WJzftIedz66AL3WEYl/o+Z4et11BCGYE/oYulTR52yFoOKt3M= 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=iacaLUk3; arc=none smtp.client-ip=209.85.214.182 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="iacaLUk3" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2a0d6f647e2so141190915ad.1 for ; Sun, 28 Dec 2025 19:18:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978286; x=1767583086; 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=MrGC9JWDCaxJgv6rvmu39tdxxA/sQpLqKQTMa9mrb4s=; b=iacaLUk3nMfNHEybQMMsLwlCv2tL/ICNvpWzHsIAAFohD/w1Pm7wt42SFubR/SWenb u5JgG2ss622uIMUyDCe+couOewqlI2xXKrZ73j8n4xehVS6Xzr+RaD1l/aO+AmGEkP/+ w+yGCSKsXXX+9lmp2z48lIqVZiMHCx7RmxONjl7Q5RAFLD3kY3G98V8vO9QPGYA4Gnwt WmAx3T+9xL4Ljt597TJpwuQRbhtBcPan5S7oE1aboGM+g/Wy+soE8d3FA7c19hjlJiHy lA47rnjBGieXC2zNbHr+kpGtfdtMcp1aKRihOQWbu9bJt8QJ2BTIaQtQ2jRqwO9KUx7j z60Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978286; x=1767583086; 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=MrGC9JWDCaxJgv6rvmu39tdxxA/sQpLqKQTMa9mrb4s=; b=Ta8+bpAHUuHJMaJS0ACNRTJM0fLo5P9v0Ko/0GaPdeag5+3UjjlcJHgiyoazUPv7N8 K9gpVmcfPySkIETioT8d4U55o7TXlt/a//qBqnEkwoSfpDHAWwISYyp2ScL5DRnoNOj4 m4boCsMS1yMFuvkwvtypY5Mc8dzvj1Kyk8VRnX5gqRr4BB2XrO323ICQJMu8gPf5x7X1 cGyapFxW3yvsGL8tpDoz7axjDdrQaKp/5cfrjfrB9ADS5OqLLBTXP8vcyFiebKOVc22z qhq6zezrkzOHMQESiHcAtjF3kKmI5CRWzw4ptCIFRwde4Z0dLBaX6pcXUYVqlpoaX6xG 1+qw== X-Forwarded-Encrypted: i=1; AJvYcCUEQJPG37RlAGgXAbNbVPiD2XxnEalSRJn4KByx901Wmh3cyqRUeIkt6VUk66gVcq4s6ImTm++yV2r9g3I=@vger.kernel.org X-Gm-Message-State: AOJu0Yy6CZuTKVdUeCeaT5Zl31rR4dFK2h8VH5l/GZ113eRDWnxioo0Q Wc3UFkG35+JWcx4zRU++hZc6FJ7322z7+vhs89C2Jop7ulOSiusmFJku X-Gm-Gg: AY/fxX68w3HO3JJm+BoF8aIgAk3qfp4Il5RtRSJF9kblNJ4rPb/wZjC4frlWvyzUSKn Vk+OT3KUTPtT/Qmj91rvtzT9GQtRpqo/QEG1L7eZNkjiOUxjZsTOeE4sjmLeBf5AJHa0ZcJxZBS Di7Nk+x+052TzAuK5d7HgnbtN4Upg+oJj3pPJQ3U1gNQ0nI/SAmNZZdZL0dZqjcRnxPTT3sKn3V sJoDoAvVHgtLDcDjTsxHras67yobH4IgZ3hvDZiaiLsEe02DGR71G+uGe/AgM2yL+TkvNIzMBBb O76Tbrh5gxS/ZpYZF2OOUPG5CPm4fSD3H/MRp9QTmbvovbHP2k/zoT6bEjEdhh7LIzfkbjGOw/W l6H7gEjqI9ieBXUC0hxLITNl5gTt81qnyxb/Tk73kuOsg/vfXmOKkC9D+XQO95ujpSVKrn/wVG/ ZhfPsX9w+KS3Ge9waL3JiCkXsgZRs9scDbNky8w+BQtLDEoZEoVLJjs0uA22V0ua1ZQ/Z75g35m A== X-Google-Smtp-Source: AGHT+IEM02CMJY2VQsw38v3sA96MHZUfuoCRegT+hQCz1o5/LKIzBteu28U364+Nmkl6le9V8oo7Jg== X-Received: by 2002:a05:7022:150d:b0:11b:9386:7ecd with SMTP id a92af1059eb24-121722fa7c2mr27864612c88.42.1766978286401; Sun, 28 Dec 2025 19:18:06 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:06 -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 v2 11/16] HID: hid-lenovo-go-s: Add MCU ID Attribute Date: Mon, 29 Dec 2025 03:17:48 +0000 Message-ID: <20251229031753.581664-12-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pj1-f49.google.com (mail-pj1-f49.google.com [209.85.216.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C45C272801 for ; Mon, 29 Dec 2025 03:18:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978292; cv=none; b=cKABKQucfT6OQZ3m3YRDKybdPF+oYOwVQvTDmy/hs9VxQC0U0enfUisXOxTnkyQve9a3h1qx/U/8T/4s9RHd49XaH88cSYqJoYSaQioJZ04zoHcXXFZTsbPHjGrj2MGvKC/VnZKDnjq579u1Lz0ibr2IkxfUZULyZho9A4dLYLY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978292; c=relaxed/simple; bh=TjcLc8b1McUidwujjfQYTR+PxKrP04JoWpS4YYDWZFQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mt+Ml9vKyNguwQti7+vRBhoXilF+BValNf9n/QW0D7tsHbRDbLqwDZgNYieTuyH3g+IRWBD1xD9z5gxELrXF5O2/AbtEPpLrWca9S1X+qpc9lL4gVVIWVJD4adBZAfhsTkS7P6KX8CN7k0ywLkI/m5ZaEJBKByuvropD1lMwZ7k= 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=HkP6IAn6; arc=none smtp.client-ip=209.85.216.49 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="HkP6IAn6" Received: by mail-pj1-f49.google.com with SMTP id 98e67ed59e1d1-34c84dc332cso7421495a91.0 for ; Sun, 28 Dec 2025 19:18:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978287; x=1767583087; 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=/SSUxFyBlljVoOkyziM9cW6Mg4NT0OL48ryA8PfVXFE=; b=HkP6IAn6hb8KTMz8wco1lzO4nCbWQg9wRNByiD/OCLaFO5c/Q6RHXszvMAdIyD5BPN 1c4GtSurswFEubrW7rFD6QGn7IG9h40VdZPJnSHcOjpaXTkMfiEQUElsko2bvjQWLOBJ U6w5u82xLrOc/JPCBtltuIIrXDZkEWBsOcGa9pEzEIcupjCw/b1cKEl7w55YWXyB4blA /FV/BUYE0EBHyTFJWsrOCImSNdD3lexnVYc1p3Nez0TSVAtnBqGPDu7ZvtAh6N7wgrNc benGBB/fPb5SM//viPfZhLo5+FceDh/5d/P0ijQQiNyrexnr1HbC8oeAuJ8/KmuD5MCV 5D5g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978287; x=1767583087; 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=/SSUxFyBlljVoOkyziM9cW6Mg4NT0OL48ryA8PfVXFE=; b=h6RihERKa1l926Na2oVx8pYcn+FuR92iJ/NiXplST+AkqzOxZTp/bOu3Cycgshjh+F 0ycKdHlgcD5dFP74cKQBawJoLTrQuHzL71caafNJoqPALRcrotmd12bFaoheGPrPU9St BNKBcZNz33Az6v30qdj2gLc/vxX9KOHlQbvVQqbqQvWox5jPUl5XJr+9SsSc6gQxfxOc Bd0R1Wy3CSq+iDlun/pGGGRl/MSsq/9h/F6+OXDeyJpMgUpg127yGKuT2q239tK8Ekgx 3Cfj+jl+5SILz2t32BmXzy4rLA9KSyVQIGbKWPFwAG64Ydlkf6n2i9beOaGaD6ZAHVoB VvFg== X-Forwarded-Encrypted: i=1; AJvYcCUjInpuo/r+II1uhroGmJ9nySdRCKy0t8Yed6hLeo2FATSFOz4V7WRQksLd8N7X+L+aUC36m6Qs1/BUdcA=@vger.kernel.org X-Gm-Message-State: AOJu0YwNwUdEY6AzMzb8XObqLDI17oEc4oPa/gbM3JiWWlkcSbbe91yL ATzG9S+DkEF+jsK7nZGKf2h24tm1FE4JzxzwOC35DrSGowMooiNckWGT X-Gm-Gg: AY/fxX4dCPgbkJ7kyfc4vUJ2W525oY3LypkwvyPU0f8G3IvD8kajfMzpMiut0ZCV2YM 0NdeGss6bDgQ0QzC6w+cSIPcIAMbNbq47Uy1kYh2vlUB6c2DlB9xayB+hGMSuLGe1TaKmbhVCz+ fubvezg+9wnqBnan2Uxe/L8dpmav0LlovkwU3HfX6I3TGRPc2pJLwq+Na5MYXdCUGZvxKmuQTOX H+032Ximg2//TozeGYQGiVdVLSYHEEuM5y3p8MBgmfqNUsLm46g/NZroIaY2ql7l6hLVugEBHwv t3DC2SlTYY/IeMBXeR5BYZDjwLD5NaT79RXdIA3YpBDuv2btwpWr+s9EIOPn47eGjsXbGQoPWnV QLH44MSGqwI4mMq89FjJZjDSgis/LAIMsTJeOMTr/kdyIzonHQfyqf3zIW+D6UDGvmvslt8cNCr acIzxLl7h7yUaQWvMfyEsD/e91nr47P1Uv/7pQtCUfmaeDg6pmu6HmB8LfctpK6Io= X-Google-Smtp-Source: AGHT+IFy6JlhPUmYZyhP9/6iKZQqnm4h0/gpD7xRl7MJ8NcHJHy2QxB6lQ6mbz7HEpi07optHW5p6Q== X-Received: by 2002:a05:7022:eac1:b0:11b:9b9f:426b with SMTP id a92af1059eb24-121722c3d35mr27502722c88.20.1766978287185; Sun, 28 Dec 2025 19:18:07 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:06 -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 v2 12/16] HID: hid-lenovo-go-s: Add Feature Status Attributes Date: Mon, 29 Dec 2025 03:17:49 +0000 Message-ID: <20251229031753.581664-13-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pg1-f176.google.com (mail-pg1-f176.google.com [209.85.215.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 4484F2741C9 for ; Mon, 29 Dec 2025 03:18:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978292; cv=none; b=jyEmJhE+v3oTGswpL59muSPlsG0B2sUKv1joMWiqORf4G/vw8xSiPA0SyHPthWEN+jOrokLCWaqwggUCWeDGjgdATwmLGACkvpA09aEotjCYwWumWMrHGqPjdHyxt40KSzPjQiRlW2bAvtmKU3PIWXEiTXwYNd1VaZB0vkKqzdI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978292; c=relaxed/simple; bh=oqKm237OssMghJKlQPmco5wSMzp57040uiDrPySMFAA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lhquY71cFOCArxpmRc057BhnaUInhWGCmoPsCOq8c6sW1idfqc+UIISvLF/uVZEQU7tUlN9hP0OqXhMV1oyxWM27MedmyC1fvYmdvB8fgsAD1A6qhVxwEyqRWe18ISDR6g3FZG2XC1IW4fRmExhpI7l2IOentHeakoMR4AbrLF0= 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=RHQ0GfPF; arc=none smtp.client-ip=209.85.215.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="RHQ0GfPF" Received: by mail-pg1-f176.google.com with SMTP id 41be03b00d2f7-bde0f62464cso8579506a12.2 for ; Sun, 28 Dec 2025 19:18:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978288; x=1767583088; 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=miGogj6YnndhpvXP0429HX4qoehDUgcQ4H71U57ueIM=; b=RHQ0GfPFUX7gQgJ/0yz2r6szOKR9Vv9QZo3LxkDoi3Nh6UGNSfolffzF1mnuwLqc7e UpDzCUydWbijSktaICAobi82oPH56MTJOdmWM7drMxb65y8JYWuE6e7zjhHOeuUfZL/p 4CphOaqh/NSGTG9cjuViFhEEMPJjZt7xurywhfTcfYG9/goVhPuejDjVAD1Dfh1FCQwP as3tt050GRPIWX8m0VOpz2f7RBPoyRsATw9MA72eLWdUeJ96IbGSpWHNhPOkXU8IXrWZ pQl3GDnfe74ANpvs2+3gZMti/nreVgo3qFcBsNSjJp0iSLCSCWkyixZ/Vc+cwUZ7/0Jj K+PA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978288; x=1767583088; 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=miGogj6YnndhpvXP0429HX4qoehDUgcQ4H71U57ueIM=; b=gchJuEUJArXGPdOicg8guiFZlWeBYmpQW9JuIuTdP68u0hC1G/IPvx6sDaaUgIkfub fb+dvyGy+LSMX4BwYy0voo+IOY4Q3Kyd7bqyIh+JEEHCYb40ho1y9zoh9erQIPxhJVwq RuAsJc5+4GkH0pYQv1L4jNkLnyXys6j6IinsSIdEB0+k+E2HB+8DGy3aO6Pxa2JEWcOc bmAkZsP2azvhHNqpudRhY6/bZCC2OJopvJ6v28xYztJPjgiPYWBa84yJPobBYf36nHrO PE6Dq0/qjiyLGVSnEwgHn6ti5KS7UXJA7bS8ZfvNcFXPvyDipU2SmMZe4NENRS0CSZ1t WnCw== X-Forwarded-Encrypted: i=1; AJvYcCUzxB0aeyyctAoBoGDwj1flq+FHIWhNqn8XqIA/fRYQcy2wc3wVm5YxLNNN8i8zedlvmL+TO/VYsLWVsr8=@vger.kernel.org X-Gm-Message-State: AOJu0YwyBZCMTr4YBYBoJda3U3ooHUWJYll+vNSZPwzX8tk89jm35ESc ekg+cYNIms/itnTBlj811SRImyGdaKxgAQ+/pPYRPIZUWt4Fzr5YGy5W X-Gm-Gg: AY/fxX4TTOqk9cBjiCRYVxBhR4TbYIUEjx0xjmezMTM7dwMWq9QWfacKlNIUe+aCOPi +7Hbr7TKstWXhJvyeUjCyDJaFHLq/wLByRnXj9sLwxruCquLFQmQcnUxArbFAzKSHpXeik4ei5H oNlpfbzhoxmT0Eh76VGknw4sOA25WKNgwhZ77ceQhN7Dpq3zignxF8sjYb2o/v8CI/lVp8ieCBW vkfNckiOkupJPLImLsGssSaGGhAE9JaAI7N/0QFhR2jiAYM8wedwLnYmXMgDFjSIOux2qW4bIBa T+MgvZvo33w6ffi/SiYCtJszMrOjbbwujmHaDP55W8bF2NSgeiwVX9rFCpwGZfZ32jrNlKd8B66 wzGFqmQTlY2LHTDheXxb5iyznTHbTr/2p/oqb6FvJcnxysYKzKzT7r54W48//xcGU1KwCZmWhEv n4NzDD0ju7yE0ImhcBtp1AIFspQb/5LFMSKth+1LfGxLgYU/tvPW9YpStFbI15fzY= X-Google-Smtp-Source: AGHT+IHjwK9SUF/qOruDxO9wu7yjpn2zEeJHGppohIix6cKExalnAY9LGjRftJ0II/rrFdgWUa8T1w== X-Received: by 2002:a05:7022:2217:b0:11b:9386:a3c2 with SMTP id a92af1059eb24-121722ff9fbmr29439286c88.45.1766978287954; Sun, 28 Dec 2025 19:18:07 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:07 -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 v2 13/16] HID: hid-lenovo-go-s: Add Touchpad Mode Attributes Date: Mon, 29 Dec 2025 03:17:50 +0000 Message-ID: <20251229031753.581664-14-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pj1-f45.google.com (mail-pj1-f45.google.com [209.85.216.45]) (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 ABAF6284B36 for ; Mon, 29 Dec 2025 03:18:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978295; cv=none; b=Zgt1ye1vU2czC+eYglqwysF3VmgO8sZxSoAK+gPbQeNr/3xMewbaJQCAhmhP6kghjxibu5ODCu2V6F2IUA6jmRHGYEKqHwPsjmSw+4DXyWj0e8KvGjRE3RlJDYdGzAQyn+XDQJsIo8EM9tqSPjQtYKZ14xF6FLHZ/NBah3Ew72E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978295; c=relaxed/simple; bh=bsCXtH+xHvo/XHzwrBplARcv2ty3R2ko1aKo5Nj6GnQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=J0yh7khcgBWJDGqsj51eGBHtp8PQfNrlLfFsuVcJDwFjvxAuag5YoCi3ib/aq6I9yDqhJd0QJ1hSZtGthQuqTvp2Y3yTfGJXqbzsdZ/cl7zNstQTNh82jHR/j8f+4Au0pV8P9vYREs0kEKZKVPdYrUY3HbtM3a7iSUFdhR0Agzw= 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+zmUt3o; arc=none smtp.client-ip=209.85.216.45 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+zmUt3o" Received: by mail-pj1-f45.google.com with SMTP id 98e67ed59e1d1-34f0bc64a27so901652a91.1 for ; Sun, 28 Dec 2025 19:18:10 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978289; x=1767583089; 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=2Oe90aig2nxQqXeUKkflxOqj7hol26DzvMxlXeYSJeY=; b=Y+zmUt3oyf/LcWc1ybeGgCXUSSEBYm7xdqIc54WXeDgNxZ256NLxSMfqvOiweYujUf xRNuI4uoLt9KRVlT8Ds8Xt+ITtQS41fJtUU83lEj+dfPiRPDUwDHbBu0LY0q0OjHJVAH rOwNobzyB1P60l8Nuchg60hPYCW86OlQp0YTatgmaOlzSR+cvIkqLBV/MYzzpj87CPS2 iGOvn9ljQ1W1fsWphBVmLFSVAjmAMa89JqXcGLal9Y9ZZG8qpMB9DjM2Id6WGtd3Wn0X 0tDVj1EeMVvOfJjs0i/rMqkFAhTChrYqIrBshKowddZcxP/g4XUC1rjlZkIZiHV+0udU u62Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978289; x=1767583089; 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=2Oe90aig2nxQqXeUKkflxOqj7hol26DzvMxlXeYSJeY=; b=Q5yb9cKwzNuszi86qtjq/WsUH9VhKk471n+dq0Im667z6P+AxRIowndjgSLstw/US4 WYxpBGn4D/s6oLGZpHuhUOVSIaIif8GhJPy9ZiD5xLG8OpDVjHfyhAuKP71cggfmL4l0 1GL9ttdRJcQ2c+gOYexRTaO2B0mBSMtx6+RnlJKCcwz5dY3aE1GqdzTeBMyHfTw7yfun +gjdvJYdH5Ml9mZQad1bjkFrglpYt/zfk/rlN5dpixEgHco13q2NKZQaxOEZgJXHPvE0 B+4eEoWUuaOXRFLn9vXrOKdh17pjl9WPE8JJUeQF140HMft91NVLEV2uAQSnnx4BB4FX 9E6w== X-Forwarded-Encrypted: i=1; AJvYcCWmpR4JXOhGHmH19TNtDEV1YJ88x90WMlWD2MZNM8mOGIVWHjmPnEfg5IdaAiYp7Txm4oVklmkeyYwfKkc=@vger.kernel.org X-Gm-Message-State: AOJu0YziOEezIPw+M1TA8VYl+d+qMNnAz5Eeww+HfYZKt20g7oOZ6lWQ kxeB5luk9QmkCAZrXA6cCzSFqgwukUlaXe0qhIsMpnNyQJ4HnzydnWD3 X-Gm-Gg: AY/fxX5U3XbiT16hAgXpn6iPWHTZeTajpdxK9/poDRa8zfUPVHVk86CAKz1YH9bdGgd ojShplplTgoZwE7zpVD4ZG7k8YZh20q1P/+vbLdb/ZEzQmR/5Oub5xPSm41jmsfAQ71QK4xn8FJ g3H6Pits3riFGGOyPGruCW/BlHLrHa15MdKxKQWxXaa1Nmmn8EhF6widBjLv78B2eQ5JyHQ+0Sk RmdBRU//kMA24jCEId3QFYwqDW6zx9Yw5QXoSwZCOrbUI8CjRZQfr0ZfVbwF8gne/ivzRTwOM1o kA6eODS8BFCxybaf7mWsUPSAjzHNX4RiuuG9wI1v0uS0Dv/GhnW23I930kWkHBm64LPN+qwS9ci 1ovt5RUA9oZllhFIOS5Ae8MUK1nNT3WJSVnvkxvjPIYjZYMphUGCqqjAB8gR1tLeVK7ZRE3nXVl eHyi5+ZIT97xUdQnfBjceD5+07YIoyDwcYPSHCTARWjwmfxZOfFX9f/NsXYju62N8= X-Google-Smtp-Source: AGHT+IGhP1urqaDk639nK0chrd2g1l1WY0NJhnuZ3tgwveMseq4dvgZohdFGKKaEemNmKaxYib+yOA== X-Received: by 2002:a05:7022:68a8:b0:119:fac9:cdb1 with SMTP id a92af1059eb24-121722ae5f6mr30811507c88.20.1766978288743; Sun, 28 Dec 2025 19:18:08 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:08 -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 v2 14/16] HID: hid-lenovo-go-s: Add RGB LED control interface Date: Mon, 29 Dec 2025 03:17:51 +0000 Message-ID: <20251229031753.581664-15-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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.51.2 From nobody Sat Feb 7 22:55:10 2026 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.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 C98D128506C for ; Mon, 29 Dec 2025 03:18:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978295; cv=none; b=p1ogKSIzda4ZdVCCtpYqEp1DPJ0CjKybf3RGIxdfVDUe81pFVTv7TxlbsgXmhknNuQxSFCim+tt2jxxzBYVBQpAWt7JpMmyj3Ik6s7UYP+Km6ZAm78DeJY0Fk7SWh7hi4z383eQM7SgOoQkF3JCbPUAvA8pq09kLXX9AQaFoubc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978295; c=relaxed/simple; bh=IUvo+u2e1SrpUl39JLlNAQlGoj/E9x719j6K58inOTo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fK4wWIZ43rn4t7d66pL8ccAPwuQ5tDPKmliYQE0raCYrz6IAG0EwTmYpNBWw410KRbd1uj57L1Fq7cev7nVjEDbTuJ162dCuy2FPlTkj6Jxj6QTRXJiEBnf2DGEefLJeoURvxs51Y94jVQeyq+KyAAiRJsFTr40decwkz3mVFuc= 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=jRmfy+ML; arc=none smtp.client-ip=209.85.215.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="jRmfy+ML" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-c1cf2f0523eso5935360a12.3 for ; Sun, 28 Dec 2025 19:18:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978289; x=1767583089; 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=yn94LU96Z2B+v/2xA7vRHpP471K3U6dTWIu2Ddfkl2Q=; b=jRmfy+MLN+dCnqXGTT9MQmKIQjche36HVqYs8HpTf5edaF3CcFePNv/eDZ/7YgRSCS SJeVbQj9pmN7uXW5hQ7/FeoOQU9OxSugeshviGb2PDkXNavc7o4YYhcdMV5tUrYopsAU MxGOj6KgpYtB3PeMHKPJQOIBCM/NZFdmJgzPQpUUudSrpqARO4y3uEcwC0yApk+sZaKF oSgAK/UQJOZDyf3MfXAF/gAEKqLBPhJU1WjUlZdgISHQoVrGm1/lPwcZTJOhM/0/kS2z S7uqDPXKr60rMfwCRay5lbbzZ0a0JSpIT1XkadBT0jANdy/DhyjDAMMrbTE2bBsTHjWA 5/4Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978289; x=1767583089; 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=yn94LU96Z2B+v/2xA7vRHpP471K3U6dTWIu2Ddfkl2Q=; b=qIMn2rE0GhTglGbVEqGieb57WOxFTGigyQs9ohEn6g6XkHzXmpRpvfBaS1As6NeHQN 3t0a7lZSPgqnrytX8jyQSXFErqlcwYU846JXHOp7CZK9t97P4TcGSqYpxiZnytrTs7by nuev3rm3dQ9ZS4UVCJJMsPfE0TPWDuewWWBxTwzYzTwMtUniqA9XWkJpDtlejTV/Ea8h W6Flf6mfPghc682qxKeT+wiYWLS6M2T5kzpZAnWOpKTZOcwWz89ZsWdj9KsDN//GweuO KE64c9fCUSb1Gihdbw6Iau+7+lk7gaHVvLebUg/053PSg2OfKiRd8OOwGZHzJLZCLH5l 2nxA== X-Forwarded-Encrypted: i=1; AJvYcCX0OJtJ+vXO7dmif15KAjF6H4gIHKTlhrLMefMmj6cpml49W8lgDsG7aXMAUgWCvzzMf+GBQ3NLB99/PO4=@vger.kernel.org X-Gm-Message-State: AOJu0YwBZ9IQwi/zNvb+PXixOmqcA2aMIVKzHcFFLUtfIKjFr80+sScT sKRD9m4xY7tU24B6gO4fTr+MA/EcT68a6DPJibxNPYcgR2xly+8lsy7U X-Gm-Gg: AY/fxX6SGhc4qa/tEnbiGgJdVI6Mjqb+0k8v576axVePZl3qpyFIq53saFbh2AcGhZ6 2A4AFVrg3rWj0lMlVImT7M/NbzA8fQi/qBYSHhreNQWqNjPNEWJ7pL54BLAboiiM0jvvTc51+fH Gk4C8ORubsdfHI/gf4bWUNmDkUY/Oofc+EbwflokcqxwXcY3P8Cvk0CIqY34kaILO+jjSiL7w1X 0CsMmlr9sEWTJDYD5DM/WjzRGyWe2TniaTkVz2xVF/3uXM+zb/OmUJadiccgp7EejayFShUp+RD vLuyd/HhXRz3k2aRMtvuMj8Q2GtLfNQZfWz8n5VYHD5y66RLdO0KGAVNPEmiH4ZAqvA7vafmeIw Io2ZYCXTzuMcmNBRZytVlUsJzWVx3cePCgPe3HAYHQ/YzYwHdgmb+YkP2bvEc93NjA1JyY9KP2H qMVDoK/y1RstApfAVHZok4AI99WIAK+8ewYki614HYAF+Jh37UDSX7TzWnxzWyQn8= X-Google-Smtp-Source: AGHT+IFQ5fbCr21d6zsw6uYA1gOZsyHt90UhAMUtwWfZf5dhOP8cfNLe5vdZhiKaUYPmyinJjXAt8Q== X-Received: by 2002:a05:7022:2526:b0:119:e56b:98be with SMTP id a92af1059eb24-121723031f8mr27749537c88.37.1766978289476; Sun, 28 Dec 2025 19:18:09 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:09 -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 v2 15/16] HID: hid-lenovo-go-s: Add IMU and Touchpad RO Attributes Date: Mon, 29 Dec 2025 03:17:52 +0000 Message-ID: <20251229031753.581664-16-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- 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..11000cf54e56 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", touchpad); + +struct gos_cfg_attr touchpad_version =3D { TEST_TP_VER }; +LEGOS_DEVICE_ATTR_RO(touchpad_version, "version", touchpad); + 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.51.2 From nobody Sat Feb 7 22:55:11 2026 Received: from mail-pj1-f68.google.com (mail-pj1-f68.google.com [209.85.216.68]) (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 C976927700D for ; Mon, 29 Dec 2025 03:18:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.68 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978299; cv=none; b=VscpAWl3hjPfRU65ETNDhdGtyxg/vIYcMuNeYDGdHkI9jse3Fu60Io7TJ5wiezuDkf9ZIR2TM7zZPZ+Bt0MW1Zh1JIXofAOQ+GhnTM/NcSBZ2nq2Nz5K7csdwccChXzzRevBI78cRFbcfkDiWYBRFb+RLTPet5/cxPFvGYphbgY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766978299; c=relaxed/simple; bh=XYaXboJOIwWExtQVYjtJhShNUqA6WX7iezxCrD3Se64=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Qt7pl7zaLUniNN2hAQhUxRoVc56y5YVc2xzIsOHfvpcm7+hcandr5NrNl3PxsUI1F+ksXMcqaSnbc8ZPJGHTvWa1t+CCaxKVIQeQXddD0Bjks0Z0hShw2t0/gjvQhtHih+wrkU2Y8r3VaNSod8bOtCamcCSkCLGv+Gg8tmBCgIk= 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=QscQB+5K; arc=none smtp.client-ip=209.85.216.68 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="QscQB+5K" Received: by mail-pj1-f68.google.com with SMTP id 98e67ed59e1d1-34c363eb612so8675252a91.0 for ; Sun, 28 Dec 2025 19:18:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766978291; x=1767583091; 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=Je7xOayPe6lie145+99KZwI9NOHLAn6kdXP9FqSG1cs=; b=QscQB+5K9+O9dUer3qLNZWlREZbAaZxVoyHuYENtX+Gyqp4lzDxlGaYLz1yoY1wRvL XCDmtvhl4BX+DU1WdGaQNxXJxl0hxb4FpgQB0M5/6ay4X7yzfb6RouUh/CTogGto5M7C HtCKor3cCR92R9I6Py8/TFQxj4zshknYgnW4yMW/oeW+2JD6T2WfSpQ5ilHa6tp1PvCy mBV4kM8JQ3WZXuHlShXOxedOpU9SkM2UZIky9cPr98j+t3YqZNLj9Cd3A4SAAE84yeiS F99GLTyZg9sg7ZdHhAyb65TlcoFUhBgBEdiIzhkdVrVgSpWQAsjkO6lYjJT5jbDmjC1e KDAA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766978291; x=1767583091; 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=Je7xOayPe6lie145+99KZwI9NOHLAn6kdXP9FqSG1cs=; b=RRgCaw3773F9ZcywFV0sQHAPWvEOeBsTtd/Imz3GkYy/2V0bY4CmDXnRf1uvjGLNf3 IcLcHSBXF4FiXlLiB2pxjAa2cWpRDbc2q411MWSRnAZngEbLvWf1arivy1IgCUOiPTNB QVWTMO0IAYvaopPTTs0ZCCQbuqcqBCs5/RadddBkhXKL9bu/j/Phv+Bd/djOW1cBDYka kORhMRzxo6eLuM34GVpz+WYXX5GDTM7N4mN/engz20CXoB1DbDQb+/78otKnVEiH/nk3 or0qgO5lkuL0S8Os9Gvu2ai6aS3z8Z6NdJ+e7yRupgYi0p8rzEyQ3nT8adLnrX6Dqyxg gEcA== X-Forwarded-Encrypted: i=1; AJvYcCWc1zM97m2BnD+hZq+pKrkyhYI/XCBqQmqYg//LfqcEZrmpmcgQbCL5kO4aSXJCm7UBlfxNV1NnYwmiOAk=@vger.kernel.org X-Gm-Message-State: AOJu0YyNO8Oxo2SZ9JB1HyrSqKFIo7mO+G8KGmVwJEUE5BMjJO8Oyp5b xwLOjczuNtGef8/l3z0cUAJLFosGMz4GBGKSOCfa7SOqHRqnpbYTGQHr X-Gm-Gg: AY/fxX6kylilOzJCNpzG+9YMKOe1Uow0fe+9dr5yi9rTy1Gq60oJhsFzF/YndKGE6O1 4MC5zZKn/OGCLPbpMg98UrBy8fCBTKIvTs3OvLqWrVprSOeXSt2mA0BbOX7gXozf24je2Uis3OL /x8Gmz6VZUWnNzOdsIZC50zSQ3cp1VPB0McWqSMgLT+j9scAyAjaY9MkV3BahRQPobebwJ1tzsP cvbVa29mLzFbMwPAIQArHBF8lvObzeR3QUgtbBnfeut3pL3bx8opKCWUbIVxThDFFC+7aWLu6X5 RH4u+w1oEugfFqknxY7S9s48yhJNLcDdPXK7FYOJrpayDODraRzVi2bu6TpeMgv3dou5va1XQsG qfGfNaVgicXCbD1C0agWwOrQmzsN0yf0BdGVyPYG4f1lpJQ52CwGgKgdW3i/UpdALlYqdJHMApv OnCJSaOWlkszH1bRL3H7NS84oJsc1KVogn9ju/AN1R1sFOXPbQa3F7maOll7G93wU= X-Google-Smtp-Source: AGHT+IESDqEdwWWq496LupX1iGYMiPcz/7pyGpkqX72E8H1V5Yam3WMu8UVCpG0PW4J/FIC8u+Nfqw== X-Received: by 2002:a05:701a:ca0e:b0:11b:9386:a3bf with SMTP id a92af1059eb24-121722eb7f6mr27977586c88.42.1766978290223; Sun, 28 Dec 2025 19:18:10 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-121724dd7f5sm112992785c88.5.2025.12.28.19.18.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 19:18:09 -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 v2 16/16] HID: Add documentation for Lenovo Legion Go drivers Date: Mon, 29 Dec 2025 03:17:53 +0000 Message-ID: <20251229031753.581664-17-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.51.2 In-Reply-To: <20251229031753.581664-1-derekjohn.clark@gmail.com> References: <20251229031753.581664-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 --- .../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..4e298567ac40 --- /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 attribut= e. ++ ++ 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 = color. ++ Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curre= ntly ++ 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 attribut= e. ++ ++ 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 r= ainbow 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 = switch 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 att= ribute. ++ ++ 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 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/-:./::./left_handle/calibrate_gyro_index ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the left_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/-:./::./left_handle/calibrate_gyro_status ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the result of the last attempted calibration o= f 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 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/-:./::./left_handle/calibrate_joystick_i= ndex ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the left_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/-:./::./left_handle/calibrate_joystick_s= tatus ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the result of the last attempted calibration o= f 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 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/-:./::./left_handle/calibrate_gyro_trigg= er ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the left_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/-:./::./left_handle/calibrate_trigger_st= atus ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the result of the last attempted calibration o= f 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 versi= on. ++ ++ 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 = controller. ++ ++ 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 con= troller. ++ ++ 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 = of 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_i= ndex ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the left_handle/imu_= bypass_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 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/-:./::./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_= enabled 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 cont= roller. ++ ++ 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 con= troller. ++ ++ 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 event= s 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/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/-:./::./left_handle/rumble_notification ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This controls enabling haptic rumble events for the left rem= ovable 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_= index ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the left_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/-:./::./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 attribut= e. ++ ++ 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 righ= t 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_ran= ge ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the right_handle/aut= o_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 c= ontroller'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/cal= ibrate_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_stat= us ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the result of the last attempted calibration o= f 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 c= ontroller'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_= index ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the right_handle/cal= ibrate_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_= status ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the result of the last attempted calibration o= f 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 c= ontroller'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_trig= ger ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the right_handle/cal= ibrate_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_s= tatus ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the result of the last attempted calibration o= f 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 vers= ion. ++ ++ 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 co= ntroller. ++ ++ 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 = of 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_= index ++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 rem= ovable 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 con= troller. ++ ++ 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 co= ntroller. ++ ++ 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 event= s 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/rum= ble_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 re= movable 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/rum= ble_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 removabl= e 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/vibrati= on_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_ind= ex ++Date: April 2026 ++Contact: linux-input@vger.kernel.org ++Description: This displays the available options for the touchpad/vibrati= on_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 = transmission 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 wirele= ss 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 = transmission 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 t= ransmission 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 = transmission 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..c3c7b0918986 --- /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 attribut= e. ++ ++ 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 = color. ++ Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curre= ntly ++ 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 attribut= e. ++ ++ 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 r= ainbow 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 buil= t-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_sle= ep_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_mod= e 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 att= ribute. ++ ++ 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_rat= e 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_enabl= ed 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 u= nit. ++ ++ 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_enabl= ed 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 attri= bute. ++ ++ 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 operatin= g 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 attribut= e. ++ ++ 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 i= s 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_m= ode 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 i= s 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 be4a0fcf23dd..9893d26d5434 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14139,6 +14139,8 @@ LENOVO HID drivers M: Derek J. Clark 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 =20 --=20 2.51.2