From nobody Sun Apr 5 13:04:07 2026 Received: from mail-dy1-f174.google.com (mail-dy1-f174.google.com [74.125.82.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E2BED3A961F for ; Tue, 10 Mar 2026 07:29:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127783; cv=none; b=Q7e9ADDGhxYMkAogD6GsQLGLLVzF8JWUYG4k096iixrzG55hYInlvHApYBZvSbDTe0ZO99IuX+V8iLEUKxl9b8e+DBFmkRpXuJAyWDhDN7/CGBl/Xyy8iRWgE8fW7ZRQtb/fwDG1oL1mU1YArY+NerU4x1HsVdqDCteKgqkc+IQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127783; c=relaxed/simple; bh=bpBQJR0Lh7t53ADUklCY+fVEC2VAWri9ShCtFOB9yXU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CfTAG03ATDjmdbR/uZlxdFWdVKtZX3Ute8A5a/WjWJkCrg3D9pKuKoWsO6sCKnw21Nv8kPPCDQXmKNLh3F2rWOXm08kdonS4n8GBRWhoIApxLm60DKZx7HJqZT1P1yv82bi4gGnBb09UYf7WA1DcknH5Mavfu5fp6j/+a59yrwg= 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=CmiPnC1A; arc=none smtp.client-ip=74.125.82.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="CmiPnC1A" Received: by mail-dy1-f174.google.com with SMTP id 5a478bee46e88-2bdcf5970cdso8313858eec.0 for ; Tue, 10 Mar 2026 00:29:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127781; x=1773732581; 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=8XpzAJs17AL49DrJB7/7GFvQC9N3pX4aoTE0ck01glE=; b=CmiPnC1ADHVve5+P2uBi660XU8nmd0O7OKjZQIxEIaupAtgdg25ds4sd/8hAu2zgtX lx4cf2TUoAnom468+1WEjVfOTr4J4egqJmTndpBIztNzfEZSPRBh+fSVPDlcJTrk027S VoUYFxmOXpehBRrmWqcS5HolJn5eA0dq0SXwoa5JK4e0YXe2M5UVGO6St2C3UrzEVEud gmbbAs+zaLotT452odH5l2T74Md0DiHTiyVS41Rh1UAIlOcY4/xVSmOf+IHm8JAWrllw 6xJXCFoNNT2kMGYLR6OP3PWEbNsF0aMM/DyJeKQmCtwg4bGZvhjHyd8LsqKiPy31PnUq Cydg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127781; x=1773732581; 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=8XpzAJs17AL49DrJB7/7GFvQC9N3pX4aoTE0ck01glE=; b=YXRoeNSvmTNX9xpj9dVgaOToBXwM/+QCt5/EACFkLMzvMT3AEJxwruNFM0KwQ5Jjce Fo6vkjeQ2Q3nAtljvdW4RLtNI3ybt+hQx5ktxPq7n7YYayMxQZ/i/n++bjeqHJmf0xpu CUPAUwZx0DhY3czZosC24wWMy1kMN1iLwcCpA736l47mOetiIgqW6pOM7yrhrjeLpLTI 2y6j58jLXtm8JtisCFsMngVztXkMKcT4KmfyRhNp7DJggfv9bVN0UGW0R9cAFUVP0P+e +RI0Yr0owQuzKrg7HnpU+92uEprLE2LmA2ZFRPET0W4PpKogILT2AsG8tv2ZPkUNa4kW Lasg== X-Forwarded-Encrypted: i=1; AJvYcCXan+bpMtzqYi/pdewzUIkDXmDXTA04C8IItDwl3UwkrpzF6HaIMli7n+rCCoB4JmIxxldVq6cchwQ+6Sw=@vger.kernel.org X-Gm-Message-State: AOJu0YybM3oS3vHfSxtbEa5KJaUpSyhB6tdoo9ercyy2mVTtYh2m8MG7 Fou1lvtNM7PgSfWUrtiQi/4CdjBPcy9KOb5lLIyKjTFxuMy2t+LSoadS X-Gm-Gg: ATEYQzx1xWlsHBfctxvusn4fgIcppuMmjG27UblXdodNq4WzlAHTqcEVgkv8kaMlMUX to2syPp2k3ZiaG32H3oB8BNIoxkIvCympdY0jk/R8OMBpjNmbsTLsaPQ3lWynOP+BIePR8Gufpm zB0+pew3a8sZFPOAAoX4AKhBOXUg9KvxANiAk1W1gGypYYMunxqzUmQjX0yF4xCK9g+rZ6vnIw4 yFh9Qo28sY+hm3mTE76w7JCk6SteS3kep2fhnCPjvXs1yqM6DuhDPzZJs8lZnamUADUrSOpL4Wh xoRYu2PoovpjP2o3Z+kjzYLDwA/aT8Smgz7YBYnO4o738TEsqmXrX3NZKo9k8fA6NN+cek2KhB3 TrUhRa9US7EMSfI3DifYM5mMq4RigWnrIKHRQD8SNPZ3kImFGC7kgC8XWqvoVIOM870LLn9HugG W3askHlroP4WZacWguY+IbnUvUFcmh/opHSwfuPT3D60XnBuPrRpy6Y54II3bkHwI8eVeCTzjmq JWu X-Received: by 2002:a05:7300:5712:b0:2ba:9cc4:aebb with SMTP id 5a478bee46e88-2be7a0ff626mr852103eec.10.1773127781051; Tue, 10 Mar 2026 00:29:41 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:40 -0700 (PDT) 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, Greg Kroah-Hartman Subject: [PATCH v6 01/19] include: device.h: Add named device attributes Date: Tue, 10 Mar 2026 07:29:19 +0000 Message-ID: <20260310072937.3295875-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds DEVICE_ATTR_[RW|RO|WO]_NAMED macros for adding attributes that reuse the same sysfs name in a driver under separate subdirectories. When dealing with some devices it can be useful to be able to reuse the same name for similar attributes under a different subdirectory. For example, a single logical HID endpoint may provide a configuration interface for multiple physical devices. In such a case it is useful to provide symmetrical attribute names under different subdirectories on the configuration device. The Lenovo Legion Go is one such device, providing configuration to a detachable left controller, detachable right controller, the wireless transmission dongle, and the MCU. It is therefore beneficial to treat each of these as individual devices in the driver, providing a subdirectory for each physical device in the sysfs. As some attributes are reused by each physical device, it provides a much cleaner interface if the same driver can reuse the same attribute name in sysfs while uniquely distinguishing the store/show functions in the driver, rather than repeat string portions. Example new WO attrs: ATTRS{left_handle/reset}=3D=3D"(not readable)" ATTRS{right_handle/reset}=3D=3D"(not readable)" ATTRS{tx_dongle/reset}=3D=3D"(not readable)" vs old WO attrs in a subdir: ATTRS{left_handle/left_handle_reset}=3D=3D"(not readable)" ATTRS{right_handle/right_handle_reset}=3D=3D"(not readable)" ATTRS{tx_dongle/tx_dongle_reset}=3D=3D"(not readable)" or old WO attrs with no subdir: ATTRS{left_handle_reset}=3D=3D"(not readable)" ATTRS{right_handle_reset}=3D=3D"(not readable)" ATTRS{tx_dongle_reset}=3D=3D"(not readable)" While the third option is usable, it doesn't logically break up the physical devices and creates a device directory with over 80 attributes once all attrs are defined. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark Acked-by: Greg Kroah-Hartman --- include/linux/device.h | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/include/linux/device.h b/include/linux/device.h index 0be95294b6e6..381463baed6d 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.53.0 From nobody Sun Apr 5 13:04:07 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 E401A3E8C5F for ; Tue, 10 Mar 2026 07:29:42 +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=1773127786; cv=none; b=kh0OXmfwSqwSmc6r9bGkECtFVjN8LELcgzPb6Rt2ACKMiHDvF2i1Zc/VZYakf7qQvP7QIl7ri/BdCCdDp+Zp2GE04OKC1OgfHfTBTNu9scgYUrGJJWP11V57/2rU2ds02es6iCP/BpcElFUqv3iI3DckYAO7I3F7GmLAbbDG7jw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127786; c=relaxed/simple; bh=Z+3l3k1U2vdMyA8Jk5loUT5RkYG3Zwq2HYeMIff2wuY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Knnkx4Kg0G2Q46rdI09ugnwKPf+gOFt15ct8349IraiIXOmBBY6MXZI7Docy1EXoehwZm36mVuXhV1S+dc88pqa5XMFlya3VSTYRMclR9cbjN3P3xBj43uoxMqvfOrDfN0YhKfDRDe6J0eou5+JSalce08T0j2UuosU/VamjGtM= 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=DMDm9YaN; 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="DMDm9YaN" Received: by mail-dy1-f182.google.com with SMTP id 5a478bee46e88-2ba9c484e5eso12429197eec.1 for ; Tue, 10 Mar 2026 00:29:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127782; x=1773732582; 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=1DcKeokK00rX4KoznzviE7R13UnLuz7mDJgy81EP1Zc=; b=DMDm9YaN+XwTNifkfc6FrCVdwWeFeQfcBZ+QedIRUC6TTyuNPO7SmNCvXk5mN+2R4O mN08DTGYu6hQm3A/uOuie7wo2wtkvxSDFFOm0lwmJfkDGR5nAfiL4KR7TGEQTYoj2hrq LDkJXPDr9pJ62IDj1x0Hf/jXup/JopKfNkG0XM7e9EjBCSojQBBaQgRN8BknlHPMtW4l JvtgJd/fwJRYpbIXwzExDdU2qJEm7C/aLF6yBnpPK9YrSGB1rFfCXEl8dtxVlew/EyE1 5QKCl+KLytmUEDdTZw94P9jc15yOPPOLQtpn1qfvuDpDNGKD0bkzPcHTEnV7j4tJTNfe TpvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127782; x=1773732582; 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=1DcKeokK00rX4KoznzviE7R13UnLuz7mDJgy81EP1Zc=; b=PGc61l0zvxsV2b4U/vo4NDbPgcZ2WDJk6LDb5PvulY7h/O953Ce2azKpFGki+y1DQp UP/BAwWwGbR4MCiyx2iJincjbKdO7ju4pby6S3nfgMs1yB8/7x0VQmSVThmSUR//nwy6 B57d2PcyA9mSyH3Gwxu4TI8UcLGCzgLscmP6nGqum9H5UJanykksz55zeyM+3AXZInzd EUXhq+N6coHzExtqXFhfDwnlMYjdjTGExphmq9GiRUPrGytCxmOlhv/nDHjtfvhXOgjh 7Q+bD2wzdQ2huF/2TTZiyFg0RN0A/HOxUmhrHwW4Y2hriHEbPo9dZjiTpSxbZ2qDVfEj cKVQ== X-Forwarded-Encrypted: i=1; AJvYcCWMV6zwmOv+8c83p54RysBhvKODTXVLzbbKrET5k3UpjZEauJvW0UkgKn0owmUeT9gwhA8WIGNvcMKVcj8=@vger.kernel.org X-Gm-Message-State: AOJu0YxievblxPifzT16a/tm9OFKWa5tqvhlcOFI045HZJzCTKtLvWQT WXK75c9UJsCubBTU5f63VjvWHm80gRErXRn0CyqkDhjgNVvib/bfxCUT X-Gm-Gg: ATEYQzxW427DqNvxxhgCHSwqrQYkIGxZ+T/MFsqyIZzdEWPOQsKupystxFXt1B0blbi zWiwbXBq1+fR6PKwjjWSNL6de4IlbttINqiS96Ctnky6IwUGQQlYl0H+FJXpGgDkTklOZKrjYKr 8xOSAlcGQO8n9FtMe98UU6mFP49wbflT3LtArGU4jZmQH/R6NKHWAdYJ0xY+WKdv5tsoDfuYoaV LOXihbxgqvj76phaAuIQQqLErELhGG4DNWXHsbsFW7zOt4O/2NhjnJIytH2F2uuajjBLuMBvtyF +23vr3PyBulsmSJNwL01NvEzXjQblT0Fk0j2YWISsgfpsxVs3J3DNVhaDuKhEbpg7akypmO+PNj Y0FBGc5Aa00CXQ4bpSyvVyKhb4Lo8SH2tKLUsiP7PCrhx9dkyltmAYLzB4xCeQMYgc1IP2Z4k2e 0Jlhsos//4owkujO2LAwukdaLm9gMKbD8EVgt0ESUE0ebdkGPrlQjCvcZvJC7bXxOc3S+RoGIU9 yH7 X-Received: by 2002:a05:7300:e614:b0:2a4:3593:c7d3 with SMTP id 5a478bee46e88-2be4e08e1d8mr5161051eec.19.1773127781807; Tue, 10 Mar 2026 00:29:41 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:41 -0700 (PDT) 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 v6 02/19] HID: hid-lenovo-go: Add Lenovo Legion Go Series HID Driver Date: Tue, 10 Mar 2026 07:29:20 +0000 Message-ID: <20260310072937.3295875-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 -- v6: - Make attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. v5: - Make version attributes static, retrieve them using delayed work during probe. - Fix endianness of version strings and print as hex. 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 | 914 ++++++++++++++++++++++++++++++++++++ 5 files changed, 938 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go.c diff --git a/MAINTAINERS b/MAINTAINERS index bacaec38aaf1..75d89590f3d2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14415,6 +14415,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 c1d9f7c6a5f2..2925dba429f5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -623,6 +623,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 e01838239ae6..79fbe4e3e2f4 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 3e299a30dcde..093ee86ebf90 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -858,7 +858,10 @@ #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093 #define USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT 0x6184 +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT 0x61eb +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT 0x61ec #define USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT 0x61ed +#define USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS 0x61ee =20 #define USB_VENDOR_ID_LETSKETCH 0x6161 #define USB_DEVICE_ID_WP9620N 0x4d15 diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c new file mode 100644 index 000000000000..a13ddbe28c7d --- /dev/null +++ b/drivers/hid/hid-lenovo-go.c @@ -0,0 +1,914 @@ +// 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 +#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 + +static struct hid_go_cfg { + struct delayed_work go_cfg_setup; + 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_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_product =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_product =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_product =3D + get_unaligned_be32(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_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_protocol =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_protocol =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_protocol =3D + get_unaligned_be32(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_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_firmware =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_firmware =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_firmware =3D + get_unaligned_be32(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_be32(cmd_rep->data); + return 0; + case TX_DONGLE: + drvdata.tx_dongle_version_hardware =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case LEFT_CONTROLLER: + drvdata.gp_left_version_hardware =3D + get_unaligned_be32(cmd_rep->data); + return 0; + case RIGHT_CONTROLLER: + drvdata.gp_right_version_hardware =3D + get_unaligned_be32(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; + + switch (index) { + case PRODUCT_VERSION: + switch (device_type) { + case USB_MCU: + count =3D sysfs_emit(buf, "%x\n", + drvdata.mcu_version_product); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_product); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_product); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\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, "%x\n", + drvdata.mcu_version_protocol); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_protocol); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_protocol); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\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, "%x\n", + drvdata.mcu_version_firmware); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_firmware); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_firmware); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\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, "%x\n", + drvdata.mcu_version_hardware); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_hardware); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_hardware); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\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, "%x\n", + drvdata.mcu_version_gen); + break; + case TX_DONGLE: + count =3D sysfs_emit(buf, "%x\n", + drvdata.tx_dongle_version_gen); + break; + case LEFT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\n", + drvdata.gp_left_version_gen); + break; + case RIGHT_CONTROLLER: + count =3D sysfs_emit(buf, "%x\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 */ +static struct go_cfg_attr version_product_mcu =3D { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, versi= on); + +static struct go_cfg_attr version_protocol_mcu =3D { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_mcu, "protocol_version", USB_MCU, ver= sion); + +static struct go_cfg_attr version_firmware_mcu =3D { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_mcu, "firmware_version", USB_MCU, ver= sion); + +static struct go_cfg_attr version_hardware_mcu =3D { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_version", USB_MCU, ver= sion); + +static 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 */ +static 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); + +static struct go_cfg_attr version_protocol_tx_dongle =3D { PROTOCOL_VERSIO= N }; +LEGO_DEVICE_ATTR_RO(version_protocol_tx_dongle, "protocol_version", TX_DON= GLE, version); + +static struct go_cfg_attr version_firmware_tx_dongle =3D { FIRMWARE_VERSIO= N }; +LEGO_DEVICE_ATTR_RO(version_firmware_tx_dongle, "firmware_version", TX_DON= GLE, version); + +static struct go_cfg_attr version_hardware_tx_dongle =3D { HARDWARE_VERSIO= N }; +LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardware_version", TX_DON= GLE, version); + +static 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 */ +static struct go_cfg_attr version_product_left =3D { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_left, "product_version", LEFT_CONTROLL= ER, version); + +static struct go_cfg_attr version_protocol_left =3D { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_left, "protocol_version", LEFT_CONTRO= LLER, version); + +static struct go_cfg_attr version_firmware_left =3D { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_left, "firmware_version", LEFT_CONTRO= LLER, version); + +static struct go_cfg_attr version_hardware_left =3D { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_version", LEFT_CONTRO= LLER, version); + +static 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 */ +static struct go_cfg_attr version_product_right =3D { PRODUCT_VERSION }; +LEGO_DEVICE_ATTR_RO(version_product_right, "product_version", RIGHT_CONTRO= LLER, version); + +static struct go_cfg_attr version_protocol_right =3D { PROTOCOL_VERSION }; +LEGO_DEVICE_ATTR_RO(version_protocol_right, "protocol_version", RIGHT_CONT= ROLLER, version); + +static struct go_cfg_attr version_firmware_right =3D { FIRMWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_firmware_right, "firmware_version", RIGHT_CONT= ROLLER, version); + +static struct go_cfg_attr version_hardware_right =3D { HARDWARE_VERSION }; +LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_version", RIGHT_CONT= ROLLER, version); + +static 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 void cfg_setup(struct work_struct *work) +{ + int ret; + + /* MCU Version Attrs */ + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Product Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Protocol Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Firmware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Hardware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, USB_MCU, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve USB_MCU Hardware Generation: %i\n", ret); + return; + } + + /* TX Dongle Version Attrs */ + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Product Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Protocol Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Firmware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Hardware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, TX_DONGLE, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve TX_DONGLE Hardware Generation: %i\n", ret); + return; + } + + /* Left Handle Version Attrs */ + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Product Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Protocol Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Firmware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Hardware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, LEFT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve LEFT_CONTROLLER Hardware Generation: %i\n", ret); + return; + } + + /* Right Handle Version Attrs */ + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PRODUCT_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Product Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + PROTOCOL_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Protocol Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + FIRMWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Firmware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_VERSION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Hardware Version: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_VERSION_DATA, + HARDWARE_GENERATION, RIGHT_CONTROLLER, NULL, 0); + if (ret < 0) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve RIGHT_CONTROLLER Hardware Generation: %i\n", ret); + return; + } +} + +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); + + /* 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; +} + +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.53.0 From nobody Sun Apr 5 13:04:07 2026 Received: from mail-dl1-f49.google.com (mail-dl1-f49.google.com [74.125.82.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 86CC33E8C7B for ; Tue, 10 Mar 2026 07:29:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127786; cv=none; b=oWtcOslg+ouET6zQ38T9QlWc8OvzbcHx/YxpLDuRghWIu+BJrQwuG/MDAR2Yo90Eo/KvyEt7r52r5yEWmEplyr8Vt6+d8o6Pfh1HLRdCQvIO9D1IHoAdd6buQjvl3kMk++FKh8ghwUw09KoQ0I/H/MJr3NR0ANQwnu6cohTh+Fg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127786; c=relaxed/simple; bh=nyiaacOorlvYQ+Zt5MOJQ+tl6W+cgcuOAuH7gXDemgE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Zbphu7set6WYqIea6FCjAw5p3LEI3KM6Df1jesu5Z7qyxo107PLJVflsPoKtlaYv2YeRfXAhmYJIomKc7NTukcDHoyh7rypWjfvHios+CLZi8e0SITDOLTcLy6kRTgRor9JCwmBYVo50jv8FXEIb9kjRlhUYFMhCAGqBR/riAKQ= 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=lpBG1+bW; arc=none smtp.client-ip=74.125.82.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="lpBG1+bW" Received: by mail-dl1-f49.google.com with SMTP id a92af1059eb24-127380532eeso1379978c88.1 for ; Tue, 10 Mar 2026 00:29:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127783; x=1773732583; 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=Ezzk6B7CgzzqIrZiOk3pdt+hbzAuGoUY77WLK5hqbZk=; b=lpBG1+bWtq9uap0WqI+M6yEtzS/Qs1hi/RJyss5EQlhdsskCuVAUm5/oROtewIFN+t yYBsG+5iMqgJZl2k+Coz34q7OKf4DEnK912Y3cqCQc9DYwQZ14Lb0SzlqkLnAmL2GsCP HB18Nf/4XvG+L9c1Z4dwxmIWRp276Fac+7n1/m4GQJm/r0GcAyo5rGrbprrbXO2vjGdA Xh3AXi14RpMC9Ez2ZrOXk+6rXSDpqa13O6AxcU1MU2LBGJBsZGs+MYVFTL72faZyq6Hv IZJ/1q/giTdNkvLU3PJAxmOVqDVQmFq9qyli8NqyQsqOmt2VtRwF6R0RDbnauMpEjVlL 2PbA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127783; x=1773732583; 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=Ezzk6B7CgzzqIrZiOk3pdt+hbzAuGoUY77WLK5hqbZk=; b=NuVTvwf4qOaD5kNX6o9wwWk570PPZ8vz/Sr+7IQhqof09hv+cpRZ9JSQLkr0yb1TTM W4g8YZHjZKF4c1WmgBRPVxmnaeC9Ps0YwNW7xrWteuoZSswMYdtZYAtaJgLrry5zQM7U JsVD6pDgSqnluO4gyTuJJ72G3gUbnw/mmvHEhEmwN2Iz3ZYetIrTaJwtNTD1qjKphGv7 cJ/CdCFc6YXUQKbUe4wmBRvN8rzzElsJ5ML7J4E5EJcvMiP3bcMWS4QaOLhKXyMK/A8s U7qQtkHz5kasgTYNodwsNeji7Rp1thytscCIUR0UCIB8dSpDoEKVO/nFTibARtj/G6GM Y0Kg== X-Forwarded-Encrypted: i=1; AJvYcCXBfNxRoLcelFg0G8cirPMs4XSHVPwWbg/ZGL2doI9Ti1M+wx6mOSo3EHLKTWwC2ZznbrGC7bBkGMc+WsI=@vger.kernel.org X-Gm-Message-State: AOJu0YxiVMnfoLEEee+XuAzf2IkZ4yumMRjfhbUDo8pcn79nX4S+76D6 p/KGD9BhyuFjmOHWc2sB5tDC2VJ/aYCqOn1dnnh3mnN0oAHu9R9kVNuS X-Gm-Gg: ATEYQzw3xLo0Sa7OtV7n1DH0Fk7x2JrSW21VwUoPIv00I1bSXrj0p4PnWlfYPKp/GBg DdXQ1s4niLmKDUQW0olDBAT16q6Xwo2Zxh/rolOQ7Q4siEmRpB8JOtsCVmot7JUhfqiMiX36ccx pXa5M3SlYY8yDdqJQIeXYCzDmEktnEgCNN7FOHmrDQqkMl/Bpns0LAaosLZB/FK0bZybProuAMX zsUgHNjSXmb7AnAIv7TClx8EUfBiFYu3l3U7yYRutlF5WwK+iF+p+TYAnFfWv8jWKJQVQZy0vrV dkfQ1shxxS4Cbp2r5EfFiwMkrd2TfiR8XzoikBBbLNREBVR6rPUqtUiPJbjHpMBInLy4d0uDi8r 7W1sBDv49aliQG1Pc++oCHPC10XLutQoapyBB0xAKSWom+oM7om5ImIXke9DidSjUu8qw3cXTQ5 7rb/a0CDbX4biGkGwybGQiyINgKSu3b6//ibA0GMwEITXkA9nJLlCS1wdMyPsNE+IN7i90RQBlj NpQEk5HalS21Ko= X-Received: by 2002:a05:7300:bc0c:b0:2be:6f6:a39f with SMTP id 5a478bee46e88-2be4e05be8bmr6459309eec.26.1773127782650; Tue, 10 Mar 2026 00:29:42 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:42 -0700 (PDT) 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 v6 03/19] HID: hid-lenovo-go: Add Feature Status Attributes Date: Tue, 10 Mar 2026 07:29:21 +0000 Message-ID: <20260310072937.3295875-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. --- 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 a13ddbe28c7d..d7d47db8362c 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -39,21 +39,31 @@ static 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; @@ -105,6 +115,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, @@ -113,6 +135,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) { @@ -222,6 +279,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); @@ -258,6 +380,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; @@ -442,6 +570,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, NULL, 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, \ @@ -496,7 +813,22 @@ LEGO_DEVICE_ATTR_RO(version_hardware_mcu, "hardware_ve= rsion", USB_MCU, version); static struct go_cfg_attr version_gen_mcu =3D { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_mcu, "hardware_generation", USB_MCU, versi= on); =20 +static struct go_cfg_attr fps_switch_status =3D { FEATURE_FPS_SWITCH_STATU= S }; +LEGO_DEVICE_ATTR_RO(fps_switch_status, "fps_switch_status", UNSPECIFIED, + feature_status); + +static 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"); + +static 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, @@ -525,7 +857,11 @@ LEGO_DEVICE_ATTR_RO(version_hardware_tx_dongle, "hardw= are_version", TX_DONGLE, v static 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 +static 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, @@ -555,7 +891,33 @@ LEGO_DEVICE_ATTR_RO(version_hardware_left, "hardware_v= ersion", LEFT_CONTROLLER, static struct go_cfg_attr version_gen_left =3D { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_left, "hardware_generation", LEFT_CONTROLL= ER, version); =20 +static struct go_cfg_attr auto_sleep_time_left =3D { FEATURE_AUTO_SLEEP_TI= ME }; +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"); + +static 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"); + +static 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"); + +static 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, @@ -585,7 +947,33 @@ LEGO_DEVICE_ATTR_RO(version_hardware_right, "hardware_= version", RIGHT_CONTROLLER static struct go_cfg_attr version_gen_right =3D { HARDWARE_GENERATION }; LEGO_DEVICE_ATTR_RO(version_gen_right, "hardware_generation", RIGHT_CONTRO= LLER, version); =20 +static struct go_cfg_attr auto_sleep_time_right =3D { FEATURE_AUTO_SLEEP_T= IME }; +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"); + +static 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"); + +static 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"); + +static 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, @@ -600,8 +988,14 @@ static const struct attribute_group right_gamepad_attr= _group =3D { }; =20 /* Touchpad */ +static 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.53.0 From nobody Sun Apr 5 13:04:07 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 6D10D3E8C78 for ; Tue, 10 Mar 2026 07:29:44 +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=1773127786; cv=none; b=WVHI+6FhkoG7w3EcHO6IdFYu34hTzpovpoh76TVoq7LfryX+lKVyoUUKQOZmMpFND7mrT3LvOCqMjPdQmgCBfChOyCY/Rm2omQp22xT6CVjOvQ9bFcdFaEt8XarmP7LFsjpkGk55/8fWxQFFhMd7zdx78E0RnTAmDHDFJUMfeuk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127786; c=relaxed/simple; bh=gCXiCKhaJmhwkLrGzM6kUf/yLBe8ayeGTu/oRw+fnHw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jq3umv1aT7cFyHaUAgaUNNFcWKS7cG1/sFQnYZdUdW/KTNcH4qgvLuKcSdAX9foDW5R96dVujHbPYSen2yvEIpC3odoRAws1CZxq/LE0lfrcnq9KuW37OhEO9zS+aBj2ZBNwV81/yPWpfOqUaJrLRonHQowBL/iGG4ozO+AJc4c= 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=GTw3vaJz; 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="GTw3vaJz" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2ba9c484e5eso12429212eec.1 for ; Tue, 10 Mar 2026 00:29:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127783; x=1773732583; 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=EbJj+BhWY40TJ6BSEM6IqA1O92CX4jlj8rIAmx9+drY=; b=GTw3vaJzVTHXb2yNbqZQ3C7/m+34hhzFE1XxGo/DTGuPCEOm/nkwSGBfzybIO4lx+L v0Zydnk0Im8AKZfda0+ZRRfH5EdVI6x//nJTr7e0dh8OTrR5Rbx/GsmOsyxGl71/lHa6 qqIYH/0JMxWaEp7w35I/gF68Ts9y0W96x9QzfgOpjIuaBfZsYm0K04N6T32pkkhBG687 jMo/liZv9n0QLyu9SZ6/7F9woNo3d0nXov/TtAQK5axXPjjq1mPopFCOlFSSDbTboRPB rII/NvtOh6AQXV5Hbqc6PCr80cQXdKeeAXFxZ4bzvz+mmzKGl74BnckJ8mYHnRtzOteo Zsxg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127783; x=1773732583; 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=EbJj+BhWY40TJ6BSEM6IqA1O92CX4jlj8rIAmx9+drY=; b=POITfyQxkKRF9D66Z8kTSLQGnsRCU1Mci1izBxy7PVBwwQl0OrtSU/hqeQfNA6iqtI jMy5/U2guTDOOEL4mL5nm84QSjd4igEw88Hw88Q3x2gdTMDgNV9cQwZdUV5rQB5fd63w o/sZW5Tn6Aqfcu+IxwN1qsyKkKG+nJOdimJyW0yI7dHDawXEoazoIe0D65ihvxzYIoyj JTbT/b/Yi23pJrBldUOeYCexKhchoC+NL+IYxdLBFOUETi7tazNJLVH//WcF7VloLDcW 6mkVIuBVwJWeNlpNdLjMNi+yM7265AV9dufVnjKiSdLW4vrSc3QXcM91ujzuph6a0zS7 +ILg== X-Forwarded-Encrypted: i=1; AJvYcCVotzLj/lNx3wccOCLEvMDrlnJKgkhVnPe5HZwFxLt6vZVnhprF0M68y5ByGh0/DhKOYbTt89usMStpWxo=@vger.kernel.org X-Gm-Message-State: AOJu0Ywq17BmKVUa0VugHG+38/t6opjcg2qX67vId8fFPdhc1eZ7ezGK V1m7ycCaUxCqjpWDJ05QnZY8devlRwKw+LvD9aClAnDXXI3CvWaKGxVj X-Gm-Gg: ATEYQzw44vwbXI5AD8XUS76pr2LqPBj7x0AKNm7qY9Y/LDP6CABC37gWNawdIm2hulb ENnyXkBDzJZ96J9FgmbToNlT8AwkeGMn2YvbQZUnmdinFfZfEdlUK8zkkfOIA37qDw/sukRYgBb n0MGWQ6e1O55nLE1GYbZ8yGE2/dQTmcCqMatqdOySAJVzw334Z2gk37VYXMdFqLSEvm3xm92HjM q9X0I0ju1351jMt19rtJnm7M/fhk3zurnPBAjZSRwRbPjtgIUtPRWKLj4f/S99GRYSmAntUu1Yg JPNkXjKuHwnq7jRuGRgRsWNPtry9ooDaMCcY0DU7WQ7WRED5V71WFMK2aTcIYF5n9wkV1QHmXO6 u6y+cDdiTW3yTDOxw9Fwv8PN4szyu7zB2OJLRmnVkPapasnUv6CVyx3m3UVEp1z7EIc1nB7Nds1 czINCOhOK4B/mXvKv1Kik6QEqGBjAufD3mu7l+8E6bMV++yS53TETEgvVn7g5Criqy1LOLiDKcZ S9W X-Received: by 2002:a05:7300:cd90:b0:2ba:7863:f0a8 with SMTP id 5a478bee46e88-2be4e0d3a1cmr5339744eec.36.1773127783429; Tue, 10 Mar 2026 00:29:43 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:43 -0700 (PDT) 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 v6 04/19] HID: hid-lenovo-go: Add Rumble and Haptic Settings Date: Tue, 10 Mar 2026 07:29:22 +0000 Message-ID: <20260310072937.3295875-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. 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 d7d47db8362c..f2a54865cfbb 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -41,6 +41,8 @@ static 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; @@ -48,11 +50,14 @@ static 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; @@ -64,6 +69,8 @@ static 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; @@ -170,6 +177,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) { @@ -336,6 +386,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) @@ -383,7 +474,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: @@ -759,6 +854,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, NULL, 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, \ @@ -824,10 +1081,18 @@ static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mod= e_index"); static struct go_cfg_attr reset_mcu =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status); =20 +static 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, @@ -910,6 +1175,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "= imu_enabled_index"); static struct go_cfg_attr reset_left =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status); =20 +static 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"); + +static 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, @@ -918,6 +1194,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, @@ -966,6 +1246,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, = "imu_enabled_index"); static struct go_cfg_attr reset_right =3D { FEATURE_RESET_GAMEPAD }; LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status); =20 +static 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"); + +static 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, @@ -974,6 +1265,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, @@ -993,9 +1288,26 @@ LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSP= ECIFIED, index, feature_status); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); =20 +static struct go_cfg_attr touchpad_vibration_enabled =3D { TP_VIBRATION_EN= ABLE }; +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"); + +static struct go_cfg_attr touchpad_vibration_intensity =3D { TP_VIBRATION_= INTENSITY }; +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.53.0 From nobody Sun Apr 5 13:04:07 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 4056C3E95A4 for ; Tue, 10 Mar 2026 07:29:45 +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=1773127787; cv=none; b=U3YA1GIzcS84eUsIUWna5Y71B0Tc1UbBZcpGMoqy0Sfi66UpdcJ7KCpm1fup00UsU1jqkTBdEqrfvMDymjZ0Iet/fZ8NM5rk1R7chinSG8Ln3W7oPYYt8cpXIscJw2nEeNT6iLpbRYIvYVOgRZ6Hc6PKEc1rLyDENyJ2y7nxYQw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127787; c=relaxed/simple; bh=akFvcySx2qtpMSSr4YkidwPl/PnWdnvEe1ChDcvUnFM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WVac9zqC0hOW22Dh7hFCPEsFbvrTHlnnAYQ3/738KqH1QTDgKaSgx79TxPtFUhHMZYMcdCDlcA5oXGyqHvsQBE2uXrVm0+WQMJ8AltEpLct+cykfUIjbpL+knK7HTwMh5Lraiqdpkrlft/dcef53qeKkjLY5RdfUdm2vKTh2Oio= 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=jH17nJUc; 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="jH17nJUc" Received: by mail-dy1-f171.google.com with SMTP id 5a478bee46e88-2ba895adfeaso10542388eec.0 for ; Tue, 10 Mar 2026 00:29:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127784; x=1773732584; 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=h0FyZLQ4D0JfML3/RqbwC4ixUWu6cfpE5AZW99yvfRg=; b=jH17nJUceyvnUg9T6Tu/jfnP6cL24lUVE9OULPINGXgOdfNw30uqJFByOw8JJEDwcn HxpOMEwxZ13v1EIb2akr+0CCCl+XbUef9W8jYt1/lZwrtm2c/ZqO8/+KlVj5P2NcHXJz wP6isi0BCEDTW9vb1IooHH1cG1bXrQACQTbJdEX8Qa8YWCQgULR4X8c0x7jJk3j/tOC7 SWzRkx9twhwWxSk9MerCL6CQxxuJPWqXKpcfMTLJDXdA6zW0tMhpnQN3Lks+JHpAwwPo Ww3vbyRYBTw6ahLtEQ2sXSy/Bo/bLxtNlIz3uVA/eQTOjjvWsbRgDTqQISh6/3A34MjY HuLw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127784; x=1773732584; 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=h0FyZLQ4D0JfML3/RqbwC4ixUWu6cfpE5AZW99yvfRg=; b=XlH7GbT71ZYXMSUZrJU1hA+8r5r/iruQ45zX4zlTDiuEkDwustVso5nbJlCzdQUaax Wc6OmWC0Uq+RoOcmDw/rynYueKpHprCGLiUEZlbYXEZa1Y3oKMHuMB9okzo7Q4LxN9dR LVp07qpRnm+F2FKq9EUc8nomqM9//wTbLX3kHnOOdGIHFedVNh56cAdNUcksWouMVCvi X4YV2MgAr86chcV35DPiRIiQVYqFqhwhcqL3gPLkywPo1mGYbZiJI9vmWUf9tYJJQhQl vSpW7rrUXL6T5o97ofGXfirC1471pG2cKTmXHtY1GVU1FJ3l+xP2L46V1aMaOiUbIh6/ WIeA== X-Forwarded-Encrypted: i=1; AJvYcCUA4u9MI8t3TEBX47f/KDErlaEYftPPN4vjz9iu1rlCWCmoyIzswQcRBn+7rmJwNRHusNWwpbLM9nbmW1A=@vger.kernel.org X-Gm-Message-State: AOJu0Yy0wM6X6URb8xT76IKT0m23y4odlRXyVRIdlVN0Sr0wDiYVddwh jc10h6Y3rDcfdC1U/1T3tDSlCKZsHUtC+SFZncKRKZVuSjE7PdArCgD3 X-Gm-Gg: ATEYQzzkST1x2oygGE2lbvrzk/pVxzbV4pS9e4OmQA+ra+g3VUxrJhwm1MllU719x97 Q0tMEqg3kSuYwxVnTfXuhtJMJ00MqeP1n8jTGzgNRFQ/R47PzXyZPrgeqNIQIyWG1hYGOHKhqzM KWQWIHimaWaK8WeNK4loVWFHyMYrGrHeUy7l7qUg0H1q0uQ+l0u7ZtaIugmhr3E3PsrZ/uAocM+ pRDrrdrLCXa6ShQyKK9bgvst6YUxKbqqHt3GkBcBiBSwOBwQt0JxWgKpc+cc0wXtzwPNSV0eXZE 7voAlZpsK2PxtrSboYOiCsCjLFM85/Usw8EtaP3sYbAiTh1eERk+w2ErhoUIrLjWJcCY+1D0Vqh w3zwVjylzJOtUVDYYhDA3KeSyVNGp+iXNhKjn/Wsj2hxdfaR0fj5JksMxxW9TfPs68ieRiv3xxI H0ZhA9mVmvGhDxIhYoClLMfApVJoce7jI4emuTvHPmyp0az6juCYinNVCCCVPvJ9aBoE4Kb7ACo xuC X-Received: by 2002:a05:7300:5723:b0:2be:833c:14a7 with SMTP id 5a478bee46e88-2be833c19d5mr37980eec.1.1773127784235; Tue, 10 Mar 2026 00:29:44 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:43 -0700 (PDT) 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 v6 05/19] HID: hid-lenovo-go: Add FPS Mode DPI settings Date: Tue, 10 Mar 2026 07:29:23 +0000 Message-ID: <20260310072937.3295875-6-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Use NULL instead of 0 in mcu_propery_out when there is no data. --- 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 f2a54865cfbb..24f9444c93cd 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -67,6 +67,7 @@ static 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; @@ -220,6 +221,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) { @@ -427,6 +430,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) @@ -477,8 +490,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: @@ -1016,6 +1033,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, NULL, 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, \ @@ -1087,7 +1150,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.53.0 From nobody Sun Apr 5 13:04:07 2026 Received: from mail-dl1-f52.google.com (mail-dl1-f52.google.com [74.125.82.52]) (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 D0D8D3E9F75 for ; Tue, 10 Mar 2026 07:29:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127789; cv=none; b=rKZU1Qm+Ffk03tsHCdazXkCx11FHhfj9dBVqZnKgo9cgVcFhWVTUpP6xoFadhCEE1yyfZ4IVLMKY4WJwcKRbvcDgmh54P6nscy8e/xIFp2Ab0PYkdVRxOR2ycjlBKzP1ZW+sZ8+IHoHNhWvKVwfO3RUUfi+XmLlt0bkr8Xyh/us= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127789; c=relaxed/simple; bh=80w6mG1IPQ177/KHDFucfZXdnOAp1gg/NFfXxbSUOyo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=l9SarRFUTBkZwTgfGsMdGpcidpk9AfmkKnWpTp/KmH5w0Ut5uV/c+q2n1Z9qP7ZpaSM5ciIUZhd4/84T1E9O7voTFUaP3lCTV24iIKbkyezSn+a92b7zfmG08Vg0H2S0eHBdCDLY6qgFpPik7W4Pt30iJZW4i5oCspQcL5LQfQQ= 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=lsk9bMdE; arc=none smtp.client-ip=74.125.82.52 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="lsk9bMdE" Received: by mail-dl1-f52.google.com with SMTP id a92af1059eb24-128cc4b4b6aso3215532c88.0 for ; Tue, 10 Mar 2026 00:29:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127786; x=1773732586; 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=sCj6LfreKTRcKvUoolIEtLTQfVyiIzNQOsFJIkKznoE=; b=lsk9bMdExUkCjRvq/K513TPCz6xJLGvPOAMPhtK8lWWi2WmUMM/ah7gqGq46kWYi58 Dyat9LFHpgX7Of2QQywNcRj8zTAOM7PW6Z/WyFMXSvhJyYCaknOs5k9xw5HcpC8kb7k8 yFa3GbV4CeFguqWhk0geAQcrnQQeagnpNpiAIDrQFEqORQT6UVdpy2HNHAMADHU7YBNu SyBbcxmr6Ga2dEvWGeKePPRDUFwE1YydhlK8g3l2XAjKWApjsc8pNXjQkrEqH4EP7bS9 meAZG+wEt4E6VQ1WWyunHZVQTCqg7IwJ/t1plETibjp1ZKCnIM2D3tPp5PU9V54rsk99 0r7g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127786; x=1773732586; 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=sCj6LfreKTRcKvUoolIEtLTQfVyiIzNQOsFJIkKznoE=; b=rgZw+XjJFJotobgMmfuPaZUTtF4LqEEmPNz+WRvlHk+m5gbxANEdQOnFgZ2Pr3tudD MzeGC8qdm6sX/ODrMRLIdP/A7vHZ/IsF8AIEjPfyQ2JFipQpwCfIDg2+IK5QqhSUYcnN Mpef9HVBki/IiO/9sXRyl5R+br9kifu803AjPpfBKJomHGt+OHhdud8yd3MqkNybu2fN JppYZJY3igwc3k5+p+AVgewrQ4tZL2dTydrbOrher5WFnhppZLb0FEjItYwxIg2HKvjL vno3UPzueu8wW4qVEU8n+yoDnwql6ZFY8aju0NFAhGj1zrJZqzm0+dcYeNxES4UEjX48 WU5A== X-Forwarded-Encrypted: i=1; AJvYcCUV6MdwjNX3IP9xy36GdqlA0EHRlYa/tHOEyMaFIDocM/jDj9peva06b74XdQexFiCz915xIRYDlD9pwaU=@vger.kernel.org X-Gm-Message-State: AOJu0Yx2Mn0IWO+WXDrybJ5u36KAGzGKBQIMTK9MDhJKGdhqnZ5z0cqM g7+9dxQSKqvzSpjRgndWw/oVSIiD6Gb4trwYgS+aTvK1weekZISheVn0 X-Gm-Gg: ATEYQzxAJNrY3ZA6aV2gtWnry8++mBx8TULWWIjKYHO2vtlV6/qoueS62B7/4MNAX0u yloCGwEwO1zcKVW1cDOJHE2Uxr6Kjul2lSWQvx+XCK7isP/OJE1TAfMVaYJUb04N/RFqM63PHn9 X95P2cc1mXaW8ZOL5yidTjtcXuLfdcBo+VqqvKAwTysNFX01ouk8DRScCPxCZ2EMjy4PZc81s7H TeDJ34+8XfpHTFbUq0358uK6mN8nQEN5e3Tg2Z2gACUP47FcZZU56yRaVnPqC3KoEGK/FShuwkb cbRvuFjpnAZZPi6wiFaCbG4xIPF0GsKp9F9+f9GOPk+6x6wp8xYfUHVpbB9yq5eM8y5AEblysgb K0IdCo2yRf0zydUYcJWSWUIp0keuwCcCsgzcATetMu5B15eZOPGBgWj11VQKL4r79M7l7CPiXhf bCmxJhYcO70SDpH/9dbcOk8eiNntAk0ewTMGsivLgkW4lUUZaZb3pBFBJ0x6/Fyhf3rkHBYl/ZJ HhJ X-Received: by 2002:a05:7022:698e:b0:128:d0b1:527 with SMTP id a92af1059eb24-128d0b10704mr4742244c88.36.1773127785784; Tue, 10 Mar 2026 00:29:45 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:45 -0700 (PDT) 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 v6 06/19] HID: hid-lenovo-go: Add RGB LED control interface Date: Tue, 10 Mar 2026 07:29:24 +0000 Message-ID: <20260310072937.3295875-7-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Invert the rgb_speed logic so larger number is faster. - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. v5: - Don't retrieve RGB state during delayed work. --- drivers/hid/hid-lenovo-go.c | 429 ++++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 24f9444c93cd..ee7d4bf23a17 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ static 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; @@ -68,7 +70,11 @@ static 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; @@ -223,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) { @@ -440,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 100 - 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) @@ -493,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: @@ -565,6 +637,12 @@ static ssize_t version_show(struct device *dev, struct= device_attribute *attr, 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, NULL, 0); + if (ret) + return ret; =20 switch (index) { case PRODUCT_VERSION: @@ -1079,6 +1157,277 @@ 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, NULL, 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; + + /* This is a delay setting, invert logic for consistency with other drive= rs */ + val =3D 100 - val; + + 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, val; + + ret =3D rgb_attr_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 100) + return -EINVAL; + + val =3D drvdata.rgb_speed; + + return sysfs_emit(buf, "%hhu\n", val); +} + +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, NULL, 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, NULL, = 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, \ @@ -1389,6 +1738,71 @@ static const struct attribute_group *top_level_attr_= groups[] =3D { &touchpad_attr_group, NULL, }; =20 +/* RGB */ +static 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, +}; + +static 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, + }, +}; + +static 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; @@ -1579,6 +1993,21 @@ 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 --=20 2.53.0 From nobody Sun Apr 5 13:04:07 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 982083E9F9B for ; Tue, 10 Mar 2026 07:29:47 +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=1773127789; cv=none; b=MOUTTBAa9ad52Tjjrmrr1FWdkZm2KYVcsJ+cT8khYWE6BKUyfnuLnUsQGoBMMK/88VRRvuyAf5FxXMtBBZV+aoKsk43J/t8DieRtTNMtjf/ITQchS32yHW/jsmrb1Pw8SqBiO0rFoxJ+tpCpgnUqE+mGim6P9Pk2XTPCxHAAzPc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127789; c=relaxed/simple; bh=0mNJFd+AQj98fZ3tSWfz7Z0My8xqM5TJXmPndGQ3Ljs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=utm+4GguqHb7DvyEnT+5MQRz5UmugGV98nIiNQulg5UFj59YtKHO9qFc6DwFiKpYrVxZSjgOpd8ggB9S5IyS50HrX4hSBv1lWHryRfysnD1qfaKAoYgxgqQEwiXtzDBLXHOCjd2GsxmTt0UG2ALa/3VBH0/CYZZWzAthY5Y97MA= 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=PP5lpB+t; 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="PP5lpB+t" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2b4520f6b32so15085694eec.0 for ; Tue, 10 Mar 2026 00:29:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127787; x=1773732587; 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=N3hV8OG4iuTgSfSheXl6jgANZDKZZJT6Bsk+J0pI0/g=; b=PP5lpB+tyxTzDV5g9vj+Tpb24cDLUAvLcVM7Z2Xmo6Nfnk7Y4ykjF3Gm+k5Fc/nBbt 7IohIxJkF8yCcm0QA8z8CIP0PHIdwlsU/8sEWRkkSdOh0IBGh8awzwe029vwK3I5ZYzA LupXSrTYTxImJ3kFVdpiupFI9N8xmB+R0LuewHBGbmEbi3bwBRS+fgtrrwDIAiqGGZ2d mP5mtMXgJgBJio/+x6Q+doYWyd3VoCYGvPVD+c/ghVf8n0o46EinA+JLgrL+VOcFBZ+N z2QcDWrJbyVjZvGVjlVl7o4/8O1MpggISaAAV/Y40jMUQoCfFuF/Kn6wAX3ncQl/OrWf 04RQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127787; x=1773732587; 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=N3hV8OG4iuTgSfSheXl6jgANZDKZZJT6Bsk+J0pI0/g=; b=MBhFcaM3Qx+DZ58Sy6LZ41le3V1P4fZ+W4Txct5j+jiWqkeNqW2q0lRQj7mT5s6AJC EkmUD9JQGfyp/rOsMQJlJvZz3y6GeUm9qMpX1LSgZLBbJE5rAO1mj1aYGKUPaDekUbvZ DdXZRGe0BfccJWIvkSIaHzD0rTMxdn+7Pq5PxCI47iCD77bWE5lIVOs8GMad8HQmM3hg wmtG5/GOh9OkdhpPPrfjRdf1UxnjAc3ugEHsi3oC2cOYUhaW1ZX/cwv+AM+vNqYhUUMi nSWQUJQNddmVDMTu/6yjla/efKP3mmRwLupi4Ahe/2/BMe4j6ivigVm/uEk+3jEa5WJ6 Qf8w== X-Forwarded-Encrypted: i=1; AJvYcCWnthnIQGumNVVyUSgRmvT9Grzgwfb93ZTv7g/SqhQOFgMmTtSxpEYS97H2C3PiI1sCHkiqgrbyuSOCgfY=@vger.kernel.org X-Gm-Message-State: AOJu0YxLM5QvmPoZZjTz9Xgtp/j+R7jSTUr/4YWTwKzEN0ljYPMbQ/ev p5DgAVM9jnOAP6QXxIieGlCcTH9VTGUHNc2sC9X4SiDkvDMGudmfPepV X-Gm-Gg: ATEYQzzZWDP9pV52J04Jjo3Im0P0PW4K1c0nG6f90qBI4QRVCl1zvSme0axK9dOolV9 4sKSrobemCp+z7HyTakx9d+pZ8vWLpo65VoFrOqhx9lK7KWgeY1cwnOwRxq8yCFDptIRNbVtGQv Ulp3xcNAwqw5yNYbgdSoECQgeGMYkmCqZhSxrUl3DAnY2g8TRRO1dx2t9TeJ31bJf6xsHG5018r AQ20O0yTLy9ZxK8ulRD4pXIPRqSkyM/2nPNtjhgvCdCUGKobct7M1mIVsNkQMobNppAe1mzMd8v Us5xZe533zW0/vKnVb9lpo/6sOXgF1vr0fMZt8UuyT8aFM2qVV0NUVQVtOS9oZS+4jF5UIQ2sHX Oj1MTTRViLcTXEGN006CfynUYiVrBUlsKvoMvhKmgMM1dub7yrKo4SgDkhqY4lhxJ6Kb2CNN/3Q Ax8RE4ENFopGPoEj8u1yGf8XPVR26jiUh2Q84PB41QMihqsEwHzgQ7uC+iETjRhhai+4rHlLkUz b9H X-Received: by 2002:a05:7300:fd03:b0:2ba:9835:1113 with SMTP id 5a478bee46e88-2be4e090029mr5561249eec.36.1773127786591; Tue, 10 Mar 2026 00:29:46 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:46 -0700 (PDT) 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 v6 07/19] HID: hid-lenovo-go: Add Calibration Settings Date: Tue, 10 Mar 2026 07:29:25 +0000 Message-ID: <20260310072937.3295875-8-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Make local attributes static. --- 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 ee7d4bf23a17..082d1b85d679 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -43,8 +43,11 @@ static 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 @@ static 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) { @@ -1466,6 +1652,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 */ static struct go_cfg_attr version_product_mcu =3D { PRODUCT_VERSION }; LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, versi= on); @@ -1603,9 +1813,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumbl= e_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index, "rumble_notification_index"); =20 +static 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= "); + +static 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"= ); + +static 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"); + +static 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); + +static 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); + +static 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, @@ -1674,9 +1920,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumb= le_notification", static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index, "rumble_notification_index"); =20 +static 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"); + +static 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= "); + +static 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"); + +static 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); + +static 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); + +static 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.53.0 From nobody Sun Apr 5 13:04:07 2026 Received: from mail-dy1-f178.google.com (mail-dy1-f178.google.com [74.125.82.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3EE653E9582 for ; Tue, 10 Mar 2026 07:29:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127790; cv=none; b=T7C4xHKUbQE4K1PmjWunQ/0QEmnXKXcvWrrSv8MdgToWgQZPhmXQOhy1xQamtOqA2x0rrxQSTQvmoexwUY8VhYtX8wb8NfdpHnozaH4eNf5HktTbhimwwL3xxGlpk295fzr09Yjf2f6aHHVfSzhSobugQVknd3Tx0aamXrO+v3E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127790; c=relaxed/simple; bh=NEydfPX1MKHXnp0TzXwbq3IncMdTuoZl+H2qWySbXdM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Kap/T15c++e8WrspiyY2H51W55T5yHf3UgXiPat/6hz5tpj/o5zgMbKCv1HTjMfjdZk53FltQLlzBqWYCAekUCMs2kEPu99AGylNqgpfmDZLaHDkiwbZ46Ru+PFjDDdvyp07pfDEzoZvFoosRA1KF//kmTzKBc9doAwtbecLa7w= 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=DtBQl6lA; arc=none smtp.client-ip=74.125.82.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DtBQl6lA" Received: by mail-dy1-f178.google.com with SMTP id 5a478bee46e88-2be19f05d7dso759388eec.1 for ; Tue, 10 Mar 2026 00:29:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127788; x=1773732588; 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=JzHNIoMecUVi7e1tRe6uwCQE+PdFWX4mR56jJmSKFks=; b=DtBQl6lAb54ZZWvEcHQjCYBjNRhfgCMxX5raDs6tjmjbyPqNdt8q/G/iES5t9vejP7 gX6Ym/ez9ikmyCXEAfxDixeyyukJGjbAFAfa7dPySElaG31rBabCMyAxwC7UKlW3fq9Q JdFJPDZFF22qeO4SX2C9xozKOFzXDk7Y823xBrp8V4NffmdJhb+lS6dFua3iw5ciUx67 fNAYS70xE2WIEQwkIAad/zoSMtrNH4FVS5oFkrNkA5+q+xCu9AZcMv0D95LFkuyUne79 m8bD6oaB/NmtJWfQOhYt64KSM9IckguDyX51HnukGN0mpQXwtgx3qOT1kGFZkuGHaOAp mzDQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127788; x=1773732588; 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=JzHNIoMecUVi7e1tRe6uwCQE+PdFWX4mR56jJmSKFks=; b=hACOz4JnjJeRR8UGVLPrCglLoK/8AM0rBZoegpdNSfpQHrGOp3ZzWFG2qGc/blRWqd mnA5q41wbClmfb/Rv0ZKqVTE3wgEiFceB4hKAISWcyQH0f4JHkvl9AHzNWBthXM5HhCm PkOBp5Xr9ERKfsBP1HtvLWzwMEaSYQOxmoE71VIjJFGZGBHk28Rqimpp0QDwsCbRVWPI vXbDZkjDv7WchX3Vvt+12PUPd17FN24REQ2Xsvxy+LbjvLU9Sx3QZXOtPRGpo49WaMcu snS9rIPzFMyh7Q/wxHO6AkWcYNpw8e/NMg0kUwS+Vjc/yp+Np8/DfkjYF7dLETyrllsl rrew== X-Forwarded-Encrypted: i=1; AJvYcCWFjXGRY4mMFHu25tZcHLP02WSSeazsxLo/iRGyKlv2me0yZBDHcvYrEdQEwfzutIRQcQQ35SNDZhTbbdQ=@vger.kernel.org X-Gm-Message-State: AOJu0YyPCFgUy6aix5uvbfRyYiZrMxkxC3maNWnlumeQMLpSEBbHqHjl 3n+OMQqNGhMmKLyrC0bt/Ivtz9ebyRr1lovLYNvLqu8fe1ncA9BpxMwe X-Gm-Gg: ATEYQzxEIj6b2nfVqSu4bjuqlJwVaKUAgj2AXbqhNCIZ8cg2H05TiQij7PH9cO9XOJg RIP7OlO7gdHDXlzZGS4xer2QYU/PkNc7pVtw6Onar2uMHUA2pqL21Ob8HMAGnm0MP/wTxjIwjZi D4zIPOhmgS9HKWRGqAhdt4y1NyRVy+Nul66hyCgKcYLrL9NddEOc/BRGzc4+4gDNtaM4VVQQXWF 1dDdQHRtkyOsIkEHxRVZ5ouZOgqC4xQPcWFqMndUipqAs7qyMVXeTYwbAJ9j6pAUZIEyvV5rCVj Ic62VypYwlwMRq9HAvE2n6fm2y7vtfi97V4V35wrG0BjpMwutCOOFOp4PBNUxgC7MadUCx4gFRA X8G9Nqh+LX5NWtowSkEqZA/00gkV43Si8eNaor0MXgDc5MWRdfbbxsHQjspNluYFw+j1SSodukO i9EGrpcwb9WzEUJa0m+0suM7EbQNWg4cuu8IDkWZDnnPe9OlKVH7NBwG/J+VjDIJooWfF+whp8q ThqOBLqcbFpIXs= X-Received: by 2002:a05:7301:4198:b0:2b7:ff39:3123 with SMTP id 5a478bee46e88-2be4de96afemr5712913eec.9.1773127788338; Tue, 10 Mar 2026 00:29:48 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:47 -0700 (PDT) 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 v6 08/19] HID: hid-lenovo-go: Add OS Mode Toggle Date: Tue, 10 Mar 2026 07:29:26 +0000 Message-ID: <20260310072937.3295875-9-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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. Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- v6: - Use NULL instead of 0 in mcu_propery_out when there is no data. v5: - Remove reset_resume as it doesn't run, the device disconnects are reconnects during suspend. Udev or userspace will reset os_mode after resume. v3: - Fix collision with os_mode_index attribute and os_mode_index enum. --- drivers/hid/hid-lenovo-go.c | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 082d1b85d679..54861f2e04fc 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -76,6 +76,7 @@ static 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, NULL, 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) { @@ -1712,6 +1808,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, @@ -1720,6 +1819,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, --=20 2.53.0 From nobody Sun Apr 5 13:04:07 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 331243EBF12 for ; Tue, 10 Mar 2026 07:29:50 +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=1773127791; cv=none; b=s37rIyVYIvVwBIUKIzEl1UZyBRg1YENE5s12fRg5y8o6XsX+Dcowt+xClZniFBAXidDfxpoArub6sbKLBoh2aK9aL0lrYZ6fDyQoz9Tw8xsyu8tswX2szcoajndBC9Ac6IbU0Ei8sTqebb7yL1E6o//zeSJ6wY7ksX5EppOLN3Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127791; c=relaxed/simple; bh=FqJg3tER+3ZakLbAqodEwX5qMpqX5ohA1t7cv/evT/s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UXjpEUdubGm53cXX//u6TaGwkdJsUxruZsJ/64mVjd84MOsSRcaOikhsGYK/29Ah/yRDJmDgdhpZgihvuab1dPQCjf/ZIMhstaMyHzkQfoiMOl3LNuepuNAAvutLdBK6j1X4KE6ETrtNm+dru1Mf3yJZ+ewEiUm5ENrgtPaOK58= 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=ke85yFH0; 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="ke85yFH0" Received: by mail-dy1-f175.google.com with SMTP id 5a478bee46e88-2be1d9c356cso7336484eec.0 for ; Tue, 10 Mar 2026 00:29:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127789; x=1773732589; 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=rfIfejfLL8bAkAX0LN1/a1iIMrdC2id01EppEdgKGLI=; b=ke85yFH0OglxdcpWrXuZso0k+xZp+5KO3yAfeNuPzEzGmhyD/rECSg6uJ2jXstAnc8 f+hEDxHbKyvz7es7Ct1P9d8r2A8Ht4wQ7bTIyWGwoGU8+/y5KT4HvoENd2AtpNxMc8tE n23gMWIeHV+f4CXpIpa8VNr/KI2MJ01LSnrwAIwUk19JCn26lFU40F4+Ms1rbdBDGGOS VhqiTs0UitWevlNtJJX1xFQSRp8Zx/QWxU9E6N1w7qGisoGruxIS3+gmoaueqbZCN2EK 9qvm1yFa5yPX5n1lUKGvPIEa/WAWt28vtbdr1w3Xj7qDHBH6/I7puE/EvN/puo5wvgR3 qnNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127789; x=1773732589; 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=rfIfejfLL8bAkAX0LN1/a1iIMrdC2id01EppEdgKGLI=; b=B60/j+X2sm3RrMzRYDwGInVyiLFVN/kc1NBBTzsWtE2yXfMOJebxX8y4soKyszYH5h hUfoYjE/k3G8GXqBoRrKFFwOVgjKddUB6q06LMgJEDDiY1D7UDzXX1PBQegmKluH0u3d OKh8gasQbID74UpeBRymkE+vnzQtPnLYdQmnh368/qDphJWOdsOjnA4UgK9+99A8L+a+ FfwwAzFkP0XLd/CcvoedMyt/Ai7dG3ZOOR6ffrsfwLk+FbdzOd2rIznCmW4bF4qRHEZM l05qivLCSkpEYCC/7rP+FY/CTCV4f41gnQCvewAx4GaEd7D/f8SKTxp0g6hfGIBMMD9D xo3Q== X-Forwarded-Encrypted: i=1; AJvYcCUSmrt5TVDN/CNyz5CGQaeXfiFifDG2NB2hztd8nTaRFGRtCNh00QAP4emM3iaIbTBSM5NBxWvKe+nl9p0=@vger.kernel.org X-Gm-Message-State: AOJu0Yy8eU2sOPFSe/YQnRVBl82gQqzi+8ZaFBJS5aSZk8OHkYdnVqkP kib+SgUuXVll9dqIvX7LC+MUbylEFI/3pgMGlI4QhyH4f0dEHzFp3Gyi X-Gm-Gg: ATEYQzwAK+v4coSmqGx7XTnVk3IR2RvWsFERZElp9X0tBcs2GLF7ac5BQkhjVDujQEu c5FhkcS6ic1blOtdqeulvAmSdBg9FGqtWrbVC7KHc/1hhxomhyiPkK51KwsKdpevgyNwc/kv7wV NhOZI6iZS/ljbeJ89I0WbDVrEKUAfyzROpDHcxdv2M/KtPx0E/VP4Z4cdTyG4W5DytvMHleAdxF wZrrK2M8L0BFib3ZRBV9p6uGkyPWH97jC3dZZbq2bFa17SB4EOlRrIjkX6NNwdphz4ej1Akvs0r OWIsyqv43bgAwQBrpmqaRWgIkuQntSFNs5FguVEojjlucUEFN0DHsiZgmTmBRU5SNGLdANC9BKc M+B8XXLpeCQaOn8QPhQSrYP7WUJrqQPJOEpZcm7b18SpxIeZgNSDhUeOD/x8FzO+15ubD46w+wJ ZC9x8209NH8lqO6z/Um5yNe6sjY0M480jzfXY/1puGF2EcEPI32DoFaZKG4cQZyWT2/IU8C4Iou W5X X-Received: by 2002:a05:7300:3724:b0:2be:27db:b16 with SMTP id 5a478bee46e88-2be4deaedb6mr6266093eec.15.1773127789119; Tue, 10 Mar 2026 00:29:49 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:48 -0700 (PDT) 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 v6 09/19] HID: Include firmware version in the uevent Date: Tue, 10 Mar 2026 07:29:27 +0000 Message-ID: <20260310072937.3295875-10-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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: Derek J. Clark 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 a5b3a8ca2fcb..524f2b9ed512 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2887,6 +2887,11 @@ static int hid_uevent(const struct device *dev, stru= ct kobj_uevent_env *env) if (add_uevent_var(env, "MODALIAS=3Dhid:b%04Xg%04Xv%08Xp%08X", hdev->bus, hdev->group, hdev->vendor, hdev->product)) return -ENOMEM; + if (hdev->firmware_version) { + if (add_uevent_var(env, "HID_FIRMWARE_VERSION=3D0x%04llX", + hdev->firmware_version)) + return -ENOMEM; + } =20 return 0; } diff --git a/include/linux/hid.h b/include/linux/hid.h index dce862cafbbd..ce728c8d5bdc 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.53.0 From nobody Sun Apr 5 13:04:07 2026 Received: from mail-dy1-f178.google.com (mail-dy1-f178.google.com [74.125.82.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 239A13ECBCA for ; Tue, 10 Mar 2026 07:29:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127793; cv=none; b=lGR050v+CL2vJMYw75xlHxMNG/0UC6BFhx0/thYV4EA8FRuS+d4oi7NjdTDZzw0nkvhS1GIWop/vU31CgiurWZ5nQga8FMciN3fgQdp+Fy+FQl/R2hMeTnTHmc/2Jn/oIxu0VXrzqEb0tLmQhEXiquVUAS8OrrQSu6mc3kpQnfI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127793; c=relaxed/simple; bh=6//YXTE6NoFJCt18pQwn5F1fPSytpaq4/2cheq//uGo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lyum0+FUQV7APmnzGI/Csi2HlykfREtwY8RsnXOVPAwOQNx99dHyV5EEbI5rZnyNq446BZVcEhBaU3fm1xpKSJ8jj0abMG30u6lVUtq1W5wHR+oiA7lVgx+os3MhcAJGUA1MRPTg1wFcbkpAS27H1oJkcAjVmTTxbTnk0sSKIew= 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=OCrtS6wu; arc=none smtp.client-ip=74.125.82.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OCrtS6wu" Received: by mail-dy1-f178.google.com with SMTP id 5a478bee46e88-2be4781d2baso97268eec.0 for ; Tue, 10 Mar 2026 00:29:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127790; x=1773732590; 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=7HAnUZ1b20vzpNMmCDP/9oCz9zBH2H2ZEXC7dyH1ogo=; b=OCrtS6wu2h10zVlbG1SQZ2z33R4+kexzu3JSiAjwwtZxhNYQ4aBuGbaCYDGIdyI+dC v/OQnKuejK7X+HWhCknYg/ffLUyRQqwb/t6FF0ZMc9J/tEFLWSMoA0rnDc2oBJSWF5+m X8Fzvc48hIYJPti0hFBxKB99rqCD4Pnk2+xSMXij1XyW5Y0MmXoRJV5NLeskT3uaDtdr qwOGniOJ5kiYNPsD1rD2kwHbrKSZhb6JDwfUSIZMfH89/wUepnM4gKbJbVxaSwWJ68E8 wC0ntlJ9TwSQvDOozaWi31gA3xeaZYlDE/I2+gN/TJ06E1RgTXm/PcPr/DuFwlFKUJ8Z Cqig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127790; x=1773732590; 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=7HAnUZ1b20vzpNMmCDP/9oCz9zBH2H2ZEXC7dyH1ogo=; b=Ez6N9if2/hDBDkxVf94ku3pSGwofqldMRt7qTpTxz2pGG5yaLJv+eve9luHCGzlvto dWsgqIOkBd8rK0rCeFYqYm2V6oMjUviki27sUtWCss2otS/O9rHmUzwPRp8Kk4hqxgHu SLAl4dt5okl/jOMNoRiLe3Q5Bq7W4KycbXxV2ur9sD/KZD6AXkKSkxBXEcmPRP+ffWaE MdwUL9N7RcYohjkiAF/0lakSdo+NEwoC4Vi3d+LLJL5onCz8kdIeiXKrYAptn2XP8X8e 4uq5BDNxKjk6QPdGxp+4tu/TU8hDHJaGiGz15u9XnCsTn28FWsP0Y1odn5bWpCqZXkIx WmJA== X-Forwarded-Encrypted: i=1; AJvYcCV5awVXlWMUBBU7KGIqVYX+3alUUP0joCONOigmE2wnl7gNrG7XHJrMrDP/V9m7lVfnnVMVLr1qvc/uoII=@vger.kernel.org X-Gm-Message-State: AOJu0YwiDQ7CYlg410Amby/UEHE0MX4WAQ3BhN7CFioQrU2MU0h4LZBM lqSPMLxhBXHafTbbogQlr3P5SulHVjrD5hBWDA0hAYaMy9Cjj0cMhuoL X-Gm-Gg: ATEYQzxS9FUkJjX+7tjy5JtQfdseisQRsHVKja6vmCoF20AWUc0r3uifLsDabZRsA0Q YAe8A/NB9veW2HSKZPjIrmcuMkL+xhqkohqJm9S1vXSL64KYl+B83sVdmzh84nvIPXTSqMb4sik ILog6Fn94mp1mDEMY07dHBwysWMgwygd6OjogRfp3z9Zlo1lWcd2bZV/Isqvmvje/8QF5C0UMHA 2ji6iPbMBBkuVGMjnFJOn1b+N7A0d+OjLckVQIZ0+nrJS444dGEUuHXlskv+Ep/Gi29v+K1n1IM Jvy3+1XOhb7fKZOzhy7qj8GfI5RUM8pngbypC83yYz/yocTF2yzE0MZLxWzo4qz3zEkZ8OvnINJ uEqfADikBeDy7Eq0eVyTzdm2FCjfE9gbTiqTZyZLXCwcDKQYfk6ojKZXqYeG2/v178AhycAIey4 ybx5IRGwpg3ZIfEOCVrqwTILrATDzltwMrW1+KbD6NW55clg0CUQrJxQzJC7GBxZSlO+6Pilso4 w5w X-Received: by 2002:a05:7300:a883:b0:2be:2798:177f with SMTP id 5a478bee46e88-2be4e0672d8mr6038724eec.20.1773127790023; Tue, 10 Mar 2026 00:29:50 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:49 -0700 (PDT) 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, Ethan Tidmore Subject: [PATCH v6 10/19] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver Date: Tue, 10 Mar 2026 07:29:28 +0000 Message-ID: <20260310072937.3295875-11-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 Co-developed-by: Ethan Tidmore Signed-off-by: Ethan Tidmore Signed-off-by: Derek J. Clark --- v6: - Include signdedness bug fix by Ethan Tidmore. - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. 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 | 278 ++++++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+) create mode 100644 drivers/hid/hid-lenovo-go-s.c diff --git a/MAINTAINERS b/MAINTAINERS index 75d89590f3d2..c81f10292ff7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14420,6 +14420,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 2925dba429f5..10c12d8e6557 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -635,6 +635,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 79fbe4e3e2f4..07dfdb6a49c5 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 093ee86ebf90..145eb9921fee 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -739,6 +739,10 @@ #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 =20 +#define USB_VENDOR_ID_QHE 0x1a86 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT 0xe310 +#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT 0xe311 + #define USB_VENDOR_ID_JABRA 0x0b0e #define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412 #define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420 diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c new file mode 100644 index 000000000000..c9f57dfa145a --- /dev/null +++ b/drivers/hid/hid-lenovo-go-s.c @@ -0,0 +1,278 @@ +// 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 + +static 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 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) { + 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; + + ep =3D get_endpoint_address(hdev); + if (ep !=3D GO_S_CFG_INTF_IN) + return 0; + + if (size !=3D GO_S_PACKET_SIZE) + return -EINVAL; + + 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; +} + +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, NULL, 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 interface %x as Go S configuration interface= \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.53.0 From nobody Sun Apr 5 13:04:07 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 D525A3E9F69 for ; Tue, 10 Mar 2026 07:29:51 +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=1773127793; cv=none; b=uPPv1zypXxzP4Mko1JgszQm9xKPuiDDsI1vQHyMBE+1q4EWdmPYxmIFtn8s/b/JTSKWcSXiO9jMcwWorGkRwLzYScL7mOA036c8Zwu2XMd9Ffs86BW43u84wFN4zYlDeOKdgMT2XXjDwAAzHcnxrhGKDjo+U35DesiRtALM6psE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127793; c=relaxed/simple; bh=yWkVa/Jth/oJdDOe1ihirkRp8Fbw+pPWZBYFbTWg034=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Y6uP6NZ5mfDFAGt5z26Q6CgcMGU3nFXs3o/IswGvdkzuI8Au38Tvuxj1js165uWeAVE63Ci46bK7zAalMW3ZZv3k6uC2HDdTvbM06jP/ZmDVoBTRcK98vn7XLyLIQZoNKAOS0YLpsCocpJ6+s0K5Kzri4luWaJ/cMDhfDsaI73A= 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=Vy107Vtx; 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="Vy107Vtx" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2be1c918173so14212425eec.1 for ; Tue, 10 Mar 2026 00:29:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127791; x=1773732591; 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=UPGAxlsgnQwH8V6ZIVfcEYRPqGnb7Td9A0j4ncYAqN0=; b=Vy107Vtxq8++Xkgtbs6Y3PSL11sp7SFTvd0ZhHtQjOHezOuhN8KWTaOdpSjuRycNjL ww4FgP+sxpuxFdbDXEk2uW/seDRjbzxUWWHgg8dUMlPyGGNddVBHi9FRTVbxqUZEhsiM ekX4cjYncpsym7wbHiEFUoAJZemfmlpqYzlaCN14OAUnCAiv56GX1SzmyiTeyem2fVf8 cbYwUPh+SIBadMM2iRpRrC249yQIUFyfKEMRMK4Q9ZdGlaPeAEk3W1JOGeIAa4Mwxqyx EPrAqLFqFAaXSMfE9TX58MqMnTcuyN9cCXEGlcgPlRIC3bJhpoaX8ysgtDduHzLqflal NWJQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127791; x=1773732591; 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=UPGAxlsgnQwH8V6ZIVfcEYRPqGnb7Td9A0j4ncYAqN0=; b=BuVULJisOuUZtG9sOQn2sVH7DgSCA7/9TE2l1FuP1OLvLNTSm3r0KCXnW/N23+Wbsn wayUBbgVwpQkpYWOP4jAls4+KwWzOdauIhLJVD87p7yhYlL+27EJyF2t5ceWWm4E+n8X 9Y5D+ZF48gTBI0frNJrNpvF23iP8zVG32cEMlDK0Omh8zim2gbKyfpYz5TukVZf9BUaH JE1j4dRvIKiptgGnT3ZsM4CXg3VTPcDWFl5uWmcJaSuUaczesUv9zCPR8FXvKTzazyXv gE+KfPBWFJFTDx+PTSIvylLstCmrXfoQEAHkrNguIQy7ve9WRzTDsYi9aA8/HlCyMMLt 2o5A== X-Forwarded-Encrypted: i=1; AJvYcCVbZ6UQmYLJNpw5ZGzWsQ3iszw7TFI4kczeZTRnwIyBdoaAKEs1uo53cBt9iEZGQELUGPyWjv64HvjS2sE=@vger.kernel.org X-Gm-Message-State: AOJu0Yy8cMgCs+99ofLoT4qJwJHlbTQQ3CjtwXYniAU4D7Q/pLUg2rdL DYA0gKk2HHeHpTKR1A47rg/Ls1Wzmf7OCnUPcAmEK49aMJhK22POrdV1 X-Gm-Gg: ATEYQzy4tbU3SdHQlPTu3EA9ri+bvdD5YnaZb9h5UN8YYw6/B0m3pS2AGnoasIcEr+G KNI6O0EdNX4CHUH0pNfXRMaYFrgR+qJU3XA6Ty2k72r5yzbLg+d+UYjT37uBqewy0o9GgHv0m4n GnZ8Cw3BvsVsEdr4MRW58eOzXGkhx1KsiO1mFYR5NdWYVsIEgyjdlT+EibfUgC0Eln2cUc15MZ7 hTaJQmjLajzAD1UtfYaUfwld8ZQQj5ezzF9HYC4G1TWO9WjLH33kpGD/TbY7i3QkRXsOfQ5OrUj z5kIB7+0DjXBCEvVycOGxl4j0yWBhRiWkYmTiu4jFRc4AwdBW5VZ33zLGRWJMbNd8G3FLlZsGdz lrLFOneFH6hOzGNftzenwuZQuJ1qEhdq6UhCtxrKwhuYlzSUOXkIXIa07nuLdV5YN7ZnP2Ig9Ir bcI9VBSL+YQvyeCv/g+ePXd3BWSOfrIsirGJaofSIJeDB5GuH9g2Gg0dzoZuMabNSfv22ELwL96 Kh7gpluKoIqmOc= X-Received: by 2002:a05:693c:8087:b0:2be:6f30:f2f9 with SMTP id 5a478bee46e88-2be6f30fabemr2064806eec.26.1773127790942; Tue, 10 Mar 2026 00:29:50 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:50 -0700 (PDT) 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 v6 11/19] HID: hid-lenovo-go-s: Add MCU ID Attribute Date: Tue, 10 Mar 2026 07:29:29 +0000 Message-ID: <20260310072937.3295875-12-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Use NULL instead of 0 in mcu_propery_out when there is no data. --- 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 c9f57dfa145a..8ee75f724b5b 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 @@ static 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 int 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; @@ -157,10 +174,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, NULL, 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, NULL, 0= ); if (ret) { dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret); @@ -177,6 +225,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 @@ -196,6 +251,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.53.0 From nobody Sun Apr 5 13:04:07 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 097C03E8C7B for ; Tue, 10 Mar 2026 07:29:54 +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=1773127796; cv=none; b=LXovy2Idcltgavk3FV0h8WtdTbGY3H/yLZKGCzawuh4/P5wPv7wIjdxixeoj3J6A5LUGKKnmsdYtalrRbRjpRQLivfUNo4BfbAjsO+xGeRWAZPmgm85fRv3NU+JKzM4aYt2qygArN42O+wNmeGx9vXAgRU1DKKfNhh9RxwWGKZo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127796; c=relaxed/simple; bh=31RASrZgzJyk2MSTHxv8uLXms4mbg9GhAuwpXPNdv9M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ik2WL/O4Iep3fbtHLpsd41AYYj+nu9benoE+1pfELlFazOkBTTmf47UpqSyoeUPf9HHf6fifBfV8AB7V32lr4pmPDfhHO3n+1dRl+74nzwxi+qdYJN7bf2nnXSypf7nAqq71DhXag40EoY/8v0W1bCJxHhb9cXk3+eS+4gtlLHc= 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=H5UiUChw; 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="H5UiUChw" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2be26842fd5so4130619eec.1 for ; Tue, 10 Mar 2026 00:29:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127793; x=1773732593; 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=4HYa6ycaKvi1sWbnN46hq7wdNbqGIWJQ59Ad+I4hx/o=; b=H5UiUChwsjZ5Qgp0jY0oZlSxuyX9tmwT+ADAeHIeYC7664FCBpqBFC4jV1U/RgEN76 EMk6N0hGZ3/kKC0EWkJdulaYAmq2HEYubMyqvOreG0PrrrL55AgnzOFWwf4vcQI6HdBQ /6l1OazW8v6CnymV0141aVXWBg+m9nStjzBPtvBwk6IqnGIUjHJ4FE+/ZBMYfBLHx62o gst6DYSPHd5FAIxb3tEjVeeuFS0mdE+CQMxNNUkArDG0AA7NXxEheX6vUpB32sV/rxbt mzvf7Hc8xZl7vmdQ9UAFLHvjXM6q8uGHkHlWdch1IDe6EHl++CQe5kdc9eP+IlGJdr9g xuGQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127793; x=1773732593; 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=4HYa6ycaKvi1sWbnN46hq7wdNbqGIWJQ59Ad+I4hx/o=; b=lVL6vTRyCp5TCn0+K1Q6zDxq08hR8oAEm5P5y7xmSlhPyawesJ3bRQTgm8YtO9xA5x UhS+brxjfSHdua3rVoLzsBmd96T2ikJkiGL6uuaCKGZkJkj0UAEClHQFcqk1vZxZyTDv tKm5qparngpz3/6pzET2gRaPNk2sjIJZnNmUa724bvGWwtdUxv4yHTT1h3qx40AUMh2Y UbWFi/S9b0tGH8kRP2Aq+IttNJeDXhV6OQIJu1fHIram0ff6ihGLfDH2RAvVpCjilpSf cBegLwmkUEhHa7bEGygRA+8dzWUB0PCkaWIMTmoRzfuLstNn7Ls9KiU9ZxPASTwXlZUf +n7w== X-Forwarded-Encrypted: i=1; AJvYcCVLlVyUetennaD1dIPg7oFgpnZFh/HvsEDgmjeAlEJN5tYnpc7m1GngaerBq7iPuPWsTL57JgTCOGJk7PI=@vger.kernel.org X-Gm-Message-State: AOJu0Yw+wOaaFWI1dfMRFSMGxcla4NeOlA45y60WBwxJCnZu4i/O1ckB pJjMSq+B180Hqcr43q7Mg+EsPc3V/vs/Xx1Y4GrHRRMRbkPTNPFFE0Ps X-Gm-Gg: ATEYQzxgzGnEyMqpAU6HsPqj6eheY5iXiMrlevyNpXpRZEZQHxiSwesZ7AZCPu9IOZO 5rnfjiGzw1PviJXK7D/A4BipnHde+mDZs0uqWJ45GG1f3wMuqZ51VN60pk6meVrT4mF2vcoJ9Cl YDCz7snH9/J7keQq3v2oEw6NbkjF/rPz6vKlGUlXoGZU/xTaH7ykSAISUVGMvMEfFFmfZv7Oxla vP1xCY4VEHVRfk9kUZiVIonyjHL0JE9UKiKHdi/QdwN3ACSqL1i6qPWg5g+qbuFbOklAxqb9Wlr JV686udnbf+QpCqCoZatR1JS5SclY31wm0ksP/x8Mw9p2d7uBHIINKOhXrIQp0TNq+w4PZ2Fyx4 2HTNIThChkbed5QVOHw1TT1oNC3i7P2uGOQX7b8ZhIFDHpgswny1nfPoeaZtt6373vYTiMZfHo7 o/0hsJRTzNtoFMf5Db+q/5L8nMm3Xy4RJNo4cJig2xoV/C4rYy+0cSEhYsagVpoDiCsTYIt5Ek2 MOp X-Received: by 2002:a05:7300:bc97:b0:2a4:701a:b9ba with SMTP id 5a478bee46e88-2be7a123402mr897945eec.14.1773127793162; Tue, 10 Mar 2026 00:29:53 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:52 -0700 (PDT) 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, Ethan Tidmore Subject: [PATCH v6 12/19] HID: hid-lenovo-go-s: Add Feature Status Attributes Date: Tue, 10 Mar 2026 07:29:30 +0000 Message-ID: <20260310072937.3295875-13-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 Co-developed-by: Ethan Tidmore Signed-off-by: Ethan Tidmore Signed-off-by: Derek J. Clark --- v6: - Include positive promotion bug fix from Ethan Tidmore. - Include impossible condition bug fix from Ethan Tidmore. - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. v4: - Cleaner formatting on debug message. --- drivers/hid/hid-lenovo-go-s.c | 484 +++++++++++++++++++++++++++++++++- 1 file changed, 483 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 8ee75f724b5b..97c572cfe66c 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 @@ static 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 int 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; @@ -174,17 +308,329 @@ 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; + 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) +{ + ssize_t count =3D 0; + u8 i; + + count =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, NULL, 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 */ +static 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); + +static 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); + +static 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"); + +static 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 */ +static 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"); + +static 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 +static 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 @@ -192,8 +638,44 @@ static const struct attribute_group mcu_attr_group =3D= { .attrs =3D legos_mcu_attrs, }; =20 +/* Mouse */ +static 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 */ +static 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 --=20 2.53.0 From nobody Sun Apr 5 13:04:07 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 CDF1D3E8C66 for ; Tue, 10 Mar 2026 07:29:54 +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=1773127796; cv=none; b=oOG/6On1gHTCD/ontV88JApCxZKsQJSMZzCxLCsX6IpilGSQFjuad8e7pEbViDiZ7mqnrIjhffdbz1P1ncUPzTjvu/Qd9rP9LD8blQV/s6oSevSLfZJtXsSo+x7NEGNq8f4/PdEOdzvYXpuHG9GAQQjadjVJMs6PBtP798RUjSA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127796; c=relaxed/simple; bh=k8Z4PJ0jlhce1UF3Xo+dF+DpVA4UIZlzE5bAxI0XUjs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kh+NHK5fWgrIonXgotgBVp7z548LMwmj0LoopYJGUocTH8nikKY6JuRpuuOmypZL/G7Nhn3RqpmYOgKU8SUOK/wQLqlG/xfIQaYj6ReKKZFx9om0xDzoWn1aliT90aC+/6v9ljTHha4I8oZniMjAFeJpYj6FgcLJbhcKf0R8N0c= 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=BBkF2sKH; 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="BBkF2sKH" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2be1ab1fa7dso5595130eec.0 for ; Tue, 10 Mar 2026 00:29:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127794; x=1773732594; 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=Z+/BdiVbU7ju/RLkpeD6wzKfiL0bcAcvC588TQS18Ao=; b=BBkF2sKHFYIVwzC1kXkqVY7aVv7tG7Qp2J7v/RQ+5LsLCW3MgARSL0wwxdN9xIOC+j rwDDd2tW87ZIyPrnh+gjZedcNmfSaKJ7tnOk+hmUZX9c48ZnlfIZBpwbXclotHAeRPZ6 as6tczJhxBzOPBsJXNOUZXYeBVuYNOfMJm6DFAv67S1wdgox1mKa1CoHxEuftcUWOXr6 yyQTneJIN0jC2wQ75gyrfhGwijTU78w/UehlI/tEScOQYG3Agz7SFZgCuKCRq+gqsnmV YBrmvbqvuNeXTSralm9QqReXrD1+l7pg8ueW7IiYnO4m5B+hovQq9Uf0i6nLCWUENfcX oTBw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127794; x=1773732594; 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=Z+/BdiVbU7ju/RLkpeD6wzKfiL0bcAcvC588TQS18Ao=; b=KI4InK1JwJcmDCguQT3aeKJrg6KakSikybW/IFf1XeGPelWd7P0VIS733kj1aQ+6Ze eaipbNBz9Bo3W9QQVVpDv6JOwlQWdOJrfcBc3kRg78iUA8dHmGmnK+xBq06D5iua9fNj R6Bvo8GwyyvGY2k+ALnFWr0A5cPA85sRhzVuY4ZSx1kCUe8IyNdzyhNISknUolSD8I2p NeO60t+jL0ytayTPt29+UBiABzPR3QPTGPNDNx9rfWJ8HygtU4Y4b3DRijLB7PlRxm1V J8HmavFE85zEvVsb6UyjrBPb3GJxMnBR+ZihwOeAgGLciowokXHsvYfSAougO3jsg4um S1qQ== X-Forwarded-Encrypted: i=1; AJvYcCVzMOCtoRHYA39xKLlU0qbJWZFBsVlZSIsJKmADmnitK3LN2hWEeH5RFGmA2Ad87PHmQ0Tc8P2Eqgct/FE=@vger.kernel.org X-Gm-Message-State: AOJu0Yy3OrZemxxckezg3l7KmfYCb5YL5r77WA0rTEkjxDDO4xa26VEq AV6Hvgi95ClojfuF7iL7nGeLjDXxuUYC8lTLXOf7NTGPxsSdXMlbXP3a X-Gm-Gg: ATEYQzxC76+5/bBGFR1u18Fv1OpJRgBsyQqG0MOu4R0OoFBuK4/BYiO+DztEfkLqDgL esA/LZStuQbBQSePkD3CY+xs9SknyNxc50MEfycc+vgpfQeRPuURhVjn+C8R6y8D3g+RgE2QN25 TFane6NkKbwT66/0aKhgIlB6LJ8sa4QAGXLF7pK/UUZzUl/7bSzK7A+dPCZ46cwVFQcbrivC411 D3CDbSNfQYxu+WFekSEDz8vwY9jzPIbI73xnMNE4HcID+WkKnhiAZJSoUR9MH9Wsi+WqCKpnZl0 vwxDOrTJ0Eia9+tVh6xR2smAMIIw7eCWoZKGQ6ohXUtqNzJXFHuLQ5fHLMx9Jwgux5ZFpom4qnG 3A+kG/bZMUk3lzZJTungYLvYQV8o/igzx28RuJrlbbrk8GFoRLSAIQrAg4Ak05Q2Er4OXyQvFCD 2FMH+O0fDn2XIrB5wFCMM6pisROdWdtNR7/dNbxdduxp1teTC2ufUBcEcJugYqa1SXsn3yWVF0J jkwBg7HYClupQY= X-Received: by 2002:a05:7300:bc97:b0:2ba:956e:d26a with SMTP id 5a478bee46e88-2be4e06797fmr5706856eec.36.1773127793959; Tue, 10 Mar 2026 00:29:53 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:53 -0700 (PDT) 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 v6 13/19] HID: hid-lenovo-go-s: Add Touchpad Mode Attributes Date: Tue, 10 Mar 2026 07:29:31 +0000 Message-ID: <20260310072937.3295875-14-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. --- 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 97c572cfe66c..5899cabe950f 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -47,6 +47,8 @@ static 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: @@ -530,6 +571,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, NULL, 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) { @@ -659,9 +789,21 @@ static struct gos_cfg_attr touchpad_enabled =3D { FEAT= URE_TOUCHPAD_ENABLE }; LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad); static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); =20 +static 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"); + +static 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.53.0 From nobody Sun Apr 5 13:04:07 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 C4563426682 for ; Tue, 10 Mar 2026 07:29:55 +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=1773127798; cv=none; b=Sv4Vp3jKw8u/9c6pouIe4SvnOGE0szsmKwdiDA4HFmv0AkJauxLd+hFakGV3s9f3eo3e5A1TY90kBh2tUDf98jsL6mSX2jlzPiOSK0c307r9jMAV4bu59BE8qmHPVwKLdmCTLFBBk35Z2LtTEP+EmyH8G3d8BGNqzBaKEjFPYYE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127798; c=relaxed/simple; bh=zoclcQhIoq427i88b5Nhy19sG+L4nQ2OnMBbqzroscE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nVOIJ6CTP/QdsMvO7lA3DnGk26PUA7GxBXGTyMsLr6EIxu70jdy9Ji9SCpVvTX/t2OlWMDH9rzONXwSL2Mf7ilMiydV1fM9kJRPueHd1Y6ir/xf6aykxiadgRF0y+1MMwIuT+rWbxRTEAzs65TQHnseQaoEPRN6q1OIo9W13ab4= 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=F1XT4eb2; 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="F1XT4eb2" Received: by mail-dy1-f182.google.com with SMTP id 5a478bee46e88-2be19f05d7dso759608eec.1 for ; Tue, 10 Mar 2026 00:29:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127795; x=1773732595; 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=SUlm66Htp2HNWDh+TzACQCB3mxzvVauhHSzODEkj1q4=; b=F1XT4eb23gsNY/xuSscrd585k8LYtQHjllhXDPJ6arLjAmD2/5dAsrWUeMLrrZEzBS 8AGU8gIj7SuoXab4RwgBWF7nO32bkQ9c1PyIwhXLBAvzNrCwL6QT0W+NYxAK0yocM/9M mwR/xhW73jHATkDm0Pf24K+EkSuwmjDmxAkFtPQkbScW50huTccSYoPEUXq8hKUrsW6r j6hEP663jnhp7i63eqy1o3poHzvYS5iAQM7b7HgCo9HhUw2v8qMnLKCYKbstCt5pGDvp 8rvya+B/GV9vvhnvo/ZzJFsp7a7ko+pzGgEFOH4BZv9KkfEhyzcM70WqjNNmeTW1+3Ys DQdw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127795; x=1773732595; 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=SUlm66Htp2HNWDh+TzACQCB3mxzvVauhHSzODEkj1q4=; b=c9KtU9lGykpjuRuh77FsWB/F39sixIyhJTthjZ3aB0LnADgDW2TJeUvM3ejluLNLdc proRcuPmDokrOQyzv1IXCjp/j49cI2E/p7xrbLW5gXnoQLEVdXPKzWdK4JCwWPn68e6L 5Rlm34Xel2OShrMJmvjFNU+WU3gutyJ8DqSGVDBAjhfBlZjJHDVo5JgtCAmDsZ+Q5WNG zSbdo0HSGtQzwUs/7xVwpdAHRKHa464P+rMlWohZyL9htEuwKqdsyMUnH0IY1q35J2TC +f4ef2G0BawFSsKD5/ybEAHTbh5o8NQk5O2atCjrd5Z7BEKDLam08bdxZezfUa55LUJ2 rA5Q== X-Forwarded-Encrypted: i=1; AJvYcCW5W4siTsTNgRziW13GkZXw9T9StOrh3vLUzs/ua6LkidMX3k7KDoqMOYNmMQZ3N/7EM6sCwaIGaQK5t2A=@vger.kernel.org X-Gm-Message-State: AOJu0Ywp7vDEQN/YKtUqH9qUm0KVYNclLHqwx+GYaqKAXZgzS2ACMFVb eyOHK316y/TEemKljfi1AI3neK/gQwUjlhGimC7puGytgEi6VF51A7B2 X-Gm-Gg: ATEYQzwqtZdnLyV6apkstfC6+Nkmyq56XC4I2kgbsiOLyShAtii6sTUEpsWE0n8SURd dkLWlx/3eKpq8XRft61KbJUdqPMG7zADtQvAPj9vXHB3krOO2MtkALfhVaVmDxCytfk91mgrMEJ 8L+yK49q3AVnL1YrU745Z130MIYNx/mmuewrh0bj1z1JGbw2TbpV7D/XkfCoI0+/3Q/21XZ/qSA gkIEpc6Be6iS5P+/NXaPvKDZJfPW10fnjhMv3l82a6QivdIVxoSKFEjORaF4XMFvXUeSQZRXfUA Bb3z0a0hdZPfBKhsbpk7+9y1f1ygdUMEnnZb+Fmco0KoKn+2Cl/bu1vAHYwHH0ayuEfhMiP4FZQ FETm4F84NNDW+3J7PL74NBkD3CD9JgpUT+6hLnXW1VCv5CcYvegtSpouSb7ERkE46831w0NCuKt OZy4MB/h7gE9VFWMuxk4XVUVgy6U/equ0Bl54uzrt39VGu29r4aEvsD/g4ROrdFjqurFxJ9xsrd oJp X-Received: by 2002:a05:7301:4591:b0:2b6:ffb9:9633 with SMTP id 5a478bee46e88-2be4de9a868mr5961943eec.15.1773127794796; Tue, 10 Mar 2026 00:29:54 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:54 -0700 (PDT) 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 v6 14/19] HID: hid-lenovo-go-s: Add RGB LED control interface Date: Tue, 10 Mar 2026 07:29:32 +0000 Message-ID: <20260310072937.3295875-15-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. v4: - Cleaner formatting on multiple debug messages. --- drivers/hid/hid-lenovo-go-s.c | 422 ++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 5899cabe950f..0123c4b03cf4 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 @@ static 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 @@ static 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; @@ -666,6 +741,274 @@ 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, NULL, 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, NULL, 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, NULL, = 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, \ @@ -821,6 +1164,70 @@ static const struct attribute_group *top_level_attr_g= roups[] =3D { NULL, }; =20 +/* RGB */ +static 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, +}; + +static 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, + }, +}; + +static 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; @@ -856,6 +1263,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.53.0 From nobody Sun Apr 5 13:04:07 2026 Received: from mail-dy1-f178.google.com (mail-dy1-f178.google.com [74.125.82.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 648DB42669D for ; Tue, 10 Mar 2026 07:29:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127798; cv=none; b=XyVv5CDjQGWbILDkXUR96xynrVUhOfqwuTj6qOQCKqbhOB9yj7/yFQU8E79KTrTR1fQ3uZI5fFg3BZ53W2A1+88Q6If/vicS0DG4/AwDefUuZz6Mg1Mru6Y4YFgnaqCopq8QyKL9dEGtNKrlQjqEgoufEwEUpf/QHuJUeBqGsE4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127798; c=relaxed/simple; bh=ByTxEpcE9fDsHD0eoQcjY11ZtAGzAn9gzw0tgVTUTks=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Hgk8zPkMgEP/AfE1OJelpJ5FZla01Z0f8gwRf1ThG7sRq5gY7cVN27cIRtnDS0P4+DOhhYu/ZdkIlTKEfsZEmb0sFjPGRe3sQzG/Pg5tQHhS9ShsShIEEKjfk+CYYwvGf6NsdRav+1ArNQusiAciXMkF9cvggiE92R2DmEFkoxI= 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=RTLoqNbL; arc=none smtp.client-ip=74.125.82.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="RTLoqNbL" Received: by mail-dy1-f178.google.com with SMTP id 5a478bee46e88-2bdcf5970cdso8313989eec.0 for ; Tue, 10 Mar 2026 00:29:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127795; x=1773732595; 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=XvS66gYJU0TWNIg1a0u1KW72qS57RtywXG3sPMwlyUY=; b=RTLoqNbLFXPEOwOmbS+IjUN1SrcWETG2haSHPnvNssNow8+8hWpUaMYCNiaMc96jGS pSti1HduSsrif5HccjyAW2WHEbyi+L6ZopMwu/GIQX1R6Sgm/wzu9uDkMDqGnqCwWBBv IKbFPY1U4MvqwapaOE+b0bE8DPImnpowVTPmfSyeuMsYcBNRgjZ9i8E18dFzSc/UYhgt 6F6mwbgY4SPB88J3E4DUzlqvAwNHRH++uQn0/6B4kHbTcgFrMbN8Tt8mYB865CzHvUCR tP1tb7qiKq8PyF81p/NxeSDGeOtWAFBXN7cYRMra1mSsUNWLpzM5grq8mzb7Swyvik7o ez0g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127795; x=1773732595; 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=XvS66gYJU0TWNIg1a0u1KW72qS57RtywXG3sPMwlyUY=; b=l+eNmNIbsBjHsRqwUTp9iO9BOiFfcDz9uZHyXlztHHgYopJ5GpNi2SO8JKqK+I1j2f Q8SykuyhuB04tpkW23c7niwdItNOdB9WqVTBHDvQM/+6rDUS31tDM0b/F5n/kDph7Jv4 +nspEXO4n+yZqKmz1ioG5t2FQGhKnGc2XStVnol39y/+asgPpaIRire9X3Ed1qvX2/kX TVAYvo4c4nfSU4BZVqRSJJ9XOura5S1+aldW2dAaqxX0eTnfqbZEIWdqujHG/tFhq6TY jvTM3VrAy1Yv1PAXpzO19n0dy6Tg1RaFp1rQh4iDvuYr0YZ6Sue9b9okhY63sB++vj+k Jo/A== X-Forwarded-Encrypted: i=1; AJvYcCUhxfWXab9BScz58Di1YZyrdCtWdxvlY4f2L/0DFAGguwU2hcFvY9kVO/2UTX0XdOO+NCxlF3njyQM24z0=@vger.kernel.org X-Gm-Message-State: AOJu0YwYeJElWzYx/X5Xtz9WV6VWRTn+/I/we/9H6scfdRvrNrSUxSWt 5te5scYOXZRZFBZ5PxCsFV9cxhZS8zs7yT9s29UZp5oC9kf2GZFNDFnW X-Gm-Gg: ATEYQzzxcBsle/s44omiKhMVsoQ4vgeYbjGOVC2os0BV6xC8JSeJtjL24mK24cH+gb9 I29ciMDQp2IS2xbl4TRlzkG/xUVBK3JuZKwe3OjA8EhlqwqA/lq3YCQoxku3/L8H2KeeCq7ZWFx 0dutJyu6sepyVpL3jsGDA/qIjDGH6eDYe/McWyVjZNg6ii8PKouoIuLdv6YZ3Ve1gXUSs04f6HM VzVyUB3f7fXYFiv5M77Gf6/aMMKeYRAB7ZUZOqjAWAyA8Rk7NRvj5U29JAYr7vxtiPKOq2Vj0Vz V6rhiRtaxXz9avGR3ql+Y3s9QmvVayJuHggaGGDiaaGV+DexUbJh7iLufCxFXWs53MAr5v8xuKn WrHJ3hQAlGO8D9+letH9MwNcnG8IPXnMJL5RcalOYcbIMKAV7oU4ge7LiOhe/Xl3+x6mNHsPS6M zqa47W7/ZG5+LsmzoDLO/kOi/yCH7Hep/ctaa46ZOgb47D8iLfetOq32aAur8NitTGM7FHnX9HE Kze X-Received: by 2002:a05:693c:60ce:b0:2be:6693:9b4d with SMTP id 5a478bee46e88-2be7a0dbcd1mr722850eec.9.1773127795462; Tue, 10 Mar 2026 00:29:55 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:55 -0700 (PDT) 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 v6 15/19] HID: hid-lenovo-go-s: Add IMU and Touchpad RO Attributes Date: Tue, 10 Mar 2026 07:29:33 +0000 Message-ID: <20260310072937.3295875-16-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 --- v6: - Make local attributes static. - Use NULL instead of 0 in mcu_propery_out when there is no data. 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 0123c4b03cf4..431fffde4695 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -43,6 +43,7 @@ static 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; @@ -55,6 +56,8 @@ static struct hid_gos_cfg { u8 tp_en; u8 tp_linux_mode; u8 tp_windows_mode; + u8 tp_version; + u8 tp_manufacturer; } drvdata; =20 struct gos_cfg_attr { @@ -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; @@ -735,6 +795,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) { @@ -1076,6 +1167,9 @@ static struct gos_cfg_attr imu_bypass_enabled =3D { F= EATURE_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 +static struct gos_cfg_attr imu_manufacturer =3D { TEST_IMU_MFR }; +LEGOS_DEVICE_ATTR_RO(imu_manufacturer, "manufacturer", test); + static 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"); @@ -1083,6 +1177,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, @@ -1136,6 +1231,12 @@ static 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"); =20 +static struct gos_cfg_attr touchpad_manufacturer =3D { TEST_TP_MFR }; +LEGOS_DEVICE_ATTR_RO(touchpad_manufacturer, "manufacturer", test); + +static struct gos_cfg_attr touchpad_version =3D { TEST_TP_VER }; +LEGOS_DEVICE_ATTR_RO(touchpad_version, "version", test); + static 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"); @@ -1145,6 +1246,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, @@ -1245,6 +1348,27 @@ static void cfg_setup(struct work_struct *work) dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret); return; } + + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_MFR, NULL, 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, NULL, 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, NULL, 0= ); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve IMU Manufacturer: %i\n", ret); + return; + } } =20 static int hid_gos_cfg_probe(struct hid_device *hdev, --=20 2.53.0 From nobody Sun Apr 5 13:04:07 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 99A833E9F9E for ; Tue, 10 Mar 2026 07:29:57 +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=1773127803; cv=none; b=oV4QeFjOKJB1YntOKkK5oSuOW7A1h9f66fVgwl5eip8fLt+dMkofmuyKmbtICRe8oZxJ4Lf/fet+YbnSXzYO5rgzI+VnVC6QvxhfkFDKlmQRFNpZFAL3OGBuluCR43LHjWOnYtQjbLpcqluzEG8pfOrPSSSYMlsu7uxgELLP39I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127803; c=relaxed/simple; bh=c22LblRWXjws9JqV8QtHpdMVhhVeTCZ/5RjS9c20Tqg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=A5S2O/NuIR4wIe2biKLNNWR1C8oFuWyJ6B9iVfbmPsC1LUanj2GSjJEwruhXnS9C0a0XhEtabKOsmxOAndlDGrX/JFaDMTSFJtG3rJwymEXqUX4qzYPb510hq6wnC4/N8GODB7RUiHJaX/kpdmFCZHE1xkmOxYO6N/q2+c8vXDQ= 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=FJWlut4K; 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="FJWlut4K" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2be4781d2baso97428eec.0 for ; Tue, 10 Mar 2026 00:29:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127797; x=1773732597; 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=Q0wb1J3eA0vOa0cYpTZKoMEDGrUwNLfSmRN+V4sggzQ=; b=FJWlut4K8mGvd0EubLk697LhtK7K+RKPBihmKU3HiqA0Pf8n9ZcuSBtWgILZk5bWZK RGr0/JqVGIe1QYIk1W/6AH/2fOXPnHBHsU7s9VThZQPH1ifJKwT6ArO7s8I++SvgspB8 wUNPEKRGkiAzRO9CqPbF7FYkP4A/BzNKyNBlHc0W7Thdzk7Lu6+lOYR+KtbuJpsImcKr cP3y167D/SZEFst7oMd3C5GVJySF6z141lKAaSnEt6ITxTSQfKTFqlvaRluYdQKONXT7 BOvQfz/6LMcg56yOVTjsOwfJabAbcp4QlnfJaMnUTr2JpO0vLzJg4/t7QA6FmOaZu7x8 uuyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127797; x=1773732597; 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=Q0wb1J3eA0vOa0cYpTZKoMEDGrUwNLfSmRN+V4sggzQ=; b=jqoDu63M0FzbZcr1s3XOXRARWs0r5kutAewf8J90xNk9nR6PPeGolSFyPFHXQkRCmt Fo8+2CIPYrfEKQZa0A3Ob/WD3ZaRYuU1eYgwixZSUPU6ySNsoFdb+DDXd4W6x2DIMISP 9Li8iVA7Fh8EK1QcIJUImp8LJHqd3icqMtzR69Mn9qOZijDqgkLG1L6K1dSlA18seqk2 NJfQVNUhpo+IMMbIUnjXqv2HQrTrGvI58j1/q4M3SJUNUDAQbZdDO16qKL6J6teFoItZ Xi+2D5pSX0T7FWLRsESWGQjm70xxilf0in+e7ASfJwTxy3azt9g/QkU7mhfrb2ctXmEW B+iQ== X-Forwarded-Encrypted: i=1; AJvYcCXDbpnaWwMeU5AJG9bZJszZ4suX0coJTv5Fb2Owlci+XQQldeQlwFWHgPhgxoDpsfNXgE/VyEK1uY/aeyE=@vger.kernel.org X-Gm-Message-State: AOJu0Yy/o0eHbuA9ky3BzWDoHLT2ruWpCxLg9RQ+gmMOtGJV/cLgwDPq otV+cpbex3TXfe+czeCJ5yQlUKcdSYfBCCg/++UnxDbW2uD1kwPUxHgp X-Gm-Gg: ATEYQzyunHCWcLX7ptzmsam+HcOsvA4vRFhfrUPdHduhFfhkYil/CL/Nb268PzdbPou LBpO3Fnd1NoUgidckfjbjVecfPJH0X/siI/1HBIevZgBz32G2FQQuDADR20JEcdjGdJkqLka1Du 5HdPX2PJMGvHhL2sqcHqYje1HurLOAxuwjNjwxALJ+ZrRhun4sx8vCD4eJX3FlVxVZbhzBv0wY8 K+6GlomMx52u736kHdpD0iIDSM+wF3h60gDSd70151rBN++SLfaXzzvNneKStYA0wFRgrbgzNqC 37mUC5LXnJ7lRqCyidlymKCvdu3gY4fFHTvfWNfnEEvJJz8qRIQ2zfsaNHxIIg1LV21CMGnkBr0 /KPwg1fEHHyjC4HWt7T9PdJJhsAwFFTOppqtdBX+eRGvBZhpO8R7rVSyjoKUj/H8rHwsAip84jI fb7ScPSbtQcUyu5Le5iCQxj/Y9ILcMF/92iSULjfhn1PNjb6Nt2W7+/p1Nfxk4lqJZDDPmkiQ89 DKw X-Received: by 2002:a05:7300:6da6:b0:2be:2bc5:b9f3 with SMTP id 5a478bee46e88-2be4dfdf27bmr6143973eec.16.1773127796312; Tue, 10 Mar 2026 00:29:56 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:56 -0700 (PDT) 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 v6 16/19] HID: Add documentation for Lenovo Legion Go drivers Date: Tue, 10 Mar 2026 07:29:34 +0000 Message-ID: <20260310072937.3295875-17-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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 000000000000..c8221373ef76 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go @@ -0,0 +1,724 @@ +What: /sys/class/leds/go:rgb:joystick_rings/effect +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the display effect of the RGB interface. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/effect_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the effect attribute. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the RGB interface. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the RGB interface. + + Values are dynamic or custom. Custom allows setting the RGB effect and c= olor. + Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curren= tly + supported under Linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are dynamic or custom. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/profile +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls selecting the configured RGB profile. + + Values are 1-3. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/profile_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the profile attribute. + + Values are 1-3. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/speed +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the change rate for the breathe, chroma, and ra= inbow effects. + + Values are 0-100. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/class/leds/go:rgb:joystick_rings/speed_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the speed attribute. + + Values are 0-100. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the firmware version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./fps_mode_dpi +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the DPI of the right handle when the FPS mode s= witch is on. + + Values are 500, 800, 1200, and 1800. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./fps_mode_dpi_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the fps_mode_dpi attr= ibute. + + Values are 500, 800, 1200, and 1800. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the internal MCU. + + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the left = removable controller. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/auto_= sleep_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's IMU. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_gyro attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_status +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's IMU. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's joystick. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick_in= dex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_jotstick attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_joystick_st= atus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's joystick. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_tirgger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the left removable con= troller's trigger. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_gyro_trigger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/calib= rate_trigger attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/calibrate_trigger_sta= tus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the left removable controller's trigger. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the left removable controller's firmware versio= n. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the left removable c= ontroller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the left removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function o= f the left removable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_bypass_enabled_in= dex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/imu_b= ypass_enabled attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU of the left remov= able controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/imu_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/imu_e= nabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the left removable contr= oller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the left removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/reset +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the left removable controller to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the response behavior for rumble events= for the left removable controller. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/rumbl= e_mode attribute. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_notification +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the left remo= vable controller. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/rumble_notification_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the left_handle/rumbl= e_notification attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controller. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./left_handle/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the behavior of built in chord combinations. + + Values are windows or linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the os_mode attribute. + + Values are windows or linux. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the internal MCU. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./reset_mcu +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the internal MCU to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the right= removable controller. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/auto= _sleep_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's IMU. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_gyro attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_status +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's IMU. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's joystick. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_jotstick attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_joystick_s= tatus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's joystick. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_tirgger +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This initiates or halts calibration of the right removable co= ntroller's trigger. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_gyro_trigg= er +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/cali= brate_trigger attribute. + + Values are start, stop. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/calibrate_trigger_st= atus +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the result of the last attempted calibration of= the right removable controller's trigger. + + Values are unknown, success, failure. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the right removable controller's firmware versi= on. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the right removable = controller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the right removable con= troller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function o= f the right removable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_bypass_enabled_i= ndex +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/imu_= bypass_enabled attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU of the right remo= vable controller. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/imu_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/imu_= enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the right removable cont= roller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the right removable con= troller. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/reset +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: Resets the right removable controller to factory defaults. + + Writing 1 to this path initiates. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the response behavior for rumble events= for the right removable controller. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/rumb= le_mode attribute. + + Values are fps, racing, standarg, spg, rpg. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_notification +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the right rem= ovable controller. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./right_handle/rumble_notification_= index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the right_handle/rumb= le_notification attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./rumble_intensity +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the rumble intensity for both removable= controllers. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./rumble_intensity_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the rumble_intensity = attribute. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the touchpad. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/enabled = attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling haptic rumble events for the touchpad. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/vibratio= n_enabled attribute. + + Values are true, false. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_intensity +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls setting the intensity of the touchpad haptics. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/vibration_intensity_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/vibratio= n_intensity attribute. + + Values are off, low, medium, high. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/firmware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the firmware version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/hardware_generation +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware generation of the internal wireles= s transmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/hardware_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the hardware version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/product_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the product version of the internal wireless tr= ansmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./tx_dongle/protocol_version +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the protocol version of the internal wireless t= ransmission dongle. + + Applies to Lenovo Legion Go and Go 2 line of handheld devices. + diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s b/Docum= entation/ABI/testing/sysfs-driver-hid-lenovo-go-s new file mode 100644 index 000000000000..4d317074bb7e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s @@ -0,0 +1,304 @@ +What: /sys/class/leds/go_s:rgb:joystick_rings/effect +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the display effect of the RGB interface. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/effect_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the effect attribute. + + Values are monocolor, breathe, chroma, or rainbow. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the RGB interface. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the enabled attribute. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the RGB interface. + + Values are dynamic or custom. Custom allows setting the RGB effect and c= olor. + Dynamic is a Windows mode for syncing Lenovo RGB interfaces not curren= tly + supported under Linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mode attribute. + + Values are dynamic or custom. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/profile +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls selecting the configured RGB profile. + + Values are 1-3. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/profile_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the profile attribute. + + Values are 1-3. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/speed +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the change rate for the breathe, chroma, and ra= inbow effects. + + Values are 0-100. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/class/leds/go_s:rgb:joystick_rings/speed_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the speed attribute. + + Values are 0-100. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/auto_sleep_time +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the sleep timer due to inactivity for the built= -in controller. + + Values are 0-255. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/auto_sleep_time_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/auto_slee= p_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/dpad_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controllers = D-pad. + + Values are 4-way or 8-way. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/dpad_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/dpad_mode= attribute. + + Values are 4-way or 8-way. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the operating mode of the built-in controller. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/mode attr= ibute. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/poll_rate +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls the poll rate in Hz of the built-in controller. + + Values are 125, 250, 500, or 1000. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad/poll_rate_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad/poll_rate= attribute. + + Values are 125, 250, 500, or 1000. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/bypass_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU bypass function. = When enabled the IMU data is directly reported to the OS through +an HIDRAW interface. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/bypass_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu/bypass_enable= d attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./imu/manufacturer +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the manufacturer of the intertial measurment un= it. + + Values are Bosch or ST. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/sensor_enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the IMU. + + Values are true, false, or wake-2s. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./imu/sensor_enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu/sensor_enable= d attribute. + + Values are true, false, or wake-2s. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mcu_id +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the MCU Identification Number + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mouse/step +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls which value is used for the mouse sensitivity. + + Values are 1-127. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./mouse/step_range +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the mouse/step attrib= ute. + + Values are 1-127. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls which value is used for the touchpads operating= mode. + + Values are windows or linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the os_mode attribute. + + Values are windows or linux. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls enabling or disabling the built-in touchpad. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/enabled_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/enabled = attribute. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/linux_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls behavior of the touchpad events when os_mode is= set to linux. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/linux_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/linux_mo= de attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/windows_mode +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This controls behavior of the touchpad events when os_mode is= set to windows. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad/windows_mode_index +Date: April 2026 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad/windows_= mode attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. diff --git a/MAINTAINERS b/MAINTAINERS index c81f10292ff7..146159a8b2e4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14420,6 +14420,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.53.0 From nobody Sun Apr 5 13:04:07 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 D2EF1426EA6 for ; Tue, 10 Mar 2026 07:29:57 +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=1773127799; cv=none; b=MvoObXNSxzFPqRtfa/dfvIbP+mJPQbox0Y8bHH7q8rbEt0bTbIGaMELHhXGPqS/VlEItpsgwC7mAosMm3XsitoYpJ7WLHb0EzFctcfgyHruhtAzZVEu3jmSO8I0F1BoI47HpBtxhSoGDqU3zszBlfvfdXkWxJkQ27eSuMwU2cig= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127799; c=relaxed/simple; bh=i32r1821H3Fvx8Z++mHgDSGJpr7CI9S6224rhikSwqM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tdDEP7hIaqOPsbGNzoWHAZEBIr+FEqwK9oKhfcRxY6oC8KfwJwttgg5Pna0EwXfxBspyaTIirycQbT20mb0usgs8Yjg7mHk4dyqe8rzutaGwu+H7KeVmdBvl7jPPS73DUDN6PGULfNPLS6xJnzG5c3UIJQ5QjgLALtpEKtGzjLI= 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=SYB94V8K; 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="SYB94V8K" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2be1c918173so14212604eec.1 for ; Tue, 10 Mar 2026 00:29:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127797; x=1773732597; 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=t3KNqP6Bo2goeMR/7FCuY+JiazyuB86qszEXwjkzo4w=; b=SYB94V8KcxfzB1n8KDgPaIKYwJ/CTThSb9H30jUmkoOkhBu3dldDLy6Qu5NVs1kCoa jvAZdnREi6DBj2MSIMXoeu2gz2gN91Rpu6y5BMypHX+3/hZxT6EGKD2x0NqLrZOvfFs+ 6EHfCrHZvcu9jgTs5OeM4D17KRxrgNFVXOPqmfIIjD001t31d7xtYIK03jdoMQcWw4pV 4seO43dFmTevjtD6SjaZ3Vp8B3oF+tmfXdWbP3KH8N5iU9oxTQ05fK1vpvNKfG5qViqD eRL9T3RK4xiZke43kfCpOb1GgH+KuDjIwUAf2NS4q3WKqPklF+mfznu423J1vkh+/fE/ wydA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127797; x=1773732597; 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=t3KNqP6Bo2goeMR/7FCuY+JiazyuB86qszEXwjkzo4w=; b=npj4IevMCEuYim3FmIkZMuH8Mcx6rtMGBUlj6C721IPA8bd8gx1m4mK88SZm4Crv13 qLEGY03QSFh2LWFkpsmy3JslshrXbtV2ORfglz+EN9QNkuiOTjCqCm7w4Ax0jGkpn4zE V172ft5qpB+X3VYNUZ2tO82h5A4SS3O2A0uhmaMniLL85CxZqrjVRWGCUKOOCzXjLgYg VJ+WAP3Y41ZpMy3zHvQFC9ZNHVA12BOYBnmaYvqYeeI/UWMuFvZn7HJn9v2zrggHfjNI QHv19MvFPKxjJ1LsX5H/s0uN4V5EowdOzMCo67QM50tXtDJdVUGBszx7Fhoe9V4JNooQ S64g== X-Forwarded-Encrypted: i=1; AJvYcCVDIirIo2VZtxVEt9P35pNuzwAelvPJxlpJmSPAlzS78+K1Ef1u07D8zZf49j8BjgcM9+78/maKt9LimOE=@vger.kernel.org X-Gm-Message-State: AOJu0YyU7FpZjeHq9TUw0t+jVKphyy8jTUlaje8+rQWE4Urfw5p2onEk xxaEotIgta7cjmR/2+tzwqYusXDQVtZUvNdoZK/TmDVaBasG7TeZXDwm X-Gm-Gg: ATEYQzxexUGtcU6eFBoIrLxETQXiMpuU2H7yS+IpG12C0jKIcLJuU/eL5GZlT5nOX89 IpOWxrXp/15JmV3vTf1mVO8DhdWBex5QaKU/2zqZrPNDoXSm6gQ0+GTUd4o/zM9/KEoibNsv6rZ 2g/cvk8RVKrSrVmBXF4LSXxp2ePkX0RgNfTDosXEWS3fwZA0m50pZQTgVNcqNQpWKr+IPKcRLVz mDQThyN4pywWTK3n2kfVZCgGgo2shGzvRnPWV8xUN00Y88f6oaueQhCv9+b5oC8ZPBa50YZQsY6 oyGQivp5CKv0tmetRYFexcx3tGjWm4NKXZj88Nw+gMeVB63utqhfwqvTRN1eFtPl6kC6dfsNJjA bO0dDHMCPoZnqPpjq5JJ0Pa3y56fu22Wx+3uBpbLqv+NZmEm70iCWURkK42wa5b61bZIdjAhA5H Tw+evUQjKt8YaGlCEsdc8m6bl9E5O5Up90oQy0ucQ7maoWxyurL8k/AXLCCBU999hm38wKD0rgg mZN X-Received: by 2002:a05:7300:a44d:b0:2ba:96d8:530b with SMTP id 5a478bee46e88-2be4e06436bmr5559508eec.32.1773127797107; Tue, 10 Mar 2026 00:29:57 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:56 -0700 (PDT) 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, Chen Ni Subject: [PATCH v6 17/19] HID: hid-lenovo-go-s: Remove unneeded semicolon Date: Tue, 10 Mar 2026 07:29:35 +0000 Message-ID: <20260310072937.3295875-18-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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: Chen Ni Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Signed-off-by: Chen Ni Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- drivers/hid/hid-lenovo-go-s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 431fffde4695..8ffa25b20f9c 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -1097,7 +1097,7 @@ static void hid_gos_brightness_set(struct led_classde= v *led_cdev, default: dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); - }; + } } =20 #define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) = \ --=20 2.53.0 From nobody Sun Apr 5 13:04:07 2026 Received: from mail-dy1-f178.google.com (mail-dy1-f178.google.com [74.125.82.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B2C1D426EB1 for ; Tue, 10 Mar 2026 07:29:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127801; cv=none; b=eka3Hmi2tLUTkhwn5Zs8yzO7DEcEdB1YMNEbtlLArrCF++gcBPKIHWV4pmVLPSY2rU98tuLW3EdZx3zjSYSUQjXwNdEvBGLaOx/4InFimUnl06uG76adRvyhxeEB/GNV5dSJCHZiZ33jgra9giVAhc/J9dbzeK9jTI9Tcxf0Pa0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127801; c=relaxed/simple; bh=gLmiYhsjCO2ewaSKXWLLWhUHbePsZVGcZmhlJxVefm0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jQSiI/RokeF+pARfxsJRG+tS12rTRx+97bIgECSJEd38l92wfTkA0zR/+F8G/WAuiT1SIJ67oRMh4RVKBUjrlLQJXp8zI/wvYhQRwis7QhwOJ24pATbTkph+rC7/GlbuXtjMyukp+I97apiFpcghSAvZwUwfpJ1YfR89QTFbOrM= 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=dEdPyJSN; arc=none smtp.client-ip=74.125.82.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="dEdPyJSN" Received: by mail-dy1-f178.google.com with SMTP id 5a478bee46e88-2ba9c484e5eso12429332eec.1 for ; Tue, 10 Mar 2026 00:29:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127798; x=1773732598; 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=BxcExIxLmkhlT7+NSIJGyGNNqrKogJfbqsFiKHCpzZM=; b=dEdPyJSN8JWBpBxn9ZapJUfRp1bb3MzkQDwNItg5U/1d9x2kaVu2FkAwQjcwnW98Vp jr6tXFSorYv8NuK3NpFwJb82c9/hSsKx6WVv6pgY5Csgaot6gRdItei44QRnlZu37ws7 2imLReW+Khu2tbDbfRihb9W3RK2x8BzdKTnAx1TSzPGApGoop4ooGUOLY1shik8TsHqq W0W1aEY2mMDH+FhJnCmMuB0ILZXl0Y8a5HHioB+5N/hXjI/roWH6FUkmuE9+3rEbaUwX YEWaYjAFMP35Db6oWPdLGQmbjJJjNGAv+PMCAm483ZYK9/n3x0/1ORYnU7CPkfGP4WeD klfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127798; x=1773732598; 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=BxcExIxLmkhlT7+NSIJGyGNNqrKogJfbqsFiKHCpzZM=; b=OVRb/0BTDeloQF1usvN0peVqXbBXoJPn6X0/hWDvJ+em3slIil8nqe91F14mCpaSMv 6On358gZ30ureM2wW9g1u68Aivwoj8oXqAtGbNWjqJCO7K4CmF9hoB5ZGE/73pbhmObZ dQpWNGC7gOKO+Z+o4ON58N3CAa3OaI7LuEGWcaoclEWWNCc2DV/8SxK4gOlbBymEZpoi hiRYuUTxc5epPeydCGayNo9WYJa4Ce4uX9heaHNdQOTo4ob+RYJq+Lay0hytfhrzAmtp RUxU1KMF/gMvo7QApCKhw2+hI3e+1UKEeuAKLIdehlcLqRTZE8ix4qEu5J+ORzf7ZJ/z DlXQ== X-Forwarded-Encrypted: i=1; AJvYcCUtP/e7MtnbUw543RUlSb/ltApfomqMi4w7fepzS49M7puF1oEsWcvoC73eZdigEbVA20DUnIWhbQk5Hn0=@vger.kernel.org X-Gm-Message-State: AOJu0Yz6j+vAB3569Y8LebP8J3TCEdhDtkP3L4VUGCSrvbODj1Yzz5tv Uj8aDW2Mpta/6EBp+nizQFdI4qgJdYwFM+8xCCltAsvUh6TN62BcaYTOe3y4LQ== X-Gm-Gg: ATEYQzy0RdzNLo8b+qOXmBmVmiZTNhq4AdU6AKKytmFtjhI3goUzp2HyEBEA1X1ehiz LrMbWbXS9gBd0btCIEoN59FuqUoUSlLG+MOZAxxbu1/80wZizPIcJ5jfKj/0HEyfVJ6+rz6GYQv IfGWriI+PydcDEnipGCK3d+XdED07hWlaWLvULu1Pmsy1AOHxdiktlm5unaadYYP80M2fdPirro LbpnWmIKci+PiXwF0L7duJZ+8bpd7Yt0CmNooUu77t2UYCOExsNxFr/jywwOTUslpezIJc2BTyH Hsl1X8YuQ2AUkK4vnZPcIZ6RZigb8piiD+70+/Pi2mQu2SeRP8XbAOTapiOTZmj7wYGdev9m0vG utrB8Np1zNLJiYyi+w34M7G5/SEMbYZTAXpV36UhCUTMkXiu1lPIX/RhrR4OBgaQL+dOgYz7dVB EIsIeKIXEsgXRYlf4FgUSRwq6myf3+DQtUFe8bT6pVb8SgsBgNJiYvAXNmw5NH09vMRPG8XRih3 KHB X-Received: by 2002:a05:7300:a887:b0:2be:198e:438 with SMTP id 5a478bee46e88-2be4e01914fmr5746231eec.16.1773127797902; Tue, 10 Mar 2026 00:29:57 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:57 -0700 (PDT) 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, Chen Ni Subject: [PATCH v6 18/19] HID: hid-lenovo-go: Remove unneeded semicolon Date: Tue, 10 Mar 2026 07:29:36 +0000 Message-ID: <20260310072937.3295875-19-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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: Chen Ni Remove unnecessary semicolons after switch statements and function bodies. Most issues were reported by Coccinelle/coccicheck using the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Additional instances found during manual code review were also fixed. Signed-off-by: Chen Ni Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- drivers/hid/hid-lenovo-go.c | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c index 54861f2e04fc..d4d26c783356 100644 --- a/drivers/hid/hid-lenovo-go.c +++ b/drivers/hid/hid-lenovo-go.c @@ -455,7 +455,7 @@ static int hid_go_feature_status_event(struct command_r= eport *cmd_rep) return 0; default: return -EINVAL; - }; + } case FEATURE_IMU_BYPASS: switch (cmd_rep->device_type) { case LEFT_CONTROLLER: @@ -466,7 +466,7 @@ static int hid_go_feature_status_event(struct command_r= eport *cmd_rep) return 0; default: return -EINVAL; - }; + } break; case FEATURE_LIGHT_ENABLE: drvdata.rgb_en =3D cmd_rep->data[0]; @@ -481,7 +481,7 @@ static int hid_go_feature_status_event(struct command_r= eport *cmd_rep) return 0; default: return -EINVAL; - }; + } break; case FEATURE_TOUCHPAD_ENABLE: drvdata.tp_en =3D cmd_rep->data[0]; @@ -515,7 +515,7 @@ static int hid_go_motor_event(struct command_report *cm= d_rep) return 0; default: return -EINVAL; - }; + } break; case RUMBLE_MODE: switch (cmd_rep->device_type) { @@ -527,7 +527,7 @@ static int hid_go_motor_event(struct command_report *cm= d_rep) return 0; default: return -EINVAL; - }; + } case TP_VIBRATION_ENABLE: drvdata.tp_vibration_en =3D cmd_rep->data[0]; return 0; @@ -625,7 +625,7 @@ static int hid_go_os_mode_cfg_event(struct command_repo= rt *cmd_rep) return 0; default: return -EINVAL; - }; + } } =20 static int hid_go_set_event_return(struct command_report *cmd_rep) @@ -699,14 +699,14 @@ static int hid_go_raw_event(struct hid_device *hdev, = struct hid_report *report, default: ret =3D -EINVAL; break; - }; + } break; case OS_MODE_DATA: ret =3D hid_go_os_mode_cfg_event(cmd_rep); break; default: goto passthrough; - }; + } dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n", GO_PACKET_SIZE, data); =20 @@ -925,7 +925,7 @@ static ssize_t feature_status_store(struct device *dev, break; default: return -EINVAL; - }; + } =20 if (ret < 0) return ret; @@ -1013,7 +1013,7 @@ static ssize_t feature_status_show(struct device *dev, break; default: return -EINVAL; - }; + } count =3D sysfs_emit(buf, "%u\n", i); break; case FEATURE_FPS_SWITCH_STATUS: @@ -1032,7 +1032,7 @@ static ssize_t feature_status_show(struct device *dev, break; default: return -EINVAL; - }; + } =20 return count; } @@ -1070,7 +1070,7 @@ static ssize_t feature_status_options(struct device *= dev, break; default: return -EINVAL; - }; + } =20 if (count) buf[count - 1] =3D '\n'; @@ -1111,7 +1111,7 @@ static ssize_t motor_config_store(struct device *dev, ret =3D sysfs_match_string(intensity_text, buf); val =3D ret; break; - }; + } =20 if (ret < 0) return ret; @@ -1161,7 +1161,7 @@ static ssize_t motor_config_show(struct device *dev, break; default: return -EINVAL; - }; + } if (i >=3D ARRAY_SIZE(enabled_status_text)) return -EINVAL; =20 @@ -1177,7 +1177,7 @@ static ssize_t motor_config_show(struct device *dev, break; default: return -EINVAL; - }; + } if (i >=3D ARRAY_SIZE(rumble_mode_text)) return -EINVAL; =20 @@ -1197,7 +1197,7 @@ static ssize_t motor_config_show(struct device *dev, =20 count =3D sysfs_emit(buf, "%s\n", intensity_text[i]); break; - }; + } =20 return count; } @@ -1232,7 +1232,7 @@ static ssize_t motor_config_options(struct device *de= v, enabled_status_text[i]); } break; - }; + } =20 if (count) buf[count - 1] =3D '\n'; @@ -1333,7 +1333,7 @@ static ssize_t device_status_show(struct device *dev, break; default: return -EINVAL; - }; + } =20 if (i >=3D ARRAY_SIZE(cal_status_text)) return -EINVAL; @@ -1459,7 +1459,7 @@ static int rgb_attr_show(void) index =3D drvdata.rgb_profile + 3; =20 return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, NULL, 0); -}; +} =20 static ssize_t rgb_effect_store(struct device *dev, struct device_attribute *attr, const char *buf, @@ -1489,7 +1489,7 @@ static ssize_t rgb_effect_store(struct device *dev, =20 drvdata.rgb_effect =3D effect; return count; -}; +} =20 static ssize_t rgb_effect_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1555,7 +1555,7 @@ static ssize_t rgb_speed_store(struct device *dev, drvdata.rgb_speed =3D val; =20 return count; -}; +} =20 static ssize_t rgb_speed_show(struct device *dev, struct device_attribute = *attr, char *buf) @@ -1599,7 +1599,7 @@ static ssize_t rgb_mode_store(struct device *dev, str= uct device_attribute *attr, drvdata.rgb_mode =3D val; =20 return count; -}; +} =20 static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *= attr, char *buf) @@ -1614,7 +1614,7 @@ static ssize_t rgb_mode_show(struct device *dev, stru= ct device_attribute *attr, return -EINVAL; =20 return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]); -}; +} =20 static ssize_t rgb_mode_index_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1653,7 +1653,7 @@ static ssize_t rgb_profile_store(struct device *dev, drvdata.rgb_profile =3D val; =20 return count; -}; +} =20 static ssize_t rgb_profile_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1668,7 +1668,7 @@ static ssize_t rgb_profile_show(struct device *dev, return -EINVAL; =20 return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); -}; +} =20 static ssize_t rgb_profile_range_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1707,7 +1707,7 @@ static void hid_go_brightness_set(struct led_classdev= *led_cdev, break; default: dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n", ret); - }; + } } =20 #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group) = \ --=20 2.53.0 From nobody Sun Apr 5 13:04:07 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 B41FA4279E5 for ; Tue, 10 Mar 2026 07:29:59 +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=1773127801; cv=none; b=MKO9u9NUGwovMOa8q80dXgmEabKPxO7TQ3inzeNT+XoqC4WHLUleZ4BOWKkrGPrVfvxVHiThfWUOHeuAp+Iqaf7tsSo1tu6Gs/CMcqrpWBtJ2ayWSTi6lBvJsUkxigPSLeFmVikQX7hQJz7Nw3VnN4oASe8mcz24PVOBR4mrzDY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773127801; c=relaxed/simple; bh=+kEIsEkNrgiMDln9QLi9kLSl7tnqi3h+peDwDxpuRsk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=iNPPiXpqWFeQS33nRLUnHsBKngretaFgx7K0M/DVv+0JfN7Qx5R8NfKhSMqZDLoFirHdWLLoa0EpogMIOmpBkzyJD5dMC+xjVdpK3XTUfhuDVqCtegY7xpy73BrTdc+83Lf7LNaHRE/TYstdNT1E5kW1U3stnEV+9HOkkMAk8bg= 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=ABS1b5gB; 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="ABS1b5gB" Received: by mail-dy1-f172.google.com with SMTP id 5a478bee46e88-2be06c02f66so78403eec.1 for ; Tue, 10 Mar 2026 00:29:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773127799; x=1773732599; 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=2K/YC43JaMjg/lgU+h9WNDYLR6OqHnqHgs5VzidUdCY=; b=ABS1b5gBkufIbCd+Y3k/PdnZYG1ndnEGiHrSFMjIyGgsGGK5Oy6B7MRwWU7hoNzSgX xt3RCW3mB2UOnvmaE4jA5u7V8HwdmxaKb8Iq/U8aGRxAKHaK68kBENu0HgpdwY+sywUz Xv1SFAuEJ7IYMrdoQwkFWyx4PmgYPtWSYZSfH0BsDP9VT69v8LuNoiawKg+hkSuCKxak bMYh8ZE8U9VkCN8FHOog3KSNn4HCyjj4gxPpqMLsDbpQCGpOFNTvohfnbZj19Ed2xrak q9riO+SN/D/YjZCpXkTSE//5JTd1+DapFYVrM/D6K0w0fctN3LX2MFiSjdSXm+oj98zm vr+Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1773127799; x=1773732599; 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=2K/YC43JaMjg/lgU+h9WNDYLR6OqHnqHgs5VzidUdCY=; b=YgCPrJRum/WRMURqOOdEV7ixBm/EkFDQJA86E1bT+7zOMZ8eKKdBhuy/6whrI2ACqM cF4JsDkOXA8RQviNCX5uJqvB3/TGx90fE3oyzbNC6JDF2G6xiTegdWyfpEqTe1b3/rwt Bne2G5gJP/n5hCKcFPSasTMvGYqmy7nXjMJNMMp4q350TLHySRevhhbAC0pmyNA3ef9J qdoVpURG+hpNRklguJRYXs9DMHnQ5dPWFgJ56+Y5DhWMDd31YmMdzxc1bsuz0e3f57qn cJPjYUX6vB38Q2Y0yoIeVCAhQ7v05p++e80G6wv8pph6CQJQgHxRS1SlldSsHiT71jKx sxaA== X-Forwarded-Encrypted: i=1; AJvYcCUfcC118Nt7OWH7G117e2OCMV9ilHYIGwrcxTHnI/59hssG0Xyb6L494WVwSd+jL2exjELsTQCG1VhjkkY=@vger.kernel.org X-Gm-Message-State: AOJu0YyVyFkdKgErgqEsvCuSPudOL4Q8kEBpxxFjgD+f/E3S3dJESISi GY2DMawYamZZ71JqQmzlgGF39FhIIrjYr0CyB+fPB83Y29eTma+gC6pS X-Gm-Gg: ATEYQzyvzDwcMUrG3OiZ0Qx/bC1N0REj5drN6QNfZxgEYuNwZGu/RvxT8/iY/uW5hGi xTG4lJOxmbvRAvRR6PXDM5A+uDPZZcL89ejbKB+gbyXbvHai+wLh78o265E0IyKBH1o+XP2mMej RZscHKMCQ2BlzUHWmSgWqsjKaPiN5k1oYe0rGkJkwb0ybnl5L64N5Zt9nhkCciMpj8fIqNQnRVf Mr+v+cUcy8T3akQmXoRkyBMYWJxNf1XHZ5h9ceYCKxvg9XaehTPUJHvcl77XoSv+Ht5ukLmtB7P Ssu8vU/pFiMyN8/pe/syaI+vY924+enhwqK7ToIf8UuP8FVZ1c8uoxKClQn1bc1bVMxnMw4fvVe pdSo3kf5iTtyCXFUh9i7aq0j0MZ/vNSKgShc1Q794su4xx+qxNEnKUEszmQHp5sUwSVBxknplLt 5kKkVjuaEA9iON7wx54UpeNrdIks2IwLLIZuWdBCfOwJjtKacFHjcmbQ0jclGhtJsAJ7FZxqSg+ BodxuPPZnysS24= X-Received: by 2002:a05:7300:a10c:b0:2be:b50:68fa with SMTP id 5a478bee46e88-2be4deb932bmr6181300eec.8.1773127798813; Tue, 10 Mar 2026 00:29:58 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2be81209142sm721925eec.12.2026.03.10.00.29.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 10 Mar 2026 00:29:58 -0700 (PDT) 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, Colin Ian King Subject: [PATCH v6 19/19] HID: hid-lenovo-go-s: Fix spelling mistake "configuratiion" -> "configuration" Date: Tue, 10 Mar 2026 07:29:37 +0000 Message-ID: <20260310072937.3295875-20-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260310072937.3295875-1-derekjohn.clark@gmail.com> References: <20260310072937.3295875-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: Colin Ian King There is a spelling mistake in a dev_err_probe message. Fix it. Signed-off-by: Colin Ian King Reviewed-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- drivers/hid/hid-lenovo-go-s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c index 8ffa25b20f9c..01c7bdd4fbe0 100644 --- a/drivers/hid/hid-lenovo-go-s.c +++ b/drivers/hid/hid-lenovo-go-s.c @@ -1396,7 +1396,7 @@ static int hid_gos_cfg_probe(struct hid_device *hdev, 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"); + "Failed to create RGB configuration attributes\n"); return ret; } =20 --=20 2.53.0