[PATCH 5/6] HID: Add lenovo-legos-hid core

From: Derek J. Clark
Date: Wed Jul 02 2025 - 20:51:31 EST


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 <derekjohn.clark@xxxxxxxxx>
---
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 <derekjohn.clark@xxxxxxxxx>
L: linux-input@xxxxxxxxxxxxxxx
S: Maintained
F: Documentation/ABI/testing/sysfs-driver-lenovo-legos-hid
+F: drivers/hid/lenovo-legos-hid/*

LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hansg@xxxxxxxxxx>
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

source "drivers/hid/usbhid/Kconfig"

+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) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/

obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/
+
+obj-$(CONFIG_LENOVO_LEGOS_HID) += lenovo-legos-hid/
diff --git a/drivers/hid/lenovo-legos-hid/Kconfig b/drivers/hid/lenovo-legos-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-legos-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 := core.o
+obj-$(CONFIG_LENOVO_LEGOS_HID) := 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 <derekjohn.clark@xxxxxxxxx>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hid.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+
+#include "core.h"
+#include "../hid-ids.h"
+
+u8 get_endpoint_address(struct hid_device *hdev)
+{
+ struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+ struct usb_host_endpoint *ep;
+
+ if (intf) {
+ ep = 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 = 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 = get_endpoint_address(hdev);
+ if (ep <= 0)
+ return ep;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "Parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "Failed to start HID device\n");
+ return ret;
+ }
+
+ ret = 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 = 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[] = {
+ { 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 = {
+ .name = "lenovo-legos-hid",
+ .id_table = lenovo_legos_devices,
+ .probe = lenovo_legos_hid_probe,
+ .remove = lenovo_legos_hid_remove,
+ .raw_event = 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 <derekjohn.clark@xxxxxxxxx> */
+
+#ifndef _LENOVO_LEGOS_HID_CORE_
+#define _LENOVO_LEGOS_HID_CORE_
+
+#include <linux/types.h>
+
+#define GO_S_PACKET_SIZE 64
+
+struct hid_device;
+
+enum legos_interface {
+ LEGION_GO_S_IAP_INTF_IN = 0x81,
+ LEGION_GO_S_TP_INTF_IN = 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_*/
--
2.50.0