From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f177.google.com (mail-dy1-f177.google.com [74.125.82.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 44CFF2D7DEF for ; Fri, 20 Feb 2026 07:05:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571159; cv=none; b=WCL1PnFVtLQqIn6OlAeKrVxQK7sRhlrd7rcfHci13byHXTpxT/x+qHnC2r1cq/4hZ4yxjZ/yaDW+OrwR6SxpiRpxKL6FhbqH6DXFLDJQmjXFT4rfM8vZc3C4ztLVbuId4usy4TGEwywjRNKqdnqDLpkl6uNHTB6hbrqxw43x6zA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571159; c=relaxed/simple; bh=+4nfO/MXU83pOeG2DXV/A8HvHCPLmR6w67tZZuPB84I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tcdo2NwoAixvytxeg+VQJRJ84n8DmfFaoqar3oe4wPlAk/rGMd04PhArxl+eBvFP4l6oYcwf+OKpyvwooiGGOoJnSrK0Q5GjVhXK6DUfPdRD6Q3BwHlHEfmPLwJ5FzNqFxl93MEnp1+81+/5c7uRTIvgDsqJ0gI6KEKH5AcaYrc= 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=Y9HuPT7I; arc=none smtp.client-ip=74.125.82.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Y9HuPT7I" Received: by mail-dy1-f177.google.com with SMTP id 5a478bee46e88-2b740872a01so2610512eec.1 for ; Thu, 19 Feb 2026 23:05:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571157; x=1772175957; 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=sCIGvEeI9Tq/VaiBgRhw0In7nNopiUbXBw8UFPFZx+I=; b=Y9HuPT7I1O/YS9sBVuowtWIpzq+IK3hu1XcX6Qtk/Sv0/trHeuvDk6a+wwzze2oiQz +bGN3XceFn0VP891/I54QZArzIK3fWQRWmNPXtZDt9hVHQxpswuf+EiFznYvGpYxsvU1 RdrNf6WsvfGzoQMsPoDWRC77uRaQ8fKcRwqPbG/j/OtJXVeFjHTFWPDzbPKCotYbnY3Y IjJzdW8IfBOD5+HMT537p7k2mEdh8g6GAR5Z2g4gNvvrVWH4C+SDN+ZtqMxolpgTd2mF i73Gtn72noPqm2szZqyI/rTqGqif9LmMmVLIKHPVc1hWBSfI1zv75GBfkqsMDKA0f49T 5XVQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571157; x=1772175957; 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=sCIGvEeI9Tq/VaiBgRhw0In7nNopiUbXBw8UFPFZx+I=; b=YMOD6vN0JM2ONJa18GSbqiwFf8eKdwIOEGL+BYRMCa3TZtkoyxcJfZ0VVvVjVoVOMk Rk+xk5xx6P5uvlOpNjaUtfl0tKZf96gctcNIxnjO1VpTVXGsR5dgu+VOwCDVx8+SJOr3 EwDm1Wa4hHZ7NQO6LdIkFVJJWSq7UDXOI6MhMfzYW0stjUaQG/IQI40cSVDrBfbd8bq4 dhvZnuvZC9PoXDy2ohLOPYmosDU4iOMWp6QlUXLyad3GQvCC+Bg9mZvhVvx3e0BhvYx1 b+8HFZh6KzotmTbolYBNmmPGA325uAHCEL5J/xQPvXl67gNN4WMWO8FQqKP4qTocVcNl 6x8g== X-Forwarded-Encrypted: i=1; AJvYcCW89U9ye4FlUfHNi2ITgEi4/YbZUFAvAGk+FlW7ZkOv2fFNB2K+KDqgzR1y474xr03kiHzn0tRwf5659v8=@vger.kernel.org X-Gm-Message-State: AOJu0Yw/ssIs1K+Vl+wUZ7Rf5h5xjFc5rzFcjYbklY/k4HmNwrZICkXn i8l6gF3UgM7eYeh3xflnjgo6MXqj1juz3AKX8ZM3Lu57lof2zIbuASIO X-Gm-Gg: AZuq6aI50ZEy8stpV579OaT6tYn2rWuVwo17COHtWnuS7yTdqnmAlBEJBk4Ntqb5+zP PBnmqT59oIrx7YlZOL+FqFovPTaK3x01lEUShR1mP7CuLHwFn3+F3gis7tB7F3nIE/e5NPafC8u AxezMIpztIiMxNn8VYWQVTDBTR1fMrK1h3pOaWsGEvrlqXsF8e5HZReGlyCa48ThdQAGchnKhCE vsVxP4sgliI5wJwRponbsBFaydBXUAkYJ2R74zuQ4/HXAWegL1PKW3jcEqfxcjR338UHQCxdAfq mdHRuQGcOEBhmzk/jGCVLvih2sLu4K5heb48zftP3E++zDA/k1uM5GEWiNYML+OulLMyc2R0jMC aHzALOHjDDDs6HRME3I16x0+o7s41mNi4klaRakyvpmu/6gH95bKUe5U/rSnhnIWYtAKLXkfFtq nzUMEJGksJgBuiTRoIHbrD9zxJWe74koMbn5R9R9vSAioNonqYJWmVe5HkGyjYPLv5tQW5pRZRX CQ= X-Received: by 2002:a05:7300:508:b0:2ba:72b1:40c7 with SMTP id 5a478bee46e88-2bd4ffe8e34mr5486846eec.4.1771571157271; Thu, 19 Feb 2026 23:05:57 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.05.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:05:56 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 01/16] include: device.h: Add named device attributes Date: Fri, 20 Feb 2026 07:05:14 +0000 Message-ID: <20260220070533.4083667-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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 by 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. Reviewed-by: Mark Pearson 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 0be95294b6e61..381463baed6d3 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -189,6 +189,22 @@ ssize_t device_show_string(struct device *dev, struct = device_attribute *attr, #define DEVICE_ATTR_ADMIN_RW(_name) \ struct device_attribute dev_attr_##_name =3D __ATTR_RW_MODE(_name, 0600) =20 +/** + * DEVICE_ATTR_RW_NAMED - Define a read-write device attribute with a sysf= s name + * that differs from the function name. + * @_name: Attribute function preface + * @_attrname: Attribute name as it wil be exposed in the sysfs. + * + * Like DEVICE_ATTR_RW(), but allows for reusing names under separate path= s in + * the same driver. + */ +#define DEVICE_ATTR_RW_NAMED(_name, _attrname) \ + struct device_attribute dev_attr_##_name =3D { \ + .attr =3D { .name =3D _attrname, .mode =3D 0644 }, \ + .show =3D _name##_show, \ + .store =3D _name##_store, \ + } + /** * DEVICE_ATTR_RO - Define a readable device attribute. * @_name: Attribute name. @@ -207,6 +223,21 @@ ssize_t device_show_string(struct device *dev, struct = device_attribute *attr, #define DEVICE_ATTR_ADMIN_RO(_name) \ struct device_attribute dev_attr_##_name =3D __ATTR_RO_MODE(_name, 0400) =20 +/** + * DEVICE_ATTR_RO_NAMED - Define a read-only device attribute with a sysfs= name + * that differs from the function name. + * @_name: Attribute function preface + * @_attrname: Attribute name as it wil be exposed in the sysfs. + * + * Like DEVICE_ATTR_RO(), but allows for reusing names under separate path= s in + * the same driver. + */ +#define DEVICE_ATTR_RO_NAMED(_name, _attrname) \ + struct device_attribute dev_attr_##_name =3D { \ + .attr =3D { .name =3D _attrname, .mode =3D 0444 }, \ + .show =3D _name##_show, \ + } + /** * DEVICE_ATTR_WO - Define an admin-only writable device attribute. * @_name: Attribute name. @@ -216,6 +247,21 @@ ssize_t device_show_string(struct device *dev, struct = device_attribute *attr, #define DEVICE_ATTR_WO(_name) \ struct device_attribute dev_attr_##_name =3D __ATTR_WO(_name) =20 +/** + * DEVICE_ATTR_WO_NAMED - Define a read-only device attribute with a sysfs= name + * that differs from the function name. + * @_name: Attribute function preface + * @_attrname: Attribute name as it wil be exposed in the sysfs. + * + * Like DEVICE_ATTR_WO(), but allows for reusing names under separate path= s in + * the same driver. + */ +#define DEVICE_ATTR_WO_NAMED(_name, _attrname) \ + struct device_attribute dev_attr_##_name =3D { \ + .attr =3D { .name =3D _attrname, .mode =3D 0200 }, \ + .store =3D _name##_store, \ + } + /** * DEVICE_ULONG_ATTR - Define a device attribute backed by an unsigned lon= g. * @_name: Attribute name. --=20 2.52.0 From nobody Fri Apr 17 10:35:54 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 59AD12ECE93 for ; Fri, 20 Feb 2026 07:05:59 +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=1771571161; cv=none; b=rpsMtIJTRvMmuts0z/XNiecaeyvGIGotsIGWJMbmZFW+8DtCkOcXjKyPIL0Auah5MxAw1bguyYslTy5dpf1cVed3MVoRyCu/eEXeem1ge1ZR4Se7ynh6Z3n7/sBXUhFNeiINI1kZRcGKnybWAhDSFXrwaVdK1wj0K/CRnPw+/yE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571161; c=relaxed/simple; bh=TCsZY5FdXxai5ouju1iyP91Ou3OSqBLMqguV65vzai8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=azImupuEPbhBRyIjPx+MBayhAONDJQudtX0eJSx/nZu5z/mIX4JQ9r+QO5gHt/2VZv/lIztnEI4xGg5dBzoWJ7vCC7RSreNfsPsPqT7yO3xAx6BLFBvPaEH+ONShxXddqm+W3bFazO7oRfMvVYdCKHNzn3z1T2OiUGhTKIEZZl0= 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=ga1GICIO; 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="ga1GICIO" Received: by mail-dy1-f182.google.com with SMTP id 5a478bee46e88-2b86ce04c5cso3446738eec.1 for ; Thu, 19 Feb 2026 23:05:59 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571158; x=1772175958; 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=OKylJQ7dEy1VAiEQMJ+L1Vvy3yCsQJQdti5xoLcUsJs=; b=ga1GICIOiyCFv9slFX90NjWeLbAS4ouJvR9NFoWl9IzdcZVJyHzhEJKIVA1TdlCe1G RYO5Ufsv1jGwIb501Yx+i/YEd6Q7a+lZ7JhvuNrJiv34hnx00t5bLvSAjBMiY0lqg/t4 LRfINeywU0hzMpPU+Brx0f2hstUYyt9RUh1xWrrVCz4ak+16J0cGw6uXcB7txMS+9nwD E3cZ1t9N/syzsEh4mFzcAqVdz8OEiz2MzYRGmgRZ5iZdLMwMskWbNcmcz2OFgUtuUrkT 8feRqe523CIFX/Iz8il/WzEom2Xf3dyf9/AUg7b8SbAepKY7qYPBAs7WKxL5xMiZRWQG inug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571158; x=1772175958; 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=OKylJQ7dEy1VAiEQMJ+L1Vvy3yCsQJQdti5xoLcUsJs=; b=tFXZ3hzc4OCSJR3lYg0LgTS2x65rZg89aCM10+69InG7fnkainM42NnM0RUAwt1/id BkBrXB+SWFOxnUkOx9/oCwhSZMGVCVD4pypsLA7SSiQ+Lwu3802Xh2ODPh8/5OV7Wt2Q oJFMhwfz4AwZTu+Y4YYOEOE/tmcDvo9tooBfLWhr2bx60zyMDIpwwYyAdaPAUjopYiL2 t22HPGzrL8CQEowxpJIhjWLzXAIw6XrZp+B6nEAFtn0J06PPQkM7yGcL3/TKHNvECQRb 0B4cExbx6O4ov3FuJ+LoTScqL7Jh77KDjR4nFlRjXdeSD8DrD/ISlXPJ8YCcEejn+sIu GuQQ== X-Forwarded-Encrypted: i=1; AJvYcCV96PxODTIDGne+255xtzyeFXn68/UDTpJXhbpxXXu4FqoEdMScBmGnVm4e7DvIk2LcjfUoMnFbabDIELc=@vger.kernel.org X-Gm-Message-State: AOJu0Ywaa2HH9S28KPGMYDKQ3uqsdyciuEqzpRBeU7EhKIvNrWPO15Ue R3M6xpsSxbqgtmm4ei2hlEcDWEWS+zoBRLh5SmyCWzR/2ZS2FWZkeY3n X-Gm-Gg: AZuq6aKlWs98DKF0+0Ko10fdU0aX071zQHxdhu039HIXyJPfdMZ81K0EpK3TK1Vz4MR hXQehH/48tNRt8O3O5dzmM3v7hYTfcN0zC6IcbLb576f4rjGfzwQVBNgNhYmPiEpKrqBq0tCza+ ABhye/1+xQP/xQt7k0LR814H5QMqwBdDcwBcZ61yqPCdo5EEg5yVhMLvKpZOI769sLcvZKhHSYi t4UUVD0MYLrv3oXzRMmZUDzWZvbNd+05KckHRiYm9NbvlkpAVt5EITUvivDpOpaGOyxl2zvy/bv lNT8WASMumM4e9lHRilyLh8/S6XQ8uJlXfrLhRC+hiVDNBjoetaHLcUI5mTMrzb3zIntw4sbCBc VZgyUJiO9C1AaJIF12P9I4nf2c0MPdMStMICKgBhmwjb+tQenLJfFUpDIORc7P/W+3vUzrsgOGm uKZi4pDkAqpmu7mgQ3HIBdbYKxxrmViFA86KDDqb9wFvZG29I+I3xy+VjwlNY5oHSpnbOoUIYJx SeOLYYN4nne8A== X-Received: by 2002:a05:7301:410c:b0:2ba:7875:10c9 with SMTP id 5a478bee46e88-2bd733be0c5mr264891eec.26.1771571158396; Thu, 19 Feb 2026 23:05: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.05.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:05:58 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 02/16] HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver Date: Fri, 20 Feb 2026 07:05:15 +0000 Message-ID: <20260220070533.4083667-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- v4: - Use dmabuf per request instead of devm allocated static buffer. Resolves bug with side effects during suspend. v3: - Add hid-lenovo.c and Mark Pearson to LENOVO HID DRIVERS entry in MAINTA= INERS --- MAINTAINERS | 8 + drivers/hid/Kconfig | 12 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-lenovo-go.c | 738 ++++++++++++++++++++++++++++++++++++ 5 files changed, 762 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go.c diff --git a/MAINTAINERS b/MAINTAINERS index e087673237636..9db6292c62ec6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14313,6 +14313,14 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c =20 +LENOVO HID drivers +M: Derek J. Clark +M: Mark Pearson +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-lenovo-go.c +F: drivers/hid/hid-lenovo.c + LETSKETCH HID TABLET DRIVER M: Hans de Goede L: linux-input@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 920a64b66b25b..d6c31a2cbaf3b 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -622,6 +622,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 361a7daedeb85..11435bce4e475 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 9c2bf584d9f6f..bd41ddbbbee15 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -847,7 +847,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 0000000000000..8a9f8063ee738 --- /dev/null +++ b/drivers/hid/hid-lenovo-go.c @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go series gamepads. + * + * Copyright (c) 2026 Derek J. Clark + * Copyright (c) 2026 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 { + 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) +{ + unsigned char *dmabuf __free(kfree) =3D NULL; + u8 header[] =3D { GO_OUTPUT_REPORT_ID, id, command, index, device }; + size_t header_size =3D ARRAY_SIZE(header); + int timeout =3D 50; + int ret; + + if (header_size + len > GO_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + /* We can't use a devm_alloc reusable buffer without side effects during = suspend */ + dmabuf =3D kzalloc(GO_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, len); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_PACKET_SIZE, dmabuf); + + ret =3D hid_hw_output_report(hdev, dmabuf, 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; + + reinit_completion(&drvdata.send_cmd_complete); + return 0; +} + +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.hdev =3D hdev; + mutex_init(&drvdata.cfg_mutex); + + ret =3D sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create gamepad configuration attributes\n"); + return ret; + } + + init_completion(&drvdata.send_cmd_complete); + + return 0; +} + +static void hid_go_cfg_remove(struct hid_device *hdev) +{ + guard(mutex)(&drvdata.cfg_mutex); + sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); + hid_hw_close(hdev); + hid_hw_stop(hdev); + hid_set_drvdata(hdev, NULL); +} + +static int hid_go_probe(struct hid_device *hdev, const struct hid_device_i= d *id) +{ + int ret, ep; + + hdev->quirks |=3D HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; + + ret =3D hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret =3D hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep =3D get_endpoint_address(hdev); + if (ep !=3D GO_GP_INTF_IN) { + dev_dbg(&hdev->dev, "Started interface %x as generic HID device\n", ep); + return 0; + } + + ret =3D hid_go_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, "Failed to start configuration interface\= n"); + + dev_dbg(&hdev->dev, "Started Legion Go HID Device: %x\n", ep); + + return ret; +} + +static void hid_go_remove(struct hid_device *hdev) +{ + int ep =3D get_endpoint_address(hdev); + + if (ep <=3D 0) + return; + + switch (ep) { + case GO_GP_INTF_IN: + hid_go_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + break; + } +} + +static const struct hid_device_id hid_go_devices[] =3D { + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, + USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS) }, + {} +}; +MODULE_DEVICE_TABLE(hid, hid_go_devices); + +static struct hid_driver hid_lenovo_go =3D { + .name =3D "hid-lenovo-go", + .id_table =3D hid_go_devices, + .probe =3D hid_go_probe, + .remove =3D hid_go_remove, + .raw_event =3D hid_go_raw_event, +}; +module_hid_driver(hid_lenovo_go); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go Series Gamepads."); +MODULE_LICENSE("GPL"); --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dl1-f41.google.com (mail-dl1-f41.google.com [74.125.82.41]) (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 4E463331217 for ; Fri, 20 Feb 2026 07:06:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571163; cv=none; b=Xo1M16vXrWEyW59d6CTsbcjJ4T2T7nP17tBfKxK81g1PCPFfL03mExJd4RCGkdnPT+pij9n47r045iS2/22GfEF2Rel9AjZP3JIMobHqPXLEZHFdTvIB+g3vd/DasykIdtsRYLeixx8Wv3T0u5hiSgUDqNV8iQtKma4zcSUSZbY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571163; c=relaxed/simple; bh=qSQwRYItgVMi3wY3B4B828Qul/NMS1RjkdsJQqRWAXI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rjAnu4ZNG8yNFams+VCUrHam+Qm7xASMqEV+L+BFZgrA4tQW8Yc3YxUZ0dtbSRZIoJkkbemFAfcx6eJLTQ4r1TXWXVcpD+KffvSHPAZmer54zP/bvz5Wk57RA3ubQ9nW1RAeurZ10zOJhGvE8SaU0c2MV6Fb9z6Ua6IBw/YZBw0= 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=SKsFZMfV; arc=none smtp.client-ip=74.125.82.41 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="SKsFZMfV" Received: by mail-dl1-f41.google.com with SMTP id a92af1059eb24-1273349c56bso2214504c88.0 for ; Thu, 19 Feb 2026 23:06:00 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571160; x=1772175960; 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=LYLPU24pjTvBDvHxIyOgyVH+aGVKK2c1KDnSBknwkng=; b=SKsFZMfVpV7rSklgvNp7hr1W9F8Cm3azQmoylEpPhIvEZRdOhK/686jA5iHKYHvKAF UVu8n4cwxnDy00JFND486zu7QclTpdSlqOYvtwUyeUpBP2+R+ptC7m9h17Gbfy8O2OMY yVZDwckefg/Vfx0G4STT8MyYRsiR4PhE4lnyljmBNRWq3DPPLi8j/rGTd/nL0nHaE8qX XeirWPvP0uHGx4tadMWX0ZykzA5jguiUfi1CzoYH8wF/H3lDAC3w17BQdR73pdc2nRn6 yZx3G6KPTrZL2qdoC+acSMDbZNmGfbuU5H4UK7mcvhV+6nExZz/DPFm2rcc9d/+F/rr/ 6CtQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571160; x=1772175960; 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=LYLPU24pjTvBDvHxIyOgyVH+aGVKK2c1KDnSBknwkng=; b=eF0IgAiqIZyra5odEtZwocRlz9j9PllJOAS3HCuy/1iq7a3ZIHpnQlwVf/0xs2ndGX /aNxfeRIXUyOGP8e5S4MrDR+1q/IdDtr8jwuIqzBXESoyBpuDJkCBx2fEB8WHJD9FpHu /qrXr0egdUQFfB1XwmQubQbmDDCfcdlqKpJvzK2DFf0rwdOHboqZ6uz/cGNIiVcB/6xO /NXvsii9qNiBuRfgoF6OTkqY/oVTpct1bpSi8hO+pndTLzYsfP6iRTOxY4jbB/dEHxgC idfSc2Asb3h8jlNQTO39COqtD4QlweHKASdoh4wCwVM2UidXhFeb33dsmOuL7PJRHG/u h9TA== X-Forwarded-Encrypted: i=1; AJvYcCUs98SS/+fOFyyqdhW7CUFSDfEbEd4hHYvRjk9WpNXZMrhtZQTb+zRrGes5EbSP6lR8Uek2ipUQTYsMcvw=@vger.kernel.org X-Gm-Message-State: AOJu0YxtA8CcCAfc7MY/R+IQ4aUwzY0X4Qao6Qfj+iI9BYoYvb/+zBrq SsK1sK1ONZ5lsAVgDPRx2ldc5bsXNQxZw5FCauGl+OH4kT22aN/OvqjI X-Gm-Gg: AZuq6aJZOzhO3e5khGe13UpAG7Mfn1QjHVRrl6YITaqnqxP7eV/vtNWyFqhLeKAm99P VOnzG2nASLAkcAJ/H9RDexQ0xO2KdUZbTNTCsE3XNoCT8fAEs/il6zoxK52puJWohke0dWms5/I Bge8/KP3AwQyzhT75mFNZn6hVKf3qM+ICBmBA320aL+zkOtToMxSQleJ1OcT9X+febeNYFJ+Bjf 2QcOqdUYEqpLD5h4i/TNd/A+Ch0g2sq6hl6x7DgFvnhRYspovl7DWwuzyawN0pgpwWksxMDUu2v z2kmtgrjpjVMaC85/XGYQ4e7hBHFg8a9Xpuk++9tNXQaKbNh4AlPz3xkxgXkboar/xKQ76LKPxz Cw2So4sZrqXXLdK9qgBupSbMcCi0ui4S8no3wmiIp3E3KRCbn4hrAEOqC3Cc4TnRy/lCajpss5l yCRF/FgxQt6h4aES8Cdjcu7hL03YZGP5+mKXtUQIUz/cDY+CW/wWXqavHWd/XMjLocbRd7o1orD xys+8SPNsdq3g== X-Received: by 2002:a05:7300:d513:b0:2ba:9115:2fab with SMTP id 5a478bee46e88-2bac9356416mr7433934eec.12.1771571159922; Thu, 19 Feb 2026 23:05: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.05.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:05:59 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 03/16] HID: hid-lenovo-go: Add Feature Status Attributes Date: Fri, 20 Feb 2026 07:05:16 +0000 Message-ID: <20260220070533.4083667-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson 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 8a9f8063ee738..308a544c3c911 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; @@ -445,6 +573,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, \ @@ -499,7 +816,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, @@ -528,7 +860,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, @@ -558,7 +894,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, @@ -588,7 +950,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, @@ -603,8 +991,14 @@ static const struct attribute_group right_gamepad_attr= _group =3D { }; =20 /* Touchpad */ +struct go_cfg_attr touchpad_enabled =3D { FEATURE_TOUCHPAD_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSPECIFIED, index, + feature_status); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); + static struct attribute *touchpad_attrs[] =3D { - NULL, + &dev_attr_touchpad_enabled.attr, + &dev_attr_touchpad_enabled_index.attr, }; =20 static const struct attribute_group touchpad_attr_group =3D { --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f169.google.com (mail-dy1-f169.google.com [74.125.82.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9362A33122D for ; Fri, 20 Feb 2026 07:06:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571163; cv=none; b=Z0YhT8rlm1J4/mQskOGD+JDRNQ/AFSXCt2Rwr4w91HA6Md7ARbIHyX/u6Dg2I5VRMwwQoMajXwHvnNMLw7V8qMO35/+b9OvPhK69sisTBzHJmAhdEBYToMYwvyfpAe7Xf/fHvOdDVRL+UQFtBHiD+5dIL1/8Iz8ix4kDgTDxINk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571163; c=relaxed/simple; bh=dJRe1ABTKg/fRE14dp2vk0kTsGsB9ixwAU9u1mcS7lo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bptcC+U9hoPFhAoNVRJY268dLUOhnQ/yanuxLmJw+J17npp30rj5A/nnxV0udLTL85hiS28u+9xgomq87wz592zBtP6EXcEmTBXVG1hZp1Z8sI1SnPnBALB6NvSV8aeEraX4S48m/tTzc0Sva+wGswOJjmcObY2GASHQIMv4M54= 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=VmGspsn0; arc=none smtp.client-ip=74.125.82.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="VmGspsn0" Received: by mail-dy1-f169.google.com with SMTP id 5a478bee46e88-2bd3b0bc201so4106307eec.1 for ; Thu, 19 Feb 2026 23:06:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571161; x=1772175961; 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=PeI6REGvbpAa0afYUU3hZE+sw+gXV1jOhS+q2zZrnUI=; b=VmGspsn0k6lmhacr2AnuA1n7LdfUm2iMw5/DfZApMp6WFsQSCTbxwBHMN6PymaScZK mYZkPqdqaJuNbRzs8UCBtrB5sIp8yECieiIX3HXF56PcEOTMghn0Pq2rMTeJx59nB3tl fcBlj6ZtTJT/2XcfMikqsSKVXwrMIlrZZL2dxescizP1vRSwKEfywR/WxTmZXEFgD+pI VjD1DanU51T8zrvLdF6R2ysOhGO2GqkzHTgY2LZOc+pCrXWbvhofAx69AhhEcPCgqVZo HFop/svBeHE8qyrzXYCM3Tz1Lk8qhYPdPKPk89NMLc70o3rTomNKd8PyDFbLicwSw4/z JP4g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571161; x=1772175961; 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=PeI6REGvbpAa0afYUU3hZE+sw+gXV1jOhS+q2zZrnUI=; b=HR4dAL8xn/GCjIuK6nlQVhHXD1QilvGA2ejtG/FW3Ms9n82E5Zs0W3uNFr6BRPQzIK o6RsalMLlJLpqzOtjaW5hzl8G8KdOOr5BaRze/pY7cEBHPGy6BHNYYx6Yumu1+pZi9TO W7u24Dd7hvDVEkmW7JhQcnp12tAhpa+ewYnCvQb2prWipc5n7DXUKxwimHgzc3AxACSn bn2en2RQdCIxNg13c+A6S0Cp5GjQc4hSawnRms3VVHkQD5/PCn9jl+EyreNkCnTnYG4U 0d2wQmeW0NAAvaW4YW9GtVUSysBTQag1mzncVgwhmNbfYdjKGEemTs6/mt8nGQuc4wul sgQQ== X-Forwarded-Encrypted: i=1; AJvYcCVqSBM3jMENZ+jwyAHUcjqiV+eF2hQCMLQdxH3W2SqtZK/CGZ3E6IFwOnhZe11/omeB0LkYx6xjuaoybos=@vger.kernel.org X-Gm-Message-State: AOJu0Yx/DYWCn7wSAG1p9H0wkdXDpV637eyPufoChcxjrGkflbU9Su+t OvXsVWETHBcaRU8Lb7olGmsd4xJV2XlHAc6LTjWENBV64dFrHpgj0yp0 X-Gm-Gg: AZuq6aICJOoeMSlYzdK4p4gatHVRH541S/1ACJ1VnSLHy3JGKpOmA/JedKrqDV4xjN8 YuW2phtzuvPjMW61Ooagw0JklBVYPgklZiEqRKXZiM2q9reoSsHHhgrWj/xm4It6w/18gPAjSRt PiPqnJjeCeRSlC/Y5KkcfxJEkkq9ekWG/BiKD/+Cb6/L2NNclS91om3LVjlzoc64+SYYbJVBxp2 Mbks0JpUrld2Pfb9vz+Y2ByPqmyOAIYn3SjWavU1jvrn4qGP4Np3dFtcILC3tqKhY4hpdcP0179 FJCrzURMzGNOShw+1YuW+8jheOBD9ZCkE7CaRHMPI6VOXR0pmTVTUNuMCKd/TfutagjdxgvaZS8 OQHvdmLWKP5MTrhAcrmGSIQFzZOJz2HRM+n4gWWLxq5EQdSrP58ynqjI2k/ipxgKBYOzOCbyeAb AsB/ew/N1idosOySGs4s47gEwEIPOW+feoAytXPW+zw/0Lxuk76GylgF/HT5/LqgEdRYcCF2XQ+ pg= X-Received: by 2002:a05:7300:8812:b0:2ba:96d8:530b with SMTP id 5a478bee46e88-2bd5b3ee2b5mr2213897eec.32.1771571160582; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:00 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 04/16] HID: hid-lenovo-go: Add Rumble and Haptic Settings Date: Fri, 20 Feb 2026 07:05:17 +0000 Message-ID: <20260220070533.4083667-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- V3: - Remove erroneous renaming of enabled -> enable for some left & right handle attributes. --- drivers/hid/hid-lenovo-go.c | 312 ++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 308a544c3c911..318c953f04602 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: @@ -762,6 +857,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, \ @@ -827,10 +1084,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, @@ -913,6 +1178,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "= imu_enabled_index"); struct go_cfg_attr reset_left =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); =20 +struct go_cfg_attr rumble_mode_left =3D { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_left, "rumble_mode", LEFT_CONTROLLER, inde= x, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_left_index, "rumble_mode_index"); + +struct go_cfg_attr rumble_notification_left =3D { VIBRATION_NOTIFY_ENABLE = }; +LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification", + LEFT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, + "rumble_notification_index"); + static struct attribute *left_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_left.attr, &dev_attr_auto_sleep_time_left_range.attr, @@ -921,6 +1197,10 @@ static struct attribute *left_gamepad_attrs[] =3D { &dev_attr_imu_enabled_left.attr, &dev_attr_imu_enabled_left_index.attr, &dev_attr_reset_left.attr, + &dev_attr_rumble_mode_left.attr, + &dev_attr_rumble_mode_left_index.attr, + &dev_attr_rumble_notification_left.attr, + &dev_attr_rumble_notification_left_index.attr, &dev_attr_version_hardware_left.attr, &dev_attr_version_firmware_left.attr, &dev_attr_version_gen_left.attr, @@ -969,6 +1249,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, = "imu_enabled_index"); struct go_cfg_attr reset_right =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); =20 +struct go_cfg_attr rumble_mode_right =3D { RUMBLE_MODE }; +LEGO_DEVICE_ATTR_RW(rumble_mode_right, "rumble_mode", RIGHT_CONTROLLER, in= dex, + motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_mode_right_index, "rumble_mode_index"); + +struct go_cfg_attr rumble_notification_right =3D { VIBRATION_NOTIFY_ENABLE= }; +LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification", + RIGHT_CONTROLLER, index, motor_config); +static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, + "rumble_notification_index"); + static struct attribute *right_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_right.attr, &dev_attr_auto_sleep_time_right_range.attr, @@ -977,6 +1268,10 @@ static struct attribute *right_gamepad_attrs[] =3D { &dev_attr_imu_enabled_right.attr, &dev_attr_imu_enabled_right_index.attr, &dev_attr_reset_right.attr, + &dev_attr_rumble_mode_right.attr, + &dev_attr_rumble_mode_right_index.attr, + &dev_attr_rumble_notification_right.attr, + &dev_attr_rumble_notification_right_index.attr, &dev_attr_version_hardware_right.attr, &dev_attr_version_firmware_right.attr, &dev_attr_version_gen_right.attr, @@ -996,9 +1291,26 @@ LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSP= ECIFIED, index, feature_status); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); =20 +struct go_cfg_attr touchpad_vibration_enabled =3D { TP_VIBRATION_ENABLE }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_enabled, "vibration_enabled", UNSPE= CIFIED, + index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_enabled_index, + "vibration_enabled_index"); + +struct go_cfg_attr touchpad_vibration_intensity =3D { TP_VIBRATION_INTENSI= TY }; +LEGO_DEVICE_ATTR_RW(touchpad_vibration_intensity, "vibration_intensity", + UNSPECIFIED, index, motor_config); +static DEVICE_ATTR_RO_NAMED(touchpad_vibration_intensity_index, + "vibration_intensity_index"); + static struct attribute *touchpad_attrs[] =3D { &dev_attr_touchpad_enabled.attr, &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_vibration_enabled.attr, + &dev_attr_touchpad_vibration_enabled_index.attr, + &dev_attr_touchpad_vibration_intensity.attr, + &dev_attr_touchpad_vibration_intensity_index.attr, + NULL, }; =20 static const struct attribute_group touchpad_attr_group =3D { --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f169.google.com (mail-dy1-f169.google.com [74.125.82.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 738502D9EE2 for ; Fri, 20 Feb 2026 07:06:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571164; cv=none; b=PauGhN7JH7Pn4oWA9DfcOUYcjuzPJvvPn/UKEKd1e7LEvhN92QSwQdbgCPLtQhJOBVsY47ZsyBIzA7VLA39cluWwE4eQUAGLunWhB+TeQA/DzF4knsAOFw1F68YcuoQWWOySYyCwjfN1tSkv2eZb+mwp4yi1BJywVEdC52yensI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571164; c=relaxed/simple; bh=bD1RU4IEQzJCVvEZbE10cBCc8vgMc285hT1fhBJIyU4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=N/BQ4TX2VrvsTm/lA0xXJ3Gxe20LMgqaMNPjApkjobaXPcOTSO5rsO9nwO5Zlxqs3/3pKAmT10hhg0As3MgPqbyLXt9VoKnSHKAJ1NJ6/7kr0sGA/bpAD+k55DFMWCFScd0t+sawF+BYfHr9zRvIxauLVWFZwlcRv2k+JB8DwIs= 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=O9C9zcO6; arc=none smtp.client-ip=74.125.82.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="O9C9zcO6" Received: by mail-dy1-f169.google.com with SMTP id 5a478bee46e88-2bd62e10a72so3389691eec.0 for ; Thu, 19 Feb 2026 23:06:02 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571161; x=1772175961; 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=ZVrOgfcGMMtv2b3N9rWACFBYfZ2ehUACsWfNXNjDP2A=; b=O9C9zcO6NtzUfFATWaQXSa2MdSZMGDPtiRVlKJAI/Gpq0STckPILQ6xVrIjEeGeTEt ELVocG9L+rRTMI8DDwU4oxay+KY9dYICn5gOjY3eKSwOTxgo0jzrWN+dZDBVF+aLM8md pl5ooQ4ts6XIBq3HGI24fuFdF8RKjIbbG/MVN+hV0S9CwKescFBQVggVAZYnhMM60aqS Qw9ULTdwoF2CTYs6g7C5B4sZRcXkkZJktLafljkJYuFdq3wSuqcCkC+7yUpqnUh08wmp 96lDPdyxT+G1RgeGx2AP6ZUM5iKwC7a73Ru1lyqpNV1bisXBVlAo2Y7KfGTI2EO3ENOA ntMQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571161; x=1772175961; 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=ZVrOgfcGMMtv2b3N9rWACFBYfZ2ehUACsWfNXNjDP2A=; b=waZhaXTC8BvF/ATgloYWEJgRXt77/Qnir71KfPGe/VBSKtuFFwp0MsiTJYv98Xyph9 kTWclkPgC/DWr5zqbIPuc+cMNKQ1LdBZMmysvcN2Rvlq9eEiKwcOuGlvlFhmsKWTHbMU tPP9oxBGWv4UfJwGM6SbOaJfeeK1fijynNnaGaT/vCSEo0801nwB3awlEBLa4R0X9dLX XiNvXjNACCfh50UD8avQAxK1zrxyZaxGNIbRzTpsm7FY3olG2Rt6lgsOeM7YzPZPVgvs T9uIRlJF2SUo35hDzOUDC2/RfzVON6gYe5UtilrKvmrTrPvjaGGycMX8sFl72LfaM3nQ 0ROQ== X-Forwarded-Encrypted: i=1; AJvYcCX/FcvktjFPd9xWXhTpXcWrVgCYjQsSdmzGiiv/+Dj/UsBXA1xOALRY75MzWnvcuP2syMEOxWyHR4QFfFw=@vger.kernel.org X-Gm-Message-State: AOJu0YzWtL7COP6Pl7Y9x03jNaiCLbDYiPnSQO+QqeZfgbrf6oImWPqR 5k8euhFPxDt6Jin7kZiocChaymi3Idedvmv3pKBTMxiZ4cOSvFtGWEe+ X-Gm-Gg: AZuq6aJASlOXJ9glOOWeZj9T7OloFM+ENx35ilKqTWJYTpI0fTCH+CtHdFprsKEzmJ4 xTSQbOYtEud3ltC6vGVF8g5fcZQukC0WEDk9/ewncvfMa22E8UuLC/8cQyVEhEKzaV7hle9c0B+ 0rVgEiAflILxTZ/pGpab4Wi5KI6x3Fn3KHaHOdv4vj6eoBPVjJw29//rBzPwg43s8ggKmI29e1k Of3DUSpATC4OSDRVeMsKbUEZVGlTshu/Jh3Y4oCDICVRWxU9a34KnAL/xQ0ZGfIIpNkWyk8/Y/5 osIGE82/PKyIKw8mmPdTgFUBTQMytCy7XwUkvwFl87hGPCSuY97LFAXXUyLOz0Xf7X7QmHBc1Wp J4i+ey77vIXuLRUMtSKvt0l61C5F6gwBbUa2geNdA16awv5OSOci8rMx93Tms898ThDpE+a/pgi gyFHNXF585YjKOWpjihA/RzsIyQtQcWYRLJQhst++ihLBG/W8TPTFNaqkzQaMe6UI1Ah605TRSm 1f/7KHKXPktDg== X-Received: by 2002:a05:7300:a984:b0:2b7:a3a9:9c28 with SMTP id 5a478bee46e88-2bac9795c4cmr9842247eec.20.1771571161578; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:00 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 05/16] HID: hid-lenovo-go: Add FPS Mode DPI settings Date: Fri, 20 Feb 2026 07:05:18 +0000 Message-ID: <20260220070533.4083667-6-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson 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 318c953f04602..e7f44400accf8 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: @@ -1019,6 +1036,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, \ @@ -1090,7 +1153,12 @@ LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumbl= e_intensity", UNSPECIFIED, static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index, "rumble_intensity_index"); =20 +static DEVICE_ATTR_RW(fps_mode_dpi); +static DEVICE_ATTR_RO(fps_mode_dpi_index); + static struct attribute *mcu_attrs[] =3D { + &dev_attr_fps_mode_dpi.attr, + &dev_attr_fps_mode_dpi_index.attr, &dev_attr_fps_switch_status.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f175.google.com (mail-dy1-f175.google.com [74.125.82.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 85D9E2D7DEF for ; Fri, 20 Feb 2026 07:06:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571165; cv=none; b=Vq9pIlL1xNjz9vpqQgzR9DRM8PmFnyiKNs474U2mmOjqGvA+b92oAGhcxjUnb/a6PNv80vmBqB+r1MZ70vVWKiUdozkBkTw6V4+jWCloNpuXXnwybknU8SDdoN7FImecLXlpFxmmheKWvdUiwJp6Y5Xoqow076cFqNWSpukGv5A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571165; c=relaxed/simple; bh=DpQI60Hq4ElxT1fLg8bQws2GkkyiuTlr/2qLo9hol+Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SH47O4rFXFQgzXSfZCKLW6Au+rc7WA+idvxC6JXI93Pr/3ub5AgdkSzzui9L40JrZl9fMG5ntuKbAIKwa2dAqX9fzaZ+eqGj26pwz6vzu2RfoUm2eg0iLEQ+tAJrSY9PzTCjF4Pipy3zLjuEV0BYdQKUrW0yp+OVw1evRZZ2t8w= 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=lJ6mn/7k; arc=none smtp.client-ip=74.125.82.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="lJ6mn/7k" Received: by mail-dy1-f175.google.com with SMTP id 5a478bee46e88-2b86ce04c5cso3446848eec.1 for ; Thu, 19 Feb 2026 23:06:03 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571163; x=1772175963; 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=PZ1cqGUaISKR5MY1Wda/cmxfMo3Y3UStzlounSzAtj0=; b=lJ6mn/7kLrE8iqoXKQlL7TuC3w1pMReaOjhRjfEA51HusL50DBXXdWaBCVPp6jrVKq X/qQuvSEdgfrYdSFqL6U2sAhoU69mJzeKXkYq5p13QM6KTTYOwKpmJujiLXRi+/92rg+ lGbCy6RHZV8B3veOYXElPDQJIzqPV1N2K/VqF4wF8JILTYZgtacZiTaagKoRAWWX7JR6 j5D8TrFH30IPTh0WUy35A6cRvTPCOCP5C0vrMAhMEuQLaNowgQrg19x3T7jq0ZiWpUf6 JG5O4HRKrbyRinRW0W851q1BFaAK5C7W4rc74C65O4VIo1ZHI99wSKB13gZkrMOrP345 0Pew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571163; x=1772175963; 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=PZ1cqGUaISKR5MY1Wda/cmxfMo3Y3UStzlounSzAtj0=; b=ZivLb3+Jor1XTWaxTOUE4JajyXSk3GvsHT1uTF5lf6JzJRQpoBgIF+qomDokuWHl5c Zb7PYriQSMlYFTfASAl/QLA8tZfRtF89fxzjSgDXVJzEsEfoCUfEyI3s8pc9qsBe2k9I zASGqwlihaKGEYmDpwAtyMp4SDycYgLbeEKXc4a+kvD37smLZkufyBK1hmnetcCN14e5 mhD3dXtZ2ODFT6gZZ1zqq21k3o2ZFH3ScEM/aa+I9L8rl3OfRTc8GGBfUl+PPcDdMuE3 kdS0ejBJHgJH0Yw2Llf+OXUfvOc4LxYY6Y1bn+gCgQ2hpuaqJ9TZSUFvRQCZLJS/aqde Effw== X-Forwarded-Encrypted: i=1; AJvYcCW3GirKgwSo8j0/Hag75GaRGlVX/dUwTd9yh8TJjxJ7C4yFUbDEBnt4fEnSnL6Vb8mj7HbSS9w/4ie4Qhw=@vger.kernel.org X-Gm-Message-State: AOJu0YxLGb+HoYldFCHLhtE/YhvwiI5VQSbwnaVjTLSFaaGnvUsveVlB 9Pqm+Kl43VDyfwFywRo5Mspm2ZO2lOqakJOqf5qwXcgaQLTMIfxfoMcL X-Gm-Gg: AZuq6aJnyr/ZEQ/R++IDMPt+zGyuiDReIPpEVkb7h6HaonEyrnkbSU+Jqqwu+bTs3wM i7dwd9a6WmFastSbRrDd0bJoLd4HRaIjMCUwHUhrNYOHsS2zIiCFbaRTZymQuqbufh5EvOvY5g4 VmjZ4V9Tgqbci7WjezATdxHeyZ5aEyfCyFY97DBE8TGQiWv8lRg+VIzknwS5TI81dILsXpS/xSC syOfXbVPHDWRWO8QX2A7uAyQxhDvahmLwMplhpUyvqkY5939NqrLm069+wO/s7+8x1kcD3CxUyD Fzct9i8Ugud+AO37SGUasRdZQx+X9za5onRj2Yb1Kpvg+FuRBu/r1rhpRoim6OCO8VWtrErUe3b JVIDknFhzFtvqPbv1I9tyvTrttpnJMM4e17vbaswnFkZDjGOYw6a++Z3qi7NR+U0pV2oq13rw/Q tU07iHuu+82hchfIYibWSLOsJ3WdPeHI8EfuqDX+bMdLQl346dqmz/TjUr0A2HCWV8d9uotZbFu t0d9zwXCrMToA== X-Received: by 2002:a05:693c:3108:b0:2b8:66f3:2502 with SMTP id 5a478bee46e88-2bd73342a4cmr254437eec.1.1771571162659; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:01 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 06/16] HID: hid-lenovo-go: Add RGB LED control interface Date: Fri, 20 Feb 2026 07:05:19 +0000 Message-ID: <20260220070533.4083667-7-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson 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 e7f44400accf8..70dd5d5d690b8 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -18,12 +18,15 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include =20 #include "hid-ids.h" =20 @@ -33,7 +36,9 @@ #define GO_PACKET_SIZE 64 =20 struct hid_go_cfg { + 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: @@ -1082,6 +1157,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, \ @@ -1392,6 +1735,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) { @@ -1413,14 +1859,40 @@ static int hid_go_cfg_probe(struct hid_device *hdev, return ret; } =20 + ret =3D devm_led_classdev_multicolor_register(&hdev->dev, &go_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret =3D devm_device_add_group(go_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuration attributes\n"); + return ret; + } + + drvdata.led_cdev =3D &go_cdev_rgb.led_cdev; + init_completion(&drvdata.send_cmd_complete); =20 + /* Executing calls prior to returning from probe will lock the MCU. Sched= ule + * initial data call after probe has completed and MCU can accept calls. + */ + INIT_DELAYED_WORK(&drvdata.go_cfg_setup, &cfg_setup); + ret =3D schedule_delayed_work(&drvdata.go_cfg_setup, msecs_to_jiffies(2)); + if (!ret) { + dev_err(&hdev->dev, + "Failed to schedule startup delayed work\n"); + return -ENODEV; + } return 0; } =20 static void hid_go_cfg_remove(struct hid_device *hdev) { guard(mutex)(&drvdata.cfg_mutex); + cancel_delayed_work_sync(&drvdata.go_cfg_setup); sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); hid_hw_stop(hdev); --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f175.google.com (mail-dy1-f175.google.com [74.125.82.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6A5E93321A1 for ; Fri, 20 Feb 2026 07:06:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571167; cv=none; b=I3zsXRcSLPfdcgku4c49lBYBn1pStQbXuQRUDxXGRsS12aZErYp+VaW4YOKg2hC5tf1g0LMi2lx5Tmozbf3YKgvEq/F0eGpHQo1WNuEnQ+By3k07fRAUeI45xuLjURuQscR00TgGs3ib2LG2TqfGLlyQzxnDTzlF6RxnGm0s8n8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571167; c=relaxed/simple; bh=GlUZT/HLo+GD7n4X1p/J1FMYZbG+BVL2U/ZxVhirBfU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NRuE0IRbSG8Jgp0gqEqb85kRGJiufwX8RaBUlVYIaa77fAhKu/BxOl1sQ0243DXyx3v63aZscEcZtFWY5TUV0tpzsjo/AW+0cqA2ASXpYJqgD3XTEP6A8wOKWd6Vk3mfKHTLGocsueUjlwl+qz81HeZ1hnUk9w2mxcv3uTkRv/M= 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=k60DvCh6; arc=none smtp.client-ip=74.125.82.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="k60DvCh6" Received: by mail-dy1-f175.google.com with SMTP id 5a478bee46e88-2bab70f8c8aso1581336eec.1 for ; Thu, 19 Feb 2026 23:06:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571163; x=1772175963; 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=ho4nlHycelakbKu3/3x0iLmv+R5QP/vjh1LtRyDnvrA=; b=k60DvCh6t+/5bEHmEahWiYyw4iPxBBQCnjRWEfPGI3YBLHN/+GavUB3ir47YkLK4XV ZTjnKjV6ElIUeYyNcL/HNhGKXvLB6oDPu9krQpOgEw2iPyJus0l8X+T91yXGI2Dy5yGi iJ3nHSrMLhDouKvV+X15AsSr+zWVkppd17gNxDaRfVZ/O1+tkrQt0AN8mTFeKood/P7O Kb0UV3IfKFNB4hxb7AoRAZrNRjsf/GGRr98Z5pswOAYxpYbtAb6YlmvBtIsXfm4u45aY n+i3/0WK/d/l1JwQo5by0P2uHNzjfCd4EQI0RJCiW0uCHKyJTcT+BRaJHu/fHyycnyaU ZLvg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571163; x=1772175963; 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=ho4nlHycelakbKu3/3x0iLmv+R5QP/vjh1LtRyDnvrA=; b=dY07Bk5DDZLs04fXzhYjOkNozBuOybNVI64RyF2/N3hNTeUwUxh0RW3lDLjzLzkZJ1 t9a9YcJkVlA819djU+CRfYav4xeQpMm2uKWSMlWXt3agUwpwL/T3IpiAz7sdsgNDK9pg NDqxlNCPd8vtCSpc+HKEhf7s/ZFwRtimAJv87+kFtzI08LhAirbwAgcNj8slEa6QXdq5 vOpPA3sqg8HSA/j1DTZiQpAZsbISprsPyRYewoD5HpEKS0vzcYC7j3kN0J1AHV/2GDhx AWRBo48CuqWMCYSrbKyRnsCCj4XEqxkVQHEo7oaPiRXLAaz+5oSozblS4iXasxZlb+a3 IC4w== X-Forwarded-Encrypted: i=1; AJvYcCWqRzufiVGIjKKOikDjC5QNWVyYaYj+BmoHtK4OsfkDjpaX3UVZZAtsWqdTNh/yycyF3i8NgEccZKYtMFE=@vger.kernel.org X-Gm-Message-State: AOJu0YzCN4KipXMJhaYaFbEBDJxXtIV+gnLNvRCnzUp/GI9WTiRDiojO y6m2cjl2vmROnXqbfJxnR14uIgAucUjsIYEA3mvPEGzTqq5C+8TfQaO+ X-Gm-Gg: AZuq6aLO9x5fCoEptdcAVeygoSSCevP6Z44XDQQmVD+aTv0cLjrm9za9EA5pj8ODGmn YCJowcfbC13HeMA7ACAAnUnZRmzdH1Q7YbPyzmEMw8sLk3YNjzezDA82ao/l0GIMnOZmpiRND/Y ArVe6e5n5fvUvMOFRVMcFLcVJeeGHedKWS2PWvCCX0PW+AuSXtQm0CP6IpoGJXoWseX0xOLDUDr wpQFiBMz5WDOqw6cGfykan53SkOnLZEodFN0nyhPazyeJCCkaPmVDqboNs3y/0WGt0jlAkNtZ6o KpIhFj5vlwOdXYiFTuVvzA10rwdjBP8ausLdL8b80oshu1Y4gIbv6HjAZhfpUewirqXMS6+h1d7 rnRwuBV9vFHDvGGAOhQ4Q+dKyQ7+blAzW5RwDe0QtwCwK00wNgy75fC/ZXgOY+qzfVz5bnCnyqS F0QM2xCVV2MJSuAMbcqgOkhq1dpt9QTbsmR5IHxf7J3OJuaZHizGxzVoPUS3yBscj6ubgpIeLMR Og= X-Received: by 2002:a05:7300:d70c:b0:2b7:ee0e:e9ca with SMTP id 5a478bee46e88-2bd5008f728mr3384090eec.13.1771571163366; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:03 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 07/16] HID: hid-lenovo-go: Add Calibration Settings Date: Fri, 20 Feb 2026 07:05:20 +0000 Message-ID: <20260220070533.4083667-8-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson 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 70dd5d5d690b8..b5a1d3e1988f2 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: @@ -1157,6 +1248,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) { @@ -1463,6 +1649,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); @@ -1600,9 +1810,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumbl= e_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, "rumble_notification_index"); =20 +struct go_cfg_attr cal_trigg_left =3D { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_left, "calibrate_trigger", SET_TRIGGER_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_left_index, "calibrate_trigger_index= "); + +struct go_cfg_attr cal_joy_left =3D { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_left, "calibrate_joystick", SET_JOYSTICK_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_left_index, "calibrate_joystick_index"= ); + +struct go_cfg_attr cal_gyro_left =3D { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_left, "calibrate_gyro", SET_GYRO_CFG, + LEFT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_left_index, "calibrate_gyro_index"); + +struct go_cfg_attr cal_trigg_left_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_left_status, "calibrate_trigger_status", + LEFT_CONTROLLER, CALDEV_TRIGGER); + +struct go_cfg_attr cal_joy_left_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_left_status, "calibrate_joystick_status", + LEFT_CONTROLLER, CALDEV_JOYSTICK); + +struct go_cfg_attr cal_gyro_left_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_left_status, "calibrate_gyro_status", + LEFT_CONTROLLER, CALDEV_GYROSCOPE); + static struct attribute *left_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_left.attr, &dev_attr_auto_sleep_time_left_range.attr, + &dev_attr_cal_gyro_left.attr, + &dev_attr_cal_gyro_left_index.attr, + &dev_attr_cal_gyro_left_status.attr, + &dev_attr_cal_joy_left.attr, + &dev_attr_cal_joy_left_index.attr, + &dev_attr_cal_joy_left_status.attr, + &dev_attr_cal_trigg_left.attr, + &dev_attr_cal_trigg_left_index.attr, + &dev_attr_cal_trigg_left_status.attr, &dev_attr_imu_bypass_left.attr, &dev_attr_imu_bypass_left_index.attr, &dev_attr_imu_enabled_left.attr, @@ -1671,9 +1917,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumb= le_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, "rumble_notification_index"); =20 +struct go_cfg_attr cal_trigg_right =3D { TRIGGER_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_trigg_right, "calibrate_trigger", SET_TRIGGER_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_trigg_right_index, "calibrate_trigger_inde= x"); + +struct go_cfg_attr cal_joy_right =3D { JOYSTICK_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_joy_right, "calibrate_joystick", SET_JOYSTICK_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_joy_right_index, "calibrate_joystick_index= "); + +struct go_cfg_attr cal_gyro_right =3D { GYRO_CALIBRATE }; +LEGO_CAL_DEVICE_ATTR(cal_gyro_right, "calibrate_gyro", SET_GYRO_CFG, + RIGHT_CONTROLLER, index); +static DEVICE_ATTR_RO_NAMED(cal_gyro_right_index, "calibrate_gyro_index"); + +struct go_cfg_attr cal_trigg_right_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_trigg_right_status, "calibrate_trigger_status", + RIGHT_CONTROLLER, CALDEV_TRIGGER); + +struct go_cfg_attr cal_joy_right_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_joy_right_status, "calibrate_joystick_status", + RIGHT_CONTROLLER, CALDEV_JOYSTICK); + +struct go_cfg_attr cal_gyro_right_status =3D { GET_CAL_STATUS }; +LEGO_DEVICE_STATUS_ATTR(cal_gyro_right_status, "calibrate_gyro_status", + RIGHT_CONTROLLER, CALDEV_GYROSCOPE); + static struct attribute *right_gamepad_attrs[] =3D { &dev_attr_auto_sleep_time_right.attr, &dev_attr_auto_sleep_time_right_range.attr, + &dev_attr_cal_gyro_right.attr, + &dev_attr_cal_gyro_right_index.attr, + &dev_attr_cal_gyro_right_status.attr, + &dev_attr_cal_joy_right.attr, + &dev_attr_cal_joy_right_index.attr, + &dev_attr_cal_joy_right_status.attr, + &dev_attr_cal_trigg_right.attr, + &dev_attr_cal_trigg_right_index.attr, + &dev_attr_cal_trigg_right_status.attr, &dev_attr_imu_bypass_right.attr, &dev_attr_imu_bypass_right_index.attr, &dev_attr_imu_enabled_right.attr, --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f171.google.com (mail-dy1-f171.google.com [74.125.82.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BAEFE3321B0 for ; Fri, 20 Feb 2026 07:06:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571168; cv=none; b=BQRaHtlKbNqNMjlCFBqvHgrO4bJZou7ONIMGRUBQN1HB6H0YMR/U+JhyQPjELLnheVK3b1XBfQFWaj1hZ5p0I0R3XEsqOoFRiWEz5UaVVT/WCKRhr0axVIelyxTVXv/bXl+yiFQi6KdTusL5aWJVq1Xf4DVmcDAxFr9YuzgrihA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571168; c=relaxed/simple; bh=EDVH4dUsTcpwD5nmM1gSIYpmlDqVKpktcjiRA1M4xFw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FU1iDHqzdA14jr5xHskSbuTD6jqEksb2k/XnNXBVSi+i/aR/TIGaLkkQzqNyZuQ8awev8CkVSklt4pnxXuIBd1NETOEGrkYHXKntAm3aiBiraXpO/gqZfm+bZtnsAF/S5r5S9qbUaQ+wV9cb1keGkKHGdGcurhXHwCpyFy4l4bk= 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=FjkDKXcR; arc=none smtp.client-ip=74.125.82.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="FjkDKXcR" Received: by mail-dy1-f171.google.com with SMTP id 5a478bee46e88-2ba90683995so831008eec.1 for ; Thu, 19 Feb 2026 23:06:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571164; x=1772175964; 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=msPTXKVrZ/Wht8lz5xWWNbmXT7uXYwiOMAAoN9JAe9M=; b=FjkDKXcRC9I881oUch+SJa+8PYKtBYIvaoOLU9au0Luko590XUSez5reFwsrl8mHAZ l0hJozfUNyIzekBXory7Viv9GFsykzclHf5CqsX9JAK6OoPSC/Bs8HTTYRXIWAOcYOpb PxtBzAKNjPJ2/pX+KnkZbsMXTmqDIZc/4eDq1tRLj8Fl6/KlyQh1nOVATABZ4QeTJuEM 2BOl7BtC49FE2FjICFkY5SQzOaoN9+ajixJOuNbbx4UTY94WSN40q1uzc+badAdq72ZN +4PHRxvHgLtZaMUjT7qbWwQO6XW/H4se41bTSQr9Y5RUXSv/VvhdavhnT+5+ifSpankG hf1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571164; x=1772175964; 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=msPTXKVrZ/Wht8lz5xWWNbmXT7uXYwiOMAAoN9JAe9M=; b=u4ZgrkYBQE4E45mEybIvX9UxQ6RxVK1JDE8xaUknHBHUA/PwiItkxn9n+9GPTITbHi TwH8c/XGj2Aj1jdBu8oWCoS/59cbPadAXqtLpSqtJXYzR1gA5Iq4EgxE/IVAyEAa+1TB Wm3jV+stgSeTFz7CnoXE4walYyxi9d8V8rc1h5SZX8hK7FgqUVLKw1D1wqetq2C2cFqN wi+pELD4MdPOw31qGMuKXOTM4/qXR7ZiNfFK/+EiFbz7g5hd7aooUEGr5I6R63lOxxUL 2xFHKxcEav47jkLea/bofYCaZfqdHs+Bn0zLV8pfgzm6EjwgP2yWnSHig8QyidnAzlme 7nYQ== X-Forwarded-Encrypted: i=1; AJvYcCU3joL7CVqSdoGrGhR+RZYiM6IQZSVif9UEp9cdE4A9yq4jO3yHMIbJY6fEekbmMdhS7MUln2lLWjE3xow=@vger.kernel.org X-Gm-Message-State: AOJu0YwR0Ty/iF2kqP0P04GCAc+eiAonmghyRHHq5C8jqrPEYtlzyhjQ 1zJWWy8aOahCMbkql1mtsgI9awzwsTV0S5EU4J05o2Ycec+QsuvLx2oMTmLgYQ== X-Gm-Gg: AZuq6aIeQkfML64Pk8gF0lRfGoXhvuoZIDuZs4Z8Rhl3HmVnyYUdW+JpDX/mJINsTCC W58PxucpW8UPgXqfdDNdKp8Yke7GftucP13b9xSFoLcDAw243+8PuOanXrVhYU2tAfU7aLABywE ZItNg2Qp8RvnHeed3pOKk5pAbE5TPJMwXmw3zdOqusALOrxBOwuwWo/hCxE9Ig2Vt3EDO6g5gEO 9F0TkipLRBQr6j+0h4IrYPOVHRYO4rkvlc0XtruN1B3cju2/6Pr4d6IDiJ2AHgleekLpHUncLnx AFH0AfHpdA1DVl2bS+iAElMDqG3JTg1VQktrEI5I1dR3kotsoB0OTjBPBs6B7IdenlrmfJX1CT8 WKDmPuCZa7sy4mf6iY2nDR7m40ZVuJF1rAGeFmSAzYh9ThsIqe3SxtJTbWcOvrMelixLByvu2yh f6t1a0+MV7wSBHy9vQlmnrhp+8tZBWka6NDPFtn7x3sajcIfXb/PvtpH7Qq71iKaA0jgWQl+JzZ zU= X-Received: by 2002:a05:7300:23d3:b0:2b6:e793:caf7 with SMTP id 5a478bee46e88-2bd6081bc54mr1964279eec.18.1771571163972; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:03 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 08/16] HID: hid-lenovo-go: Add OS Mode Toggle Date: Fri, 20 Feb 2026 07:05:21 +0000 Message-ID: <20260220070533.4083667-9-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- V3: - Fix collision with os_mode_index attribute and os_mode_index enum. --- drivers/hid/hid-lenovo-go.c | 137 ++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index b5a1d3e1988f2..883e75894a437 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -76,6 +76,7 @@ struct hid_go_cfg { u32 mcu_version_product; u32 mcu_version_protocol; u32 mouse_dpi; + u8 os_mode; u8 rgb_effect; u8 rgb_en; u8 rgb_mode; @@ -166,6 +167,8 @@ enum feature_status_index { FEATURE_GAMEPAD_MODE =3D 0x0e, }; =20 +#define FEATURE_OS_MODE 0x69 + enum fps_switch_status_index { FPS_STATUS_UNKNOWN, GAMEPAD, @@ -311,6 +314,23 @@ enum device_status_index { GET_HOTKEY_TRIGG_STATUS, }; =20 +enum os_mode_cfg_index { + SET_OS_MODE =3D 0x09, + GET_OS_MODE, +}; + +enum os_mode_type_index { + OS_UNKNOWN, + WINDOWS, + LINUX, +}; + +static const char *const os_mode_text[] =3D { + [OS_UNKNOWN] =3D "unknown", + [WINDOWS] =3D "windows", + [LINUX] =3D "linux", +}; + static int hid_go_version_event(struct command_report *cmd_rep) { switch (cmd_rep->sub_cmd) { @@ -593,6 +613,21 @@ static int hid_go_device_status_event(struct command_r= eport *cmd_rep) } } =20 +static int hid_go_os_mode_cfg_event(struct command_report *cmd_rep) +{ + switch (cmd_rep->sub_cmd) { + case SET_OS_MODE: + if (cmd_rep->data[0] !=3D 1) + return -EIO; + return 0; + case GET_OS_MODE: + drvdata.os_mode =3D cmd_rep->data[0]; + return 0; + default: + return -EINVAL; + }; +} + static int hid_go_set_event_return(struct command_report *cmd_rep) { if (cmd_rep->data[0] !=3D 0) @@ -666,6 +701,9 @@ static int hid_go_raw_event(struct hid_device *hdev, st= ruct hid_report *report, break; }; break; + case OS_MODE_DATA: + ret =3D hid_go_os_mode_cfg_event(cmd_rep); + break; default: goto passthrough; }; @@ -1343,6 +1381,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) { @@ -1709,6 +1805,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, @@ -1717,6 +1816,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, @@ -2181,6 +2282,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; @@ -2239,6 +2361,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) }, @@ -2258,6 +2394,7 @@ static struct hid_driver hid_lenovo_go =3D { .probe =3D hid_go_probe, .remove =3D hid_go_remove, .raw_event =3D hid_go_raw_event, + .reset_resume =3D hid_go_reset_resume, }; module_hid_driver(hid_lenovo_go); =20 --=20 2.52.0 From nobody Fri Apr 17 10:35:54 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 EDDF93328ED for ; Fri, 20 Feb 2026 07:06:05 +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=1771571167; cv=none; b=QXwvOHobK7BwjNCq/T4qESHi6Ek87q8jRNR4LYnESx0Q5QO7ZWd/2nhPAkzHSyGDq//IeIqrL4rt1S9cylGPWDfeFuUcWxkrUt61u35uAboYGWivp8eLGh6jBkrtgmdBdbvfg2xnyA9lnTOmwLZ9Wj/qhzbi6/taJZ32JfL6y1k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571167; c=relaxed/simple; bh=hUo1MvYccpbWMaWLjJzMWrm+A+W/DdILFntNUVeXgaY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fgKigm52VlXv3iIcQYDEGAcCf9c1jbWC/OgGffjHBY1e5CfKgP3CXKBYqL1g++hJIHjKuB5+xePGGchQkeKatN28+hjM1cOF4PCcuucW7ftNJqiywp5XQp8/carwNhKb2fQN5IW7JQfWzXUcl6cRZX8Tp8cfhB8PgtaohZzFa98= 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=QawXKLT3; 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="QawXKLT3" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2b86ce04c5cso3446902eec.1 for ; Thu, 19 Feb 2026 23:06:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571165; x=1772175965; 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=yFj2OTG3aeriS2nQMueP/Sb6Nx66D9fuh6qoYGYRpGo=; b=QawXKLT3SMqxUWfFiTplT2EObJ8unPc8IiC7S1bBNjE0+kCRUwJNzOY262YVQpipN6 0/lzBOs8+xtHxMCbffzwkS1I53x8Y5Tx3B1dcz9U7ZXgUVir+eYJmgvwips1Ao0eBN4w 7NP90PsGW3m1DoZmmXH6vQSss5vHsVTVvfJW2+Bk0ZF11HajBwwUy3OD+NJC2bsyzuUz mb3e8OqTVgIBAdldVVL2QwHt+kT23Xcez4dbsHS7X2ecRevD1EJUxXEEAUr2HSy/GWWJ 0+Be4S69HG2cdlnIGS54UO4KiIUCBHM0XyhrJxczuBsKMYnj0r1qwa1gH8VQlli19/ut UFRQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571165; x=1772175965; 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=yFj2OTG3aeriS2nQMueP/Sb6Nx66D9fuh6qoYGYRpGo=; b=uPGf/l4thFA9qkPJVnkwEL5DljppqVf/FwCNyTzNWSqkC5dY8uJ9MUeek9fQ00hu7U CZ0tP2/F/pvxeswyI0wrhl/GnPTmMsNSqTBTvL8yB3nsCHsl/YAwaA+McvVMIDn8m26j iXkah/QAbiR+UkRNlSY0S66N8ntCiIERA8q/aKFm3lPpgoRNBydnyigdNZ9e8ZrEXV1E IeZbIFRbddggQOavzUMvD0Dp2voufS8GgRmsQPAPq7vW8M0dKk/04D3H2xL5kdajxJMR z4x8qohTKRrV7Xtg8UHOsIPaazD09l32be+tjK1oeT5N1loB62yzFmCcmNfz7ka65p4p h3fg== X-Forwarded-Encrypted: i=1; AJvYcCXOQKEVw/jEnCk0G6wOjZCTekuPnaX/KM+hXwewEVdfHO7H1T8JVV+Hz99pLUkE0pV9Qqv3C+XJk0ncawE=@vger.kernel.org X-Gm-Message-State: AOJu0YwdZM63xlnuU3CsJaoE/4cS67Ndr58mJv4G/yA9V+mB5FxtsBJ7 N47wQsfBHmvoGBS5Mvg6d4WJ9diQVyotPtccu3cG7mupm3TghUZKLZvZ X-Gm-Gg: AZuq6aIprhVOiowl9vFpstdZgjK/xJRNqqf32XA04Ps7TdgCuKhElaj5WPQfWv75L5x yTDZOzWSqY6aMd99Xu4rGeWPluVYVdcq+wNKdr6i/ZIGkxuTsxZHuXwnWWcCVlNF8Ufm2sOxFa3 5pNIralyXT1VrasRg/DXNmpIu0RhSz2L+y2jPYGcysrCHaKP+dIZZdDwlRhK3dn75jeeNBjV81W 6HSrV2+sJzVzBTWQRUjRA/7S5pp/DWbNUkQu/jwALBYJReUBNQQEHgN6iD//rD/GFzXvooVJVlz M7977uUDhZTXZ7gJa86GeMkrPW0GDimSh6/4s1hgmzuhm6RWJAcPT+brJh5eSksEwjfnoKTahZj uplEZn9qloyh/emk6+MtyUb4YixCm5XjGUhmU2NmyRLzexBEcaZRPfOHo0k3Fy9L0NwvKeTtWKH DK4xRpdifNy9VQ7LAAZJfZbuCb72Qgrf6w7AVoEb30/BGEDg50mE8SFuwJr5d+f3cYL6xMK8Y2m f13zUD4QEL9Yw== X-Received: by 2002:a05:7301:1f05:b0:2b0:571a:28a5 with SMTP id 5a478bee46e88-2bd733590e1mr318113eec.18.1771571165046; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:04 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 09/16] HID: Include firmware version in the uevent Date: Fri, 20 Feb 2026 07:05:22 +0000 Message-ID: <20260220070533.4083667-10-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson 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 a5b3a8ca2fcbc..524f2b9ed5121 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 dce862cafbbd3..ce728c8d5bdc4 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -698,6 +698,7 @@ struct hid_device { char name[128]; /* Device name */ char phys[64]; /* Device physical location */ char uniq[64]; /* Device unique identifier (serial #) */ + u64 firmware_version; /* Firmware version */ =20 void *driver_data; =20 --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f176.google.com (mail-dy1-f176.google.com [74.125.82.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C5290332EB4 for ; Fri, 20 Feb 2026 07:06:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571169; cv=none; b=OyvbxYrhhzAGrCe5KSyd8x0hKeJL+6OijqbKPoQJiQDzx+Ag23CHg0+QEDl8Hreq3FJ4T1prkxNLWYqhvuODmjjvzT3PF9hXub2VRTHVawWKHiorcz9MeA9/+RO2+t77zfVcwtM1JXMarRHusPIcpo81cZTcdYQ+px69j+QKCdE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571169; c=relaxed/simple; bh=eGdoSv0ZQao2sy6vJIavc++ikRNmW6/ATvDSNrQ1bL8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=DK0zZcQZ98ff79JygE31S9PfAC4Ujy3wTfw4onu7kkimsQ08/emz8voOAw3ij2DnAeorhXJ2u4tPx3z1g5opI0atlV406x/GEhLkiIiYsErTwcWXq7onx4l3mD72irRJASu52dzPgI05l068zYyBNEMXctmxBTHnVzxGPYG5hZE= 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=SHMcMQX6; arc=none smtp.client-ip=74.125.82.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="SHMcMQX6" Received: by mail-dy1-f176.google.com with SMTP id 5a478bee46e88-2ba90683995so831017eec.1 for ; Thu, 19 Feb 2026 23:06:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571166; x=1772175966; 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=AVV4EM0yFjLFfGJDxNQyyIdAK6RL+8nKptT+3DYibjE=; b=SHMcMQX6V4BbzR5DTEtl7M0euK0DC2u0zJOXAnB8kRwFTYVdyZsMes6GxetAoF8tDe tYnNT8oYvp8UEaI/UQ8C4rWnRwREgfhavLY1b82VHyAg08c06/k9mML5OsHRLwuAbVlC rol/1WQA+boDDdAtf+dmRkVdbQlEERNzLok0r8qQ4rZqqC8I/jtyyi20TpAP4BuEOS6N olil2wo52lEIslh1l9ogNaUDSaJ0GTUgEvEL/Q3AA2FjJ4i+iDiQs5Vn4PU2bgUtwBIl JsUTYvMZGugiD9LNXDfU/k0gkD53TsvLJEQYfqD84Hr181m3gKitz/hie5OwZbcEsK7n pZPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571166; x=1772175966; 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=AVV4EM0yFjLFfGJDxNQyyIdAK6RL+8nKptT+3DYibjE=; b=tx7w40RlKh9kLNc31gPIxrI/swvHPeOzweNTEgWOhg8d/OUfL8nEvDkPACFdC2A7zU AIsloIh6icMNureI8G8buSnRva71IMgeAysjhq3QSoOtmoBkWVQW76UV2kTBuFp4AqCm YqOTcsiUyDvL+PFwNvmQuhuVB4y/lJnUkdlJJ9Fw1iKweWLmQd3Aa6UBe+WTuL01eQbx Ka3ZwlOVYGIFOwikpytIQaCgXuG4POFb0Yml3R0FF+Gc3q1GPLkxQhXxW8Qg3LEvqFKd MiHUFxF3ANAGhtfyO+Qwb8Z80m3PXrilmKMHHBNryWI/UHpHV5/QROy0IPAaq9vycGWm c51g== X-Forwarded-Encrypted: i=1; AJvYcCXuu9GzY+T7ah0/jQ0Ya1RjgBA2NrlNEFg05gJmvrrokl31eAXq5EizBr95Ng2YhzlPvvAgw2oRGJ1+6oM=@vger.kernel.org X-Gm-Message-State: AOJu0YyY9HQNBtBKT2l/lPG/ef6yIHyf5LYUK1oS+rvtOTYg5WqDIRpB VpFGtpNRm62Ae9N6mrIQWtZxQ7hjppTDxBaR7vvGl5AnZUn2ykR0pDqS X-Gm-Gg: AZuq6aJVtTMLaRLOLm7YDtJCy6Qkd8b1ShovHv0FjyzeTvELVgVm8zgBYGi8zRGB1ym sRGTodEXPgKPTzDSZDhFP+ONTpzn3C17wgoLRZkcOI8aj+vY/D420k+n26FyMj5tVDVwvGWwVoE gG8igwgaVab84Z2hNmthzfs/gYt7ykonhh+bwyk7pNzRcOkN5l29SwtxN7raSXbb/uuPs9zKSqW QhQ8kRK7vfpRP56nEzfJD+sW6Po2+bYfdOBZVuPd3MXcfLtn8imt79QCN3p+pAvaveWN3b6ipMA EyhbybeAqWRklH0kjEGhurYVV90RWEbhpL3kHjyvTeKQrTL0svI8ACfWITyGzVIDZ08PIxEs8gz NnmCGsmSjmUMYR4RJJT0d6ohZBvMhst0Ss3f89oLYqFvBKbKqkIed7Pl6kWcBR6FxYqsyM/dJix NTLKKLboc4AxoZZ7w139gShOFIu/t83JOmjRzzPDRL5O1P0l+Z75Pt8PJwb1dNRffAoNcRk+sdT +oMcWX3o4V7Jg== X-Received: by 2002:a05:7301:6505:b0:2ba:7c92:82fb with SMTP id 5a478bee46e88-2bd60769b84mr2019330eec.5.1771571165722; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:05 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 10/16] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver Date: Fri, 20 Feb 2026 07:05:23 +0000 Message-ID: <20260220070533.4083667-11-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Signed-off-by: Derek J. Clark --- v4: - Use dmabuf per request instead of devm allocated static buffer. Resolves bug with side effects during suspend. - Remove unnecessary HID quirks and return to HID_CONNECT_HIDRAW. - Adjust delayed work time to 5ms to fix some side effects during resume when the MCU disconnects in some circumstances. - Cleaner formatting on multiple debug messages. v3: - Include Mario's SOB tag --- MAINTAINERS | 1 + drivers/hid/Kconfig | 12 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 4 + drivers/hid/hid-lenovo-go-s.c | 287 ++++++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go-s.c diff --git a/MAINTAINERS b/MAINTAINERS index 9db6292c62ec6..1d0468906788a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14318,6 +14318,7 @@ M: Derek J. Clark M: Mark Pearson L: linux-input@vger.kernel.org S: Maintained +F: drivers/hid/hid-lenovo-go-s.c F: drivers/hid/hid-lenovo-go.c F: drivers/hid/hid-lenovo.c =20 diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index d6c31a2cbaf3b..8a04a69b8f259 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -634,6 +634,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 11435bce4e475..ef9169974bf00 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 bd41ddbbbee15..486d8baae0257 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -729,6 +729,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 0000000000000..20eb472d7a247 --- /dev/null +++ b/drivers/hid/hid-lenovo-go-s.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go S devices. + * + * Copyright (c) 2026 Derek J. Clark + * Copyright (c) 2026 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_S_CFG_INTF_IN 0x84 +#define GO_S_PACKET_SIZE 64 + +struct hid_gos_cfg { + 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) +{ + unsigned char *dmabuf __free(kfree) =3D NULL; + u8 header[] =3D { command, index }; + size_t header_size =3D ARRAY_SIZE(header); + int timeout, ret; + + if (header_size + len > GO_S_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + /* We can't use a devm_alloc reusable buffer without side effects during = suspend */ + dmabuf =3D kzalloc(GO_S_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, len); + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + GO_S_PACKET_SIZE, dmabuf); + + ret =3D hid_hw_output_report(hdev, dmabuf, GO_S_PACKET_SIZE); + if (ret < 0) + return ret; + + ret =3D ret =3D=3D GO_S_PACKET_SIZE ? 0 : -EINVAL; + if (ret) + return ret; + + /* PL_TEST commands can take longer because they go out to another device= */ + timeout =3D (command =3D=3D GET_PL_TEST) ? 200 : 5; + 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; + + reinit_completion(&drvdata.send_cmd_complete); + return 0; +} + +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) +{ + int ret; + + hid_set_drvdata(hdev, &drvdata); + 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; + + ret =3D hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret =3D hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret =3D hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + hid_hw_stop(hdev); + return ret; + } + + ep =3D get_endpoint_address(hdev); + if (ep !=3D GO_S_CFG_INTF_IN) { + dev_dbg(&hdev->dev, + "Started interface %x as generic HID device.\n", ep); + return 0; + } + + ret =3D hid_gos_cfg_probe(hdev, id); + if (ret) + dev_err_probe(&hdev->dev, ret, + "Failed to start configuration interface"); + + dev_dbg(&hdev->dev, "Started Legion Go S HID Device: %x\n", ep); + return ret; +} + +static void hid_gos_remove(struct hid_device *hdev) +{ + int ep =3D get_endpoint_address(hdev); + + switch (ep) { + case GO_S_CFG_INTF_IN: + hid_gos_cfg_remove(hdev); + break; + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + + break; + } +} + +static const struct hid_device_id hid_gos_devices[] =3D { + { HID_USB_DEVICE(USB_VENDOR_ID_QHE, + USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_QHE, + USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, hid_gos_devices); +static struct hid_driver hid_lenovo_go_s =3D { + .name =3D "hid-lenovo-go-s", + .id_table =3D hid_gos_devices, + .probe =3D hid_gos_probe, + .remove =3D hid_gos_remove, + .raw_event =3D hid_gos_raw_event, +}; +module_hid_driver(hid_lenovo_go_s); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go S Series gamepad."); +MODULE_LICENSE("GPL"); --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f179.google.com (mail-dy1-f179.google.com [74.125.82.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 28AFA33469C for ; Fri, 20 Feb 2026 07:06:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571170; cv=none; b=hML0T5Fb7nN1zddS9YG5iNhpi+lkxuuIRtc/Ks22re6EzlQ035ZwYe18vbR8zODMF8yBcXDBuawRWi094XYqUQ6/2rlcZZinYsdQTaT4vFEsUgB8HGoCsZncbESxZjONH5UgkUjldGAlUduhIoSu3GETH5dW7IPlTuYjC8gSY8Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571170; c=relaxed/simple; bh=KdCh9zJQLpSdDTZM6J9VhfrHq2GUKWp1PL8O4tMF0Lw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=F0ArbhcpyhsgCaCOc2kVD9EA/eJPewC5NSw9Nx257QZxpQFotvxmw5p1En23NB0xmIog8LQ90BbKAU5KkMAHbI2eE4JMPTmTUGGO7rwEAM6IPq8d9O/uALekZIC/M62uhcevfpQJ9m8htVuGV5T5wXUqgvmUSXgCm9l0NYqChXU= 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=j6zX5APc; arc=none smtp.client-ip=74.125.82.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="j6zX5APc" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2b86ce04c5cso3446951eec.1 for ; Thu, 19 Feb 2026 23:06:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571167; x=1772175967; 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=X2RTMkBWMa6cd5JBc3xaI+nYZ2JTuzryHpI6NlmsJjI=; b=j6zX5APcjI992y10m3tYH+X287OYidxL8lIvJOu3ditprMJJWwONezPUdNrQeZnm2U mQzbumLl2cFIfzZg8CIUcWRGYIY1tfZSkelEvveUf3XK9FK2vGENJnLwoZD7PPNxT2d/ 4stJ+VduBF28fN4LaJdhREH4CPuJFVvrTHAZeGM9ErPMtDh/gk9IY6QlFePIwrnMrMsr FCsrAjagDTjVJroBAixajkSZRcrSzoMh9GpUadhkmRzsRRDI3raI95YgDR2DPEmuT+I5 2HnHuyErdzVzoSvjdBbSqSu6VnTmn0mxEDIUoW+DYkEHKh2ifoiZNVZQCu9SaSU8vB3q z2hQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571167; x=1772175967; 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=X2RTMkBWMa6cd5JBc3xaI+nYZ2JTuzryHpI6NlmsJjI=; b=eWeAmxWc8hZIfNnFvHXctT0829v7iFF7AkORaXE47KiDoqU4Zl5E0ls6gwn+N5yEV5 I5HW9QtC7kufRzaGFTJ5ks6p3vrPqG/Bprpck+OG1v+E4RE122XFy4ZF/bHOOwv3D7Z7 gmeHQ1Q6kHWD4ux1d1WYyvrdvoUDIbK8GZ80qNFgnFx4iZ0AFJQPKDgFRKO6A5JwYs6x vtHK5TSTPx/FH6eMX+vRUkQB0ZnuWFvPU0XULXrAkUiXCjVl84GKrqr/UdClDD6L/APx NlltxltAGyDxK4xJ3VUuD3IwR5otY8Rpl8+73ctGkXO/juUBiisE7xwnDJ136PuCNEM8 v/eA== X-Forwarded-Encrypted: i=1; AJvYcCWiEiMId6/HRy2nPPDrSdf3xWDCIwIDZvzeJoPU0eOnxqQfN7wrUNMwGHTVuC4lYF44Idj+vIFYh9SNt2w=@vger.kernel.org X-Gm-Message-State: AOJu0YyuCzzJu4Af5BxfNDRP6sRU8GiYzZSSG5/M9nfoDxatlPr4vWKO PXuucOR6ctTDA/+kvaCfTE8iAxyCx+rVptAJUzticqsw1ebNDwxvPJxX X-Gm-Gg: AZuq6aJFWhhI5yDf4vuguBhyFld5ZgXiY7Q2qk0oaOUgHpnjnrRA++FBkTmnb0l0L0n dEYR0fERyNtTXkTYr1q/KAY922WbbzYvGZUS688oHhskY9tYjGYfOAoYgk1O+Pt7WOJqAsCnuST vRvIiYSggbBydYPuzvis6VuCMoHjM1a/RUL2ayDybjOEfCy/SmbOaspx6m9VZK477zJv0EK21PV T8y0uKPX4vz9qSl4wVJ3Zh8eOhzHY/s08/4vuajJW8bidC6Ti4YjTCbK5dD3ZiuSliQ9twZ1RhG 1xHAquLYLZnkhfwcq5udYSrlkegY6ziybttPfkqjg/iTDst4xkLBMdMTUEYTBlIsckstw/wQSn3 YNDuBxcoPZOFTrEBB5mfXuWU0MiJmZBrZ3uS30PNxg/DRP4H1EPhbUhDtWb4wB8PwG7F/ScMzgI ufMRq8qudcN36z0UrTHyQmkjT2LPDENWNOFWIBeQEO/qgxbzpFWISrMTg093JaZ+GNmDytY+7nY 5s0Gecbdsur3A== X-Received: by 2002:a05:7300:dc88:b0:2b8:2946:72c5 with SMTP id 5a478bee46e88-2bd733d9d29mr300901eec.39.1771571167211; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:06 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 11/16] HID: hid-lenovo-go-s: Add MCU ID Attribute Date: Fri, 20 Feb 2026 07:05:24 +0000 Message-ID: <20260220070533.4083667-12-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson 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 20eb472d7a247..9dfc7a6ce84ce 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -18,6 +18,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; @@ -162,10 +179,41 @@ static int mcu_property_out(struct hid_device *hdev, = u8 command, u8 index, return 0; } =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, @@ -183,6 +231,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 @@ -203,6 +258,7 @@ static void hid_gos_cfg_remove(struct hid_device *hdev) { guard(mutex)(&drvdata.cfg_mutex); cancel_delayed_work_sync(&drvdata.gos_cfg_setup); + sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups); hid_hw_close(hdev); hid_hw_stop(hdev); hid_set_drvdata(hdev, NULL); --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dl1-f48.google.com (mail-dl1-f48.google.com [74.125.82.48]) (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 F4219334C1D for ; Fri, 20 Feb 2026 07:06:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571171; cv=none; b=Ysr7BPDj71pmMbLSr58LNr+pf307IWsEJcM1TqE8YW75KGDwPUzMEnblgCJKbs8p9TGQzenJ3qMqXz6wJ9gBZ+zZE5nAxDHq10wU03gZSecEZfXOzIuMH6tN3jAIUDkaqBKypO9oX4c3YwWiP/KoyBnF7Ntd9GQFdL8kkLDgPSU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571171; c=relaxed/simple; bh=PQS0O/xP9QrU7eCKPZ0tcugfdLKumR95mLps48bh9TU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MkiuxkGzPHdPqeptABee2HpVbjwifArGnDJ+PnOL/CmkAsoUqLjCUhnmoPOuVx/rI6dIWy12q88DbICtc75J0/Hid0GxgaQw1C1s/INcXHIbQjutPF3As3q62zeiNTYeZVi+i2Sbb75LM0BZIcMsxIPReWlTw7Ds3x984xk4ZYA= 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=k/DvAmHM; arc=none smtp.client-ip=74.125.82.48 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="k/DvAmHM" Received: by mail-dl1-f48.google.com with SMTP id a92af1059eb24-1271257ae53so1643850c88.1 for ; Thu, 19 Feb 2026 23:06:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571168; x=1772175968; 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=H7wxmW6cqvMYDt3/uzGVdSXC6lMe9n9uYlaQHuq/jfc=; b=k/DvAmHMtjOwPkZjl3dd+ViJiYacOFestuyYmymzdi1IfluOkezkFUtjvXn4aa77i7 9TBt/wuroeJb4VL8I3fQYpSdJPJFzNCsQZHbMbEnnDuJd1uP+Sd6KBmI9gXzEMugcGEP 6gQ8hLFqKkvkC8bVwWuKMJh/ZoIFSkdXTjqhPrMToGtAkJ8M2JYCnCyDn/fTbmRCCs1D cmiwZ/wQD0vbs5NQcydMRUg+g1mMhindJ1mc83JWnVxc1He8b1KsPdAcKnayjD8A4pt6 5f+WBBmnoPmPL1lM34R5QcizXiB+bg2rg6T88YCHRP0K8QF/x1VTpRtMXjvNfgcnMy3E JaLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571168; x=1772175968; 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=H7wxmW6cqvMYDt3/uzGVdSXC6lMe9n9uYlaQHuq/jfc=; b=uKw7X5KVfkmjZ4dSIf77mPnH6D4ocXQDK6NSJHeufsJ5cSe+Ob97T9N5wj+eqb1/34 suS6J47CShRFNHegrfL1BeTIr7CxR6ZSyBL4E+pcy9kPtARuc668QtqPFdGfE1WbXCd1 nNIYGOgW0TQP3itWi0a0qsqoLAVpuWkhfBDHisx6QAO37uNGdarAz+g9+Yp5so2Xqcjz /KG213/5Qa72Nv7MEC3WaKo7P8HWBnHqTWWJg5cqqu7DLjM6vTJaXKaqIt5lDkRtwDwQ P4EBWgWuWQ8E/lZeCEomhf2ToFeY9t8UpGdqNMrN8SX7PWzvi3psA2x92QFI9hkRltIG 0p8g== X-Forwarded-Encrypted: i=1; AJvYcCUQUYXW90TuQaxTYiSYgvLM54wlR0wrBxrX+/fn+TqjMhcpxADLM0fJrgFgk7BS84ZcETjP68Yc/Exl1UU=@vger.kernel.org X-Gm-Message-State: AOJu0YyG8ubx+XU+T67puQYEd3cnFNQJVIH/X3/gVNK+3qBR5gMwAIY1 N1xWH0E3AmBz0dUoHfqCubx2O+LlrtVHhxW5soAfMQHBH2xICJQdaBFn X-Gm-Gg: AZuq6aLQsW27/GMksfUqKvgPlcGiD7D7JikZZO+0Mi5TqBhL0VCx7LolXE18nyfBNU7 VzqGD7U46ury8oHQx2F23JMiB/eNSrV3IxNLq6B+2xJV2ISfDSXEwY80ERsFPZpDxvypUptc7Ik FyElCn6JP5lPiJP0XykEhLlx5QJqEcAyB5wmOULwtZdWpJ8ky2yFDpIYb6LyAy7hy6PGakFDvB/ ixH5i3cL59jSMeiSaRaCGWrR3/pWWBCSmOyXPZc22bSt8fhvywzk+otbc5/sPG/KkXMmU/UFIYE T/gDaup5YN0ksLBGkj2lbrqCpMnGo4Imi+6cX5U6GEXUrbqv5ZV5FBwqxSu7h4QZkmqVM90EVXE 1YeyO8/d92gDxIvbaoevxkFNcdTvxKTkCnCH+o+B7w/hEbVCIvIJZkhc+24G1Wh6KyWdt8L6UMl jqcXnQFKp4P+GRBAYtmls9ZrXbLPcLMzJckPXBGBJ8uNRpe7td4tu9AaoFHeYIUX0/AjDNkwtk8 hM= X-Received: by 2002:a05:7300:d717:b0:2ba:a4dc:5c06 with SMTP id 5a478bee46e88-2bd50147597mr3693517eec.20.1771571167860; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:07 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 12/16] HID: hid-lenovo-go-s: Add Feature Status Attributes Date: Fri, 20 Feb 2026 07:05:25 +0000 Message-ID: <20260220070533.4083667-13-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- v4: - Cleaner formatting on debug message. --- drivers/hid/hid-lenovo-go-s.c | 523 +++++++++++++++++++++++++++++++++- 1 file changed, 522 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 9dfc7a6ce84ce..9595994ce1bf9 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 @@ -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; @@ -179,17 +313,332 @@ static int mcu_property_out(struct hid_device *hdev,= u8 command, u8 index, return 0; } =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; + 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 @@ -197,8 +646,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 @@ -264,6 +749,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) { @@ -320,6 +826,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) }, @@ -334,6 +854,7 @@ static struct hid_driver hid_lenovo_go_s =3D { .id_table =3D hid_gos_devices, .probe =3D hid_gos_probe, .remove =3D hid_gos_remove, + .reset_resume =3D hid_gos_reset_resume, .raw_event =3D hid_gos_raw_event, }; module_hid_driver(hid_lenovo_go_s); --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f179.google.com (mail-dy1-f179.google.com [74.125.82.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 63445335072 for ; Fri, 20 Feb 2026 07:06:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571171; cv=none; b=DDnkAUbpTNviInSYGOdU83t3dqJKvUdEh4ZGqfJP3TudXGX5Vi0iHuXKyvaiU416UoAhbQflyyXwIxepuTMb3olltwL3Gcpj5gMuAR91Dkmt31eN63m5z74M0z4Ds9c1OCzGcKNFhWgP9aap4SGG/2zKAHiJ9ECsRtpj5tuimNY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571171; c=relaxed/simple; bh=+9MUdfhqu4ktA5Ox0Yc+kI51uVbupxo1vtKU3v8emIU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=LsOG82Up/KCc45I9dev2xFaOcupJCVdD63pEP2zhOQ+t63U1XCvGzrGPb45r8WP6dFRBBaxSPEubt9d3T0hP8CjXM+w0DhTDSUCfUcgipetyYAxy531OyJ1IRi/i8a9DK+7XoIp+VeEFuTh9dmxMAQno8GiTPPzA9Tt/PLN8ioA= 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=i7Mn2u11; arc=none smtp.client-ip=74.125.82.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="i7Mn2u11" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2b86ce04c5cso3447002eec.1 for ; Thu, 19 Feb 2026 23:06:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571168; x=1772175968; 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=9JJ4rxkXT2eVFW1PcohwFIvZsKZ0n8nS1xGcCEWCiXQ=; b=i7Mn2u117e6v9M06ns1o2b5mOPBMdbR0TN5FJnfiFiun8gTb/AAsu46X+9kj8rd/H+ phi23H83yh/toFmKWLFBLSUCPjWGhOS4+swRytt2QmFhij3HPF7H6bRKrW33UNx6LirB 2064/vTczPjECO7bIxduWeBkKRDcfVSwJFi8VIqlMxz9iXEUYT8uYANz4nNmp0oSyASl J/TwVG08VH6FFcaMWnJGLrESwKe/NcEVZYIta9psKoeQnRzLzwgOwcXUO1URKnxnNS/j 7Si+C7nZopS70N2ZCOolmexc8w7nJTWzp40rm9VTYkLl5i86BzEeZF0Ljy4CIUoCQ+qV YR1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571168; x=1772175968; 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=9JJ4rxkXT2eVFW1PcohwFIvZsKZ0n8nS1xGcCEWCiXQ=; b=nn2k9g42JfLXoAa0XXkFYytrxq7XUqjP/4u7Rjedj4Do79iQcNC6bzowGki48p6EPa tsGY6enXV0ycu73czyd5LQAx5NcpoGYskCk05+87jAd/L1UiuGOBDKzFboNiyxTeDdOG I2fOgADf1txKaFBlLJpnFirAJ2F0mgULZhNdd4ToCWGKY8rH+rE5E2RbNT5OlNBckkdx Lu/rvbdLvXPedRvKsh7gDfFtdLi+AMOtmV2OT5OHjVt4bYitPNJoxJEqb21lWzipzyPW FVLvCuSQYtz7QpAaebJYmktndETebHVFrX42bfGKv54p0yvewXNFjcuFbebZ0dDgasJq s7yw== X-Forwarded-Encrypted: i=1; AJvYcCVM8BY7TfEwamAN/YNSf0DiH12lnTcs8KVKqxRBfqacGBapBg0DW44dXtDGWu4uokyxKzJh8XfXxzIauUQ=@vger.kernel.org X-Gm-Message-State: AOJu0YyWzMDNOXXt9GJ7tdriqfuipPp957Co1u6igtePSeKp9PSv/GfO RqOLjk4U6bg61Ccr3fr89McIY1VnmQO+aeqwtQs4RQolaWpF4YBX/aGB X-Gm-Gg: AZuq6aJ0fVexuoQsibxqAARJy2Ww3dLl1wcoNFFnbwR+3sKAqI3hrBHVS8ypsDYRwWD ASwtVpPafcylxq69XCdTQKHODZdiBpCU/HiZkTeY+lVepKy4zBukANckhZFD6wrLtT52+npy58p p4pJz+iF+Mv0OBaUzNLDkEm24whqagCzFAsbhpGTw+9Bb/RcCI6d6/X1P3moWh7SO7fC1N+j3Fz UgFJmuudKnAfxAkFbgipjrQXnO35k5hXalEAcZaQJRRBrQCkIL3QR7/CeXpMJEK/MheJUPgYFwf ucgyFxYnyZ+5GrL9c8T5+j2PnpCupA51qH39kyCRnO0k1Ju3EBnW4qrBFivGfNUME73KQZoAtWa Eaa35fsVnI0lt2NABXy/jO5vYnZEgML6Fjc2ZSPCpOYTfpZRDoj0l47m8jdEZS0UmxzbnMJ77tm cp0mZxJfo+hfL8Xe70uL5qyjLtiD7u9B2gxV7ni3SnTDZPcFBQSg+bjYd0aVBO4y0hWA5fmcNxI e8= X-Received: by 2002:a05:693c:2c06:b0:2ba:ae6f:8149 with SMTP id 5a478bee46e88-2bd73359090mr294413eec.16.1771571168487; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:08 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 13/16] HID: hid-lenovo-go-s: Add Touchpad Mode Attributes Date: Fri, 20 Feb 2026 07:05:26 +0000 Message-ID: <20260220070533.4083667-14-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson 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 9595994ce1bf9..92ee2602273af 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: @@ -538,6 +579,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) { @@ -667,9 +797,21 @@ struct gos_cfg_attr touchpad_enabled =3D { FEATURE_TOU= CHPAD_ENABLE }; LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); =20 +struct gos_cfg_attr touchpad_linux_mode =3D { CFG_LINUX_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad); +static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); + +struct gos_cfg_attr touchpad_windows_mode =3D { CFG_WINDOWS_MODE }; +LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpa= d); +static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_ind= ex"); + static struct attribute *legos_touchpad_attrs[] =3D { &dev_attr_touchpad_enabled.attr, &dev_attr_touchpad_enabled_index.attr, + &dev_attr_touchpad_linux_mode.attr, + &dev_attr_touchpad_linux_mode_index.attr, + &dev_attr_touchpad_windows_mode.attr, + &dev_attr_touchpad_windows_mode_index.attr, NULL, }; =20 --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f172.google.com (mail-dy1-f172.google.com [74.125.82.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9E91B3358AD for ; Fri, 20 Feb 2026 07:06:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571173; cv=none; b=Fgb5GYw1BVMZl6pjo9ELfDXxDHJFwuqXBtfefjBa/X+9SgSPuajyTxJSNi1EB8K0Bc1YCsDPnKHyoYEt5qQQwwVfbFdSgp4VstENQbgmdgbzlrL2v4fjW2IQqbXZ39eyuspelfeSHnEtT8bREE/AF1fbQ9vtJdyyUP45TkMu+pU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571173; c=relaxed/simple; bh=fzKXzyOQG/cJw0TPEfKEey40TAiQrOEC2i+fUFr8eIg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JT8T8Xf83tG0rL9v/rmHixB1m127YYhy1IjhtlWQxPPCDWDauZ/4aEZsu1tQ0C8dlfiCz0uEZ1dDUGHkEuC1tsLNQ/J4ftcUK0keke8qIxF2L5zzM5Fxk1tw+XDrBQqjid12dWcsXszANCwiKVcQkxgFfIaZeIuj7aSqx7UAfh8= 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=Ws6D8yig; arc=none smtp.client-ip=74.125.82.172 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="Ws6D8yig" Received: by mail-dy1-f172.google.com with SMTP id 5a478bee46e88-2b82c605dbdso175143eec.0 for ; Thu, 19 Feb 2026 23:06:10 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571170; x=1772175970; 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=Npz/DO6A1To9+IfbBZ/vWNWNQ5vniB5ysHW7wgH0VI4=; b=Ws6D8yigMgJenuDfCuKkpr1UPnvfTdaRtEIQKIk7lK4TSJS1CloIhAXwPdeSkxFW1o NyGvvAaecrjLGFavzmz20g+rtom4iYiR1STkKTKpmZxtrqWsiXa1CTte885JWHXzXuu0 3y2VDa7XkRMKWeN9NzLsRzrmObdL3TOmnuBoHnOge5VwJ4L3iz2Dc8wRphHFOI87rCXg QUXaYY1BJ1RAPL7+h6f+XZ2exG5u4D1a2/N5SXo1tAE6gv+kyvmJGcyLo/4bAPM0YuQS 4ylsvMcUwrO987gaZsKyCw7r6X57yZOVSGRX9VlEIqtU1TdqCeG5C9Us/rZ40sfXXnMM 2j6Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571170; x=1772175970; 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=Npz/DO6A1To9+IfbBZ/vWNWNQ5vniB5ysHW7wgH0VI4=; b=uZPgO8wNfC2M9SoO77vJXo/3bG5l0ujGbOPOSw01KwK47PIvla7Sl32jXcx96cdOLn Ka5t0g+VGvBEpkFOoFcuI0gpk0W0P6xF2OHqkBes/81t+xQhOW1jJxAkIVFWgHDl3A1m B63hq78OKLZWD6c9eBxsMZErHUYP0mXppEUbgyAgOzELOflS0VcorP8716M7pCOXQ2w1 bqV6p6bpYI8GXpYI2+lY/0umP15ftIxOwgQeHZfhlFYR/ZB+59Mh+p7ZE1KdK2ttgi64 OzU0//2+nR23DfD/kgnBES3VbQEK4xy7AD4VMDU0WZI1MMfDPdWiURm0BF3vxhMQjMNo /kdw== X-Forwarded-Encrypted: i=1; AJvYcCWVIMg01UA6cYqzj5kbh2is8uab1zC+7w/Bf8Tas7IjdZt3Mit7xYemHwOJK4ClLKRz61Z7pUoatMwgwpM=@vger.kernel.org X-Gm-Message-State: AOJu0YzLxSBg2lIaZRP07tPxvN3qG4ArZKvWAJwV/iwgeYjjwbpeVR4v qQ2afP7w1VNsSJFWe86cJXSlJbKk6frch8Bv0Gpde0Nz5NweuL1kOs2t X-Gm-Gg: AZuq6aLBlBJMy9FW7UZbOCs9WsOfFb3RcJPBLu22LiJOJpUWFR/Jj2uDGEjucvZFPNX RRxd0DSy507+SnR++JAa098q+oI4eckKp8fCI/xrVXe8+F0PHIF/CoVedZ+jUcFFIgc24cazV0C hTW056417HFiLNZMMGuG9dLGHB/lGFRWePAA+OArBi1tEKiHIRJOZKA8xXNZPa/1bb3cRfHIS29 unqlFI1i6NSU9rP9v7m0m7YayjHQj79jX/0d7Ulu2lKRAN8YoE0gNRhJX0yJqJCF/qRlKmxayPC 2kQz5IBazZolGlEcaXDSSbv2CEC2Nul4pvJ5j0p7/qCkmzUKcQ379KOgPmR85vCOwwoaGq1ybKR 5+nnm3+boKSJKha88qO9kiHflPXfU3kQOrLtPrBK7A6zFSQcEX+FWIvukcsggOEbrclpjtFKkGF WhXfxZyceqFAykx4ZLnJ7wbbtSjuL40cl7u/qzSYnNtYyH2IMbP4LguIISD87YhduHPvQ3yXI5F 8w= X-Received: by 2002:a05:693c:2c07:b0:2b9:80c1:bb5 with SMTP id 5a478bee46e88-2bd50187596mr3276852eec.33.1771571169618; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:08 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 14/16] HID: hid-lenovo-go-s: Add RGB LED control interface Date: Fri, 20 Feb 2026 07:05:27 +0000 Message-ID: <20260220070533.4083667-15-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- v4: - Cleaner formatting on multiple debug messages. --- 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 92ee2602273af..2a3b8ef9db06d 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ struct hid_gos_cfg { 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; @@ -674,6 +749,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, \ @@ -829,6 +1174,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; @@ -847,6 +1256,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, @@ -865,6 +1306,21 @@ static int hid_gos_cfg_probe(struct hid_device *hdev, return ret; } =20 + ret =3D devm_led_classdev_multicolor_register(&hdev->dev, &gos_cdev_rgb); + if (ret) { + dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n"); + return ret; + } + + ret =3D devm_device_add_group(gos_cdev_rgb.led_cdev.dev, &rgb_attr_group); + if (ret) { + dev_err_probe(&hdev->dev, ret, + "Failed to create RGB configuratiion attributes\n"); + return ret; + } + + drvdata.led_cdev =3D &gos_cdev_rgb.led_cdev; + init_completion(&drvdata.send_cmd_complete); =20 /* Executing calls prior to returning from probe will lock the MCU. Sched= ule --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dy1-f172.google.com (mail-dy1-f172.google.com [74.125.82.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 86BFA3358D0 for ; Fri, 20 Feb 2026 07:06:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571173; cv=none; b=eAeYRh1tNtjy4NEKFqnqpJi34jvHqjDdNmZrIZ1rB2gFecTQ8Nkex7xnVD87VssoVEJjF8wrbPjGolXGyZ6OTf8PEkPYoMH0p+fRZReaMvdiYxWR1EXUwKNWTyjGKd/0IwEy414owzBC4XZ9KmzmpkEBXGWtBuFGTGBL/b88MYw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571173; c=relaxed/simple; bh=FDsEWTPcoALfgSUDbXF74OgZhwSh9yLvG288QGfUHp8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=J6saum33as8suWSINDIf/mmsYKxo6DWxw8il1sikjQVeWV/hp71AhgblEXFMKaNg4uZENTjdSkgLkcchQSAohRdFnXaTGNv6K7q/ZxSKDp9ljD0E5fZ1c+j9vY90myCqNMpwNVM/Yoj1kYjXWN28gZvvKVpVAeIViDjNrOMXgOw= 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=JvWBcgTM; arc=none smtp.client-ip=74.125.82.172 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="JvWBcgTM" Received: by mail-dy1-f172.google.com with SMTP id 5a478bee46e88-2bab70f8c8aso1581408eec.1 for ; Thu, 19 Feb 2026 23:06:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571171; x=1772175971; 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=R/cWKYU5Imx6hHH8nw9m+kmRNo61QGfuhGO9F8IkTNk=; b=JvWBcgTMZpt5cYXFVobYDmRXDPlUNt9cqCjIuR9In8YpA7EQCQElgqkhIl06q+/EGz ptPYgkXoWxoqHKyjIJhRrLU9cMSDpYtbmzcUcYIiTv1I7rqV8h9LmfTUuu+EUU1laWo1 4rVlYt9m/sDPf4jDLgEY5BzZhdiXZ8VJaDzRZ8cYmYSriYwY/9TDbT1opvXGK0xx3Ir0 X8YCehVHZDFQV7mCujSaERFiQSWSbJGrqW3OoPeKh1G4f9NwawYrDB7ciFJWGiXPcSsW XuELV3PFN4dWoExaZ9bpPGmi59zZQ26TsOTn9Q+Qe5M5706im5y5isuNQvPUZp415oKD TpZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571171; x=1772175971; 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=R/cWKYU5Imx6hHH8nw9m+kmRNo61QGfuhGO9F8IkTNk=; b=dZZn1N4VR8CP0d+GmEmj1HpbRilo3pqyH7f/qn2MNWEe/CN5zHH5P422AEvjz9VyzG rrsbLuL8AYDuH2DTFR3pEP4PKW9fJK3ZZU7aU6NjuuuisGBNP62Acvt6EX8Y65lBIS3s o9i1bpdj+jBVUC3qRofeWM8vZqbm/LgXx/WDCD7KlUSG34IVOlPWLTITm49eM2Ryzu6c KxVxqJFz3hlFZg/ARPXvzw0BhSvynWdcBksEipkZQYkb7f3sgu88xcuDcT+59i76tq4/ klnkUKjNbK2VG/ZKUd11DIfu8Quf61pFNgPUg5icwOzLi6M24VRXCVfpLsBUY/3kzQh2 eZMA== X-Forwarded-Encrypted: i=1; AJvYcCXtga2ndmj1E14trXr+7tlpDHQ2Fbw5ofAa+pcEAGlFT8YcHulcse9OuKTOEAkI8IdZKol1r7X07VGJyYU=@vger.kernel.org X-Gm-Message-State: AOJu0YwdDXNU9Fgpprm24+VHkBrkRZT4wcZMyrerEzqFFoOSsTgAij1O +YvoEfo7TIrMbfiDl85OFl0/gqXLOlfHZSK1qogX7t5I64YMR74WaTUs X-Gm-Gg: AZuq6aKzhdMw+D9VJ3NbxFRe5+MO0cnM+9SC1sFifW0ofb8nkmPN/SvLR0fbyTEpBmH 0D81fIQd6kJL9bQXyhH2wAUnbQGiNJMKDWgpNlVFnhdtYIaEAGIlCYFh3WeUB02CRzHIMXDtRXJ I+NgC3I+btpYfeRj/7LSmqyYG8xthO1qq3SGsPkjzGDdy0AEeLf7KPgDbBW5BvKwLRuTiPPThA/ 7cQfK6xPLqwlHSsGwj+QXZ4hKV5n9ZSfSU5u1hM2TPAwO2HxCfA4dyiqIaZNWB9h2TCHFylfYJn AgI6vnRHtFhayQI7rrko429u/5U9Vd8SoPhmg4hpciLkMpf134ttoPdMRN1Dd2W2YpvvMq0rJ0S 5WaqU5UayMt/WF2SlCdTugC9lkqg0SWSd6CGqnshvfJiE94qhZeAj0O/n7qlsvLcpwUTzRcaufD iwQXI+J9M1IU1qiJCgFuGJ4ay6UlHY0bxVOyCETTdy7ot5n76dNpvxeqw5r8DM2d9MQSYyrQvXN B8= X-Received: by 2002:a05:7300:a284:b0:2ba:8da5:fd6d with SMTP id 5a478bee46e88-2bd5009a48amr3487321eec.15.1771571170626; Thu, 19 Feb 2026 23:06: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 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:10 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 15/16] HID: hid-lenovo-go-s: Add IMU and Touchpad RO Attributes Date: Fri, 20 Feb 2026 07:05:28 +0000 Message-ID: <20260220070533.4083667-16-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- v4: - Cache RO values at probe instead of calling them at request. Values don't change and reading the touchpad data is slow. v3: - Fix bug where the touchpad attributes were assigned to the touchpad _show function instead of the test _show function. --- drivers/hid/hid-lenovo-go-s.c | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 2a3b8ef9db06d..cabee8c958696 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; @@ -743,6 +803,37 @@ 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; + u8 i; + + 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) { @@ -1086,6 +1177,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"); @@ -1093,6 +1187,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, @@ -1146,6 +1241,12 @@ struct gos_cfg_attr touchpad_linux_mode =3D { CFG_LI= NUX_MODE }; LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad); static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); =20 +struct gos_cfg_attr touchpad_manufacturer =3D { TEST_TP_MFR }; +LEGOS_DEVICE_ATTR_RO(touchpad_manufacturer, "manufacturer", test); + +struct gos_cfg_attr touchpad_version =3D { TEST_TP_VER }; +LEGOS_DEVICE_ATTR_RO(touchpad_version, "version", test); + struct gos_cfg_attr touchpad_windows_mode =3D { CFG_WINDOWS_MODE }; LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpa= d); static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_ind= ex"); @@ -1155,6 +1256,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, @@ -1257,6 +1360,27 @@ static void cfg_setup(struct work_struct *work) return; } =20 + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_MFR, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve Touchpad Manufacturer: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_VER, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve Touchpad Firmware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_IMU_MFR, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve IMU Manufacturer: %i\n", ret); + return; + } + /* RGB */ ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_RGB_ENABL= E, 0, 0); --=20 2.52.0 From nobody Fri Apr 17 10:35:54 2026 Received: from mail-dl1-f65.google.com (mail-dl1-f65.google.com [74.125.82.65]) (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 36D1F337102 for ; Fri, 20 Feb 2026 07:06:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571177; cv=none; b=cLmlfgNV7Iua9wlpgy7tTtqH/KIaqWj53v4fzzDX1wz/+V2HX2r3iQmu3lT1Inn0sG8pvNy+eNNu007mAG9GRRjMxmV5YyX01bGt1YWxy2znqRCkiHVJEBk1oQ2VDRIZ8ZHlVTqU8dFAWh/+E1lkR9ZY6E8qJ6rig6g6S4k4S+Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771571177; c=relaxed/simple; bh=ikfzksm+wLsqocEkArkkEVPF+9NtY6K0g1U6x4qnaHk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NEl1jnFtr9YS8xjS9sDKIgdLTwMpEtVJf/w25htjjpAN4R8ynLXGCWRLAFTpJctB6SkxqtN+sWODV98HX0ncwgLJanUlv5VDfk/43sVQVQ8TgIKA6OfqB+8n6PU4BMzcPdpOjkCnVDMzvR/JAehfnsYHu0Seqm/QXxukUCGtI4E= 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=CLJJEd6h; arc=none smtp.client-ip=74.125.82.65 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="CLJJEd6h" Received: by mail-dl1-f65.google.com with SMTP id a92af1059eb24-126ea4e9694so1840096c88.1 for ; Thu, 19 Feb 2026 23:06:13 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771571172; x=1772175972; 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=22FKswPIkgTaZrxf06G8nEvQoSlteqKZSt+NVe2oBnI=; b=CLJJEd6hDYCYeWaLqRFCJU7cYkoy49WLZLWIJReuYWpClBW3fXBDJ6oLK6pL1C550w 2YzkL+MosoCgMqAGPx6hhyUcHUtyI9j97At4H9ixQ9wGSSScg9LoZog0sdhz02YEARHG l33wxJo+41tDkY10JmXWJZtjpWW2ZUDMeqfY/t6QLtqOrIIj0Ht2ZyfZYV6xFpwlm0PC XuBdrjrZMOYWFrT509IwjfbBOvsPLbv9znNrZgM+WsZkvHiQ79+LBrDOjBTSnyn97M6v raWtkAZWjIlQ7jrL6nt2xHuNj28TTYYBsfG2OfF8DMTtWJqfT5vHfJNNjumVkufN+OW8 ORlg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771571172; x=1772175972; 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=22FKswPIkgTaZrxf06G8nEvQoSlteqKZSt+NVe2oBnI=; b=wDiX0zyk/JHoCSahaXnp+l6cuu5BTXISIYJUIxY7xrdFR1Jdc6w3VuN07JvRhjc8pV is+DWrCVslIPmZbUiuViuyuzHTehYgCYEwXVEb2lt6GjqqeDTFDd8jlet8y+9aa1xHZY ST6t8Lo3otARek7+6RsoIF3I9sRkGLYpbAxTtjEQ0bINKWH6Ns+0j1Co/CGIJMk018y8 fSNz3D9JzY1d0lC9hJ+oz3Bq5gZsOT2pT6DNVI2ccMzQrhAZlyBDOxgPNMZS+2RpzroG K9qGIMf/GXApbLMAWeyn7w3BFNAB9bH7X9qEZTe6NUGa5zV9vCSHjZfzUNlxQK5q0ycu DDLA== X-Forwarded-Encrypted: i=1; AJvYcCX6lGFKlu0hhzofe/tYCk9B4+0coOuuHLxdS1HxjENx+a2rFzvad7Q75R8Lo+F+5H9lAlJBNAL4tCKc4hc=@vger.kernel.org X-Gm-Message-State: AOJu0YwVnXeLR+pnBctsa41kGvwhd0IY9xnR0Tw5OGpUImqYlicwI2a+ Q+XDahiXEjMdVxj6GYk/4g5lxa3UFPHjTutrdbpkTHZMIfujQqQdzKzU X-Gm-Gg: AZuq6aIIk6h7RdcYmcxoDz9puC2iLt0r1hRe3kgco8kx2CKer741FhLeHEXMFkLGoVn AFXyNAgmCCcndTRg+wkh/xhOleFNiDxuYCIqwyLAUiYGFMrRHlzybNKb7I0yDq7lVoNWT0Y/CX0 YnwVR5zJlj3/3GDYeGT1iUD1Z6SaVZCfJDYR/AYNuNj5mlrfJ2AhuG6NRCJhES8rsAX4aAo7Iz1 SUoEOuYLuOF3pCp68TC0gzdJAW2aSR4/HUcmkupPOkTyEYnbxdLCY4H1zkq+34GV8LEdCQS+6Jy u7jNcGUDSVKrulUto8u4BXABXr1FA/Nect9bfZLTC2k2ahOfSoRwqaqjl+wmQuSrpSewqdldBhj wZUMp5K4dXu7jDNiuAfq3UTZ0j3OohOOpZQinHV8rIx8HGSO4QUqtY6JFdHTYc109Svdz97a4Sz 3yPtr/MbUqi02bORShZD3QktuSRI99uD6bAJIJg9C+4FGJrsEgUZj0b4N2fPNFbTbT917vH+7fw +E= X-Received: by 2002:a05:693c:3013:b0:2ba:a3c5:ccd with SMTP id 5a478bee46e88-2bd733cc98amr260099eec.37.1771571171831; Thu, 19 Feb 2026 23:06:11 -0800 (PST) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2bacb669f7dsm22163335eec.23.2026.02.19.23.06.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Feb 2026 23:06:11 -0800 (PST) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Richard Hughes , 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 v4 16/16] HID: Add documentation for Lenovo Legion Go drivers Date: Fri, 20 Feb 2026 07:05:29 +0000 Message-ID: <20260220070533.4083667-17-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com> References: <20260220070533.4083667-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- V3: - Remove excess + from every line of patch. --- .../ABI/testing/sysfs-driver-hid-lenovo-go | 724 ++++++++++++++++++ .../ABI/testing/sysfs-driver-hid-lenovo-go-s | 304 ++++++++ MAINTAINERS | 2 + 3 files changed, 1030 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-lenovo-go create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go b/Documen= tation/ABI/testing/sysfs-driver-hid-lenovo-go new file mode 100644 index 0000000000000..c8221373ef76a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go @@ -0,0 +1,724 @@ +What: /sys/class/leds/go:rgb:joystick_rings/effect +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the display effect of the RGB interface. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/effect_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the effect attribute. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the RGB interface. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the RGB interface. + + Values are dynamic or custom. Custom allows setting the RGB effect and c= olor. + Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curren= tly + supported under Linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are dynamic or custom. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/profile +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls selecting the configured RGB profile. + + Values are 1-3. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/profile_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the profile attribute. + + Values are 1-3. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/speed +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the change rate for the breathe, chroma, and ra= inbow effects. + + Values are 0-100. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/speed_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the speed attribute. + + Values are 0-100. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the firmware version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./fps_mode_dpi +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the DPI of the right handle when the FPS mode s= witch is on. + + Values are 500, 800, 1200, and 1800. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./fps_mode_dpi_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the fps_mode_dpi attr= ibute. + + Values are 500, 800, 1200, and 1800. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the internal MCU. + + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the left = removable controller. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/auto_= sleep_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's IMU. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_gyro attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_status +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's IMU. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's joystick. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick_in= dex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_jotstick attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick_st= atus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's joystick. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_tirgger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's trigger. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_trigger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_trigger attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_trigger_sta= tus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's trigger. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the left removable controller's firmware versio= n. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the left removable c= ontroller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the left removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function o= f the left removable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_bypass_enabled_in= dex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/imu_b= ypass_enabled attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU of the left remov= able controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/imu_e= nabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the left removable contr= oller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the left removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/reset +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the left removable controller to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the response behavior for rumble events= for the left removable controller. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/rumbl= e_mode attribute. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_notification +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the left remo= vable controller. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_notification_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/rumbl= e_notification attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controller. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the behavior of built in chord combinations. + + Values are windows or linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the os_mode attribute. + + Values are windows or linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./reset_mcu +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the internal MCU to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the right= removable controller. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/auto= _sleep_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's IMU. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_gyro attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_status +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's IMU. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's joystick. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_jotstick attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick_s= tatus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's joystick. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_tirgger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's trigger. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_trigg= er +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_trigger attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_trigger_st= atus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's trigger. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the right removable controller's firmware versi= on. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the right removable = controller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the right removable con= troller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function o= f the right removable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_bypass_enabled_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/imu_= bypass_enabled attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU of the right remo= vable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/imu_= enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the right removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the right removable con= troller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/reset +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the right removable controller to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the response behavior for rumble events= for the right removable controller. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/rumb= le_mode attribute. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_notification +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the right rem= ovable controller. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_notification_= index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/rumb= le_notification attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./rumble_intensity +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the rumble intensity for both removable= controllers. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./rumble_intensity_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the rumble_intensity = attribute. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the touchpad. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/enabled = attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the touchpad. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/vibratio= n_enabled attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_intensity +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the intensity of the touchpad haptics. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_intensity_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/vibratio= n_intensity attribute. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the firmware version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the internal wireles= s transmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the internal wireless tr= ansmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s b/Docum= entation/ABI/testing/sysfs-driver-hid-lenovo-go-s new file mode 100644 index 0000000000000..4d317074bb7e6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s @@ -0,0 +1,304 @@ +What: /sys/class/leds/go_s:rgb:joystick_rings/effect +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the display effect of the RGB interface. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/effect_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the effect attribute. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the RGB interface. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the RGB interface. + + Values are dynamic or custom. Custom allows setting the RGB effect and c= olor. + Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curren= tly + supported under Linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are dynamic or custom. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/profile +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls selecting the configured RGB profile. + + Values are 1-3. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/profile_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the profile attribute. + + Values are 1-3. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/speed +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the change rate for the breathe, chroma, and ra= inbow effects. + + Values are 0-100. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/speed_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the speed attribute. + + Values are 0-100. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the built= -in controller. + + Values are 0-255. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/auto_slee= p_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/dpad_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controllers = D-pad. + + Values are 4-way or 8-way. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/dpad_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/dpad_mode= attribute. + + Values are 4-way or 8-way. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controller. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/mode attr= ibute. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/poll_rate +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the poll rate in Hz of the built-in controller. + + Values are 125, 250, 500, or 1000. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/poll_rate_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/poll_rate= attribute. + + Values are 125, 250, 500, or 1000. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function. = When enabled the IMU data is directly reported to the OS through +an HIDRAW interface. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/bypass_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu/bypass_enable= d attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./imu/manufacturer +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the manufacturer of the intertial measurment un= it. + + Values are Bosch or ST. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/sensor_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU. + + Values are true, false, or wake-2s. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/sensor_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu/sensor_enable= d attribute. + + Values are true, false, or wake-2s. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mcu_id +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the MCU Identification Number + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mouse/step +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls which value is used for the mouse sensitivity. + + Values are 1-127. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mouse/step_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mouse/step attrib= ute. + + Values are 1-127. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls which value is used for the touchpads operating= mode. + + Values are windows or linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the os_mode attribute. + + Values are windows or linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the built-in touchpad. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/enabled = attribute. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/linux_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls behavior of the touchpad events when os_mode is= set to linux. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/linux_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/linux_mo= de attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/windows_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls behavior of the touchpad events when os_mode is= set to windows. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/windows_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/windows_= mode attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. diff --git a/MAINTAINERS b/MAINTAINERS index 1d0468906788a..8eea5f231e809 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14318,6 +14318,8 @@ M: Derek J. Clark M: Mark Pearson L: linux-input@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go +F: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s F: drivers/hid/hid-lenovo-go-s.c F: drivers/hid/hid-lenovo-go.c F: drivers/hid/hid-lenovo.c --=20 2.52.0