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