From nobody Wed Oct 8 02:01:45 2025 Received: from mail-pf1-f174.google.com (mail-pf1-f174.google.com [209.85.210.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6D069EEB5; Thu, 3 Jul 2025 00:49:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503790; cv=none; b=pWZrZgUcAI6/jz52fu9uFyv7bvUo72JrJwUUrI1L3bAYj7t6fOQccSP7U9GtnnK78VLOX1CHS0AGOrbCcaPdXxVN5uFoVK16bIC2kBUm3uYAn09b+92jwR8yD+/HLXKqUHeJI0ZEP77ce5YvNxfLBxn1adA7mKIWNYbNMDDsrxA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503790; c=relaxed/simple; bh=M9kWEGAhDW825E2dRVpfz8rVhUGV1kb2l+v53XfQ6oQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=A/jr5xf1hWAcJG8a6YRR9ogSiT++OBIkEJ4+TAHyLAlOLDHgozfax/Lotbr/0t60HynxexJSuVtJZSnzfYApgUEuIRShFM4X6032v7Kw3tZbk+OIO049aQ8tHsV6sCi2HSmE5EYwvAhXcqTFMbHU559lb8zBMYdW/f/A0Irs9/4= 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=fUfLwwuH; arc=none smtp.client-ip=209.85.210.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="fUfLwwuH" Received: by mail-pf1-f174.google.com with SMTP id d2e1a72fcca58-74b52bf417cso873323b3a.0; Wed, 02 Jul 2025 17:49:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1751503789; x=1752108589; 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=EmDi07nOjAzfthy7KOI6FMj+Sn5t55qF/tEJAm78X24=; b=fUfLwwuHAzACMj668BlVjPmEBuo8yPqr9D2i84Ndz/ir+0w+quPFjUGS1bvl9FBWJT el13ibFKUD2Igt4K3hJAWDyIF4+4cqiThB2+ejmvjY7hloz6ti60SqhoOQGzhsnL0USu pJny+uDCzIGCJ9+27Fn3uT+iNRYqNia23sw43ErSR6KqmulE4C+xZC4uV2qXRpiX0LCW 1+WLc0mtqJL91Hot+UXDUnERrYgJ/haCGicYHDpU7KZR0yN+c6m/4jSTkYiIW95g+4f2 iB8cHrtV09dHVdRjm0YLqIxMoBs+kZFNqFegqS/8hqKxDJHcc0iFxLgzzkiE7Noa99PL m8EA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751503789; x=1752108589; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=EmDi07nOjAzfthy7KOI6FMj+Sn5t55qF/tEJAm78X24=; b=aX0iF/d5dqvoQFAsOtq+HnSyhALUMBWaCkTlNa5NhfFqCEdVk2G3kqiLPtC4tmrN0z HxoJfZZzqvEXR3KRcgQx7CCv9v0CEVQ8936cfQ+8GtCERHE26Gf7EJjWilsLurxDAJe4 hox5X862nyegVR52g024nZgYbo+MiwZyM/DnIuhK+foc2yzhIQV5w/X5Dw0ZI4E0yMZq H/TGNx8U1J3mQTmHcCj2ia05MQFBVy9XY0sQ6HYt09H0859x1extmDJeBZu6x7EIEI4p Dy9Yt/xji/znBgv/DZhzWMuGdWwILQRYIo5Y2icJGkGAKJPG6n7VyPnoVIYuNFpvDSJt acXA== X-Forwarded-Encrypted: i=1; AJvYcCUpHiHa5YiLStpxxOzDscpukyVtpe+Uhq3HfOHYgq6djsU3gn1L3BP7QN3GeJQ+LI7XUZvZp8iHTlk=@vger.kernel.org, AJvYcCVIYH/doTR10H/A3RetX+XAc5MtklEhzATLnEbGILKIs9jf6iJOI7xHtkihYM9v7kfTIDWojq01UIVZ4NzD@vger.kernel.org, AJvYcCVj7TEaG4rxcspzhbebzdzjfYYeiBDuwH22GorEcrrcwV8PTM7HOI6I+e4lrKR0o4n21RerLrXmpnQNu3A=@vger.kernel.org X-Gm-Message-State: AOJu0Yw6jEBFStez7EB0BUR4Qj+LP/t1icOofo9a8mPcI93IpfcLkf9h Do1sNYnx6F6eTGJEnz4ixp5pPNWg3SfQ/nWph3qp/ilDjp8byoNBVF/a X-Gm-Gg: ASbGncvzXX9INgd6CdQOv3Ys6hovo+F7Rvk0CWKhHSjhIe8bxGHEaS+/utlQ85NqfO4 Y9kT8uMuJwBj6hgfP5mOgIbN0oSAFkIWIKgBd3VC7qKsZLp1AEgLfSVA5y5kTAHhaGy4Jsa28Ii WUf4ov28ttLxwyXQ9vtugcemnOsrlKSdZw3AKRdbL/V+mcNVPqYYYIz0XH+44rsTkAlBU4mQCBa HU2Q5GyMB/t0JtCf0dh2eq6gbj2hsf64p3XYe6Q67NaxLwkB76jYZGPiy/wXVrai3m/QTIFXhYn OyjJch8xD3kBrUJ81BvlUigIzgYyhqyIyMYxeSy2FJeTev2GO4YPLtCg1aSIo3tLfI5jaWMlcvu EyhLPqSXr0tKXMq4TH/t6kIFnzx7zVR4uKdGXhJQtlw== X-Google-Smtp-Source: AGHT+IGh5Wi3j/XRvpI+hFQo5glrzU9rr/IvkQwz8hOUSysPgjMEczChL3NjTTFUt5yfJTGeueA/RQ== X-Received: by 2002:a05:6a21:e92:b0:1f5:9208:3ad6 with SMTP id adf61e73a8af0-2240c4a44afmr2490229637.41.1751503788634; Wed, 02 Jul 2025 17:49:48 -0700 (PDT) Received: from bliptop (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-74af55c7546sm15369815b3a.111.2025.07.02.17.49.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Jul 2025 17:49:48 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Xino Ni , 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, Mario Limonciello , Richard Hughes Subject: [PATCH 1/6] HID: Include firmware version in the uevent Date: Wed, 2 Jul 2025 17:49:38 -0700 Message-ID: <20250703004943.515919-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20250703004943.515919-1-derekjohn.clark@gmail.com> References: <20250703004943.515919-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Mario Limonciello Userspace software fwupd probes some HID devices when the daemon starts up to determine the current firmware version in order to be able to offer updated firmware if the manufacturer has made it available. In order to do this fwupd will detach the existing kernel driver if one is present, send a HID command and then reattach the kernel driver. This can be problematic if the user is using the HID device at the time that fwupd probes the hardware and can cause a few frames of input to be dropped. In some cases HID drivers already have a command to look up the firmware version, and so if that is exported to userspace fwupd can discover it and avoid needing to detach the kernel driver until it's time to update the device. Introduce a new member in the struct hid_device for the version and export a new uevent variable HID_FIRMWARE_VERSION that will display the version that HID drivers obtained. Cc: Richard Hughes Signed-off-by: Mario Limonciello --- drivers/hid/hid-core.c | 5 +++++ include/linux/hid.h | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 46d552b1d250..1b18e0dadbac 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2839,6 +2839,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 7f260e0e2049..ffc81a8c7a49 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -667,6 +667,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.50.0 From nobody Wed Oct 8 02:01:45 2025 Received: from mail-pf1-f171.google.com (mail-pf1-f171.google.com [209.85.210.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 CCD2C155326; Thu, 3 Jul 2025 00:49:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503792; cv=none; b=NDTOsjBZB8rbyxPbCqb9zvnKgDU+Nef8jiPcJQkVsolvulBYzYiFeWulLNFtg2bQhT+bwakk6WKHZUBtGMf8o/kBlN9XBEwoO1/4v1K2IwbLHjZIoK2wcfpGBOEsQuCYVNB6Uk/MmpU/ItZn1ULiijH7MgqTC3Y/Kzv+qsEX9/g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503792; c=relaxed/simple; bh=kbOJ+SVzxgHgcjtJAwuuBtJZsGqx2HGLyKpoLGigDFk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=maVi76907IAwHtGN5tgDknbiwVW9EXGe1DsvUIb8yPJbMxwjJJHqfWH7wEB+8rB0VXhf0uJk1PshQVGGRhy6KncWzrZ5BZ2OfWBN0Sr/nwHNLwHh/SVL6nC83H5TfYFYYOqCU6T+J/4bATVP6Z3pVZw2vqQDGNO/EHs0QjG7axE= 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=LPTdXp2s; arc=none smtp.client-ip=209.85.210.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="LPTdXp2s" Received: by mail-pf1-f171.google.com with SMTP id d2e1a72fcca58-74af4af04fdso350203b3a.1; Wed, 02 Jul 2025 17:49:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1751503790; x=1752108590; 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=aGATnmCcv8bKEMSFbUJtU/clKEjpCFE8Kdapoxyn7mE=; b=LPTdXp2so+xsPlABvYy9ar+yWP7DoyZA0IpEAf/Rb512syBIAFI5Gt5jP9DG42MIHN HmUMJ1SGKC6jIbmjvJ4OHjaA1C4gjQeuAb514kEwN/v1oJakq2xyyIElcHdLZ07EnhKV wHi8bXlihCOvOC2ssvPdvMpF5AQAZXieXOAkCxNX0//95AiXgtrG0SmwaOGgdxHKOm1R DTXueD8fEV4c6aOShvhpXwAn1BcNS8mwVsHg6ije8JjvyWOhK/BEFgYs656NEOQmi/0j yNEvi2Rb/nh+nMS8uFvAQWXTPJ5PkYAZ3Lmn6/c+XoWfh9hTVE9E6eqBIYlzCC6nSGLI /U5Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751503790; x=1752108590; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=aGATnmCcv8bKEMSFbUJtU/clKEjpCFE8Kdapoxyn7mE=; b=gNh3oJEXCzeixHUI28ReJNuGgWLIV3Q8zCchDQZTNK/lbi1V2huDlhPnGATdymiD2+ xCls85pz+VUuYCPtqMBl+KoLqY12DpigpYoG0lmVZzHT3Wns8bc8sIpY9Ci7jMYuYx7W 3tfqcE0ZtoLhkWy+TcWW67x/NMmtjFw7BZsaXqVdzUxwENapdEXQjt7fozJyL97SP/dU 5pIy/F/c4DWJ0tbQWhccA2qUTvdm6WCcZZE9ZIeLKeC7iaqV9qgusDJCM2rLVK2Ga257 QxjaJ4Aea6RoY9j5+CLoe04k8oohB8KFx3mwt43R297D3Qd7mhZcef/u2nr8/Ql9lLyq eW+g== X-Forwarded-Encrypted: i=1; AJvYcCUl9Vyia/XgvoVxduf+Me9aK9RrMG4KLmZ6f+1Fu6pih14TgudvCXvlQjVGhRf3xu2RGc+eBT7FbiohBTw=@vger.kernel.org, AJvYcCVTWd8K4UzoRDlDOY8nGixZtZCfLSF1ca7GsXhQebtpoflZra2h9G8RQRXfrqxza0ymUXAnNTJSYBEhNAR+@vger.kernel.org, AJvYcCWtLguG7YGCSPiVMlyIRhb1IMtwmn0GdRJZgEIApEGsS+shbxrNvUpvR5Mc006ktaiPudwAY1wEWUY=@vger.kernel.org X-Gm-Message-State: AOJu0Yz0NB7028V+oLzwSXUBL4QCPQ/nbpd07T3o012GGDxhs/Cu5xp5 7mCBZzRHL1fZbTnE90SStUA/XmYxYLCtWFdUvpoKE6Oohd76/kkcHytD X-Gm-Gg: ASbGncv9bNuNFs3R+/BwqgzaaU7dw2hqtx0Fq4ngYcRNCNR2L60SnA7YkfZh1CCf3Ut AHHwJBGf2WZiqFbc+qEBx3JEVzKqM0BQ1NuYFYQ/VdCNYzXab+u9+weYPRtXKdaFWc+Zu4fE7N6 kMzhK2ZYiV0HxPYXAGOwatnCXSgZewaEInttwRUPzW75CaN3ZcDDGTEF4R8vrMOpdRfX3X+o4Wx xsNL4XwFwWtBYbEF67cT5x7De5iFlWlnN+cYawwGEPiNWXSoNIb630qT73qFCMW07kjASVLvJ9L 7SbPFvMLA7ro66W75l4AWI+XVzCIgquzWRSiGaKNb3XpI3Mi++8hQ9ZQ2fnnwUFQsblnkmsflJh wkn0Via4k7xtp4hU6csMLoO51D5Nq6cjd2BGa64S2Ipxp9Cmay8fj X-Google-Smtp-Source: AGHT+IGe2LurB5rUoqZZosD68WDNmWiYaJOb+Z8NX91En/ga3S3mVE2ErPuv7Vwu37y5rc2kmSWmng== X-Received: by 2002:a05:6a20:394b:b0:220:1843:3b7b with SMTP id adf61e73a8af0-224873caebbmr298895637.4.1751503790057; Wed, 02 Jul 2025 17:49:50 -0700 (PDT) Received: from bliptop (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-74af55c7546sm15369815b3a.111.2025.07.02.17.49.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Jul 2025 17:49:49 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Xino Ni , 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, Mario Limonciello , Richard Hughes Subject: [PATCH 2/6] HID: Allow HID drivers to add more uevent variables Date: Wed, 2 Jul 2025 17:49:39 -0700 Message-ID: <20250703004943.515919-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20250703004943.515919-1-derekjohn.clark@gmail.com> References: <20250703004943.515919-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 Some drivers have static information that can be useful for userspace to have, but maintaining a sysfs file is overkill. Add an optional callback for drivers to be able to add their own uevent variables. Cc: Richard Hughes Signed-off-by: Mario Limonciello --- drivers/hid/hid-core.c | 6 ++++++ include/linux/hid.h | 1 + 2 files changed, 7 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 1b18e0dadbac..de95470066d9 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2844,6 +2844,12 @@ static int hid_uevent(const struct device *dev, stru= ct kobj_uevent_env *env) hdev->firmware_version)) return -ENOMEM; } + if (hdev->uevent) { + int ret =3D hdev->uevent(dev, env); + + if (ret) + return ret; + } =20 return 0; } diff --git a/include/linux/hid.h b/include/linux/hid.h index ffc81a8c7a49..36e3c167c7ff 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -680,6 +680,7 @@ struct hid_device { void (*hiddev_hid_event) (struct hid_device *, struct hid_field *field, struct hid_usage *, __s32); void (*hiddev_report_event) (struct hid_device *, struct hid_report *); + int (*uevent)(const struct device *dev, struct kobj_uevent_env *env); =20 /* debugging support via debugfs */ unsigned short debug; --=20 2.50.0 From nobody Wed Oct 8 02:01:45 2025 Received: from mail-pf1-f176.google.com (mail-pf1-f176.google.com [209.85.210.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1DE7D188000; Thu, 3 Jul 2025 00:49:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503793; cv=none; b=Sqi1IYoI79JuHtEj+dh29kBcnVvznF34HPm7Ilkah3UXGeZO8Gd06kx7yZNa4obMY7qrQ3FOp032G4JI2YdYE/uBWrLnRlZliv/qRVgCO70xopEiRtxgrHijw5RWZgeJ6ccn3jfoKKTj4Cv1+hxQmVRyHQhL9rIqAycO8WMb1MI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503793; c=relaxed/simple; bh=CexwO3jjShcKKFVHS1HjLtL54JBpgiYdT0DrQYZM9hc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Q/HTZ8KYrzbfs5DSnn1wrQrH5vqGnGq74vmAFBc7hVrQoPdKqKBoEBiTfXtnB2tmTG9XOeD394gG+OJEdGkYp8hC7qVYblLQjuezBY/MxZ0+Sc6lQPbRt4jPqIWfFKWBxjHtbVjnlE8bmGedwqKTI4YK50QQGdFl+IpJI4Xdu+8= 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=ZHFqoove; arc=none smtp.client-ip=209.85.210.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ZHFqoove" Received: by mail-pf1-f176.google.com with SMTP id d2e1a72fcca58-74af4af04fdso350210b3a.1; Wed, 02 Jul 2025 17:49:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1751503791; x=1752108591; 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=qCqciUy+8vDjRXBMfFNrkzEgOLBYRKF32qXyyNEG31w=; b=ZHFqooveHPIX7LCH5EtjdmYADKa46oHlspYRAS/0Grd4E5aEvHD2bpHXFTdBuFJ+BX VlK/34HtVmZY5K6jtPlTXs8AeCH16Gt8wHXT5NyXrTrirnZWvb/jjn9PXLfhuGRZc+8e RJMLa6u/DfVtrMK27k+wM5vftn90b4u7EeUXfxO5ieAV7WcUXIaZY7mVkhrnrdbdOHuC f6CokLmMK8weSHEJos8epnkxcAbapeGx49vjyg6KX35lL9Rn1DGvI7B3WZItSBLfwbx7 9TGK3hHCjgxR904JDXHIbNskGwDC6K5CkzpzMJsaker0Ge1YL0SN48TAAdXi12Gwh2qk bkcQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751503791; x=1752108591; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=qCqciUy+8vDjRXBMfFNrkzEgOLBYRKF32qXyyNEG31w=; b=cdCM3WypDgGSTCc46615rXZ9PKfLuXXh0w2qDFKq/WrUrhhgqG1xD1gkQaGBb8eU3i fHhyXF3jPE0BWaQYCLRdmA3LL8L8gXpyhTXAIYuzTiOFWl2JbUWiOu6grCPTpZ7RwX3W hE5P7dSyeLKautiNMN6LEGILm9cl1KeEuZM4VHqotLwPHE4iD5wTyG4M3rBg+XGNh51l ZMOZMo0H6jX4dkgMak38jwY34lRwcDkp+2ZYxDlKtVphCSFx4+mWKMQpCJwF9EcfFKwq x3CXVQ57k6nRVbVHx2gi/Xt7jpX/AYPYX7LvGe5SkrxGz/fbArNLiYQM+7etl/LJEtx9 DB/Q== X-Forwarded-Encrypted: i=1; AJvYcCUX7mIfSOlaar6z3ggpjGEIpGEd5qlYziKWb2QwXJTj+bqY0PWMtdqmC5y2vzBg9XsHGgtz2iOPUBt2MNY=@vger.kernel.org, AJvYcCX/p7yk8QWWXUIBFKmGYeySve04UhWNGKQTfLdiGgOmsdKvXEQCOdQq75UAlNA/rixHq4k7QL3nq/k=@vger.kernel.org, AJvYcCXKUCFIdFgC/NUcS7xEgVz6yy0tQMpVQSbjyrWTUdIkDOtIvNsMEmjMD9ZMbN0GNteOmh0GpBxXah7dclBz@vger.kernel.org X-Gm-Message-State: AOJu0Yyd4zgaSuwAMLLV7fXgrom6CbTOHd0MdzkJOKjMe2EBwjj8ElYY X0OgH1zlM8qgMZ8HJvcmV9PAxSG9gAx0g+sePc/64OLEPYYxgCg3Y9zc X-Gm-Gg: ASbGncuov8f/Yx/z81F/GSCl1gCTwSt4VpnEipslUe1vt0TNjAhyY1iVzojIgRIVAYO 8Jo9kxFB/yrrAScs6obxaF1cLnnCsH8RDfqWJIqiyd5RhA6lt4cQoWxeQ+dfBspKi44XBsqVZxm Ovqid8XVMTbepRCPS0a+recN9OhXGhq3GxHQFf/0ymQFQ6XvtNgedcrLVxnJ4aGyAg5ABsMTDuq I8B43e3VanurN44UXgbSfoMApM4jiEW083gfAv9vGXlkeHwfGFK3TBAn1NNV9fn7FXZXNV2IJxX zCLL2//4ODjKqaL3i572RtJC7Z9fXWcXVedAcRlWLYBMkwejavkytDs0mAJfl6YhMWrccI3OXaB mO/9R11tSCrhSwfoMxlHSvMROp8Tor2cnu69lSATKCP9l9WIdpdg0 X-Google-Smtp-Source: AGHT+IHLaWkr+G/B/OUZFhAeunc6RSIFNUA4AcdMVW1xOasTg+uXX9AzKr1Elf9r+6aThccBYr6MRA== X-Received: by 2002:a05:6a21:6d88:b0:21f:56d0:65dc with SMTP id adf61e73a8af0-2248857876dmr309323637.13.1751503791373; Wed, 02 Jul 2025 17:49:51 -0700 (PDT) Received: from bliptop (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-74af55c7546sm15369815b3a.111.2025.07.02.17.49.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Jul 2025 17:49:51 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Xino Ni , 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 3/6] HID: Add Legion Go S ID's Date: Wed, 2 Jul 2025 17:49:40 -0700 Message-ID: <20250703004943.515919-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20250703004943.515919-1-derekjohn.clark@gmail.com> References: <20250703004943.515919-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 QHE vendor ID for Legion Go S Controller Adds xinput and dinput mode Product ID for Legion Go S controller Signed-off-by: Derek J. Clark --- drivers/hid/hid-ids.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8cdc789fbf2b..d6c096a20eaf 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -716,6 +716,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 --=20 2.50.0 From nobody Wed Oct 8 02:01:45 2025 Received: from mail-pf1-f178.google.com (mail-pf1-f178.google.com [209.85.210.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 8EE7219E971; Thu, 3 Jul 2025 00:49:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503795; cv=none; b=QQBgo1qR1ByL2Fi5Dbv/ka8QzOhRYAnYNwhDi2mokhhbXHpE14fxocX6+vNN4IMTh5QctWuMuDpj6ieRL77eHYjjmPIVSfufF7JztsZ8W3z0S5UC7Bcok3oW4B8boNkiaekQy1bXHRgppjrYPZSIzr3qcmraNFMTIxGR5WyoDUw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503795; c=relaxed/simple; bh=78kqT5f0D9kNYbpfmtOXWCn1emJzOQL/453RuMZ6n2I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AFHqfoKLFPbDlOQwcCKULCHpETAIKhYyMBmxKDQrGpIv+rP6lsr9jYsw4J1gCtAl2XlpGZkDIhm2NTaDb63KOjRT/F81NLMhu7adBYmWRrqP4qSK2Z5oOdCjU7yte0du4KN2NDIGeDUy4ebkh+fRyGaFlB5fN14K6oSKce/IpCI= 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=JdusqzEJ; arc=none smtp.client-ip=209.85.210.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="JdusqzEJ" Received: by mail-pf1-f178.google.com with SMTP id d2e1a72fcca58-7425bd5a83aso4676988b3a.0; Wed, 02 Jul 2025 17:49:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1751503793; x=1752108593; 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=pz4m+IpKOsEltbdskHgzBLceOoahW0W/XCt4Wjncc/s=; b=JdusqzEJA5FBcxULh6NTRHgZ2qVPYUqpOupVndBGiu5wdTpqDnpLytcmAysJqBo2Ov /76aGgPErQmzrqf0sECCzbN/MZOhjrnLziygKcF+GeSU2VuBoilD5tDjPm11pQA6XnEk TznOaBjxKdE8LoEbTy0cA8jRV4XkU8FiRll1jgm/qUQjVd89VU5+vEC8tQyrJn+mSYEt dI6lyIR8iFv7eSuj+GEkK2+FimB2d4yBLxEIw6j0NELuFKq3t8O1ezP8bd2snb8CRjx0 SeHeOMnODd4XKlVkWptn2Y0zCJHmVvsDZdQMwCQeQu2rtzbhjMgk700WYJKB6fzMmr/C 81gQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751503793; x=1752108593; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=pz4m+IpKOsEltbdskHgzBLceOoahW0W/XCt4Wjncc/s=; b=uwdVtG2EB7y9EXX5d9ZqdUmwLIOjZlBL3LKxMZiUJaod3YcLaTqh5wmYGQWSw1cJf7 dqK9WVU4A6DRVRc8Iw0b7DJMiPT8FvqAgKuSpkig/95TsTmpVgy+Zzjd2BNZ1xz7Lnf0 vNQMKDmXlRSF4Gou0PnQIhQE+HUUNgu9llAHHC4vNVKULOiUMXMaSp5G2R4Ty/fQ3EcF TJ0tKwSnckB8pBa75BeL91m8la0DApF6DOSa87ZgVb0O/T+DSP02n1YyYWwEymYR43km TXGkqPZSv1csG0/zWgGjXBtrF7RKWCEjenuVmZ00KcYJTEYYhUAq4xMHoSf2C0zm8QQN 6OzA== X-Forwarded-Encrypted: i=1; AJvYcCUdQ3HXZUP+tZqmpEPDBDIGh9f8472la7tnSNzqwaml7ht3ur6LwloGLjyrv0hauGGW2kc39ZbQN3o=@vger.kernel.org, AJvYcCUdyfrXStkx00Pe1jmofGNg4Gsv69f+0cSDIIxwfy9puBkfcFxXjxV7sjqfxVAwaY2Jdn1Y2pNLcaZmvxU9@vger.kernel.org, AJvYcCXufD45VZUAvHyev6/vVQor+9tkzfy3FHZN4Y7VxhEyTgl6VC6T46FggNKDubcFYVybWLrOo3NZAIGa34s=@vger.kernel.org X-Gm-Message-State: AOJu0YyDt8SO7bywKSQQg9nGsiHv/qLqs4/+F2zZ9ko9lpsFpeg8iqGf +rzSytVd/krrFgMaOZ0E79lIc8B2/k7dSeSBVINeI3P8YKlrSoq6gSNv X-Gm-Gg: ASbGncsD6E5lGrYRuIK+AI38k5bVculMF2ykKRq6xsz6vXECO3pGcw0MJurcj5O/iNC 2NMtyZxu8hJMht1LJxRdQfmaoGGCmxF1tGUBboaqqXZ+U7FAyE0lZb4Yw4+t9K+rICugST7WKby zZUbVPQxUvsudoIVDp5I5JYHItzC9FT3bo1HpTjqvEjzxUNQRubY5fK/WeNI9EHQMkFnH4EAZhr MLi1r72fPPnNbip9KQrgeKxHlAEqTRscOwy6coADa0kPrRTayAT9zzi9J7vYHoT6CdePt5BacVV 32HIXD0PkWDBmuwc8+2wI0bqixKgNkzZ1Yr+VVUBRE8/NwYV3wMGVG4ihMcF33GxxnEu8SZrJ/I kD1SMUj3bYu0dvPXlPMQkmeaV7ImMNWISmE+eGKgJVg== X-Google-Smtp-Source: AGHT+IENHWb+lqG7dM8w91Tnc6V5WN0WHdNR4MyrasQRst9wI2IFDRWvc+a/Kf+MEfrtR4GhJq7A9A== X-Received: by 2002:a05:6a00:14cb:b0:748:e9e4:d970 with SMTP id d2e1a72fcca58-74b50ff200fmr6429132b3a.1.1751503792710; Wed, 02 Jul 2025 17:49:52 -0700 (PDT) Received: from bliptop (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-74af55c7546sm15369815b3a.111.2025.07.02.17.49.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Jul 2025 17:49:52 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Xino Ni , 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 4/6] HID: Add documentation for lenovo-legos-hid driver Date: Wed, 2 Jul 2025 17:49:41 -0700 Message-ID: <20250703004943.515919-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20250703004943.515919-1-derekjohn.clark@gmail.com> References: <20250703004943.515919-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 lenovo-legos-hid driver Signed-off-by: Derek J. Clark --- .../ABI/testing/sysfs-driver-lenovo-legos-hid | 270 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 276 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-legos-hid diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-legos-hid b/Docu= mentation/ABI/testing/sysfs-driver-lenovo-legos-hid new file mode 100644 index 000000000000..af99df79843d --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-legos-hid @@ -0,0 +1,270 @@ +What: /sys/bus/usb/devices/-:.= /::./leds/go_s:rgb:joystick_rings/effe= ct +Date: July 2025 +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/bus/usb/devices/-:.= /::./leds/go_s:rgb:joystick_rings/effe= ct_index +Date: July 2025 +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/bus/usb/devices/-:.= /::./leds/go_s:rgb:joystick_rings/enab= le +Date: July 2025 +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/enable_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the enable 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: July 2025 +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: July 2025 +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: July 2025 +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: July 2025 +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: July 2025 +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: July 2025 +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_config/auto_sleep_time +Date: July 2025 +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_config/auto_sleep_time_ra= nge +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad_config/au= to_sleep_time attribute. + + Values are 0-255. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad_config/dpad_mode +Date: July 2025 +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_config/dpad_mode_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad_config/dp= ad_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_config/mode +Date: July 2025 +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_config/mode_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad_config/mo= de attribute. + + Values are xinput or dinput. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./gamepad_config/poll_rate +Date: July 2025 +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_config/poll_rate_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the gamepad_config/po= ll_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_config/bypass_enable +Date: July 2025 +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_config/bypass_enable_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu_config/bypass= _enable attribute. + + Values are true or false. + +What: /sys/bus/usb/devices/-:.= /::./imu_config/sensor_enable +Date: July 2025 +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_config/sensor_enable_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the imu_config/sensor= _enable attribute. + + Values are true, false, or wake-2s. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./os_mode +Date: July 2025 +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: July 2025 +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_config/enable +Date: July 2025 +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_config/enable_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad_config/e= nable attribute. + + Values are true or false. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad_config/linux_mode +Date: July 2025 +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_config/linux_mode_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad_config/l= inux_mode attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. + +What: /sys/bus/usb/devices/-:.= /::./touchpad_config/windows_mode +Date: July 2025 +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_config/windows_mode_index +Date: July 2025 +Contact: linux-input@vger.kernel.org +Description: This displays the available options for the touchpad_config/w= indows_mode attribute. + + Values are absolute or relative. + + Applies to Lenovo Legion Go S line of handheld devices. diff --git a/MAINTAINERS b/MAINTAINERS index 5bdae246605d..68211d6eb236 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13746,6 +13746,12 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c =20 +LENOVO LEGION GO S HID +M: Derek J. Clark +L: linux-input@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-lenovo-legos-hid + LETSKETCH HID TABLET DRIVER M: Hans de Goede L: linux-input@vger.kernel.org --=20 2.50.0 From nobody Wed Oct 8 02:01:45 2025 Received: from mail-pf1-f180.google.com (mail-pf1-f180.google.com [209.85.210.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 D2F071BC07A; Thu, 3 Jul 2025 00:49:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503796; cv=none; b=BIcMU3nJaei1sbWT6Z/uUibXVZ8xVThJ4yV328mLhADIbY8Jc0VDx3Aa/pPpop+5cZ4rd5zqMmcfxKVBuATF9tECB9JZygwk47Y9y6QD/q7GTi2pCexZfvLZS/GXosIkZfX566i5vLyr7CBL8nOlK/xlAhMemQqS0Gs53qTt0xs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503796; c=relaxed/simple; bh=bOy995SJyhjRK5/Z/8f1x2x31z5fNp5nxklFZ+Kw+3A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WdjpZ56SWJlKdcWbKwS3znSBLz0i8QWFKs/NBFa0RdvQ7NovQ5zZ2N668nfzZq90CMa0hWhJdoIrMJ+MPo0QzqgzmDGH0JV0Ehll0T2DZz3DwVUDYpKD0Imx7GF39V+IZxJejrlwB6dDeANV/40hnOQ0klRMhFrkvGSgGIk8D58= 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=LfOii2pA; arc=none smtp.client-ip=209.85.210.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="LfOii2pA" Received: by mail-pf1-f180.google.com with SMTP id d2e1a72fcca58-739b3fe7ce8so8036646b3a.0; Wed, 02 Jul 2025 17:49:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1751503794; x=1752108594; 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=8HC06ywZ8LZw2hqsJkACHK+KIbMcHl7fvZJDUy4IJYk=; b=LfOii2pAfgpKbWsE+mlf82WwAz4cT4DyIpJpE3YBsIpzwQXtwUA9WpMi6b+Gp675VM wUjW6xsGvIkQCizpxullRRLIhOgS238Sgdb7lqnonbOwDEsfzR4iCdDhwATEbX4zTmzO 136gWeXEu1Qsy1JCNH91QQGKpo+Uawyaw+g6Cg3/VyChpneQmlQErPbtamQsb/itEOEk qZhtG1YUi1UV0ZpkO0zMUg3WrN4IG7QKmK7gnd6qAdbcCaxFBjau+OjlbBOK68bMQGp1 7uHQ4muiVzh+5Fgm4JDwudOEySbH0RgXOeRKFuRHfAdsFu1VC5aZQrcXgLx1hdCbZSsa 06wg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751503794; x=1752108594; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=8HC06ywZ8LZw2hqsJkACHK+KIbMcHl7fvZJDUy4IJYk=; b=kA19EcpIyebMGu7khMlpiH33umY2tO2J2fETT4+ebRAvr18sxNFwA/iMYl12m1UF2s BRHObVqOk2dox8qRuamh26TEuek7VsT/o7DQRJ4snXbV9Fi9Aat6VFL2l4+nWXUtPhmw JQfJI9ZYwUAlN/eNUaH06eettVYgW7nz4LFqGuI4ZAN4JRDZRDR/7AGnUOOFIBhlbofy ir5kzkEx7RAeyex3G0cxjb39c3YJnJDe188U718742UhbcHuCKswsvVviae7mjJoghHr f/nm1vDBX3bVuhQGOVhPth26ZEMYU2CX977gMjrE3h0ApZ92oJgkubDljxoOPHsdPEnL drsQ== X-Forwarded-Encrypted: i=1; AJvYcCVI8BAP2jqgRnbArf2fQRDVQ8RGx8h6Cg6/qUkLAyYplMXO0bCo9oSFnAEHiipURlEHCv9zsvTshbsNQyyL@vger.kernel.org, AJvYcCVdAJmRcdcCxaZ6lOFP4whjOCbBiLa0vHII193P3Wv91JUgqg8GTFTX24Z08wptBiFGf/GPn80Jz/U=@vger.kernel.org, AJvYcCWmiufqGBQ0XcnZg4thSAvRUMonhh31f9XlmJE7m1fYbRNN0X+RolAMJixy7KlikHsCkF31Z2f/WFRs7rY=@vger.kernel.org X-Gm-Message-State: AOJu0Yz3T0rgV3TYEUFkbJzT14QyD8/bNcnv3T559eFK+xZTAbTYu8ps leUrhOsLuIixhibeWkP4is/RN+9WExh8HmPxcuTrD+azKZcRdnsxERag X-Gm-Gg: ASbGncsoBwIye88q2PVroCRh2mP/mSl+LEdqmt9zPFn9ncZDkz7KxP2BEuQ2BUDojSg lQFdpzKUy1zMPqL3e29ceWWykdI9CdGIUhm9cOAeoQcJpSoleOBFkbv1fcYYo58cZ2nmv2NYMAg J5pXdDC3f7c1GNd2SyItYqFaC0F/MSn9DnyM5wDt2s/mCXe0EmclOmhwyb84KcJ3hOH26/+ALbe LjaqKXe9Yz4tYVfcHKgkjNt4WA/z79KcbYfiolDWh+Ieu4DsyNQCmikHwHvVwaRDby7kR0icI9c GKMfZWAFdDliAOi8gzUl2qc4LMHfd3RFkPcGhQiZsRsQLvlj/BYNSjLNC753pE68udyiEzKAjf7 MMDC86cXJPdZAJaOzBRVzpcgAijwOmhStmmdFHsUhmA== X-Google-Smtp-Source: AGHT+IEE8mJnRI3HZz0kzto013rZNzNOPylDlTOagfl8qupa09MDNzNfnEHdBA/vZvMXhAnETJABtg== X-Received: by 2002:a05:6a00:3d52:b0:749:93d:b098 with SMTP id d2e1a72fcca58-74b512c06f2mr6161578b3a.22.1751503794069; Wed, 02 Jul 2025 17:49:54 -0700 (PDT) Received: from bliptop (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-74af55c7546sm15369815b3a.111.2025.07.02.17.49.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Jul 2025 17:49:53 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Xino Ni , 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 5/6] HID: Add lenovo-legos-hid core Date: Wed, 2 Jul 2025 17:49:42 -0700 Message-ID: <20250703004943.515919-6-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20250703004943.515919-1-derekjohn.clark@gmail.com> References: <20250703004943.515919-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 core interface for the lenovo-legos-hid driver. The purpose of core is to identify each available endpoint for the MCU in the Lenovo Legion Go S and route each one to the appropriate initialization and event handling functions. Endpoint specific logic will be implemented in subsequent patches. Signed-off-by: Derek J. Clark --- MAINTAINERS | 1 + drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/lenovo-legos-hid/Kconfig | 11 +++ drivers/hid/lenovo-legos-hid/Makefile | 6 ++ drivers/hid/lenovo-legos-hid/core.c | 113 ++++++++++++++++++++++++++ drivers/hid/lenovo-legos-hid/core.h | 25 ++++++ 7 files changed, 160 insertions(+) create mode 100644 drivers/hid/lenovo-legos-hid/Kconfig create mode 100644 drivers/hid/lenovo-legos-hid/Makefile create mode 100644 drivers/hid/lenovo-legos-hid/core.c create mode 100644 drivers/hid/lenovo-legos-hid/core.h diff --git a/MAINTAINERS b/MAINTAINERS index 68211d6eb236..aa61be9e5bc1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13751,6 +13751,7 @@ M: Derek J. Clark L: linux-input@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-driver-lenovo-legos-hid +F: drivers/hid/lenovo-legos-hid/* =20 LETSKETCH HID TABLET DRIVER M: Hans de Goede diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a57901203aeb..494e8386b598 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1436,4 +1436,6 @@ endif # HID =20 source "drivers/hid/usbhid/Kconfig" =20 +source "drivers/hid/lenovo-legos-hid/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 10ae5dedbd84..bdf3ebaf11e5 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -175,3 +175,5 @@ obj-$(CONFIG_AMD_SFH_HID) +=3D amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) +=3D surface-hid/ =20 obj-$(CONFIG_INTEL_THC_HID) +=3D intel-thc-hid/ + +obj-$(CONFIG_LENOVO_LEGOS_HID) +=3D lenovo-legos-hid/ diff --git a/drivers/hid/lenovo-legos-hid/Kconfig b/drivers/hid/lenovo-lego= s-hid/Kconfig new file mode 100644 index 000000000000..6918b25e191c --- /dev/null +++ b/drivers/hid/lenovo-legos-hid/Kconfig @@ -0,0 +1,11 @@ +config LENOVO_LEGOS_HID + tristate "Lenovo Legion Go S HID" + depends on USB_HID + depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR + help + Say Y here to include support for the Lenovo Legion Go S Handheld + Console Controller. + + To compile this driver as a module, choose M here: the module will + be called lenovo-legos-hid. diff --git a/drivers/hid/lenovo-legos-hid/Makefile b/drivers/hid/lenovo-leg= os-hid/Makefile new file mode 100644 index 000000000000..707f1be80c78 --- /dev/null +++ b/drivers/hid/lenovo-legos-hid/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile - Lenovo Legion Go S Handheld Console Controller driver +# +lenovo-legos-hid-y :=3D core.o +obj-$(CONFIG_LENOVO_LEGOS_HID) :=3D lenovo-legos-hid.o diff --git a/drivers/hid/lenovo-legos-hid/core.c b/drivers/hid/lenovo-legos= -hid/core.c new file mode 100644 index 000000000000..9049cbb8bd6c --- /dev/null +++ b/drivers/hid/lenovo-legos-hid/core.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go S series gamepad. + * + * Copyright (c) 2025 Derek J. Clark + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "core.h" +#include "../hid-ids.h" + +u8 get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf =3D to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *ep; + + if (intf) { + ep =3D intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; + } + + return -ENODEV; +} + +static int lenovo_legos_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + int ep; + + ep =3D get_endpoint_address(hdev); + + switch (ep) { + default: + break; + } + return 0; +} + +static int lenovo_legos_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret, ep; + + ep =3D get_endpoint_address(hdev); + if (ep <=3D 0) + return 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; + } + + switch (ep) { + default: + break; + } + + return ret; +} + +static void lenovo_legos_hid_remove(struct hid_device *hdev) +{ + int ep =3D get_endpoint_address(hdev); + + switch (ep) { + default: + hid_hw_close(hdev); + hid_hw_stop(hdev); + + break; + } +} + +static const struct hid_device_id lenovo_legos_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, lenovo_legos_devices); +static struct hid_driver lenovo_legos_hid =3D { + .name =3D "lenovo-legos-hid", + .id_table =3D lenovo_legos_devices, + .probe =3D lenovo_legos_hid_probe, + .remove =3D lenovo_legos_hid_remove, + .raw_event =3D lenovo_legos_raw_event, +}; +module_hid_driver(lenovo_legos_hid); + +MODULE_AUTHOR("Derek J. Clark"); +MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go S Series gamepad."); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/lenovo-legos-hid/core.h b/drivers/hid/lenovo-legos= -hid/core.h new file mode 100644 index 000000000000..efbc50896536 --- /dev/null +++ b/drivers/hid/lenovo-legos-hid/core.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright(C) 2025 Derek J. Clark */ + +#ifndef _LENOVO_LEGOS_HID_CORE_ +#define _LENOVO_LEGOS_HID_CORE_ + +#include + +#define GO_S_PACKET_SIZE 64 + +struct hid_device; + +enum legos_interface { + LEGION_GO_S_IAP_INTF_IN =3D 0x81, + LEGION_GO_S_TP_INTF_IN =3D 0x83, + LEGION_GO_S_CFG_INTF_IN, + LEGION_GO_S_IMU_INTF_IN, + LEGION_GO_S_GP_INFT_IN, + LEGION_GO_S_UNK_INTF_IN, +}; + +u8 get_endpoint_address(struct hid_device *hdev); + +#endif /* !_LENOVO_LEGOS_HID_CORE_*/ --=20 2.50.0 From nobody Wed Oct 8 02:01:45 2025 Received: from mail-pf1-f179.google.com (mail-pf1-f179.google.com [209.85.210.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 896231DDC1B; Thu, 3 Jul 2025 00:49:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503799; cv=none; b=RWww3pVSyMB25tAE0l8nK7YdW8Bo+TJCM486U0YHwQJuNPRev16TgeMAj3iI+ZgRJHOkLaRL9EzMND8w/CIQI5jRrqyASSfEn8Q9DmrqsfLm5Gw345SVqKpHDwiLqpBSKnD9i+pJQGMovIahtbyT5wDRn6OGgs9Dh8YjfXx4LmA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751503799; c=relaxed/simple; bh=kiXH7jDtyuRQQEy0RlM9enfx3lFrGVRjNc8pceWvr/4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jrRY8Dv6uhDo8XTS2Nw/HizAQU0t75ILYt7QtHOpHXBPdtP8e/jJiLBZrXjTr5n5Yb3mw0pIKUUnccW1kaBrLbf+ZFiHrQlbQpkWucTQQdAax9XhEdek/A7E196OMjnMIihz77LKfL6GF7MLrrVpgZsqSbncZ3GUAKOkuAnYD90= 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=GfES109k; arc=none smtp.client-ip=209.85.210.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="GfES109k" Received: by mail-pf1-f179.google.com with SMTP id d2e1a72fcca58-74924255af4so4293047b3a.1; Wed, 02 Jul 2025 17:49:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1751503796; x=1752108596; 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=6eo450L8q5WAnDHHsBBHicHtmkoM2oH+ORKs7xuLc0U=; b=GfES109kMC6SDWRTY4Rut92ZGdAaB4OmePryxuAwGo11piihNL8QhCB2y6cqNzpBvD XKMBF8kxMEGAyf/C5EnwY94dG6Jap6ku+pENv71AQR6dSKkPE895JzvtZecLDOeETTKz XWr3GGi54T3ZjgglXiXor/zx64kLRGFD5ojTlcZA5jF2d/zHDFOXKRjCtZlvZetQlQix 1GU/c007DqCIx6DP9OagFPzcMzFYKqjo6y/9UbqIYU28Wa1sCkxcGUVApYyF+9b4LMXw QcAMsXRC4XGyxhCgvF/pW4XFiAHhJ0VLT9o0oLMHvAmubNOm5h+PCS0iyJygrsJ2mUct XRcQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751503796; x=1752108596; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=6eo450L8q5WAnDHHsBBHicHtmkoM2oH+ORKs7xuLc0U=; b=xDH3y+f3N8ItMgYD+CIBFxxk17QyYIOv9o3GHjZG4FuHeF9lhLn9iNtoyVUl9ygMBg TKZp2OsjeJn2dCxBhUCd2+ByVsxL3JEdlJ6i/91PvcUyeNs3TDmfVWQoH8cryURnjnms FCQdWtG7EHfhprJD4HDtncCRwR6ah5ZoxlRlWoWEgJlX5FFtg0SmJDWyArcET1joSb/v vBHx0QeV3/K1ZJfHKrtezkGI3ZzsjmSJDBB0m9z8IEl6ma1jf1WMwuU3Fp83A0FcOYCE Gi6up8o0m1UrYs7cLyLWxsMJXucVWzvSDu92VBjGqbfjmKbggbOOhwAUICT3wIxROdbA YRGw== X-Forwarded-Encrypted: i=1; AJvYcCV7pMvzTQ5kY29udwxpnf7hSFryPLF6TZkX/ssRXbR5c9aRKCnPSIWF/FcY7P3ciWoCIj68s5sAtQVl/NU=@vger.kernel.org, AJvYcCVbOwBET/5suq+amVK8rHRbEt91pbYVEgBjBFPIxDk97LzD8nL3dlpVMuSc+ktt5dKY1uyzxKw9/XQmzX39@vger.kernel.org, AJvYcCXhQgpJ5VkqugY5QM37Oq4+QhPhTlwdnD4WKoziIpPWU4SleppMn8/eRTMAeILbfGrhQ2eVvbyfpv8=@vger.kernel.org X-Gm-Message-State: AOJu0Yw338eng4Ef66z+o2Cv55Hy3Bx7vpXtDCGaZyymlP3QhaXopZox ZNqOX9Z05GUgmx1LHo0N+7Lqu7HzGdgdtgReW7GLFdaZAUnXWa1At1lq0yBN9tRy X-Gm-Gg: ASbGncte102R2KnwKoNnAW0oyB5D1sKH4WiFnHQhbIj8AsHuWnXBwHfAffagpqMiKGI zbdb+kNPlQ70XvHVO2oZSKlX4wcaw1v5qdL/kV/W1UENlt8gVMb/wq66oyi/QUyGb0WsTNQHWOf PcwR1Xmv+DGX3gInX1PjcDBiC/Bs6jl/fgRLgBofIW7sU+ztJhhabpgSy3WTwEl0i8gmc7FftJw SFvSXKwXkFT0bXBl7x7bSjP3iGuVu9Uw7NvUgI2tXXtdvymW2arXXiP6krJSKLuE/5PEAjTKUjt 0hNpcfC/zDcWdHtG8Agg/pkTRPQ3a/DqAylzJfcyg548+G6WPHTg/raOPciGJG1gIg92301OvtD modLckxq6keKtIwIfXTYFQwcr92rwBONhclJZFOXRIA== X-Google-Smtp-Source: AGHT+IHDqEJXPzx1AOtVQqHuwTrmib2LVCStFFuGQFWA4yoO53bxn2DkkII/dwbOA/NznYiJyGPQbw== X-Received: by 2002:a05:6a00:1814:b0:740:b5f9:287b with SMTP id d2e1a72fcca58-74b512c2167mr6523841b3a.1.1751503795563; Wed, 02 Jul 2025 17:49:55 -0700 (PDT) Received: from bliptop (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-74af55c7546sm15369815b3a.111.2025.07.02.17.49.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Jul 2025 17:49:55 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: Mario Limonciello , Xino Ni , 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, Mario Limonciello Subject: [PATCH 6/6] HID: Add lenovo-legos-hid configuration endpoint interface Date: Wed, 2 Jul 2025 17:49:43 -0700 Message-ID: <20250703004943.515919-7-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20250703004943.515919-1-derekjohn.clark@gmail.com> References: <20250703004943.515919-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 the logical interface for the Lenovo Legion Go S configuration endpoint. This endpoint provides settings for the gamepad, trackpad, joystick RGB, and MCU information. The MCU only responds to raw data through hid_raw_output_report. Returns from this interface take ~800usec and multiple requests will lock the MCU, so a mutex is used to block additional calls until it can return. After 5ms the return call is aborted for normal MCU commands. The touchpad is queried through the MCU over I2C and as such takes longer to respond. The response for those calls aborts after 200ms. Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Signed-off-by: Derek J. Clark --- drivers/hid/lenovo-legos-hid/Makefile | 2 +- drivers/hid/lenovo-legos-hid/config.c | 1509 +++++++++++++++++++++++++ drivers/hid/lenovo-legos-hid/config.h | 19 + drivers/hid/lenovo-legos-hid/core.c | 9 + 4 files changed, 1538 insertions(+), 1 deletion(-) create mode 100644 drivers/hid/lenovo-legos-hid/config.c create mode 100644 drivers/hid/lenovo-legos-hid/config.h diff --git a/drivers/hid/lenovo-legos-hid/Makefile b/drivers/hid/lenovo-leg= os-hid/Makefile index 707f1be80c78..ded6158bcbf2 100644 --- a/drivers/hid/lenovo-legos-hid/Makefile +++ b/drivers/hid/lenovo-legos-hid/Makefile @@ -2,5 +2,5 @@ # # Makefile - Lenovo Legion Go S Handheld Console Controller driver # -lenovo-legos-hid-y :=3D core.o +lenovo-legos-hid-y :=3D core.o config.o obj-$(CONFIG_LENOVO_LEGOS_HID) :=3D lenovo-legos-hid.o diff --git a/drivers/hid/lenovo-legos-hid/config.c b/drivers/hid/lenovo-leg= os-hid/config.c new file mode 100644 index 000000000000..bd148f26c8c7 --- /dev/null +++ b/drivers/hid/lenovo-legos-hid/config.c @@ -0,0 +1,1509 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Lenovo Legion Go S devices. + * + * Copyright (c) 2025 Derek J. Clark + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define BOOL_TO_STR(x) x ? "true" : "false" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "config.h" + +struct legos_cfg { + struct delayed_work legos_cfg_setup; + struct completion send_cmd_complete; + struct led_classdev *led_cdev; + struct hid_device *hdev; + struct mutex cfg_mutex; /* Avoid Send/Rx MCU locking */ + u8 gp_auto_sleep_time; + u8 gp_dpad_mode; + u8 gp_mode; + u8 gp_poll_rate; + bool imu_bypass_en; + u8 imu_manufacturer; + u8 imu_sensor_en; + u8 mcu_id[12]; + u8 mouse_step; + u8 os_mode; + u8 rgb_effect; + bool rgb_en; + u8 rgb_mode; + u8 rgb_profile; + u8 rgb_speed; + bool tp_en; + u8 tp_linux_mode; + u8 tp_manufacturer; + u8 tp_version; + u8 tp_windows_mode; +} drvdata; + +/* GET/SET_GAMEPAD_CFG */ +enum GAMEPAD_MODE { + XINPUT, + DINPUT, +}; + +static const char *const GAMEPAD_MODE_TEXT[] =3D { + [XINPUT] =3D "xinput", + [DINPUT] =3D "dinput", +}; + +enum IMU_ENABLED { + IMU_OFF, + IMU_ON, + IMU_OFF_2S, +}; + +static const char *const IMU_ENABLED_TEXT[] =3D { + [IMU_OFF] =3D "false", + [IMU_ON] =3D "true", + [IMU_OFF_2S] =3D "wake-2s", +}; + +enum OS_TYPE { + WINDOWS, + LINUX, +}; + +static const char *const OS_TYPE_TEXT[] =3D { + [WINDOWS] =3D "windows", + [LINUX] =3D "linux", +}; + +enum POLL_RATE { + 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 { + DIR8, + DIR4, +}; + +static const char *const DPAD_MODE_TEXT[] =3D { + [DIR8] =3D "8-way", + [DIR4] =3D "4-way", +}; + +enum GAMEPAD_CFG_INDEX { + NONE =3D 0x00, + CFG_GAMEPAD_MODE, // GAMEPAD_MODE + CFG_AUTO_SLP_TIME =3D 0x04, // 1-255 + CFG_PASS_ENABLE, // FEATURE_ENABLED + CFG_LIGHT_ENABLE, // FEATURE_ENABLED + CFG_IMU_ENABLE, // FEATURE_ENABLED + CFG_TP_ENABLE, // FEATURE_ENABLED + CFG_OS_TYPE =3D 0x0A, // OS_TYPE + CFG_POLL_RATE =3D 0x10, // POLL_RATE + CFG_DPAD_MODE, // DPAD_MODE + CFG_MS_WHEEL_STEP, // 1-127 +}; + +/* GET/SET_TP_PARAM */ +enum TOUCHPAD_MODE { + TP_REL, + TP_ABS, +}; + +static const char *const TOUCHPAD_MODE_TEXT[] =3D { + [TP_REL] =3D "relative", + [TP_ABS] =3D "absolute", +}; + +enum TOUCHPAD_CFG_INDEX { + CFG_WINDOWS_MODE =3D 0x03, // TOUCHPAD_MODE + CFG_LINUX_MODE, // TOUCHPAD_MODE + +}; + +enum RGB_MODE { + 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 { + 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", +}; + +/* GET/SET_LIGHT_CFG */ +enum LIGHT_CFG_INDEX { + LIGHT_MODE_SEL =3D 0x01, + LIGHT_PROFILE_SEL, + USR_LIGHT_PROFILE_1, + USR_LIGHT_PROFILE_2, + USR_LIGHT_PROFILE_3, +}; + +enum MCU_COMMAND { + SEND_HEARTBEAT, + GET_VERSION, + GET_MCU_ID, + GET_GAMEPAD_CFG, + SET_GAMEPAD_CFG, + GET_TP_PARAM, + SET_TP_PARAM, + GET_MOTOR_CFG, + SET_MOTOR_CFG, + GET_TRIGGER_CFG, + SET_TRIGGER_CFG, + GET_STICK_CFG, + SET_STICK_CFG, + GET_GYRO_CFG, + SET_GYRO_CFG, + GET_LIGHT_CFG, + SET_LIGHT_CFG, + GET_KEY_MAP, + SET_KEY_MAP, + INT_EVENT_REPORT =3D 0xc0, + INT_EVENT_CLEAR, + GET_PL_TEST =3D 0xdf, + SET_PL_TEST, + START_IAP_UPGRADE, + DBG_CTRL, + PL_TP_TEST, + RESTORE_FACTORY, + IC_RESET, +}; + +/*GET/SET_PL_TEST */ +enum TEST_INDEX { + TEST_EN =3D 0x01, + TEST_TP_MFR, // TP_MANUFACTURER + TEST_IMU_MFR, // IMU_MANUFACTURER + TEST_TP_VER, // u8 + MOTOR_F0_CALI =3D 0x10, + READ_MOTOR_F0, + SAVE_MOTOR_F0, + TEST_LED_L =3D 0x20, + TEST_LED_R, + LED_COLOR_CALI, + STICK_CALI_TH =3D 0x30, + TRIGGER_CALI_TH, + STICK_CALI_DEAD, + TRIGGER_CALI_DEAD, + STICK_CALI_POLARITY, + TRIGGER_CALI_POLARITY, + GYRO_CALI_CFG, + STICK_CALI_TOUT, + TRIGGER_CALI_TOUT, +}; + +enum TP_MANUFACTURER { + TP_NONE, + TP_BETTERLIFE, + TP_SIPO, +}; + +static const char *const TP_MANUFACTURER_TEXT[] =3D { + [TP_NONE] =3D "None", + [TP_BETTERLIFE] =3D "BetterLife", + [TP_SIPO] =3D "SIPO", +}; + +enum IMU_MANUFACTURER { + 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", +}; + +struct command_report { + u8 cmd; + u8 sub_cmd; + u8 data[63]; +} __packed; + +struct version_report { + u8 cmd; + u32 version; + u8 reserved[59]; +} __packed; + +struct legos_cfg_rw_attr { + u8 index; +}; + +int legos_cfg_raw_event(u8 *data, int size) +{ + struct led_classdev_mc *mc_cdev; + struct command_report *cmd_rep; + struct version_report *ver_rep; + int ret; + + print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, data, size, false); + + if (size !=3D GO_S_PACKET_SIZE) + return -EINVAL; + + cmd_rep =3D (struct command_report *)data; + switch (cmd_rep->cmd) { + case GET_VERSION: + ver_rep =3D (struct version_report *)data; + drvdata.hdev->firmware_version =3D + __cpu_to_le32(ver_rep->version); + ret =3D 0; + break; + case GET_MCU_ID: + drvdata.mcu_id[0] =3D cmd_rep->sub_cmd; + memcpy(&drvdata.mcu_id[1], cmd_rep->data, 11); + ret =3D 0; + break; + case GET_GAMEPAD_CFG: + switch (cmd_rep->sub_cmd) { + case CFG_GAMEPAD_MODE: + drvdata.gp_mode =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_AUTO_SLP_TIME: + drvdata.gp_auto_sleep_time =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_PASS_ENABLE: + drvdata.imu_bypass_en =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_LIGHT_ENABLE: + drvdata.rgb_en =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_IMU_ENABLE: + drvdata.imu_sensor_en =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_TP_ENABLE: + drvdata.tp_en =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_OS_TYPE: + drvdata.os_mode =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_POLL_RATE: + drvdata.gp_poll_rate =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_DPAD_MODE: + drvdata.gp_dpad_mode =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_MS_WHEEL_STEP: + drvdata.mouse_step =3D cmd_rep->data[0]; + ret =3D 0; + break; + default: + ret =3D -EINVAL; + break; + } + break; + case GET_TP_PARAM: + switch (cmd_rep->sub_cmd) { + case CFG_LINUX_MODE: + drvdata.tp_linux_mode =3D cmd_rep->data[0]; + ret =3D 0; + break; + case CFG_WINDOWS_MODE: + drvdata.tp_windows_mode =3D cmd_rep->data[0]; + ret =3D 0; + break; + default: + ret =3D -EINVAL; + break; + } + break; + case GET_PL_TEST: + 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; + } + break; + case GET_LIGHT_CFG: + 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; + } + break; + case GET_GYRO_CFG: + case GET_KEY_MAP: + case GET_MOTOR_CFG: + case GET_STICK_CFG: + case GET_TRIGGER_CFG: + ret =3D -EINVAL; + break; + case SET_GAMEPAD_CFG: + case SET_GYRO_CFG: + case SET_KEY_MAP: + case SET_LIGHT_CFG: + case SET_MOTOR_CFG: + case SET_STICK_CFG: + case SET_TP_PARAM: + case SET_TRIGGER_CFG: + ret =3D -cmd_rep->data[0]; + break; + default: + ret =3D -EINVAL; + break; + }; + + if (ret && cmd_rep->cmd !=3D START_IAP_UPGRADE) + dev_err(&drvdata.hdev->dev, + "Command %u with index %u failed with error code: %x\n", + cmd_rep->cmd, cmd_rep->sub_cmd, ret); + + pr_debug("Last command: %u, sub_cmd: %u, ret: %u, val: [%ph]\n", + cmd_rep->cmd, cmd_rep->sub_cmd, ret, cmd_rep->data); + + complete(&drvdata.send_cmd_complete); + return ret; +} + +static int legos_cfg_send_cmd(struct hid_device *hdev, u8 *buf, int ep) +{ + unsigned char *dmabuf __free(kfree) =3D NULL; + size_t size =3D GO_S_PACKET_SIZE; + int ret; + + pr_debug("Send data as raw output report: [%*ph]\n", GO_S_PACKET_SIZE, + buf); + + dmabuf =3D kmemdup(buf, size, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + ret =3D hid_hw_output_report(hdev, dmabuf, size); + if (ret < 0) + return ret; + + return ret =3D=3D size ? 0 : -EINVAL; +} + +static int mcu_property_out(struct hid_device *hdev, enum MCU_COMMAND comm= and, + u8 index, u8 *val, size_t size) +{ + u8 outbuf[GO_S_PACKET_SIZE] =3D { command, index }; + int ep =3D get_endpoint_address(hdev); + unsigned int i; + int timeout =3D 5; + int ret; + + if (ep !=3D LEGION_GO_S_CFG_INTF_IN) + return -ENODEV; + + for (i =3D 0; i < size; i++) + outbuf[i + 2] =3D val[i]; + + guard(mutex)(&drvdata.cfg_mutex); + ret =3D legos_cfg_send_cmd(hdev, outbuf, ep); + if (ret) + return ret; + + /* PL_TEST commands can take longer because they go out to another device= */ + if (command =3D=3D GET_PL_TEST) + timeout =3D 200; + + ret =3D wait_for_completion_interruptible_timeout(&drvdata.send_cmd_compl= ete, + msecs_to_jiffies(timeout)); + + if (ret =3D=3D 0) /* timeout occurred */ + ret =3D -EBUSY; + if (ret > 0) /* timeout/interrupt didn't occur */ + ret =3D 0; + + reinit_completion(&drvdata.send_cmd_complete); + return ret; +} + +/* Read-Write Attributes */ +static ssize_t gamepad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum GAMEPAD_CFG_INDEX index) +{ + size_t size =3D 1; + u8 val =3D 0; + bool res; + int ret; + + switch (index) { + case CFG_GAMEPAD_MODE: { + ret =3D sysfs_match_string(GAMEPAD_MODE_TEXT, buf); + if (ret < 0) + return ret; + val =3D ret; + drvdata.gp_mode =3D val; + break; + } + case CFG_AUTO_SLP_TIME: + ret =3D kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 255) + return -EINVAL; + drvdata.gp_auto_sleep_time =3D val; + break; + case CFG_IMU_ENABLE: + ret =3D sysfs_match_string(IMU_ENABLED_TEXT, buf); + if (ret < 0) + return ret; + val =3D ret; + drvdata.imu_sensor_en =3D val; + break; + case CFG_PASS_ENABLE: + ret =3D kstrtobool(buf, &res); + if (ret < 0) + return ret; + drvdata.imu_bypass_en =3D res; + break; + case CFG_LIGHT_ENABLE: + ret =3D kstrtobool(buf, &res); + if (ret < 0) + return ret; + drvdata.rgb_en =3D res; + break; + case CFG_TP_ENABLE: + ret =3D kstrtobool(buf, &res); + if (ret < 0) + return ret; + drvdata.tp_en =3D res; + break; + case CFG_OS_TYPE: + ret =3D sysfs_match_string(OS_TYPE_TEXT, buf); + if (ret < 0) + return ret; + val =3D ret; + drvdata.os_mode =3D val; + break; + case CFG_POLL_RATE: + ret =3D sysfs_match_string(POLL_RATE_TEXT, buf); + if (ret < 0) + return ret; + val =3D ret; + drvdata.gp_poll_rate =3D val; + break; + case CFG_DPAD_MODE: + ret =3D sysfs_match_string(DPAD_MODE_TEXT, buf); + if (ret < 0) + return ret; + val =3D ret; + drvdata.gp_dpad_mode =3D val; + break; + case CFG_MS_WHEEL_STEP: + ret =3D kstrtou8(buf, 10, &val); + if (ret) + return ret; + if (val < 1 || val > 127) + return -EINVAL; + drvdata.mouse_step =3D val; + 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 GAMEPAD_CFG_INDEX index) +{ + size_t count =3D 0; + char *res; + u8 i; + + count =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, 0, 0); + if (count < 0) + return count; + + switch (index) { + case CFG_GAMEPAD_MODE: + i =3D drvdata.gp_mode; + if (i > DINPUT) + count =3D -EINVAL; + else + count =3D sysfs_emit(buf, "%s\n", GAMEPAD_MODE_TEXT[i]); + break; + case CFG_AUTO_SLP_TIME: + count =3D sysfs_emit(buf, "%u\n", drvdata.gp_auto_sleep_time); + break; + case CFG_IMU_ENABLE: + i =3D drvdata.imu_sensor_en; + if (i > IMU_OFF_2S) + count =3D -EINVAL; + else + count =3D sysfs_emit(buf, "%s\n", IMU_ENABLED_TEXT[i]); + break; + case CFG_PASS_ENABLE: + res =3D BOOL_TO_STR(drvdata.imu_bypass_en); + count =3D sysfs_emit(buf, "%s\n", res); + break; + case CFG_LIGHT_ENABLE: + res =3D BOOL_TO_STR(drvdata.rgb_en); + count =3D sysfs_emit(buf, "%s\n", res); + break; + case CFG_TP_ENABLE: + res =3D BOOL_TO_STR(drvdata.tp_en); + count =3D sysfs_emit(buf, "%s\n", res); + break; + case CFG_OS_TYPE: + i =3D drvdata.os_mode; + if (i > LINUX) + count =3D -EINVAL; + else + count =3D sysfs_emit(buf, "%s\n", OS_TYPE_TEXT[i]); + break; + case CFG_POLL_RATE: + i =3D drvdata.gp_poll_rate; + if (i > HZ1000) + count =3D -EINVAL; + else + count =3D sysfs_emit(buf, "%s\n", POLL_RATE_TEXT[i]); + break; + case CFG_DPAD_MODE: + i =3D drvdata.gp_dpad_mode; + if (i > DIR4) + count =3D -EINVAL; + else + count =3D sysfs_emit(buf, "%s\n", DPAD_MODE_TEXT[i]); + break; + case CFG_MS_WHEEL_STEP: + i =3D drvdata.mouse_step; + if (i < 1 || i > 127) + count =3D -EINVAL; + else + count =3D sysfs_emit(buf, "%u\n", i); + break; + default: + count =3D -EINVAL; + break; + } + + return count; +} + +static ssize_t gamepad_property_options(struct device *dev, + struct device_attribute *attr, + char *buf, enum GAMEPAD_CFG_INDEX index) +{ + size_t count =3D 0; + unsigned int i; + + switch (index) { + case CFG_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 CFG_AUTO_SLP_TIME: + return sysfs_emit(buf, "0-255\n"); + case CFG_IMU_ENABLE: + for (i =3D 0; i < ARRAY_SIZE(IMU_ENABLED_TEXT); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", IMU_ENABLED_TEXT[i]); + break; + case CFG_PASS_ENABLE: + case CFG_LIGHT_ENABLE: + case CFG_TP_ENABLE: + return sysfs_emit(buf, "true false\n"); + case CFG_OS_TYPE: + 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 CFG_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 CFG_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 CFG_MS_WHEEL_STEP: + return sysfs_emit(buf, "1-127\n"); + default: + return count; + } + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} + +static ssize_t touchpad_property_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + enum TOUCHPAD_CFG_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; + drvdata.tp_windows_mode =3D val; + break; + case CFG_LINUX_MODE: + ret =3D sysfs_match_string(TOUCHPAD_MODE_TEXT, buf); + if (ret < 0) + return ret; + val =3D ret; + drvdata.tp_linux_mode =3D val; + 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_CFG_INDEX index) +{ + int ret =3D 0; + u8 i; + + ret =3D mcu_property_out(drvdata.hdev, GET_TP_PARAM, index, 0, 0); + if (ret < 0) + return ret; + + switch (index) { + case CFG_WINDOWS_MODE: + i =3D drvdata.tp_windows_mode; + break; + case CFG_LINUX_MODE: + i =3D drvdata.tp_linux_mode; + break; + default: + return -EINVAL; + } + + if (i > TP_ABS) + 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_CFG_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; +} + +/* RGB LED */ +static int rgb_cfg_call(struct hid_device *hdev, enum MCU_COMMAND cmd, + enum LIGHT_CFG_INDEX index, u8 *val, size_t size) +{ + if (cmd !=3D SET_LIGHT_CFG && cmd !=3D GET_LIGHT_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_profile_call(enum MCU_COMMAND cmd, u8 *rgb_profile, size_t = size) +{ + enum LIGHT_CFG_INDEX index; + + index =3D drvdata.rgb_profile + 2; + + return rgb_cfg_call(drvdata.hdev, cmd, index, rgb_profile, size); +} + +static int rgb_write_profile(void) +{ + struct led_classdev_mc *mc_cdev =3D lcdev_to_mccdev(drvdata.led_cdev); + + 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, + drvdata.rgb_speed }; + + return rgb_profile_call(SET_LIGHT_CFG, rgb_profile, 6); +} + +static int rgb_attr_show(void) +{ + return rgb_profile_call(GET_LIGHT_CFG, 0, 0); +}; + +static int rgb_attr_store(void) +{ + if (drvdata.rgb_mode !=3D RGB_MODE_CUSTOM) + return -EINVAL; + + return rgb_write_profile(); +} + +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; + + return sysfs_emit(buf, "%s\n", RGB_EFFECT_TEXT[drvdata.rgb_effect]); +} + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int ret; + + ret =3D sysfs_match_string(RGB_EFFECT_TEXT, buf); + if (ret < 0) + return ret; + + drvdata.rgb_effect =3D ret; + + ret =3D rgb_attr_store(); + if (ret) + return ret; + + return count; +}; + +static ssize_t rgb_effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_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_show(struct device *dev, struct device_attribute = *attr, + char *buf) +{ + int ret; + + ret =3D rgb_attr_show(); + if (ret) + return ret; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int val =3D 0; + int ret; + + ret =3D kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val > 100 || val < 0) + return -EINVAL; + + drvdata.rgb_speed =3D val; + + ret =3D rgb_attr_store(); + if (ret) + return ret; + + return count; +}; + +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_show(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", RGB_MODE_TEXT[drvdata.rgb_mode]); +}; + +static ssize_t rgb_mode_store(struct device *dev, struct device_attribute = *attr, + const char *buf, size_t count) +{ + size_t size =3D 1; + int ret; + + ret =3D sysfs_match_string(RGB_MODE_TEXT, buf); + if (ret < 0) + return ret; + + drvdata.rgb_mode =3D ret; + + if (!drvdata.rgb_mode) + size =3D 0; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_LIGHT_CFG, LIGHT_MODE_SEL, + &drvdata.rgb_mode, size); + if (ret < 0) + return ret; + + return count; +}; + +static ssize_t rgb_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t count =3D 0; + unsigned int i; + + for (i =3D 0; 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_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile); +}; + +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 > 3 || val < 1) + return -EINVAL; + + drvdata.rgb_profile =3D val; + + if (!drvdata.rgb_profile) + size =3D 0; + + ret =3D rgb_cfg_call(drvdata.hdev, SET_LIGHT_CFG, LIGHT_PROFILE_SEL, + &drvdata.rgb_profile, size); + if (ret < 0) + return ret; + + return count; +}; + +static ssize_t rgb_profile_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1-3\n"); +} + +static enum led_brightness legos_rgb_color_get(struct led_classdev *led_cd= ev) +{ + return led_cdev->brightness; +}; + +static void legos_rgb_color_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int ret; + + led_cdev->brightness =3D brightness; + + ret =3D rgb_attr_store(); + switch (ret) { + case 0: + 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 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, \ + } + +#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, \ + } + +#define ATTR_LEGOS_GAMEPAD_RW(_name, _attrname, _rtype) = \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return gamepad_property_store(dev, attr, buf, count, \ + _name.index); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return gamepad_property_show(dev, attr, buf, _name.index); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return gamepad_property_options(dev, attr, buf, _name.index); \ + } \ + DEVICE_ATTR_RW_NAMED(_name, _attrname) + +#define ATTR_LEGOS_TOUCHPAD_RW(_name, _attrname, _rtype) = \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return touchpad_property_store(dev, attr, buf, count, \ + _name.index); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return touchpad_property_show(dev, attr, buf, _name.index); \ + } \ + static ssize_t _name##_##_rtype##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return touchpad_property_options(dev, attr, buf, _name.index); \ + } \ + DEVICE_ATTR_RW_NAMED(_name, _attrname) + +/* Gamepad */ +struct legos_cfg_rw_attr auto_sleep_time =3D { CFG_AUTO_SLP_TIME }; +struct legos_cfg_rw_attr dpad_mode =3D { CFG_DPAD_MODE }; +struct legos_cfg_rw_attr gamepad_mode =3D { CFG_GAMEPAD_MODE }; +struct legos_cfg_rw_attr gamepad_poll_rate =3D { CFG_POLL_RATE }; + +ATTR_LEGOS_GAMEPAD_RW(auto_sleep_time, "auto_sleep_time", range); +ATTR_LEGOS_GAMEPAD_RW(dpad_mode, "dpad_mode", index); +ATTR_LEGOS_GAMEPAD_RW(gamepad_mode, "mode", index); +ATTR_LEGOS_GAMEPAD_RW(gamepad_poll_rate, "poll_rate", index); +static DEVICE_ATTR_RO(auto_sleep_time_range); +static DEVICE_ATTR_RO(dpad_mode_index); +static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index"); +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, +}; + +/* IMU */ +struct legos_cfg_rw_attr imu_bypass_enabled =3D { CFG_PASS_ENABLE }; +struct legos_cfg_rw_attr imu_manufacturer =3D { TEST_IMU_MFR }; +struct legos_cfg_rw_attr imu_sensor_enabled =3D { CFG_IMU_ENABLE }; + +ATTR_LEGOS_GAMEPAD_RW(imu_bypass_enabled, "bypass_enabled", index); +ATTR_LEGOS_GAMEPAD_RW(imu_sensor_enabled, "sensor_enabled", index); +static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_inde= x"); +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, +}; + +/* MCU */ +struct legos_cfg_rw_attr os_mode =3D { CFG_OS_TYPE }; + +ATTR_LEGOS_GAMEPAD_RW(os_mode, "os_mode", index); +static DEVICE_ATTR_RO(os_mode_index); + +static struct attribute *legos_mcu_attrs[] =3D { + &dev_attr_os_mode.attr, + &dev_attr_os_mode_index.attr, + NULL, +}; + +/* RGB */ +struct legos_cfg_rw_attr rgb_enabled =3D { CFG_LIGHT_ENABLE }; + +ATTR_LEGOS_GAMEPAD_RW(rgb_enabled, "enabled", index); +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 *legos_rgb_attrs[] =3D { + &dev_attr_rgb_effect.attr, + &dev_attr_rgb_effect_index.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_speed_range.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_enabled.attr, + &dev_attr_rgb_enabled_index.attr, + NULL, +}; + +/* Touchpad */ +struct legos_cfg_rw_attr touchpad_enabled =3D { CFG_TP_ENABLE }; +struct legos_cfg_rw_attr touchpad_linux_mode =3D { CFG_LINUX_MODE }; +struct legos_cfg_rw_attr touchpad_manufacturer =3D { TEST_TP_MFR }; +struct legos_cfg_rw_attr touchpad_version =3D { TEST_TP_VER }; +struct legos_cfg_rw_attr touchpad_windows_mode =3D { CFG_WINDOWS_MODE }; + +ATTR_LEGOS_GAMEPAD_RW(touchpad_enabled, "enabled", index); +ATTR_LEGOS_TOUCHPAD_RW(touchpad_linux_mode, "linux_mode", index); +ATTR_LEGOS_TOUCHPAD_RW(touchpad_windows_mode, "windows_mode", index); +static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index"); +static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index"); +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, +}; + +static const struct attribute_group gamepad_attr_group =3D { + .name =3D "gamepad", + .attrs =3D legos_gamepad_attrs, +}; + +static const struct attribute_group imu_attr_group =3D { + .name =3D "imu", + .attrs =3D legos_imu_attrs, +}; + +static const struct attribute_group mcu_attr_group =3D { + .attrs =3D legos_mcu_attrs, +}; + +static struct attribute_group rgb_attr_group =3D { + .attrs =3D legos_rgb_attrs, +}; + +static const struct attribute_group touchpad_attr_group =3D { + .name =3D "touchpad", + .attrs =3D legos_touchpad_attrs, +}; + +static const struct attribute_group *legos_top_level_attr_groups[] =3D { + &gamepad_attr_group, + &imu_attr_group, + &mcu_attr_group, + &touchpad_attr_group, + NULL, +}; + +struct mc_subled legos_rgb_subled_info[] =3D { + { + .color_index =3D LED_COLOR_ID_RED, + .brightness =3D 0x50, + .intensity =3D 0x24, + .channel =3D 0x1, + }, + { + .color_index =3D LED_COLOR_ID_GREEN, + .brightness =3D 0x50, + .intensity =3D 0x22, + .channel =3D 0x2, + }, + { + .color_index =3D LED_COLOR_ID_BLUE, + .brightness =3D 0x50, + .intensity =3D 0x99, + .channel =3D 0x3, + }, +}; + +struct led_classdev_mc legos_cdev_rgb =3D { + .led_cdev =3D { + .name =3D "go_s:rgb:joystick_rings", + .brightness =3D 0x50, + .max_brightness =3D 0x64, + .brightness_set =3D legos_rgb_color_set, + .brightness_get =3D legos_rgb_color_get, + }, + .num_colors =3D ARRAY_SIZE(legos_rgb_subled_info), + .subled_info =3D legos_rgb_subled_info, +}; + +void cfg_setup(struct work_struct *work) +{ + int ret; + + /* Gamepad */ + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_AUTO_SLP_TIME, + 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve gamepad auto sleep time: %i\n", + ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_DPAD_MODE, 0, + 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve gamepad dpad mode: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_GAMEPAD_MODE, + 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve gamepad mode: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_POLL_RATE, 0, + 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve gamepad poll rate: %i\n", ret); + return; + } + + /* IMU */ + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_PASS_ENABLE, + 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve IMU bypass enabled: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_IMU_MFR, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve IMU Manufacturer: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_IMU_ENABLE, 0, + 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve IMU enabled: %i\n", ret); + return; + } + + /* MCU */ + ret =3D mcu_property_out(drvdata.hdev, GET_MCU_ID, NONE, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU ID: %i\n", + ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_OS_TYPE, 0, + 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve MCU OS Mode: %i\n", ret); + return; + } + + /* RGB */ + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_LIGHT_ENABLE, + 0, 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB enabled: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_LIGHT_CFG, LIGHT_MODE_SEL, 0, + 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Mode: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_LIGHT_CFG, LIGHT_PROFILE_SEL, + 0, 0); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Profile: %i\n", ret); + return; + } + + ret =3D rgb_attr_show(); + if (ret < 0) { + dev_err(drvdata.led_cdev->dev, + "Failed to retrieve RGB Profile Data: %i\n", ret); + return; + } + + /* Touchpad */ + ret =3D mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_TP_ENABLE, 0, + 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve touchpad enabled: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_TP_PARAM, CFG_LINUX_MODE, 0, + 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve touchpad Linux mode: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_MFR, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve touchpad manufacturer: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_TP_PARAM, CFG_WINDOWS_MODE, 0, + 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve touchpad Windows mode: %i\n", ret); + return; + } + + ret =3D mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_VER, 0, 0); + if (ret) { + dev_err(&drvdata.hdev->dev, + "Failed to retrieve touchpad Version: %i\n", ret); + return; + } +} + +static int legos_cfg_uevent(const struct device *dev, + struct kobj_uevent_env *env) +{ + if (add_uevent_var(env, "LEGOS_TP_MANUFACTURER=3D%s", + TP_MANUFACTURER_TEXT[drvdata.tp_manufacturer])) + return -ENOMEM; + if (add_uevent_var(env, "LEGOS_TP_VERSION=3D%u", drvdata.tp_version)) + return -ENOMEM; + if (add_uevent_var(env, "LEGOS_IMU_MANUFACTURER=3D%s", + IMU_MANUFACTURER_TEXT[drvdata.imu_manufacturer])) + return -ENOMEM; + if (add_uevent_var(env, "LEGOS_MCU_ID=3D%*phN", 12, &drvdata.mcu_id)) + return -ENOMEM; + return 0; +} + +int legos_cfg_probe(struct hid_device *hdev, const struct hid_device_id *_= id) +{ + int ret; + + mutex_init(&drvdata.cfg_mutex); + + hid_set_drvdata(hdev, &drvdata); + + drvdata.hdev =3D hdev; + hdev->uevent =3D legos_cfg_uevent; + + ret =3D sysfs_create_groups(&hdev->dev.kobj, legos_top_level_attr_groups); + if (ret) { + dev_err(&hdev->dev, + "Failed to create gamepad configuration attributes: %i\n", + ret); + return ret; + } + + ret =3D devm_led_classdev_multicolor_register(&hdev->dev, + &legos_cdev_rgb); + if (ret) { + dev_err(&hdev->dev, "Failed to create RGB device: %i\n", ret); + return ret; + } + + ret =3D devm_device_add_group(legos_cdev_rgb.led_cdev.dev, + &rgb_attr_group); + if (ret) { + dev_err(&hdev->dev, + "Failed to create RGB configuratiion attributes: %i\n", + ret); + return ret; + } + + drvdata.led_cdev =3D &legos_cdev_rgb.led_cdev; + drvdata.led_cdev->color =3D LED_COLOR_ID_RGB; + + 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.legos_cfg_setup, &cfg_setup); + schedule_delayed_work(&drvdata.legos_cfg_setup, msecs_to_jiffies(2)); + + return 0; +} + +void legos_cfg_remove(struct hid_device *hdev) +{ + guard(mutex)(&drvdata.cfg_mutex); + cancel_delayed_work_sync(&drvdata.legos_cfg_setup); + sysfs_remove_groups(&hdev->dev.kobj, legos_top_level_attr_groups); + hid_hw_close(hdev); + hid_hw_stop(hdev); + hdev->uevent =3D NULL; + hid_set_drvdata(hdev, NULL); +} diff --git a/drivers/hid/lenovo-legos-hid/config.h b/drivers/hid/lenovo-leg= os-hid/config.h new file mode 100644 index 000000000000..3d13744e2692 --- /dev/null +++ b/drivers/hid/lenovo-legos-hid/config.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Copyright(C) 2025 Derek J. Clark */ + +#ifndef _LENOVO_LEGOS_HID_CONFIG_ +#define _LENOVO_LEGOS_HID_CONFIG_ + +#include + +struct hid_device; +struct hid_device_id; +struct work_struct; + +int legos_cfg_raw_event(u8 *data, int size); +void cfg_setup(struct work_struct *work); +int legos_cfg_probe(struct hid_device *hdev, const struct hid_device_id *_= id); +void legos_cfg_remove(struct hid_device *hdev); + +#endif /* !_LENOVO_LEGOS_HID_CONFIG_*/ diff --git a/drivers/hid/lenovo-legos-hid/core.c b/drivers/hid/lenovo-legos= -hid/core.c index 9049cbb8bd6c..1a5d5396ea6d 100644 --- a/drivers/hid/lenovo-legos-hid/core.c +++ b/drivers/hid/lenovo-legos-hid/core.c @@ -11,6 +11,7 @@ #include =20 #include "core.h" +#include "config.h" #include "../hid-ids.h" =20 u8 get_endpoint_address(struct hid_device *hdev) @@ -35,6 +36,8 @@ static int lenovo_legos_raw_event(struct hid_device *hdev, ep =3D get_endpoint_address(hdev); =20 switch (ep) { + case LEGION_GO_S_CFG_INTF_IN: + return legos_cfg_raw_event(data, size); default: break; } @@ -70,6 +73,9 @@ static int lenovo_legos_hid_probe(struct hid_device *hdev, } =20 switch (ep) { + case LEGION_GO_S_CFG_INTF_IN: + ret =3D legos_cfg_probe(hdev, id); + break; default: break; } @@ -82,6 +88,9 @@ static void lenovo_legos_hid_remove(struct hid_device *hd= ev) int ep =3D get_endpoint_address(hdev); =20 switch (ep) { + case LEGION_GO_S_CFG_INTF_IN: + legos_cfg_remove(hdev); + break; default: hid_hw_close(hdev); hid_hw_stop(hdev); --=20 2.50.0