* [PATCH v4 08/16] HID: hid-lenovo-go: Add OS Mode Toggle
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds OS Mode toggle, who's primary function is to change the built-in
functional chords to use the right handle legion button instead of the
left handle legion button as the mode shift key. This setting needs to
be restored after resume, so a reset-resume hook is added.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
V3:
- Fix collision with os_mode_index attribute and os_mode_index enum.
---
drivers/hid/hid-lenovo-go.c | 137 ++++++++++++++++++++++++++++++++++++
1 file changed, 137 insertions(+)
diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c
index b5a1d3e1988f2..883e75894a437 100644
--- a/drivers/hid/hid-lenovo-go.c
+++ b/drivers/hid/hid-lenovo-go.c
@@ -76,6 +76,7 @@ struct hid_go_cfg {
u32 mcu_version_product;
u32 mcu_version_protocol;
u32 mouse_dpi;
+ u8 os_mode;
u8 rgb_effect;
u8 rgb_en;
u8 rgb_mode;
@@ -166,6 +167,8 @@ enum feature_status_index {
FEATURE_GAMEPAD_MODE = 0x0e,
};
+#define FEATURE_OS_MODE 0x69
+
enum fps_switch_status_index {
FPS_STATUS_UNKNOWN,
GAMEPAD,
@@ -311,6 +314,23 @@ enum device_status_index {
GET_HOTKEY_TRIGG_STATUS,
};
+enum os_mode_cfg_index {
+ SET_OS_MODE = 0x09,
+ GET_OS_MODE,
+};
+
+enum os_mode_type_index {
+ OS_UNKNOWN,
+ WINDOWS,
+ LINUX,
+};
+
+static const char *const os_mode_text[] = {
+ [OS_UNKNOWN] = "unknown",
+ [WINDOWS] = "windows",
+ [LINUX] = "linux",
+};
+
static int hid_go_version_event(struct command_report *cmd_rep)
{
switch (cmd_rep->sub_cmd) {
@@ -593,6 +613,21 @@ static int hid_go_device_status_event(struct command_report *cmd_rep)
}
}
+static int hid_go_os_mode_cfg_event(struct command_report *cmd_rep)
+{
+ switch (cmd_rep->sub_cmd) {
+ case SET_OS_MODE:
+ if (cmd_rep->data[0] != 1)
+ return -EIO;
+ return 0;
+ case GET_OS_MODE:
+ drvdata.os_mode = cmd_rep->data[0];
+ return 0;
+ default:
+ return -EINVAL;
+ };
+}
+
static int hid_go_set_event_return(struct command_report *cmd_rep)
{
if (cmd_rep->data[0] != 0)
@@ -666,6 +701,9 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report,
break;
};
break;
+ case OS_MODE_DATA:
+ ret = hid_go_os_mode_cfg_event(cmd_rep);
+ break;
default:
goto passthrough;
};
@@ -1343,6 +1381,64 @@ static ssize_t calibrate_config_options(struct device *dev,
return count;
}
+static ssize_t os_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ size_t size = 1;
+ int ret;
+ u8 val;
+
+ ret = sysfs_match_string(os_mode_text, buf);
+ if (ret <= 0)
+ return ret;
+
+ val = ret;
+ ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE,
+ SET_OS_MODE, USB_MCU, &val, size);
+ if (ret < 0)
+ return ret;
+
+ drvdata.os_mode = val;
+
+ return count;
+}
+
+static ssize_t os_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t count = 0;
+ int ret;
+ u8 i;
+
+ ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE,
+ GET_OS_MODE, USB_MCU, 0, 0);
+ if (ret)
+ return ret;
+
+ i = drvdata.os_mode;
+ if (i >= ARRAY_SIZE(os_mode_text))
+ return -EINVAL;
+
+ count = sysfs_emit(buf, "%s\n", os_mode_text[i]);
+
+ return count;
+}
+
+static ssize_t os_mode_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t count = 0;
+ unsigned int i;
+
+ for (i = 1; i < ARRAY_SIZE(os_mode_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", os_mode_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd,
enum rgb_config_index index, u8 *val, size_t size)
{
@@ -1709,6 +1805,9 @@ static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index,
static DEVICE_ATTR_RW(fps_mode_dpi);
static DEVICE_ATTR_RO(fps_mode_dpi_index);
+static DEVICE_ATTR_RW(os_mode);
+static DEVICE_ATTR_RO(os_mode_index);
+
static struct attribute *mcu_attrs[] = {
&dev_attr_fps_mode_dpi.attr,
&dev_attr_fps_mode_dpi_index.attr,
@@ -1717,6 +1816,8 @@ static struct attribute *mcu_attrs[] = {
&dev_attr_gamepad_mode_index.attr,
&dev_attr_gamepad_rumble_intensity.attr,
&dev_attr_gamepad_rumble_intensity_index.attr,
+ &dev_attr_os_mode.attr,
+ &dev_attr_os_mode_index.attr,
&dev_attr_reset_mcu.attr,
&dev_attr_version_firmware_mcu.attr,
&dev_attr_version_gen_mcu.attr,
@@ -2181,6 +2282,27 @@ static void hid_go_cfg_remove(struct hid_device *hdev)
hid_set_drvdata(hdev, NULL);
}
+static int hid_go_cfg_reset_resume(struct hid_device *hdev)
+{
+ u8 os_mode = drvdata.os_mode;
+ int ret;
+
+ ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE,
+ SET_OS_MODE, USB_MCU, &os_mode, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = mcu_property_out(drvdata.hdev, OS_MODE_DATA, FEATURE_OS_MODE,
+ GET_OS_MODE, USB_MCU, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ if (drvdata.os_mode != os_mode)
+ return -ENODEV;
+
+ return 0;
+}
+
static int hid_go_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret, ep;
@@ -2239,6 +2361,20 @@ static void hid_go_remove(struct hid_device *hdev)
}
}
+static int hid_go_reset_resume(struct hid_device *hdev)
+{
+ int ep = get_endpoint_address(hdev);
+
+ switch (ep) {
+ case GO_GP_INTF_IN:
+ return hid_go_cfg_reset_resume(hdev);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
static const struct hid_device_id hid_go_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO,
USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT) },
@@ -2258,6 +2394,7 @@ static struct hid_driver hid_lenovo_go = {
.probe = hid_go_probe,
.remove = hid_go_remove,
.raw_event = hid_go_raw_event,
+ .reset_resume = hid_go_reset_resume,
};
module_hid_driver(hid_lenovo_go);
--
2.52.0
^ permalink raw reply related
* [PATCH v4 07/16] HID: hid-lenovo-go: Add Calibration Settings
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds calibration enable and last calibration status indicators for the
triggers, joysticks, and handle gyros.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-lenovo-go.c | 284 +++++++++++++++++++++++++++++++++++-
1 file changed, 283 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-lenovo-go.c b/drivers/hid/hid-lenovo-go.c
index 70dd5d5d690b8..b5a1d3e1988f2 100644
--- a/drivers/hid/hid-lenovo-go.c
+++ b/drivers/hid/hid-lenovo-go.c
@@ -43,8 +43,11 @@ struct hid_go_cfg {
struct mutex cfg_mutex; /*ensure single synchronous output report*/
u8 fps_mode;
u8 gp_left_auto_sleep_time;
+ u8 gp_left_gyro_cal_status;
+ u8 gp_left_joy_cal_status;
u8 gp_left_notify_en;
u8 gp_left_rumble_mode;
+ u8 gp_left_trigg_cal_status;
u32 gp_left_version_firmware;
u8 gp_left_version_gen;
u32 gp_left_version_hardware;
@@ -52,8 +55,11 @@ struct hid_go_cfg {
u32 gp_left_version_protocol;
u8 gp_mode;
u8 gp_right_auto_sleep_time;
+ u8 gp_right_gyro_cal_status;
+ u8 gp_right_joy_cal_status;
u8 gp_right_notify_en;
u8 gp_right_rumble_mode;
+ u8 gp_right_trigg_cal_status;
u32 gp_right_version_firmware;
u8 gp_right_version_gen;
u32 gp_right_version_hardware;
@@ -227,7 +233,41 @@ static const char *const rumble_mode_text[] = {
[RUMBLE_MODE_RPG] = "rpg",
};
-#define FPS_MODE_DPI 0x02
+#define FPS_MODE_DPI 0x02
+#define TRIGGER_CALIBRATE 0x04
+#define JOYSTICK_CALIBRATE 0x04
+#define GYRO_CALIBRATE 0x06
+
+enum cal_device_type {
+ CALDEV_GYROSCOPE = 0x01,
+ CALDEV_JOYSTICK,
+ CALDEV_TRIGGER,
+ CALDEV_JOY_TRIGGER,
+};
+
+enum cal_enable {
+ CAL_UNKNOWN,
+ CAL_START,
+ CAL_STOP,
+};
+
+static const char *const cal_enabled_text[] = {
+ [CAL_UNKNOWN] = "unknown",
+ [CAL_START] = "start",
+ [CAL_STOP] = "stop",
+};
+
+enum cal_status_index {
+ CAL_STAT_UNKNOWN,
+ CAL_STAT_SUCCESS,
+ CAL_STAT_FAILURE,
+};
+
+static const char *const cal_status_text[] = {
+ [CAL_STAT_UNKNOWN] = "unknown",
+ [CAL_STAT_SUCCESS] = "success",
+ [CAL_STAT_FAILURE] = "failure",
+};
enum rgb_config_index {
LIGHT_CFG_ALL = 0x01,
@@ -264,6 +304,13 @@ static const char *const rgb_effect_text[] = {
[RGB_EFFECT_RAINBOW] = "rainbow",
};
+enum device_status_index {
+ GET_CAL_STATUS = 0x02,
+ GET_UPGRADE_STATUS,
+ GET_MACRO_REC_STATUS,
+ GET_HOTKEY_TRIGG_STATUS,
+};
+
static int hid_go_version_event(struct command_report *cmd_rep)
{
switch (cmd_rep->sub_cmd) {
@@ -508,6 +555,44 @@ static int hid_go_light_event(struct command_report *cmd_rep)
}
}
+static int hid_go_device_status_event(struct command_report *cmd_rep)
+{
+ switch (cmd_rep->device_type) {
+ case LEFT_CONTROLLER:
+ switch (cmd_rep->data[0]) {
+ case CALDEV_GYROSCOPE:
+ drvdata.gp_left_gyro_cal_status = cmd_rep->data[1];
+ return 0;
+ case CALDEV_JOYSTICK:
+ drvdata.gp_left_joy_cal_status = cmd_rep->data[1];
+ return 0;
+ case CALDEV_TRIGGER:
+ drvdata.gp_left_trigg_cal_status = cmd_rep->data[1];
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case RIGHT_CONTROLLER:
+ switch (cmd_rep->data[0]) {
+ case CALDEV_GYROSCOPE:
+ drvdata.gp_right_gyro_cal_status = cmd_rep->data[1];
+ return 0;
+ case CALDEV_JOYSTICK:
+ drvdata.gp_right_joy_cal_status = cmd_rep->data[1];
+ return 0;
+ case CALDEV_TRIGGER:
+ drvdata.gp_right_trigg_cal_status = cmd_rep->data[1];
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+}
+
static int hid_go_set_event_return(struct command_report *cmd_rep)
{
if (cmd_rep->data[0] != 0)
@@ -564,10 +649,16 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report,
case GET_RGB_CFG:
ret = hid_go_light_event(cmd_rep);
break;
+ case GET_DEVICE_STATUS:
+ ret = hid_go_device_status_event(cmd_rep);
+ break;
case SET_FEATURE_STATUS:
case SET_MOTOR_CFG:
case SET_DPI_CFG:
case SET_RGB_CFG:
+ case SET_TRIGGER_CFG:
+ case SET_JOYSTICK_CFG:
+ case SET_GYRO_CFG:
ret = hid_go_set_event_return(cmd_rep);
break;
default:
@@ -1157,6 +1248,101 @@ static ssize_t fps_mode_dpi_index_show(struct device *dev,
return sysfs_emit(buf, "500 800 1200 1800\n");
}
+static ssize_t device_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ enum device_status_index index,
+ enum dev_type device_type,
+ enum cal_device_type cal_type)
+{
+ u8 i;
+
+ switch (index) {
+ case GET_CAL_STATUS:
+ switch (device_type) {
+ case LEFT_CONTROLLER:
+ switch (cal_type) {
+ case CALDEV_GYROSCOPE:
+ i = drvdata.gp_left_gyro_cal_status;
+ break;
+ case CALDEV_JOYSTICK:
+ i = drvdata.gp_left_joy_cal_status;
+ break;
+ case CALDEV_TRIGGER:
+ i = drvdata.gp_left_trigg_cal_status;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case RIGHT_CONTROLLER:
+ switch (cal_type) {
+ case CALDEV_GYROSCOPE:
+ i = drvdata.gp_right_gyro_cal_status;
+ break;
+ case CALDEV_JOYSTICK:
+ i = drvdata.gp_right_joy_cal_status;
+ break;
+ case CALDEV_TRIGGER:
+ i = drvdata.gp_right_trigg_cal_status;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ if (i >= ARRAY_SIZE(cal_status_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", cal_status_text[i]);
+}
+
+static ssize_t calibrate_config_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, u8 cmd, u8 sub_cmd,
+ size_t count, enum dev_type device_type)
+{
+ size_t size = 1;
+ u8 val = 0;
+ int ret;
+
+ ret = sysfs_match_string(cal_enabled_text, buf);
+ if (ret < 0)
+ return ret;
+
+ val = ret;
+ if (!val)
+ size = 0;
+
+ ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, cmd, sub_cmd,
+ device_type, &val, size);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t calibrate_config_options(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t count = 0;
+ unsigned int i;
+
+ for (i = 1; i < ARRAY_SIZE(cal_enabled_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", cal_enabled_text[i]);
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd,
enum rgb_config_index index, u8 *val, size_t size)
{
@@ -1463,6 +1649,30 @@ static void hid_go_brightness_set(struct led_classdev *led_cdev,
} \
static DEVICE_ATTR_RO_NAMED(_name, _attrname)
+#define LEGO_CAL_DEVICE_ATTR(_name, _attrname, _scmd, _dtype, _rtype) \
+ static ssize_t _name##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ return calibrate_config_store(dev, attr, buf, _name.index, \
+ _scmd, count, _dtype); \
+ } \
+ static ssize_t _name##_##_rtype##_show( \
+ struct device *dev, struct device_attribute *attr, char *buf) \
+ { \
+ return calibrate_config_options(dev, attr, buf); \
+ } \
+ static DEVICE_ATTR_WO_NAMED(_name, _attrname)
+
+#define LEGO_DEVICE_STATUS_ATTR(_name, _attrname, _scmd, _dtype) \
+ static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return device_status_show(dev, attr, buf, _name.index, _scmd, \
+ _dtype); \
+ } \
+ static DEVICE_ATTR_RO_NAMED(_name, _attrname)
+
/* Gamepad - MCU */
struct go_cfg_attr version_product_mcu = { PRODUCT_VERSION };
LEGO_DEVICE_ATTR_RO(version_product_mcu, "product_version", USB_MCU, version);
@@ -1600,9 +1810,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification",
static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index,
"rumble_notification_index");
+struct go_cfg_attr cal_trigg_left = { TRIGGER_CALIBRATE };
+LEGO_CAL_DEVICE_ATTR(cal_trigg_left, "calibrate_trigger", SET_TRIGGER_CFG,
+ LEFT_CONTROLLER, index);
+static DEVICE_ATTR_RO_NAMED(cal_trigg_left_index, "calibrate_trigger_index");
+
+struct go_cfg_attr cal_joy_left = { JOYSTICK_CALIBRATE };
+LEGO_CAL_DEVICE_ATTR(cal_joy_left, "calibrate_joystick", SET_JOYSTICK_CFG,
+ LEFT_CONTROLLER, index);
+static DEVICE_ATTR_RO_NAMED(cal_joy_left_index, "calibrate_joystick_index");
+
+struct go_cfg_attr cal_gyro_left = { GYRO_CALIBRATE };
+LEGO_CAL_DEVICE_ATTR(cal_gyro_left, "calibrate_gyro", SET_GYRO_CFG,
+ LEFT_CONTROLLER, index);
+static DEVICE_ATTR_RO_NAMED(cal_gyro_left_index, "calibrate_gyro_index");
+
+struct go_cfg_attr cal_trigg_left_status = { GET_CAL_STATUS };
+LEGO_DEVICE_STATUS_ATTR(cal_trigg_left_status, "calibrate_trigger_status",
+ LEFT_CONTROLLER, CALDEV_TRIGGER);
+
+struct go_cfg_attr cal_joy_left_status = { GET_CAL_STATUS };
+LEGO_DEVICE_STATUS_ATTR(cal_joy_left_status, "calibrate_joystick_status",
+ LEFT_CONTROLLER, CALDEV_JOYSTICK);
+
+struct go_cfg_attr cal_gyro_left_status = { GET_CAL_STATUS };
+LEGO_DEVICE_STATUS_ATTR(cal_gyro_left_status, "calibrate_gyro_status",
+ LEFT_CONTROLLER, CALDEV_GYROSCOPE);
+
static struct attribute *left_gamepad_attrs[] = {
&dev_attr_auto_sleep_time_left.attr,
&dev_attr_auto_sleep_time_left_range.attr,
+ &dev_attr_cal_gyro_left.attr,
+ &dev_attr_cal_gyro_left_index.attr,
+ &dev_attr_cal_gyro_left_status.attr,
+ &dev_attr_cal_joy_left.attr,
+ &dev_attr_cal_joy_left_index.attr,
+ &dev_attr_cal_joy_left_status.attr,
+ &dev_attr_cal_trigg_left.attr,
+ &dev_attr_cal_trigg_left_index.attr,
+ &dev_attr_cal_trigg_left_status.attr,
&dev_attr_imu_bypass_left.attr,
&dev_attr_imu_bypass_left_index.attr,
&dev_attr_imu_enabled_left.attr,
@@ -1671,9 +1917,45 @@ LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification",
static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index,
"rumble_notification_index");
+struct go_cfg_attr cal_trigg_right = { TRIGGER_CALIBRATE };
+LEGO_CAL_DEVICE_ATTR(cal_trigg_right, "calibrate_trigger", SET_TRIGGER_CFG,
+ RIGHT_CONTROLLER, index);
+static DEVICE_ATTR_RO_NAMED(cal_trigg_right_index, "calibrate_trigger_index");
+
+struct go_cfg_attr cal_joy_right = { JOYSTICK_CALIBRATE };
+LEGO_CAL_DEVICE_ATTR(cal_joy_right, "calibrate_joystick", SET_JOYSTICK_CFG,
+ RIGHT_CONTROLLER, index);
+static DEVICE_ATTR_RO_NAMED(cal_joy_right_index, "calibrate_joystick_index");
+
+struct go_cfg_attr cal_gyro_right = { GYRO_CALIBRATE };
+LEGO_CAL_DEVICE_ATTR(cal_gyro_right, "calibrate_gyro", SET_GYRO_CFG,
+ RIGHT_CONTROLLER, index);
+static DEVICE_ATTR_RO_NAMED(cal_gyro_right_index, "calibrate_gyro_index");
+
+struct go_cfg_attr cal_trigg_right_status = { GET_CAL_STATUS };
+LEGO_DEVICE_STATUS_ATTR(cal_trigg_right_status, "calibrate_trigger_status",
+ RIGHT_CONTROLLER, CALDEV_TRIGGER);
+
+struct go_cfg_attr cal_joy_right_status = { GET_CAL_STATUS };
+LEGO_DEVICE_STATUS_ATTR(cal_joy_right_status, "calibrate_joystick_status",
+ RIGHT_CONTROLLER, CALDEV_JOYSTICK);
+
+struct go_cfg_attr cal_gyro_right_status = { GET_CAL_STATUS };
+LEGO_DEVICE_STATUS_ATTR(cal_gyro_right_status, "calibrate_gyro_status",
+ RIGHT_CONTROLLER, CALDEV_GYROSCOPE);
+
static struct attribute *right_gamepad_attrs[] = {
&dev_attr_auto_sleep_time_right.attr,
&dev_attr_auto_sleep_time_right_range.attr,
+ &dev_attr_cal_gyro_right.attr,
+ &dev_attr_cal_gyro_right_index.attr,
+ &dev_attr_cal_gyro_right_status.attr,
+ &dev_attr_cal_joy_right.attr,
+ &dev_attr_cal_joy_right_index.attr,
+ &dev_attr_cal_joy_right_status.attr,
+ &dev_attr_cal_trigg_right.attr,
+ &dev_attr_cal_trigg_right_index.attr,
+ &dev_attr_cal_trigg_right_status.attr,
&dev_attr_imu_bypass_right.attr,
&dev_attr_imu_bypass_right_index.attr,
&dev_attr_imu_enabled_right.attr,
--
2.52.0
^ permalink raw reply related
* [PATCH v4 09/16] HID: Include firmware version in the uevent
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
From: Mario Limonciello <mario.limonciello@amd.com>
Userspace software fwupd probes some HID devices when the daemon starts
up to determine the current firmware version in order to be able to offer
updated firmware if the manufacturer has made it available.
In order to do this fwupd will detach the existing kernel driver if one
is present, send a HID command and then reattach the kernel driver.
This can be problematic if the user is using the HID device at the time
that fwupd probes the hardware and can cause a few frames of input to be
dropped. In some cases HID drivers already have a command to look up the
firmware version, and so if that is exported to userspace fwupd can discover
it and avoid needing to detach the kernel driver until it's time to update
the device.
Introduce a new member in the struct hid_device for the version and export
a new uevent variable HID_FIRMWARE_VERSION that will display the version
that HID drivers obtained.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
drivers/hid/hid-core.c | 5 +++++
include/linux/hid.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index a5b3a8ca2fcbc..524f2b9ed5121 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2887,6 +2887,11 @@ static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
if (add_uevent_var(env, "MODALIAS=hid: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=0x%04llX",
+ hdev->firmware_version))
+ return -ENOMEM;
+ }
return 0;
}
diff --git a/include/linux/hid.h b/include/linux/hid.h
index dce862cafbbd3..ce728c8d5bdc4 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -698,6 +698,7 @@ struct hid_device {
char name[128]; /* Device name */
char phys[64]; /* Device physical location */
char uniq[64]; /* Device unique identifier (serial #) */
+ u64 firmware_version; /* Firmware version */
void *driver_data;
--
2.52.0
^ permalink raw reply related
* [PATCH v4 10/16] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds initial framework for a new HID driver, hid-lenovo-go-s, along with
a uevent to report the firmware version for the MCU.
This driver primarily provides access to the configurable settings of the
Lenovo Legion Go S controller. It will attach if the controller is in
xinput or dinput mode. Non-configuration raw reports are forwarded to
ensure the other endpoints continue to function as normal.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Co-developed-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Use dmabuf per request instead of devm allocated static buffer.
Resolves bug with side effects during suspend.
- Remove unnecessary HID quirks and return to HID_CONNECT_HIDRAW.
- Adjust delayed work time to 5ms to fix some side effects during
resume when the MCU disconnects in some circumstances.
- Cleaner formatting on multiple debug messages.
v3:
- Include Mario's SOB tag
---
MAINTAINERS | 1 +
drivers/hid/Kconfig | 12 ++
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 4 +
drivers/hid/hid-lenovo-go-s.c | 287 ++++++++++++++++++++++++++++++++++
5 files changed, 305 insertions(+)
create mode 100644 drivers/hid/hid-lenovo-go-s.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9db6292c62ec6..1d0468906788a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14318,6 +14318,7 @@ M: Derek J. Clark <derekjohn.clark@gmail.com>
M: Mark Pearson <mpearson-lenovo@squebb.ca>
L: linux-input@vger.kernel.org
S: Maintained
+F: drivers/hid/hid-lenovo-go-s.c
F: drivers/hid/hid-lenovo-go.c
F: drivers/hid/hid-lenovo.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index d6c31a2cbaf3b..8a04a69b8f259 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -634,6 +634,18 @@ config HID_LENOVO_GO
and Legion Go 2 Handheld Console Controllers. Say M here to compile this
driver as a module. The module will be called hid-lenovo-go.
+config HID_LENOVO_GO_S
+ tristate "HID Driver for Lenovo Legion Go S Controller"
+ depends on USB_HID
+ select LEDS_CLASS
+ select LEDS_CLASS_MULTICOLOR
+ help
+ Support for Lenovo Legion Go S Handheld Console Controller.
+
+ Say Y here to include configuration interface support for the Lenovo Legion Go
+ S. Say M here to compile this driver as a module. The module will be called
+ hid-lenovo-go-s.
+
config HID_LETSKETCH
tristate "Letsketch WP9620N tablets"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 11435bce4e475..ef9169974bf00 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_HID_KYSONA) += hid-kysona.o
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
obj-$(CONFIG_HID_LENOVO_GO) += hid-lenovo-go.o
+obj-$(CONFIG_HID_LENOVO_GO_S) += hid-lenovo-go-s.o
obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index bd41ddbbbee15..486d8baae0257 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -729,6 +729,10 @@
#define USB_DEVICE_ID_ITE8595 0x8595
#define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50
+#define USB_VENDOR_ID_QHE 0x1a86
+#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT 0xe310
+#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT 0xe311
+
#define USB_VENDOR_ID_JABRA 0x0b0e
#define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412
#define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
new file mode 100644
index 0000000000000..20eb472d7a247
--- /dev/null
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for Lenovo Legion Go S devices.
+ *
+ * Copyright (c) 2026 Derek J. Clark <derekjohn.clark@gmail.com>
+ * Copyright (c) 2026 Valve Corporation
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <linux/workqueue_types.h>
+
+#include "hid-ids.h"
+
+#define GO_S_CFG_INTF_IN 0x84
+#define GO_S_PACKET_SIZE 64
+
+struct hid_gos_cfg {
+ struct delayed_work gos_cfg_setup;
+ struct completion send_cmd_complete;
+ struct hid_device *hdev;
+ struct mutex cfg_mutex; /*ensure single synchronous output report*/
+} drvdata;
+
+struct command_report {
+ u8 cmd;
+ u8 sub_cmd;
+ u8 data[63];
+} __packed;
+
+struct version_report {
+ u8 cmd;
+ u32 version;
+ u8 reserved[59];
+} __packed;
+
+enum mcu_command_index {
+ GET_VERSION = 0x01,
+ GET_MCU_ID,
+ GET_GAMEPAD_CFG,
+ SET_GAMEPAD_CFG,
+ GET_TP_PARAM,
+ SET_TP_PARAM,
+ GET_RGB_CFG = 0x0f,
+ SET_RGB_CFG,
+ GET_PL_TEST = 0xdf,
+};
+
+#define FEATURE_NONE 0x00
+
+static int hid_gos_version_event(u8 *data)
+{
+ struct version_report *ver_rep = (struct version_report *)data;
+
+ drvdata.hdev->firmware_version = get_unaligned_le32(&ver_rep->version);
+ return 0;
+}
+
+static 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 hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct command_report *cmd_rep;
+ int ep, ret;
+
+ if (size != GO_S_PACKET_SIZE)
+ goto passthrough;
+
+ ep = get_endpoint_address(hdev);
+ if (ep != GO_S_CFG_INTF_IN)
+ goto passthrough;
+
+ cmd_rep = (struct command_report *)data;
+
+ switch (cmd_rep->cmd) {
+ case GET_VERSION:
+ ret = hid_gos_version_event(data);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ dev_dbg(&hdev->dev, "Rx data as raw input report: [%*ph]\n",
+ GO_S_PACKET_SIZE, data);
+
+ complete(&drvdata.send_cmd_complete);
+ return ret;
+
+passthrough:
+ /* Forward other HID reports so they generate events */
+ hid_input_report(hdev, HID_INPUT_REPORT, data, size, 1);
+ return 0;
+}
+
+static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index,
+ u8 *data, size_t len)
+{
+ unsigned char *dmabuf __free(kfree) = NULL;
+ u8 header[] = { command, index };
+ size_t header_size = ARRAY_SIZE(header);
+ int timeout, ret;
+
+ if (header_size + len > GO_S_PACKET_SIZE)
+ return -EINVAL;
+
+ guard(mutex)(&drvdata.cfg_mutex);
+ /* We can't use a devm_alloc reusable buffer without side effects during suspend */
+ dmabuf = kzalloc(GO_S_PACKET_SIZE, GFP_KERNEL);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ memcpy(dmabuf, header, header_size);
+ memcpy(dmabuf + header_size, data, len);
+
+ dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n",
+ GO_S_PACKET_SIZE, dmabuf);
+
+ ret = hid_hw_output_report(hdev, dmabuf, GO_S_PACKET_SIZE);
+ if (ret < 0)
+ return ret;
+
+ ret = ret == GO_S_PACKET_SIZE ? 0 : -EINVAL;
+ if (ret)
+ return ret;
+
+ /* PL_TEST commands can take longer because they go out to another device */
+ timeout = (command == GET_PL_TEST) ? 200 : 5;
+ ret = wait_for_completion_interruptible_timeout(&drvdata.send_cmd_complete,
+ msecs_to_jiffies(timeout));
+
+ if (ret == 0) /* timeout occurred */
+ ret = -EBUSY;
+
+ reinit_completion(&drvdata.send_cmd_complete);
+ return 0;
+}
+
+static void cfg_setup(struct work_struct *work)
+{
+ int ret;
+
+ ret = mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, 0, 0);
+ if (ret) {
+ dev_err(&drvdata.hdev->dev,
+ "Failed to retrieve MCU Version: %i\n", ret);
+ return;
+ }
+}
+
+static int hid_gos_cfg_probe(struct hid_device *hdev,
+ const struct hid_device_id *_id)
+{
+ int ret;
+
+ hid_set_drvdata(hdev, &drvdata);
+ drvdata.hdev = hdev;
+ mutex_init(&drvdata.cfg_mutex);
+
+ init_completion(&drvdata.send_cmd_complete);
+
+ /* Executing calls prior to returning from probe will lock the MCU. Schedule
+ * initial data call after probe has completed and MCU can accept calls.
+ */
+ INIT_DELAYED_WORK(&drvdata.gos_cfg_setup, &cfg_setup);
+ ret = schedule_delayed_work(&drvdata.gos_cfg_setup,
+ msecs_to_jiffies(2));
+ if (!ret) {
+ dev_err(&hdev->dev,
+ "Failed to schedule startup delayed work\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void hid_gos_cfg_remove(struct hid_device *hdev)
+{
+ guard(mutex)(&drvdata.cfg_mutex);
+ cancel_delayed_work_sync(&drvdata.gos_cfg_setup);
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+ hid_set_drvdata(hdev, NULL);
+}
+
+static int hid_gos_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret, ep;
+
+ ret = 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;
+ }
+
+ ep = get_endpoint_address(hdev);
+ if (ep != GO_S_CFG_INTF_IN) {
+ dev_dbg(&hdev->dev,
+ "Started interface %x as generic HID device.\n", ep);
+ return 0;
+ }
+
+ ret = hid_gos_cfg_probe(hdev, id);
+ if (ret)
+ dev_err_probe(&hdev->dev, ret,
+ "Failed to start configuration interface");
+
+ dev_dbg(&hdev->dev, "Started Legion Go S HID Device: %x\n", ep);
+ return ret;
+}
+
+static void hid_gos_remove(struct hid_device *hdev)
+{
+ int ep = get_endpoint_address(hdev);
+
+ switch (ep) {
+ case GO_S_CFG_INTF_IN:
+ hid_gos_cfg_remove(hdev);
+ break;
+ default:
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+
+ break;
+ }
+}
+
+static const struct hid_device_id hid_gos_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_QHE,
+ USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_QHE,
+ USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT) },
+ {}
+};
+
+MODULE_DEVICE_TABLE(hid, hid_gos_devices);
+static struct hid_driver hid_lenovo_go_s = {
+ .name = "hid-lenovo-go-s",
+ .id_table = hid_gos_devices,
+ .probe = hid_gos_probe,
+ .remove = hid_gos_remove,
+ .raw_event = hid_gos_raw_event,
+};
+module_hid_driver(hid_lenovo_go_s);
+
+MODULE_AUTHOR("Derek J. Clark");
+MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go S Series gamepad.");
+MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related
* [PATCH v4 11/16] HID: hid-lenovo-go-s: Add MCU ID Attribute
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds command to probe for the MCU ID of the Lenovo Legion Go S
Controller and assign it to a device attribute.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-lenovo-go-s.c | 56 +++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index 20eb472d7a247..9dfc7a6ce84ce 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -18,6 +18,7 @@
#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/string.h>
+#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include <linux/usb.h>
@@ -34,8 +35,13 @@ struct hid_gos_cfg {
struct completion send_cmd_complete;
struct hid_device *hdev;
struct mutex cfg_mutex; /*ensure single synchronous output report*/
+ u8 mcu_id[12];
} drvdata;
+struct gos_cfg_attr {
+ u8 index;
+};
+
struct command_report {
u8 cmd;
u8 sub_cmd;
@@ -70,6 +76,14 @@ static int hid_gos_version_event(u8 *data)
return 0;
}
+static int hid_gos_mcu_id_event(struct command_report *cmd_rep)
+{
+ drvdata.mcu_id[0] = cmd_rep->sub_cmd;
+ memcpy(&drvdata.mcu_id[1], cmd_rep->data, 11);
+
+ return 0;
+}
+
static u8 get_endpoint_address(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
@@ -103,6 +117,9 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report,
case GET_VERSION:
ret = hid_gos_version_event(data);
break;
+ case GET_MCU_ID:
+ ret = hid_gos_mcu_id_event(cmd_rep);
+ break;
default:
ret = -EINVAL;
break;
@@ -162,10 +179,41 @@ static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index,
return 0;
}
+static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id);
+}
+
+/* MCU */
+static DEVICE_ATTR_RO(mcu_id);
+
+static struct attribute *legos_mcu_attrs[] = {
+ &dev_attr_mcu_id.attr,
+ NULL,
+};
+
+static const struct attribute_group mcu_attr_group = {
+ .attrs = legos_mcu_attrs,
+};
+
+static const struct attribute_group *top_level_attr_groups[] = {
+ &mcu_attr_group,
+ NULL,
+};
+
static void cfg_setup(struct work_struct *work)
{
int ret;
+ /* MCU */
+ ret = mcu_property_out(drvdata.hdev, GET_MCU_ID, FEATURE_NONE, 0, 0);
+ if (ret) {
+ dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU ID: %i\n",
+ ret);
+ return;
+ }
+
ret = mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, 0, 0);
if (ret) {
dev_err(&drvdata.hdev->dev,
@@ -183,6 +231,13 @@ static int hid_gos_cfg_probe(struct hid_device *hdev,
drvdata.hdev = hdev;
mutex_init(&drvdata.cfg_mutex);
+ ret = sysfs_create_groups(&hdev->dev.kobj, top_level_attr_groups);
+ if (ret) {
+ dev_err_probe(&hdev->dev, ret,
+ "Failed to create gamepad configuration attributes\n");
+ return ret;
+ }
+
init_completion(&drvdata.send_cmd_complete);
/* Executing calls prior to returning from probe will lock the MCU. Schedule
@@ -203,6 +258,7 @@ static void hid_gos_cfg_remove(struct hid_device *hdev)
{
guard(mutex)(&drvdata.cfg_mutex);
cancel_delayed_work_sync(&drvdata.gos_cfg_setup);
+ sysfs_remove_groups(&hdev->dev.kobj, top_level_attr_groups);
hid_hw_close(hdev);
hid_hw_stop(hdev);
hid_set_drvdata(hdev, NULL);
--
2.52.0
^ permalink raw reply related
* [PATCH v4 12/16] HID: hid-lenovo-go-s: Add Feature Status Attributes
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds features status attributes for the gamepad, MCU, touchpad/mouse,
and IMU devices.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Cleaner formatting on debug message.
---
drivers/hid/hid-lenovo-go-s.c | 523 +++++++++++++++++++++++++++++++++-
1 file changed, 522 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index 9dfc7a6ce84ce..9595994ce1bf9 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -15,6 +15,7 @@
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/jiffies.h>
+#include <linux/kstrtox.h>
#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/string.h>
@@ -35,7 +36,17 @@ struct hid_gos_cfg {
struct completion send_cmd_complete;
struct hid_device *hdev;
struct mutex cfg_mutex; /*ensure single synchronous output report*/
+ u8 gp_auto_sleep_time;
+ u8 gp_dpad_mode;
+ u8 gp_mode;
+ u8 gp_poll_rate;
+ u8 imu_bypass_en;
+ u8 imu_sensor_en;
u8 mcu_id[12];
+ u8 mouse_step;
+ u8 os_mode;
+ u8 rgb_en;
+ u8 tp_en;
} drvdata;
struct gos_cfg_attr {
@@ -66,7 +77,73 @@ enum mcu_command_index {
GET_PL_TEST = 0xdf,
};
-#define FEATURE_NONE 0x00
+enum feature_enabled_index {
+ FEATURE_DISABLED,
+ FEATURE_ENABLED,
+};
+
+static const char *const feature_enabled_text[] = {
+ [FEATURE_DISABLED] = "false",
+ [FEATURE_ENABLED] = "true",
+};
+
+enum feature_status_index {
+ FEATURE_NONE = 0x00,
+ FEATURE_GAMEPAD_MODE = 0x01,
+ FEATURE_AUTO_SLEEP_TIME = 0x04,
+ FEATURE_IMU_BYPASS,
+ FEATURE_RGB_ENABLE,
+ FEATURE_IMU_ENABLE,
+ FEATURE_TOUCHPAD_ENABLE,
+ FEATURE_OS_MODE = 0x0A,
+ FEATURE_POLL_RATE = 0x10,
+ FEATURE_DPAD_MODE,
+ FEATURE_MOUSE_WHEEL_STEP,
+};
+
+enum gamepad_mode_index {
+ XINPUT,
+ DINPUT,
+};
+
+static const char *const gamepad_mode_text[] = {
+ [XINPUT] = "xinput",
+ [DINPUT] = "dinput",
+};
+
+enum os_type_index {
+ WINDOWS,
+ LINUX,
+};
+
+static const char *const os_type_text[] = {
+ [WINDOWS] = "windows",
+ [LINUX] = "linux",
+};
+
+enum poll_rate_index {
+ HZ125,
+ HZ250,
+ HZ500,
+ HZ1000,
+};
+
+static const char *const poll_rate_text[] = {
+ [HZ125] = "125",
+ [HZ250] = "250",
+ [HZ500] = "500",
+ [HZ1000] = "1000",
+};
+
+enum dpad_mode_index {
+ DIR8,
+ DIR4,
+};
+
+static const char *const dpad_mode_text[] = {
+ [DIR8] = "8-way",
+ [DIR4] = "4-way",
+};
static int hid_gos_version_event(u8 *data)
{
@@ -84,6 +161,57 @@ static int hid_gos_mcu_id_event(struct command_report *cmd_rep)
return 0;
}
+static int hid_gos_gamepad_cfg_event(struct command_report *cmd_rep)
+{
+ int ret = 0;
+
+ switch (cmd_rep->sub_cmd) {
+ case FEATURE_GAMEPAD_MODE:
+ drvdata.gp_mode = cmd_rep->data[0];
+ break;
+ case FEATURE_AUTO_SLEEP_TIME:
+ drvdata.gp_auto_sleep_time = cmd_rep->data[0];
+ break;
+ case FEATURE_IMU_BYPASS:
+ drvdata.imu_bypass_en = cmd_rep->data[0];
+ break;
+ case FEATURE_RGB_ENABLE:
+ drvdata.rgb_en = cmd_rep->data[0];
+ break;
+ case FEATURE_IMU_ENABLE:
+ drvdata.imu_sensor_en = cmd_rep->data[0];
+ break;
+ case FEATURE_TOUCHPAD_ENABLE:
+ drvdata.tp_en = cmd_rep->data[0];
+ break;
+ case FEATURE_OS_MODE:
+ drvdata.os_mode = cmd_rep->data[0];
+ break;
+ case FEATURE_POLL_RATE:
+ drvdata.gp_poll_rate = cmd_rep->data[0];
+ break;
+ case FEATURE_DPAD_MODE:
+ drvdata.gp_dpad_mode = cmd_rep->data[0];
+ break;
+ case FEATURE_MOUSE_WHEEL_STEP:
+ drvdata.mouse_step = cmd_rep->data[0];
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int hid_gos_set_event_return(struct command_report *cmd_rep)
+{
+ if (cmd_rep->data[0] != 0)
+ return -EIO;
+
+ return 0;
+}
+
static u8 get_endpoint_address(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
@@ -120,6 +248,12 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report,
case GET_MCU_ID:
ret = hid_gos_mcu_id_event(cmd_rep);
break;
+ case GET_GAMEPAD_CFG:
+ ret = hid_gos_gamepad_cfg_event(cmd_rep);
+ break;
+ case SET_GAMEPAD_CFG:
+ ret = hid_gos_set_event_return(cmd_rep);
+ break;
default:
ret = -EINVAL;
break;
@@ -179,17 +313,332 @@ static int mcu_property_out(struct hid_device *hdev, u8 command, u8 index,
return 0;
}
+static ssize_t gamepad_property_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count,
+ enum feature_status_index index)
+{
+ size_t size = 1;
+ u8 val = 0;
+ int ret;
+
+ switch (index) {
+ case FEATURE_GAMEPAD_MODE:
+ ret = sysfs_match_string(gamepad_mode_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_AUTO_SLEEP_TIME:
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ break;
+ case FEATURE_IMU_ENABLE:
+ ret = sysfs_match_string(feature_enabled_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_IMU_BYPASS:
+ ret = sysfs_match_string(feature_enabled_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_RGB_ENABLE:
+ ret = sysfs_match_string(feature_enabled_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_TOUCHPAD_ENABLE:
+ ret = sysfs_match_string(feature_enabled_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_OS_MODE:
+ ret = sysfs_match_string(os_type_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_POLL_RATE:
+ ret = sysfs_match_string(poll_rate_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_DPAD_MODE:
+ ret = sysfs_match_string(dpad_mode_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case FEATURE_MOUSE_WHEEL_STEP:
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+ if (val < 1 || val > 127)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!val)
+ size = 0;
+
+ ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, index, &val,
+ size);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t gamepad_property_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ enum feature_status_index index)
+{
+ size_t count = 0;
+ u8 i;
+
+ count = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, 0, 0);
+ if (count < 0)
+ return count;
+
+ switch (index) {
+ case FEATURE_GAMEPAD_MODE:
+ i = drvdata.gp_mode;
+ if (i >= ARRAY_SIZE(gamepad_mode_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", gamepad_mode_text[i]);
+ break;
+ case FEATURE_AUTO_SLEEP_TIME:
+ count = sysfs_emit(buf, "%u\n", drvdata.gp_auto_sleep_time);
+ break;
+ case FEATURE_IMU_ENABLE:
+ i = drvdata.imu_sensor_en;
+ if (i >= ARRAY_SIZE(feature_enabled_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
+ break;
+ case FEATURE_IMU_BYPASS:
+ i = drvdata.imu_bypass_en;
+ if (i >= ARRAY_SIZE(feature_enabled_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
+ break;
+ case FEATURE_RGB_ENABLE:
+ i = drvdata.rgb_en;
+ if (i >= ARRAY_SIZE(feature_enabled_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
+ break;
+ case FEATURE_TOUCHPAD_ENABLE:
+ i = drvdata.tp_en;
+ if (i >= ARRAY_SIZE(feature_enabled_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
+ break;
+ case FEATURE_OS_MODE:
+ i = drvdata.os_mode;
+ if (i >= ARRAY_SIZE(os_type_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", os_type_text[i]);
+ break;
+ case FEATURE_POLL_RATE:
+ i = drvdata.gp_poll_rate;
+ if (i >= ARRAY_SIZE(poll_rate_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", poll_rate_text[i]);
+ break;
+ case FEATURE_DPAD_MODE:
+ i = drvdata.gp_dpad_mode;
+ if (i >= ARRAY_SIZE(dpad_mode_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", dpad_mode_text[i]);
+ break;
+ case FEATURE_MOUSE_WHEEL_STEP:
+ i = drvdata.mouse_step;
+ if (i < 1 || i > 127)
+ return -EINVAL;
+ count = sysfs_emit(buf, "%u\n", i);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t gamepad_property_options(struct device *dev,
+ struct device_attribute *attr,
+ char *buf,
+ enum feature_status_index index)
+{
+ size_t count = 0;
+ unsigned int i;
+
+ switch (index) {
+ case FEATURE_GAMEPAD_MODE:
+ for (i = 0; i < ARRAY_SIZE(gamepad_mode_text); i++) {
+ count += sysfs_emit_at(buf, count, "%s ",
+ gamepad_mode_text[i]);
+ }
+ break;
+ case FEATURE_AUTO_SLEEP_TIME:
+ return sysfs_emit(buf, "0-255\n");
+ case FEATURE_IMU_ENABLE:
+ for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) {
+ count += sysfs_emit_at(buf, count, "%s ",
+ feature_enabled_text[i]);
+ }
+ break;
+ case FEATURE_IMU_BYPASS:
+ case FEATURE_RGB_ENABLE:
+ case FEATURE_TOUCHPAD_ENABLE:
+ for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) {
+ count += sysfs_emit_at(buf, count, "%s ",
+ feature_enabled_text[i]);
+ }
+ break;
+ case FEATURE_OS_MODE:
+ for (i = 0; i < ARRAY_SIZE(os_type_text); i++) {
+ count += sysfs_emit_at(buf, count, "%s ",
+ os_type_text[i]);
+ }
+ break;
+ case FEATURE_POLL_RATE:
+ for (i = 0; i < ARRAY_SIZE(poll_rate_text); i++) {
+ count += sysfs_emit_at(buf, count, "%s ",
+ poll_rate_text[i]);
+ }
+ break;
+ case FEATURE_DPAD_MODE:
+ for (i = 0; i < ARRAY_SIZE(dpad_mode_text); i++) {
+ count += sysfs_emit_at(buf, count, "%s ",
+ dpad_mode_text[i]);
+ }
+ break;
+ case FEATURE_MOUSE_WHEEL_STEP:
+ return sysfs_emit(buf, "1-127\n");
+ default:
+ return count;
+ }
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id);
}
+#define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) \
+ static ssize_t _name##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ return _group##_property_store(dev, attr, buf, count, \
+ _name.index); \
+ } \
+ static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return _group##_property_show(dev, attr, buf, _name.index); \
+ } \
+ static ssize_t _name##_##_rtype##_show( \
+ struct device *dev, struct device_attribute *attr, char *buf) \
+ { \
+ return _group##_property_options(dev, attr, buf, _name.index); \
+ } \
+ static DEVICE_ATTR_RW_NAMED(_name, _attrname)
+
+#define LEGOS_DEVICE_ATTR_RO(_name, _attrname, _group) \
+ static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return _group##_property_show(dev, attr, buf, _name.index); \
+ } \
+ static DEVICE_ATTR_RO_NAMED(_name, _attrname)
+
+/* Gamepad */
+struct gos_cfg_attr auto_sleep_time = { FEATURE_AUTO_SLEEP_TIME };
+LEGOS_DEVICE_ATTR_RW(auto_sleep_time, "auto_sleep_time", range, gamepad);
+static DEVICE_ATTR_RO(auto_sleep_time_range);
+
+struct gos_cfg_attr dpad_mode = { FEATURE_DPAD_MODE };
+LEGOS_DEVICE_ATTR_RW(dpad_mode, "dpad_mode", index, gamepad);
+static DEVICE_ATTR_RO(dpad_mode_index);
+
+struct gos_cfg_attr gamepad_mode = { FEATURE_GAMEPAD_MODE };
+LEGOS_DEVICE_ATTR_RW(gamepad_mode, "mode", index, gamepad);
+static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index");
+
+struct gos_cfg_attr gamepad_poll_rate = { FEATURE_POLL_RATE };
+LEGOS_DEVICE_ATTR_RW(gamepad_poll_rate, "poll_rate", index, gamepad);
+static DEVICE_ATTR_RO_NAMED(gamepad_poll_rate_index, "poll_rate_index");
+
+static struct attribute *legos_gamepad_attrs[] = {
+ &dev_attr_auto_sleep_time.attr,
+ &dev_attr_auto_sleep_time_range.attr,
+ &dev_attr_dpad_mode.attr,
+ &dev_attr_dpad_mode_index.attr,
+ &dev_attr_gamepad_mode.attr,
+ &dev_attr_gamepad_mode_index.attr,
+ &dev_attr_gamepad_poll_rate.attr,
+ &dev_attr_gamepad_poll_rate_index.attr,
+ NULL,
+};
+
+static const struct attribute_group gamepad_attr_group = {
+ .name = "gamepad",
+ .attrs = legos_gamepad_attrs,
+};
+
+/* IMU */
+struct gos_cfg_attr imu_bypass_enabled = { FEATURE_IMU_BYPASS };
+LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad);
+static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_index");
+
+struct gos_cfg_attr imu_sensor_enabled = { FEATURE_IMU_ENABLE };
+LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad);
+static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index");
+
+static struct attribute *legos_imu_attrs[] = {
+ &dev_attr_imu_bypass_enabled.attr,
+ &dev_attr_imu_bypass_enabled_index.attr,
+ &dev_attr_imu_sensor_enabled.attr,
+ &dev_attr_imu_sensor_enabled_index.attr,
+ NULL,
+};
+
+static const struct attribute_group imu_attr_group = {
+ .name = "imu",
+ .attrs = legos_imu_attrs,
+};
+
/* MCU */
static DEVICE_ATTR_RO(mcu_id);
+struct gos_cfg_attr os_mode = { FEATURE_OS_MODE };
+LEGOS_DEVICE_ATTR_RW(os_mode, "os_mode", index, gamepad);
+static DEVICE_ATTR_RO(os_mode_index);
+
static struct attribute *legos_mcu_attrs[] = {
&dev_attr_mcu_id.attr,
+ &dev_attr_os_mode.attr,
+ &dev_attr_os_mode_index.attr,
NULL,
};
@@ -197,8 +646,44 @@ static const struct attribute_group mcu_attr_group = {
.attrs = legos_mcu_attrs,
};
+/* Mouse */
+struct gos_cfg_attr mouse_wheel_step = { FEATURE_MOUSE_WHEEL_STEP };
+LEGOS_DEVICE_ATTR_RW(mouse_wheel_step, "step", range, gamepad);
+static DEVICE_ATTR_RO_NAMED(mouse_wheel_step_range, "step_range");
+
+static struct attribute *legos_mouse_attrs[] = {
+ &dev_attr_mouse_wheel_step.attr,
+ &dev_attr_mouse_wheel_step_range.attr,
+ NULL,
+};
+
+static const struct attribute_group mouse_attr_group = {
+ .name = "mouse",
+ .attrs = legos_mouse_attrs,
+};
+
+/* Touchpad */
+struct gos_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE };
+LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad);
+static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index");
+
+static struct attribute *legos_touchpad_attrs[] = {
+ &dev_attr_touchpad_enabled.attr,
+ &dev_attr_touchpad_enabled_index.attr,
+ NULL,
+};
+
+static const struct attribute_group touchpad_attr_group = {
+ .name = "touchpad",
+ .attrs = legos_touchpad_attrs,
+};
+
static const struct attribute_group *top_level_attr_groups[] = {
+ &gamepad_attr_group,
+ &imu_attr_group,
&mcu_attr_group,
+ &mouse_attr_group,
+ &touchpad_attr_group,
NULL,
};
@@ -264,6 +749,27 @@ static void hid_gos_cfg_remove(struct hid_device *hdev)
hid_set_drvdata(hdev, NULL);
}
+static int hid_gos_cfg_reset_resume(struct hid_device *hdev)
+{
+ u8 os_mode = drvdata.os_mode;
+ int ret;
+
+ ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, FEATURE_OS_MODE,
+ &os_mode, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_OS_MODE, 0,
+ 0);
+ if (ret < 0)
+ return ret;
+
+ if (drvdata.os_mode != os_mode)
+ return -ENODEV;
+
+ return 0;
+}
+
static int hid_gos_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -320,6 +826,20 @@ static void hid_gos_remove(struct hid_device *hdev)
}
}
+static int hid_gos_reset_resume(struct hid_device *hdev)
+{
+ int ep = get_endpoint_address(hdev);
+
+ switch (ep) {
+ case GO_S_CFG_INTF_IN:
+ return hid_gos_cfg_reset_resume(hdev);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
static const struct hid_device_id hid_gos_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_QHE,
USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) },
@@ -334,6 +854,7 @@ static struct hid_driver hid_lenovo_go_s = {
.id_table = hid_gos_devices,
.probe = hid_gos_probe,
.remove = hid_gos_remove,
+ .reset_resume = hid_gos_reset_resume,
.raw_event = hid_gos_raw_event,
};
module_hid_driver(hid_lenovo_go_s);
--
2.52.0
^ permalink raw reply related
* [PATCH v4 13/16] HID: hid-lenovo-go-s: Add Touchpad Mode Attributes
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds attributes for managing the touchpad operating modes.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-lenovo-go-s.c | 142 ++++++++++++++++++++++++++++++++++
1 file changed, 142 insertions(+)
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index 9595994ce1bf9..92ee2602273af 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -47,6 +47,8 @@ struct hid_gos_cfg {
u8 os_mode;
u8 rgb_en;
u8 tp_en;
+ u8 tp_linux_mode;
+ u8 tp_windows_mode;
} drvdata;
struct gos_cfg_attr {
@@ -145,6 +147,22 @@ static const char *const dpad_mode_text[] = {
[DIR4] = "4-way",
};
+enum touchpad_mode_index {
+ TP_REL,
+ TP_ABS,
+};
+
+static const char *const touchpad_mode_text[] = {
+ [TP_REL] = "relative",
+ [TP_ABS] = "absolute",
+};
+
+enum touchpad_config_index {
+ CFG_WINDOWS_MODE = 0x03,
+ CFG_LINUX_MODE,
+
+};
+
static int hid_gos_version_event(u8 *data)
{
struct version_report *ver_rep = (struct version_report *)data;
@@ -204,6 +222,25 @@ static int hid_gos_gamepad_cfg_event(struct command_report *cmd_rep)
return ret;
}
+static int hid_gos_touchpad_event(struct command_report *cmd_rep)
+{
+ int ret = 0;
+
+ switch (cmd_rep->sub_cmd) {
+ case CFG_LINUX_MODE:
+ drvdata.tp_linux_mode = cmd_rep->data[0];
+ break;
+ case CFG_WINDOWS_MODE:
+ drvdata.tp_windows_mode = cmd_rep->data[0];
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
static int hid_gos_set_event_return(struct command_report *cmd_rep)
{
if (cmd_rep->data[0] != 0)
@@ -251,7 +288,11 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report,
case GET_GAMEPAD_CFG:
ret = hid_gos_gamepad_cfg_event(cmd_rep);
break;
+ case GET_TP_PARAM:
+ ret = hid_gos_touchpad_event(cmd_rep);
+ break;
case SET_GAMEPAD_CFG:
+ case SET_TP_PARAM:
ret = hid_gos_set_event_return(cmd_rep);
break;
default:
@@ -538,6 +579,95 @@ static ssize_t gamepad_property_options(struct device *dev,
return count;
}
+static ssize_t touchpad_property_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count,
+ enum touchpad_config_index index)
+{
+ size_t size = 1;
+ u8 val = 0;
+ int ret;
+
+ switch (index) {
+ case CFG_WINDOWS_MODE:
+ ret = sysfs_match_string(touchpad_mode_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ case CFG_LINUX_MODE:
+ ret = sysfs_match_string(touchpad_mode_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (!val)
+ size = 0;
+
+ ret = mcu_property_out(drvdata.hdev, SET_TP_PARAM, index, &val, size);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t touchpad_property_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ enum touchpad_config_index index)
+{
+ int ret = 0;
+ u8 i;
+
+ ret = mcu_property_out(drvdata.hdev, GET_TP_PARAM, index, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ switch (index) {
+ case CFG_WINDOWS_MODE:
+ i = drvdata.tp_windows_mode;
+ break;
+ case CFG_LINUX_MODE:
+ i = drvdata.tp_linux_mode;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (i >= ARRAY_SIZE(touchpad_mode_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", touchpad_mode_text[i]);
+}
+
+static ssize_t touchpad_property_options(struct device *dev,
+ struct device_attribute *attr,
+ char *buf,
+ enum touchpad_config_index index)
+{
+ size_t count = 0;
+ unsigned int i;
+
+ switch (index) {
+ case CFG_WINDOWS_MODE:
+ case CFG_LINUX_MODE:
+ for (i = 0; i < ARRAY_SIZE(touchpad_mode_text); i++) {
+ count += sysfs_emit_at(buf, count, "%s ",
+ touchpad_mode_text[i]);
+ }
+ break;
+ default:
+ return count;
+ }
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -667,9 +797,21 @@ struct gos_cfg_attr touchpad_enabled = { FEATURE_TOUCHPAD_ENABLE };
LEGOS_DEVICE_ATTR_RW(touchpad_enabled, "enabled", index, gamepad);
static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index");
+struct gos_cfg_attr touchpad_linux_mode = { CFG_LINUX_MODE };
+LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad);
+static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index");
+
+struct gos_cfg_attr touchpad_windows_mode = { CFG_WINDOWS_MODE };
+LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpad);
+static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_index");
+
static struct attribute *legos_touchpad_attrs[] = {
&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,
};
--
2.52.0
^ permalink raw reply related
* [PATCH v4 14/16] HID: hid-lenovo-go-s: Add RGB LED control interface
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds an LED multicolor class device and attribute group for controlling
the RGB of the Left and right joystick rings. In addition to the standard
led_cdev attributes, additional attributes that allow for the control of
the effect (monocolor, breathe, rainbow, and chroma), speed of the
effect change, an enable toggle, and profile.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Cleaner formatting on multiple debug messages.
---
drivers/hid/hid-lenovo-go-s.c | 456 ++++++++++++++++++++++++++++++++++
1 file changed, 456 insertions(+)
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index 92ee2602273af..2a3b8ef9db06d 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -16,6 +16,7 @@
#include <linux/hid.h>
#include <linux/jiffies.h>
#include <linux/kstrtox.h>
+#include <linux/led-class-multicolor.h>
#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/string.h>
@@ -34,6 +35,7 @@
struct hid_gos_cfg {
struct delayed_work gos_cfg_setup;
struct completion send_cmd_complete;
+ struct led_classdev *led_cdev;
struct hid_device *hdev;
struct mutex cfg_mutex; /*ensure single synchronous output report*/
u8 gp_auto_sleep_time;
@@ -45,7 +47,11 @@ struct hid_gos_cfg {
u8 mcu_id[12];
u8 mouse_step;
u8 os_mode;
+ u8 rgb_effect;
u8 rgb_en;
+ u8 rgb_mode;
+ u8 rgb_profile;
+ u8 rgb_speed;
u8 tp_en;
u8 tp_linux_mode;
u8 tp_windows_mode;
@@ -163,6 +169,38 @@ enum touchpad_config_index {
};
+enum rgb_mode_index {
+ RGB_MODE_DYNAMIC,
+ RGB_MODE_CUSTOM,
+};
+
+static const char *const rgb_mode_text[] = {
+ [RGB_MODE_DYNAMIC] = "dynamic",
+ [RGB_MODE_CUSTOM] = "custom",
+};
+
+enum rgb_effect_index {
+ RGB_EFFECT_MONO,
+ RGB_EFFECT_BREATHE,
+ RGB_EFFECT_CHROMA,
+ RGB_EFFECT_RAINBOW,
+};
+
+static const char *const rgb_effect_text[] = {
+ [RGB_EFFECT_MONO] = "monocolor",
+ [RGB_EFFECT_BREATHE] = "breathe",
+ [RGB_EFFECT_CHROMA] = "chroma",
+ [RGB_EFFECT_RAINBOW] = "rainbow",
+};
+
+enum rgb_config_index {
+ LIGHT_MODE_SEL = 0x01,
+ LIGHT_PROFILE_SEL,
+ USR_LIGHT_PROFILE_1,
+ USR_LIGHT_PROFILE_2,
+ USR_LIGHT_PROFILE_3,
+};
+
static int hid_gos_version_event(u8 *data)
{
struct version_report *ver_rep = (struct version_report *)data;
@@ -241,6 +279,39 @@ static int hid_gos_touchpad_event(struct command_report *cmd_rep)
return ret;
}
+static int hid_gos_light_event(struct command_report *cmd_rep)
+{
+ struct led_classdev_mc *mc_cdev;
+ int ret = 0;
+
+ switch (cmd_rep->sub_cmd) {
+ case LIGHT_MODE_SEL:
+ drvdata.rgb_mode = cmd_rep->data[0];
+ ret = 0;
+ break;
+ case LIGHT_PROFILE_SEL:
+ drvdata.rgb_profile = cmd_rep->data[0];
+ ret = 0;
+ break;
+ case USR_LIGHT_PROFILE_1:
+ case USR_LIGHT_PROFILE_2:
+ case USR_LIGHT_PROFILE_3:
+ mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
+ drvdata.rgb_effect = cmd_rep->data[0];
+ mc_cdev->subled_info[0].intensity = cmd_rep->data[1];
+ mc_cdev->subled_info[1].intensity = cmd_rep->data[2];
+ mc_cdev->subled_info[2].intensity = cmd_rep->data[3];
+ drvdata.led_cdev->brightness = cmd_rep->data[4];
+ drvdata.rgb_speed = cmd_rep->data[5];
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
static int hid_gos_set_event_return(struct command_report *cmd_rep)
{
if (cmd_rep->data[0] != 0)
@@ -291,7 +362,11 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report,
case GET_TP_PARAM:
ret = hid_gos_touchpad_event(cmd_rep);
break;
+ case GET_RGB_CFG:
+ ret = hid_gos_light_event(cmd_rep);
+ break;
case SET_GAMEPAD_CFG:
+ case SET_RGB_CFG:
case SET_TP_PARAM:
ret = hid_gos_set_event_return(cmd_rep);
break;
@@ -674,6 +749,276 @@ static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr,
return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id);
}
+static int rgb_cfg_call(struct hid_device *hdev, enum mcu_command_index cmd,
+ enum rgb_config_index index, u8 *val, size_t size)
+{
+ if (cmd != SET_RGB_CFG && cmd != GET_RGB_CFG)
+ return -EINVAL;
+
+ if (index < LIGHT_MODE_SEL || index > USR_LIGHT_PROFILE_3)
+ return -EINVAL;
+
+ return mcu_property_out(hdev, cmd, index, val, size);
+}
+
+static int rgb_attr_show(void)
+{
+ enum rgb_config_index index;
+
+ index = drvdata.rgb_profile + 2;
+
+ return rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, index, 0, 0);
+};
+
+static ssize_t rgb_effect_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
+ enum rgb_config_index index;
+ u8 effect;
+ int ret;
+
+ ret = sysfs_match_string(rgb_effect_text, buf);
+ if (ret < 0)
+ return ret;
+
+ effect = ret;
+ index = drvdata.rgb_profile + 2;
+ u8 rgb_profile[6] = { effect,
+ mc_cdev->subled_info[0].intensity,
+ mc_cdev->subled_info[1].intensity,
+ mc_cdev->subled_info[2].intensity,
+ drvdata.led_cdev->brightness,
+ drvdata.rgb_speed };
+
+ ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6);
+ if (ret)
+ return ret;
+
+ drvdata.rgb_effect = effect;
+ return count;
+};
+
+static ssize_t rgb_effect_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+
+ ret = rgb_attr_show();
+ if (ret)
+ return ret;
+
+ if (drvdata.rgb_effect >= ARRAY_SIZE(rgb_effect_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", rgb_effect_text[drvdata.rgb_effect]);
+}
+
+static ssize_t rgb_effect_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t count = 0;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rgb_effect_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", rgb_effect_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
+static ssize_t rgb_speed_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
+ enum rgb_config_index index;
+ int val = 0;
+ int ret;
+
+ ret = kstrtoint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val < 0 || val > 100)
+ return -EINVAL;
+
+ index = drvdata.rgb_profile + 2;
+ u8 rgb_profile[6] = { drvdata.rgb_effect,
+ mc_cdev->subled_info[0].intensity,
+ mc_cdev->subled_info[1].intensity,
+ mc_cdev->subled_info[2].intensity,
+ drvdata.led_cdev->brightness,
+ val };
+
+ ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6);
+ if (ret)
+ return ret;
+
+ drvdata.rgb_speed = val;
+
+ return count;
+};
+
+static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+
+ ret = rgb_attr_show();
+ if (ret)
+ return ret;
+
+ if (drvdata.rgb_speed > 100)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed);
+}
+
+static ssize_t rgb_speed_range_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0-100\n");
+}
+
+static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ u8 val;
+
+ ret = sysfs_match_string(rgb_mode_text, buf);
+ if (ret <= 0)
+ return ret;
+
+ val = ret;
+
+ ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_MODE_SEL, &val,
+ 1);
+ if (ret)
+ return ret;
+
+ drvdata.rgb_mode = val;
+
+ return count;
+};
+
+static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+
+ ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, 0, 0);
+ if (ret)
+ return ret;
+
+ if (drvdata.rgb_mode >= ARRAY_SIZE(rgb_mode_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", rgb_mode_text[drvdata.rgb_mode]);
+};
+
+static ssize_t rgb_mode_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t count = 0;
+ unsigned int i;
+
+ for (i = 1; i < ARRAY_SIZE(rgb_mode_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", rgb_mode_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
+static ssize_t rgb_profile_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ size_t size = 1;
+ int ret;
+ u8 val;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val < 1 || val > 3)
+ return -EINVAL;
+
+ ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, LIGHT_PROFILE_SEL, &val,
+ size);
+ if (ret)
+ return ret;
+
+ drvdata.rgb_profile = val;
+
+ return count;
+};
+
+static ssize_t rgb_profile_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+
+ ret = rgb_cfg_call(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL, 0,
+ 0);
+ if (ret)
+ return ret;
+
+ if (drvdata.rgb_profile < 1 || drvdata.rgb_profile > 3)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile);
+};
+
+static ssize_t rgb_profile_range_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "1-3\n");
+}
+
+static void hid_gos_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
+ enum rgb_config_index index;
+ int ret;
+
+ if (brightness > led_cdev->max_brightness) {
+ dev_err(led_cdev->dev, "Invalid argument\n");
+ return;
+ }
+
+ index = drvdata.rgb_profile + 2;
+ u8 rgb_profile[6] = { drvdata.rgb_effect,
+ mc_cdev->subled_info[0].intensity,
+ mc_cdev->subled_info[1].intensity,
+ mc_cdev->subled_info[2].intensity,
+ brightness,
+ drvdata.rgb_speed };
+
+ ret = rgb_cfg_call(drvdata.hdev, SET_RGB_CFG, index, rgb_profile, 6);
+ switch (ret) {
+ case 0:
+ led_cdev->brightness = brightness;
+ break;
+ case -ENODEV: /* during switch to IAP -ENODEV is expected */
+ case -ENOSYS: /* during rmmod -ENOSYS is expected */
+ dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n",
+ ret);
+ break;
+ default:
+ dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n",
+ ret);
+ };
+}
+
#define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) \
static ssize_t _name##_store(struct device *dev, \
struct device_attribute *attr, \
@@ -829,6 +1174,70 @@ static const struct attribute_group *top_level_attr_groups[] = {
NULL,
};
+/* RGB */
+struct gos_cfg_attr rgb_enabled = { FEATURE_RGB_ENABLE };
+LEGOS_DEVICE_ATTR_RW(rgb_enabled, "enabled", index, gamepad);
+static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index");
+
+static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect");
+static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index");
+static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode");
+static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index");
+static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile");
+static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range");
+static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed");
+static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range");
+
+static struct attribute *gos_rgb_attrs[] = {
+ &dev_attr_rgb_enabled.attr,
+ &dev_attr_rgb_enabled_index.attr,
+ &dev_attr_rgb_effect.attr,
+ &dev_attr_rgb_effect_index.attr,
+ &dev_attr_rgb_mode.attr,
+ &dev_attr_rgb_mode_index.attr,
+ &dev_attr_rgb_profile.attr,
+ &dev_attr_rgb_profile_range.attr,
+ &dev_attr_rgb_speed.attr,
+ &dev_attr_rgb_speed_range.attr,
+ NULL,
+};
+
+static struct attribute_group rgb_attr_group = {
+ .attrs = gos_rgb_attrs,
+};
+
+struct mc_subled gos_rgb_subled_info[] = {
+ {
+ .color_index = LED_COLOR_ID_RED,
+ .brightness = 0x50,
+ .intensity = 0x24,
+ .channel = 0x1,
+ },
+ {
+ .color_index = LED_COLOR_ID_GREEN,
+ .brightness = 0x50,
+ .intensity = 0x22,
+ .channel = 0x2,
+ },
+ {
+ .color_index = LED_COLOR_ID_BLUE,
+ .brightness = 0x50,
+ .intensity = 0x99,
+ .channel = 0x3,
+ },
+};
+
+struct led_classdev_mc gos_cdev_rgb = {
+ .led_cdev = {
+ .name = "go_s:rgb:joystick_rings",
+ .brightness = 0x50,
+ .max_brightness = 0x64,
+ .brightness_set = hid_gos_brightness_set,
+ },
+ .num_colors = ARRAY_SIZE(gos_rgb_subled_info),
+ .subled_info = gos_rgb_subled_info,
+};
+
static void cfg_setup(struct work_struct *work)
{
int ret;
@@ -847,6 +1256,38 @@ static void cfg_setup(struct work_struct *work)
"Failed to retrieve MCU Version: %i\n", ret);
return;
}
+
+ /* RGB */
+ ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_RGB_ENABLE,
+ 0, 0);
+ if (ret < 0) {
+ dev_err(drvdata.led_cdev->dev,
+ "Failed to retrieve RGB enabled: %i\n", ret);
+ return;
+ }
+
+ ret = mcu_property_out(drvdata.hdev, GET_RGB_CFG, LIGHT_MODE_SEL, 0,
+ 0);
+ if (ret < 0) {
+ dev_err(drvdata.led_cdev->dev,
+ "Failed to retrieve RGB Mode: %i\n", ret);
+ return;
+ }
+
+ ret = mcu_property_out(drvdata.hdev, GET_RGB_CFG, LIGHT_PROFILE_SEL,
+ 0, 0);
+ if (ret < 0) {
+ dev_err(drvdata.led_cdev->dev,
+ "Failed to retrieve RGB Profile: %i\n", ret);
+ return;
+ }
+
+ ret = rgb_attr_show();
+ if (ret < 0) {
+ dev_err(drvdata.led_cdev->dev,
+ "Failed to retrieve RGB Profile Data: %i\n", ret);
+ return;
+ }
}
static int hid_gos_cfg_probe(struct hid_device *hdev,
@@ -865,6 +1306,21 @@ static int hid_gos_cfg_probe(struct hid_device *hdev,
return ret;
}
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &gos_cdev_rgb);
+ if (ret) {
+ dev_err_probe(&hdev->dev, ret, "Failed to create RGB device\n");
+ return ret;
+ }
+
+ ret = devm_device_add_group(gos_cdev_rgb.led_cdev.dev, &rgb_attr_group);
+ if (ret) {
+ dev_err_probe(&hdev->dev, ret,
+ "Failed to create RGB configuratiion attributes\n");
+ return ret;
+ }
+
+ drvdata.led_cdev = &gos_cdev_rgb.led_cdev;
+
init_completion(&drvdata.send_cmd_complete);
/* Executing calls prior to returning from probe will lock the MCU. Schedule
--
2.52.0
^ permalink raw reply related
* [PATCH v4 15/16] HID: hid-lenovo-go-s: Add IMU and Touchpad RO Attributes
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds attributes for reporting the touchpad manufacturer, version, and
IMU manufacturer.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Cache RO values at probe instead of calling them at request. Values
don't change and reading the touchpad data is slow.
v3:
- Fix bug where the touchpad attributes were assigned to the touchpad
_show function instead of the test _show function.
---
drivers/hid/hid-lenovo-go-s.c | 124 ++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/drivers/hid/hid-lenovo-go-s.c b/drivers/hid/hid-lenovo-go-s.c
index 2a3b8ef9db06d..cabee8c958696 100644
--- a/drivers/hid/hid-lenovo-go-s.c
+++ b/drivers/hid/hid-lenovo-go-s.c
@@ -43,6 +43,7 @@ struct hid_gos_cfg {
u8 gp_mode;
u8 gp_poll_rate;
u8 imu_bypass_en;
+ u8 imu_manufacturer;
u8 imu_sensor_en;
u8 mcu_id[12];
u8 mouse_step;
@@ -54,6 +55,8 @@ struct hid_gos_cfg {
u8 rgb_speed;
u8 tp_en;
u8 tp_linux_mode;
+ u8 tp_manufacturer;
+ u8 tp_version;
u8 tp_windows_mode;
} drvdata;
@@ -201,6 +204,36 @@ enum rgb_config_index {
USR_LIGHT_PROFILE_3,
};
+enum test_command_index {
+ TEST_TP_MFR = 0x02,
+ TEST_IMU_MFR,
+ TEST_TP_VER,
+};
+
+enum tp_mfr_index {
+ TP_NONE,
+ TP_BETTERLIFE,
+ TP_SIPO,
+};
+
+static const char *const touchpad_manufacturer_text[] = {
+ [TP_NONE] = "none",
+ [TP_BETTERLIFE] = "BetterLife",
+ [TP_SIPO] = "SIPO",
+};
+
+enum imu_mfr_index {
+ IMU_NONE,
+ IMU_BOSCH,
+ IMU_ST,
+};
+
+static const char *const imu_manufacturer_text[] = {
+ [IMU_NONE] = "none",
+ [IMU_BOSCH] = "Bosch",
+ [IMU_ST] = "ST",
+};
+
static int hid_gos_version_event(u8 *data)
{
struct version_report *ver_rep = (struct version_report *)data;
@@ -279,6 +312,30 @@ static int hid_gos_touchpad_event(struct command_report *cmd_rep)
return ret;
}
+static int hid_gos_pl_test_event(struct command_report *cmd_rep)
+{
+ int ret = 0;
+
+ switch (cmd_rep->sub_cmd) {
+ case TEST_TP_MFR:
+ drvdata.tp_manufacturer = cmd_rep->data[0];
+ ret = 0;
+ break;
+ case TEST_IMU_MFR:
+ drvdata.imu_manufacturer = cmd_rep->data[0];
+ ret = 0;
+ break;
+ case TEST_TP_VER:
+ drvdata.tp_version = cmd_rep->data[0];
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
static int hid_gos_light_event(struct command_report *cmd_rep)
{
struct led_classdev_mc *mc_cdev;
@@ -362,6 +419,9 @@ static int hid_gos_raw_event(struct hid_device *hdev, struct hid_report *report,
case GET_TP_PARAM:
ret = hid_gos_touchpad_event(cmd_rep);
break;
+ case GET_PL_TEST:
+ ret = hid_gos_pl_test_event(cmd_rep);
+ break;
case GET_RGB_CFG:
ret = hid_gos_light_event(cmd_rep);
break;
@@ -743,6 +803,37 @@ static ssize_t touchpad_property_options(struct device *dev,
return count;
}
+static ssize_t test_property_show(struct device *dev,
+ struct device_attribute *attr, char *buf,
+ enum test_command_index index)
+{
+ size_t count = 0;
+ u8 i;
+
+ switch (index) {
+ case TEST_TP_MFR:
+ i = drvdata.tp_manufacturer;
+ if (i >= ARRAY_SIZE(touchpad_manufacturer_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", touchpad_manufacturer_text[i]);
+ break;
+ case TEST_IMU_MFR:
+ i = drvdata.imu_manufacturer;
+ if (i >= ARRAY_SIZE(imu_manufacturer_text))
+ return -EINVAL;
+ count = sysfs_emit(buf, "%s\n", imu_manufacturer_text[i]);
+ break;
+ case TEST_TP_VER:
+ count = sysfs_emit(buf, "%u\n", drvdata.tp_version);
+ break;
+ default:
+ count = -EINVAL;
+ break;
+ }
+
+ return count;
+}
+
static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -1086,6 +1177,9 @@ struct gos_cfg_attr imu_bypass_enabled = { FEATURE_IMU_BYPASS };
LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad);
static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_index");
+struct gos_cfg_attr imu_manufacturer = { TEST_IMU_MFR };
+LEGOS_DEVICE_ATTR_RO(imu_manufacturer, "manufacturer", test);
+
struct gos_cfg_attr imu_sensor_enabled = { FEATURE_IMU_ENABLE };
LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad);
static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index");
@@ -1093,6 +1187,7 @@ static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index");
static struct attribute *legos_imu_attrs[] = {
&dev_attr_imu_bypass_enabled.attr,
&dev_attr_imu_bypass_enabled_index.attr,
+ &dev_attr_imu_manufacturer.attr,
&dev_attr_imu_sensor_enabled.attr,
&dev_attr_imu_sensor_enabled_index.attr,
NULL,
@@ -1146,6 +1241,12 @@ struct gos_cfg_attr touchpad_linux_mode = { CFG_LINUX_MODE };
LEGOS_DEVICE_ATTR_RW(touchpad_linux_mode, "linux_mode", index, touchpad);
static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index");
+struct gos_cfg_attr touchpad_manufacturer = { TEST_TP_MFR };
+LEGOS_DEVICE_ATTR_RO(touchpad_manufacturer, "manufacturer", test);
+
+struct gos_cfg_attr touchpad_version = { TEST_TP_VER };
+LEGOS_DEVICE_ATTR_RO(touchpad_version, "version", test);
+
struct gos_cfg_attr touchpad_windows_mode = { CFG_WINDOWS_MODE };
LEGOS_DEVICE_ATTR_RW(touchpad_windows_mode, "windows_mode", index, touchpad);
static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_index");
@@ -1155,6 +1256,8 @@ static struct attribute *legos_touchpad_attrs[] = {
&dev_attr_touchpad_enabled_index.attr,
&dev_attr_touchpad_linux_mode.attr,
&dev_attr_touchpad_linux_mode_index.attr,
+ &dev_attr_touchpad_manufacturer.attr,
+ &dev_attr_touchpad_version.attr,
&dev_attr_touchpad_windows_mode.attr,
&dev_attr_touchpad_windows_mode_index.attr,
NULL,
@@ -1257,6 +1360,27 @@ static void cfg_setup(struct work_struct *work)
return;
}
+ ret = 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 = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_VER, 0, 0);
+ if (ret) {
+ dev_err(&drvdata.hdev->dev,
+ "Failed to retrieve Touchpad Firmware Version: %i\n", ret);
+ return;
+ }
+
+ ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_IMU_MFR, 0, 0);
+ if (ret) {
+ dev_err(&drvdata.hdev->dev,
+ "Failed to retrieve IMU Manufacturer: %i\n", ret);
+ return;
+ }
+
/* RGB */
ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, FEATURE_RGB_ENABLE,
0, 0);
--
2.52.0
^ permalink raw reply related
* [PATCH v4 16/16] HID: Add documentation for Lenovo Legion Go drivers
From: Derek J. Clark @ 2026-02-20 7:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Richard Hughes, Mario Limonciello, Zhixin Zhang, Mia Shao,
Mark Pearson, Pierre-Loup A . Griffais, Derek J . Clark,
linux-input, linux-doc, linux-kernel
In-Reply-To: <20260220070533.4083667-1-derekjohn.clark@gmail.com>
Adds ABI documentation for the hid-lenovo-go-s and hid-lenovo-go
drivers.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
V3:
- Remove excess + from every line of patch.
---
.../ABI/testing/sysfs-driver-hid-lenovo-go | 724 ++++++++++++++++++
.../ABI/testing/sysfs-driver-hid-lenovo-go-s | 304 ++++++++
MAINTAINERS | 2 +
3 files changed, 1030 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-lenovo-go
create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go
new file mode 100644
index 0000000000000..c8221373ef76a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go
@@ -0,0 +1,724 @@
+What: /sys/class/leds/go:rgb:joystick_rings/effect
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the display effect of the RGB interface.
+
+ Values are monocolor, breathe, chroma, or rainbow.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/effect_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the effect attribute.
+
+ Values are monocolor, breathe, chroma, or rainbow.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the RGB interface.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the enabled attribute.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the operating mode of the RGB interface.
+
+ Values are dynamic or custom. Custom allows setting the RGB effect and color.
+ Dynamic is a Windows mode for syncing Lenovo RGB interfaces not currently
+ supported under Linux.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the mode attribute.
+
+ Values are dynamic or custom.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/profile
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls selecting the configured RGB profile.
+
+ Values are 1-3.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/profile_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the profile attribute.
+
+ Values are 1-3.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/speed
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the change rate for the breathe, chroma, and rainbow effects.
+
+ Values are 0-100.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/class/leds/go:rgb:joystick_rings/speed_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the speed attribute.
+
+ Values are 0-100.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/firmware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the firmware version of the internal MCU.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/fps_mode_dpi
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the DPI of the right handle when the FPS mode switch is on.
+
+ Values are 500, 800, 1200, and 1800.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/fps_mode_dpi_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the fps_mode_dpi attribute.
+
+ Values are 500, 800, 1200, and 1800.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/hardware_generation
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware generation of the internal MCU.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/hardware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware version of the internal MCU.
+
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/auto_sleep_time
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the sleep timer due to inactivity for the left removable controller.
+
+ Values are 0-255.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/auto_sleep_time_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/auto_sleep_time attribute.
+
+ Values are 0-255.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_gyro
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This initiates or halts calibration of the left removable controller's IMU.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_gyro_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/calibrate_gyro attribute.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_gyro_status
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the result of the last attempted calibration of the left removable controller's IMU.
+
+ Values are unknown, success, failure.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_joystick
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This initiates or halts calibration of the left removable controller's joystick.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_joystick_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/calibrate_jotstick attribute.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_joystick_status
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the result of the last attempted calibration of the left removable controller's joystick.
+
+ Values are unknown, success, failure.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_tirgger
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This initiates or halts calibration of the left removable controller's trigger.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_gyro_trigger
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/calibrate_trigger attribute.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/calibrate_trigger_status
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the result of the last attempted calibration of the left removable controller's trigger.
+
+ Values are unknown, success, failure.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/firmware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the left removable controller's firmware version.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/hardware_generation
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware generation of the left removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/hardware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware version of the left removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/imu_bypass_enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the IMU bypass function of the left removable controller.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/imu_bypass_enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/imu_bypass_enabled attribute.
+
+ Values are true or false.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/imu_enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the IMU of the left removable controller.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/imu_enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/imu_enabled attribute.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/product_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the product version of the left removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/protocol_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the protocol version of the left removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/reset
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: Resets the left removable controller to factory defaults.
+
+ Writing 1 to this path initiates.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/rumble_mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls setting the response behavior for rumble events for the left removable controller.
+
+ Values are fps, racing, standarg, spg, rpg.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/rumble_mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/rumble_mode attribute.
+
+ Values are fps, racing, standarg, spg, rpg.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/rumble_notification
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling haptic rumble events for the left removable controller.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/rumble_notification_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the left_handle/rumble_notification attribute.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the operating mode of the built-in controller.
+
+ Values are xinput or dinput.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/left_handle/mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the mode attribute.
+
+ Values are xinput or dinput.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/os_mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the behavior of built in chord combinations.
+
+ Values are windows or linux.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/os_mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the os_mode attribute.
+
+ Values are windows or linux.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/product_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the product version of the internal MCU.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/protocol_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the protocol version of the internal MCU.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/reset_mcu
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: Resets the internal MCU to factory defaults.
+
+ Writing 1 to this path initiates.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/auto_sleep_time
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the sleep timer due to inactivity for the right removable controller.
+
+ Values are 0-255.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/auto_sleep_time_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/auto_sleep_time attribute.
+
+ Values are 0-255.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_gyro
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This initiates or halts calibration of the right removable controller's IMU.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_gyro_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/calibrate_gyro attribute.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_gyro_status
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the result of the last attempted calibration of the right removable controller's IMU.
+
+ Values are unknown, success, failure.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_joystick
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This initiates or halts calibration of the right removable controller's joystick.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_joystick_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/calibrate_jotstick attribute.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_joystick_status
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the result of the last attempted calibration of the right removable controller's joystick.
+
+ Values are unknown, success, failure.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_tirgger
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This initiates or halts calibration of the right removable controller's trigger.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_gyro_trigger
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/calibrate_trigger attribute.
+
+ Values are start, stop.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/calibrate_trigger_status
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the result of the last attempted calibration of the right removable controller's trigger.
+
+ Values are unknown, success, failure.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/firmware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the right removable controller's firmware version.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/hardware_generation
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware generation of the right removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/hardware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware version of the right removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/imu_bypass_enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the IMU bypass function of the right removable controller.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/imu_bypass_enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/imu_bypass_enabled attribute.
+
+ Values are true or false.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/imu_enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the IMU of the right removable controller.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/imu_enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/imu_enabled attribute.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/product_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the product version of the right removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/protocol_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the protocol version of the right removable controller.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/reset
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: Resets the right removable controller to factory defaults.
+
+ Writing 1 to this path initiates.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/rumble_mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls setting the response behavior for rumble events for the right removable controller.
+
+ Values are fps, racing, standarg, spg, rpg.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/rumble_mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/rumble_mode attribute.
+
+ Values are fps, racing, standarg, spg, rpg.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/rumble_notification
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling haptic rumble events for the right removable controller.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/right_handle/rumble_notification_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the right_handle/rumble_notification attribute.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/rumble_intensity
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls setting the rumble intensity for both removable controllers.
+
+ Values are off, low, medium, high.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/rumble_intensity_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the rumble_intensity attribute.
+
+ Values are off, low, medium, high.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the touchpad.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the touchpad/enabled attribute.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/vibration_enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling haptic rumble events for the touchpad.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/vibration_enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the touchpad/vibration_enabled attribute.
+
+ Values are true, false.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/vibration_intensity
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls setting the intensity of the touchpad haptics.
+
+ Values are off, low, medium, high.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/vibration_intensity_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the touchpad/vibration_intensity attribute.
+
+ Values are off, low, medium, high.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/tx_dongle/firmware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the firmware version of the internal wireless transmission dongle.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/tx_dongle/hardware_generation
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware generation of the internal wireless transmission dongle.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/tx_dongle/hardware_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the hardware version of the internal wireless transmission dongle.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/tx_dongle/product_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the product version of the internal wireless transmission dongle.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/tx_dongle/protocol_version
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the protocol version of the internal wireless transmission dongle.
+
+ Applies to Lenovo Legion Go and Go 2 line of handheld devices.
+
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s
new file mode 100644
index 0000000000000..4d317074bb7e6
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s
@@ -0,0 +1,304 @@
+What: /sys/class/leds/go_s:rgb:joystick_rings/effect
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the display effect of the RGB interface.
+
+ Values are monocolor, breathe, chroma, or rainbow.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/effect_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the effect attribute.
+
+ Values are monocolor, breathe, chroma, or rainbow.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the RGB interface.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the enabled attribute.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the operating mode of the RGB interface.
+
+ Values are dynamic or custom. Custom allows setting the RGB effect and color.
+ Dynamic is a Windows mode for syncing Lenovo RGB interfaces not currently
+ supported under Linux.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the mode attribute.
+
+ Values are dynamic or custom.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/profile
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls selecting the configured RGB profile.
+
+ Values are 1-3.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/profile_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the profile attribute.
+
+ Values are 1-3.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/speed
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the change rate for the breathe, chroma, and rainbow effects.
+
+ Values are 0-100.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/class/leds/go_s:rgb:joystick_rings/speed_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the speed attribute.
+
+ Values are 0-100.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/auto_sleep_time
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the sleep timer due to inactivity for the built-in controller.
+
+ Values are 0-255.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/auto_sleep_time_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the gamepad/auto_sleep_time attribute.
+
+ Values are 0-255.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/dpad_mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the operating mode of the built-in controllers D-pad.
+
+ Values are 4-way or 8-way.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/dpad_mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the gamepad/dpad_mode attribute.
+
+ Values are 4-way or 8-way.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the operating mode of the built-in controller.
+
+ Values are xinput or dinput.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the gamepad/mode attribute.
+
+ Values are xinput or dinput.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/poll_rate
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls the poll rate in Hz of the built-in controller.
+
+ Values are 125, 250, 500, or 1000.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/gamepad/poll_rate_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the gamepad/poll_rate attribute.
+
+ Values are 125, 250, 500, or 1000.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/imu/bypass_enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the IMU bypass function. When enabled the IMU data is directly reported to the OS through
+an HIDRAW interface.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/imu/bypass_enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the imu/bypass_enabled attribute.
+
+ Values are true or false.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/imu/manufacturer
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the manufacturer of the intertial measurment unit.
+
+ Values are Bosch or ST.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/imu/sensor_enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the IMU.
+
+ Values are true, false, or wake-2s.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/imu/sensor_enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the imu/sensor_enabled attribute.
+
+ Values are true, false, or wake-2s.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/mcu_id
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the MCU Identification Number
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/mouse/step
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls which value is used for the mouse sensitivity.
+
+ Values are 1-127.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/mouse/step_range
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the mouse/step attribute.
+
+ Values are 1-127.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/os_mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls which value is used for the touchpads operating mode.
+
+ Values are windows or linux.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/os_mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the os_mode attribute.
+
+ Values are windows or linux.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/enabled
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls enabling or disabling the built-in touchpad.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/enabled_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the touchpad/enabled attribute.
+
+ Values are true or false.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/linux_mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls behavior of the touchpad events when os_mode is set to linux.
+
+ Values are absolute or relative.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/linux_mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the touchpad/linux_mode attribute.
+
+ Values are absolute or relative.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/windows_mode
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This controls behavior of the touchpad events when os_mode is set to windows.
+
+ Values are absolute or relative.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
+
+What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/touchpad/windows_mode_index
+Date: April 2026
+Contact: linux-input@vger.kernel.org
+Description: This displays the available options for the touchpad/windows_mode attribute.
+
+ Values are absolute or relative.
+
+ Applies to Lenovo Legion Go S line of handheld devices.
diff --git a/MAINTAINERS b/MAINTAINERS
index 1d0468906788a..8eea5f231e809 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14318,6 +14318,8 @@ M: Derek J. Clark <derekjohn.clark@gmail.com>
M: Mark Pearson <mpearson-lenovo@squebb.ca>
L: linux-input@vger.kernel.org
S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go
+F: Documentation/ABI/testing/sysfs-driver-hid-lenovo-go-s
F: drivers/hid/hid-lenovo-go-s.c
F: drivers/hid/hid-lenovo-go.c
F: drivers/hid/hid-lenovo.c
--
2.52.0
^ permalink raw reply related
* Subject: [PATCH] Input: atkbd - validate scancode in firmware keymap entries
From: Ariel Silver @ 2026-02-20 8:44 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, linux-kernel
The SCANCODE() macro extracts a 16-bit value (0..65535) from firmware
device property data, but atkbd_get_keymap_from_fwnode() uses it
directly to index atkbd->keycode[], which only has ATKBD_KEYMAP_SIZE
(512) elements. A firmware-supplied scancode >= 512 causes a heap
out-of-bounds write that can corrupt adjacent struct atkbd fields and
neighboring slab objects.
Add a bounds check that rejects the entire firmware keymap if any entry
contains an out-of-range scancode, consistent with the validation
performed by matrix_keypad_parse_keymap() in drivers/input/matrix-keymap.c
for the same "linux,keymap" property format. When rejected, the driver
falls back to the default keycode table.
Fixes: 9d17ad2369dc ("Input: atkbd - receive and use physcode->keycode
mapping from FW")
Reported-by: Ariel Silver <arielsilver77@gmail.com>
Signed-off-by: Ariel Silver <arielsilver77@gmail.com>
Cc: stable@vger.kernel.org
---
drivers/input/keyboard/atkbd.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index XXXXXXX..XXXXXXX 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -1111,6 +1111,13 @@ static int atkbd_get_keymap_from_fwnode(struct
atkbd *atkbd)
for (i = 0; i < n; i++) {
scancode = SCANCODE(ptr[i]);
keycode = KEYCODE(ptr[i]);
+ if (scancode >= ATKBD_KEYMAP_SIZE) {
+ dev_warn(dev,
+ "invalid scancode 0x%x in FW keymap entry %d\n",
+ scancode, i);
+ kfree(ptr);
+ return -EINVAL;
+ }
atkbd->keycode[scancode] = keycode;
}
^ permalink raw reply
* [PATCH v2] HID: generic: fix build when LEDS_CLASS_MULTICOLOR is disabled
From: Tim Guttzeit @ 2026-02-20 10:03 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: wse, Tim Guttzeit, linux-input, linux-kernel
Building HID generic with LampArray support enabled caused link
failures when LEDS_CLASS_MULTICOLOR was disabled, due to unresolved
led_classdev_multicolor_* and led_mc_* symbols.
hid-lamparray.o was built whenever HID_GENERIC was enabled,
regardless of LED multicolor support.
Introduce CONFIG_HID_LAMPARRAY with dependencies on HID_GENERIC &&
LEDS_CLASS && LEDS_CLASS_MULTICOLOR and build hid-lamparray.o only
when enabled. Provide stub helpers so hid-generic.c builds cleanly
when the option is disabled.
Reported-by: kernel test robot <lkp@intel.com>
Link: https://lore.kernel.org/oe-kbuild-all/202602200233.9Bwav9tZ-lkp@intel.com/
Link: https://lore.kernel.org/oe-kbuild-all/202602200241.6ypuWvE5-lkp@intel.com/
Link: https://lore.kernel.org/oe-kbuild-all/202602192025.xrvVo680-lkp@intel.com/
Link: https://lore.kernel.org/oe-kbuild-all/202602192131.Q9y8Kqvt-lkp@intel.com/
Changes in v2:
- Add CONFIG_HID_LAMPARRAY with proper dependencies
- Build hid-lamparray.o only when enabled
- Add stub helpers when disabled
- Fix build failures reported by kernel test robot
Signed-off-by: Tim Guttzeit <tgu@tuxedocomputers.com>
---
drivers/hid/Kconfig | 10 ++++++++++
drivers/hid/Makefile | 3 ++-
drivers/hid/hid-generic.c | 4 ++--
drivers/hid/hid-lamparray.h | 25 ++++++++++++++++++++++++-
4 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 920a64b66b25..91547dfa5661 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,6 +92,16 @@ config HID_GENERIC
If unsure, say Y.
+config HID_LAMPARRAY
+ bool "HID LampArray helper"
+ depends on HID_GENERIC && LEDS_CLASS && LEDS_CLASS_MULTICOLOR
+ default y
+ help
+ Helper for HID devices exposing a Lighting/LampArray collection.
+ Treats LampArray devices as a single-zone device and exposes a sysfs
+ interface for changing color and intensity values. Also exposes a
+ sysfs flag to be disabled e.g. by a userspace driver.
+
config HID_HAPTIC
bool "Haptic touchpad support"
default n
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e6903be9c4df..5a14b4b0970d 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -12,7 +12,8 @@ obj-$(CONFIG_HID) += hid.o
obj-$(CONFIG_UHID) += uhid.o
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
-obj-$(CONFIG_HID_GENERIC) += hid-lamparray.o
+
+obj-$(CONFIG_HID_LAMPARRAY) += hid-lamparray.o
hid-$(CONFIG_HIDRAW) += hidraw.o
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index 650c2121b403..57b81c86982c 100644
--- a/drivers/hid/hid-generic.c
+++ b/drivers/hid/hid-generic.c
@@ -77,7 +77,7 @@ static int hid_generic_probe(struct hid_device *hdev,
* Optional: attach LampArray support if present.
* Never fail probe on LampArray errors; keep device functional.
*/
- if (lamparray_is_supported_device(hdev)) {
+ if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && lamparray_is_supported_device(hdev)) {
const struct lamparray_init_state init = {
.r = 0xff,
.g = 0xff,
@@ -108,7 +108,7 @@ static void hid_generic_remove(struct hid_device *hdev)
{
struct lamparray *la = hid_get_drvdata(hdev);
- if (la)
+ if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && la)
lamparray_unregister(la);
hid_hw_stop(hdev);
diff --git a/drivers/hid/hid-lamparray.h b/drivers/hid/hid-lamparray.h
index b786ca00c404..ac3edd366a5b 100644
--- a/drivers/hid/hid-lamparray.h
+++ b/drivers/hid/hid-lamparray.h
@@ -6,6 +6,7 @@
#include <linux/types.h>
#include <linux/leds.h>
#include <linux/err.h>
+#include <linux/errno.h>
struct hid_device;
struct lamparray;
@@ -21,6 +22,8 @@ struct lamparray_init_state {
enum led_brightness brightness;
};
+#if IS_ENABLED(CONFIG_HID_LAMPARRAY)
+
/**
* lamparray_is_supported_device() - check whether a HID device supports LampArray
* @hdev: HID device to inspect
@@ -65,4 +68,24 @@ struct lamparray *lamparray_register(struct hid_device *hdev,
*/
void lamparray_unregister(struct lamparray *la);
-#endif
+#else /* !CONFIG_HID_LAMPARRAY */
+
+static inline bool lamparray_is_supported_device(struct hid_device *hdev)
+{
+ return false;
+}
+
+static inline struct lamparray *
+lamparray_register(struct hid_device *hdev,
+ const struct lamparray_init_state *led_init_state)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void lamparray_unregister(struct lamparray *la)
+{
+}
+
+#endif /* CONFIG_HID_LAMPARRAY */
+
+#endif /* _HID_LAMPARRAY_H */
--
2.43.0
^ permalink raw reply related
* [PATCH v3] HID: generic: add LampArray support via hid-lamparray helper
From: Tim Guttzeit @ 2026-02-20 13:51 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: wse, Tim Guttzeit, linux-kernel, linux-input
Add a new hid-lamparray helper module and integrate it with the
hid-generic driver.
The helper provides basic support for devices exposing a
Lighting/LampArray application collection (usage page 0x59) and
registers a single-zone RGB LED representation via the LED
subsystem.
hid-generic now checks for LampArray support after hid_parse() and
optionally registers a lamparray instance. Failures in the helper
do not abort device probe to keep the device functional.
LampArray resources are released on driver remove.
Signed-off-by: Tim Guttzeit <tgu@tuxedocomputers.com>
---
V1->V2: Fix kconfig to avoid build errors when LEDS_CLASS_MULTICOLOR is disabled
V2->V3: Squash V1 and V2 into one patch
drivers/hid/Kconfig | 10 +
drivers/hid/Makefile | 2 +
drivers/hid/hid-generic.c | 41 +-
drivers/hid/hid-lamparray.c | 855 ++++++++++++++++++++++++++++++++++++
drivers/hid/hid-lamparray.h | 91 ++++
5 files changed, 997 insertions(+), 2 deletions(-)
create mode 100644 drivers/hid/hid-lamparray.c
create mode 100644 drivers/hid/hid-lamparray.h
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 920a64b66b25..91547dfa5661 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,6 +92,16 @@ config HID_GENERIC
If unsure, say Y.
+config HID_LAMPARRAY
+ bool "HID LampArray helper"
+ depends on HID_GENERIC && LEDS_CLASS && LEDS_CLASS_MULTICOLOR
+ default y
+ help
+ Helper for HID devices exposing a Lighting/LampArray collection.
+ Treats LampArray devices as a single-zone device and exposes a sysfs
+ interface for changing color and intensity values. Also exposes a
+ sysfs flag to be disabled e.g. by a userspace driver.
+
config HID_HAPTIC
bool "Haptic touchpad support"
default n
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 361a7daedeb8..5a14b4b0970d 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -13,6 +13,8 @@ obj-$(CONFIG_UHID) += uhid.o
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
+obj-$(CONFIG_HID_LAMPARRAY) += hid-lamparray.o
+
hid-$(CONFIG_HIDRAW) += hidraw.o
hid-logitech-y := hid-lg.o
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index c2de916747de..57b81c86982c 100644
--- a/drivers/hid/hid-generic.c
+++ b/drivers/hid/hid-generic.c
@@ -20,6 +20,7 @@
#include <asm/byteorder.h>
#include <linux/hid.h>
+#include "hid-lamparray.h"
static struct hid_driver hid_generic;
@@ -31,7 +32,7 @@ static int __check_hid_generic(struct device_driver *drv, void *data)
if (hdrv == &hid_generic)
return 0;
- return hid_match_device(hdev, hdrv) != NULL;
+ return !!hid_match_device(hdev, hdrv);
}
static bool hid_generic_match(struct hid_device *hdev,
@@ -60,6 +61,7 @@ static int hid_generic_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
+ struct lamparray *la = NULL;
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
@@ -67,7 +69,31 @@ static int hid_generic_probe(struct hid_device *hdev,
if (ret)
return ret;
- return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ /*
+ * Optional: attach LampArray support if present.
+ * Never fail probe on LampArray errors; keep device functional.
+ */
+ if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && lamparray_is_supported_device(hdev)) {
+ const struct lamparray_init_state init = {
+ .r = 0xff,
+ .g = 0xff,
+ .b = 0xff,
+ .brightness = LED_FULL,
+ };
+
+ la = lamparray_register(hdev, &init);
+ if (IS_ERR(la)) {
+ hid_warn(hdev, "LampArray init failed: %ld\n", PTR_ERR(la));
+ la = NULL;
+ }
+ }
+
+ hid_set_drvdata(hdev, la);
+ return 0;
}
static int hid_generic_reset_resume(struct hid_device *hdev)
@@ -78,6 +104,16 @@ static int hid_generic_reset_resume(struct hid_device *hdev)
return 0;
}
+static void hid_generic_remove(struct hid_device *hdev)
+{
+ struct lamparray *la = hid_get_drvdata(hdev);
+
+ if (IS_ENABLED(CONFIG_HID_LAMPARRAY) && la)
+ lamparray_unregister(la);
+
+ hid_hw_stop(hdev);
+}
+
static const struct hid_device_id hid_table[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
{ }
@@ -90,6 +126,7 @@ static struct hid_driver hid_generic = {
.match = hid_generic_match,
.probe = hid_generic_probe,
.reset_resume = hid_generic_reset_resume,
+ .remove = hid_generic_remove,
};
module_hid_driver(hid_generic);
diff --git a/drivers/hid/hid-lamparray.c b/drivers/hid/hid-lamparray.c
new file mode 100644
index 000000000000..5be46fff0191
--- /dev/null
+++ b/drivers/hid/hid-lamparray.c
@@ -0,0 +1,855 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * hid-lamparray.c - HID LampArray helper module (single-zone RGB)
+ *
+ * Helper module for HID drivers supporting devices that expose a
+ * Lighting and Illumination (LampArray) application collection
+ * (usage page 0x59).
+ *
+ * The module provides a minimal integration with the LED subsystem
+ * and treats the device as a single zone: all lamps share one RGB
+ * value and a global brightness level. It does not implement multi-
+ * zone layouts or hardware effects.
+ *
+ *
+ * If enabled, one multicolor LED class device is registered under
+ * /sys/class/leds/<HID-ID> to expose the single-zone RGB control.
+ *
+ * The use_leds_uapi sysfs attribute is attached directly to the HID device
+ * under /sys/bus/hid/devices/<HID-ID>/use_leds_uapi.Writing 0 to use_leds_uapi
+ * unregisters the LED class device. The last state is kept cached. Writing 1
+ * registers it again and restores the cached state to hardware.
+ *
+ * State is cached as last known RGB + brightness. Setting sends a HID
+ * SET_REPORT. Getting issues a HID GET_REPORT and updates the cache on
+ * mismatch. Since the device is handled as single-zone, readback only queries
+ * lamp 0 when a lamp range is available.
+ *
+ * The module does not bind to devices on its own. Instead, a HID
+ * driver may query support via lamparray_is_supported_device() after
+ * hid_parse() and create an instance using lamparray_register().
+ *
+ * Copyright (C) 2026 Tim Guttzeit <tgu@tuxedocomputers.com>
+ */
+
+#include "hid-lamparray.h"
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/mutex.h>
+#include <linux/bitops.h>
+#include <linux/xarray.h>
+
+/* Constants */
+
+/* HID usages (LampArray, etc.) */
+#define HID_LIGHTING_ILLUMINATION_USAGE_PAGE 0x0059
+
+#define HID_LAIP_LAMP_COUNT 0x0003
+#define HID_LAIP_LAMP_ID 0x0021
+#define HID_LAIP_RED_UPDATE_CHANNEL 0x0051
+#define HID_LAIP_GREEN_UPDATE_CHANNEL 0x0052
+#define HID_LAIP_BLUE_UPDATE_CHANNEL 0x0053
+#define HID_LAIP_INTENSITY_UPDATE_CHANNEL 0x0054
+#define HID_LAIP_LAMP_ID_START 0x0061
+#define HID_LAIP_LAMP_ID_END 0x0062
+#define HID_LAIP_AUTONOMOUS_MODE 0x0071
+
+#define HID_APPLICATION_COLLECTION_USAGE_TYPE 0x0001
+
+/* Device state */
+
+struct lamparray_quirks {
+ unsigned long flags;
+ int fixed_lamp_count;
+};
+
+#define LAMPARRAY_QUIRK_LAMPCOUNT_FIXED BIT(0)
+
+struct lamparray_quirk_entry {
+ u16 vendor;
+ u16 product;
+ unsigned long flags;
+ int fixed_lamp_count;
+};
+
+static const struct lamparray_quirk_entry lamparray_quirk_table[] = {
+ { 0xcafe, 0x4005, LAMPARRAY_QUIRK_LAMPCOUNT_FIXED, 12 },
+ {}
+};
+
+static const struct lamparray_quirk_entry *
+lamparray_lookup_quirks(struct hid_device *hdev)
+{
+ const struct lamparray_quirk_entry *e;
+
+ for (e = lamparray_quirk_table; e->vendor; e++) {
+ if (hdev->vendor == e->vendor && hdev->product == e->product)
+ return e;
+ }
+ return NULL;
+}
+
+struct lamparray_device {
+ const struct lamparray_quirk_entry *quirks;
+
+ struct hid_device *hdev;
+ struct hid_report *update_report;
+
+ struct hid_field *red_field;
+ int red_index;
+ struct hid_field *green_field;
+ int green_index;
+ struct hid_field *blue_field;
+ int blue_index;
+ struct hid_field *intensity_field;
+ int intensity_index;
+
+ struct hid_report *autonomous_report;
+ struct hid_field *autonomous_field;
+
+ struct hid_field *range_start_field;
+ int range_start_index;
+
+ struct hid_field *range_end_field;
+ int range_end_index;
+
+ struct hid_field *lamp_count_field;
+ int lamp_count;
+ int lamp_count_index;
+
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subleds[3];
+
+ struct mutex lock; /* protects cached state and HID access */
+
+ u8 last_r;
+ u8 last_g;
+ u8 last_b;
+ enum led_brightness last_brightness;
+
+ bool use_leds_uapi;
+ bool led_registered;
+};
+
+/*
+ * Opaque handle exposed to callers via the header.
+ * Keep the actual state in lamparray_device, but return a stable pointer.
+ */
+struct lamparray {
+ struct lamparray_device ldev;
+};
+
+static DEFINE_XARRAY(lamparray_by_hdev);
+
+/* HID helper functions */
+
+#ifdef DEBUG
+static void lamparray_dump_reports(struct hid_device *hdev)
+{
+ struct hid_report_enum *re;
+ struct hid_report *report;
+ int i, j, report_type;
+
+ for (report_type = 0; report_type < HID_REPORT_TYPES; report_type++) {
+ re = &hdev->report_enum[report_type];
+ hid_dbg(hdev, "Dumping reports for report type %d",
+ report_type);
+ list_for_each_entry(report, &re->report_list, list) {
+ hid_dbg(hdev,
+ "lamparray: report id=%u type=%d maxfield=%u\n",
+ report->id, report->type, report->maxfield);
+
+ for (i = 0; i < report->maxfield; i++) {
+ struct hid_field *field = report->field[i];
+
+ for (j = 0; j < field->maxusage; j++) {
+ u32 usage = field->usage[j].hid;
+ u16 page = usage >> 16;
+ u16 id = usage & 0xFFFF;
+
+ hid_dbg(hdev,
+ "lamparray: report %u field %d usage[%d]: page=0x%04x id=0x%04x\n",
+ report->id, i, j, page, id);
+ }
+ }
+ }
+ }
+}
+#else
+static inline void lamparray_dump_reports(struct hid_device *hdev)
+{}
+#endif
+
+static int lamparray_read_lamp_count(struct lamparray_device *ldev)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report *report;
+
+ if (ldev->quirks &&
+ (ldev->quirks->flags & LAMPARRAY_QUIRK_LAMPCOUNT_FIXED)) {
+ ldev->lamp_count = ldev->quirks->fixed_lamp_count;
+ hid_dbg(hdev, "LampCount from quirk: %d\n", ldev->lamp_count);
+ return 0;
+ }
+ if (!ldev->lamp_count_field) {
+ hid_warn(hdev, "No LampCount field found\n");
+ return -ENODEV;
+ }
+
+ report = ldev->lamp_count_field->report;
+
+ if (!report) {
+ hid_warn(hdev, "LampCount field has no report\n");
+ return -ENODEV;
+ }
+ hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+ ldev->lamp_count =
+ ldev->lamp_count_field->value[ldev->lamp_count_index];
+
+ hid_dbg(hdev, "LampCount from device: %d\n", ldev->lamp_count);
+
+ if (ldev->lamp_count <= 0) {
+ hid_warn(hdev, "LampCount is %d (invalid)\n", ldev->lamp_count);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lamparray_parse_update_report(struct lamparray_device *ldev)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report_enum *re;
+ struct hid_report *report;
+ struct hid_field *field;
+ int i, j;
+
+ lamparray_dump_reports(hdev);
+
+ re = &hdev->report_enum[HID_FEATURE_REPORT];
+
+ list_for_each_entry(report, &re->report_list, list) {
+ for (i = 0; i < report->maxfield; i++) {
+ field = report->field[i];
+ if (!field)
+ continue;
+
+ if (!field->usage || !field->maxusage)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ u32 usage = field->usage[j].hid;
+ u16 page = usage >> 16;
+ u16 id = usage & 0xffff;
+
+ if (page !=
+ HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
+ continue;
+ switch (id) {
+ case HID_LAIP_LAMP_COUNT:
+ ldev->lamp_count_field = field;
+ ldev->lamp_count_index = j;
+ break;
+ case HID_LAIP_RED_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->red_field = field;
+ ldev->red_index = j;
+ break;
+ case HID_LAIP_GREEN_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->green_field = field;
+ ldev->green_index = j;
+ break;
+ case HID_LAIP_BLUE_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->blue_field = field;
+ ldev->blue_index = j;
+ break;
+ case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->intensity_field = field;
+ ldev->intensity_index = j;
+ break;
+ case HID_LAIP_LAMP_ID_START:
+ ldev->range_start_field = field;
+ ldev->range_start_index = j;
+ break;
+ case HID_LAIP_LAMP_ID_END:
+ ldev->range_end_field = field;
+ ldev->range_end_index = j;
+ break;
+ case HID_LAIP_AUTONOMOUS_MODE:
+ ldev->autonomous_field = field;
+ ldev->autonomous_report = report;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if (!ldev->update_report || !ldev->red_field || !ldev->green_field ||
+ !ldev->blue_field || !ldev->autonomous_report || !ldev->autonomous_field)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int lamparray_hw_set_autonomous(struct lamparray_device *ldev,
+ bool enable)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_field *field = ldev->autonomous_field;
+ struct hid_report *report = ldev->autonomous_report;
+
+ if (!field || !report)
+ return -ENODEV;
+
+ field->value[0] = enable ? 1 : 0;
+
+ hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+ return 0;
+}
+
+static int lamparray_hw_set_state(struct lamparray_device *ldev, u8 r, u8 g,
+ u8 b, u8 intensity)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report *report = ldev->update_report;
+ int i, j;
+
+ if (!report || !ldev->red_field || !ldev->green_field ||
+ !ldev->blue_field || !ldev->intensity_field)
+ return -ENODEV;
+
+ if (ldev->range_start_field && ldev->range_end_field) {
+ ldev->range_start_field->value[ldev->range_start_index] = 0;
+ ldev->range_end_field->value[ldev->range_end_index] = ldev->lamp_count - 1;
+ }
+
+ for (i = 0; i < report->maxfield; i++) {
+ struct hid_field *field = report->field[i];
+
+ if (!field || !field->usage || !field->maxusage)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ u32 usage = field->usage[j].hid;
+ u16 page = usage >> 16;
+ u16 id = usage & 0xffff;
+
+ if (page != HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
+ continue;
+
+ switch (id) {
+ case HID_LAIP_RED_UPDATE_CHANNEL:
+ field->value[j] = r;
+ break;
+ case HID_LAIP_GREEN_UPDATE_CHANNEL:
+ field->value[j] = g;
+ break;
+ case HID_LAIP_BLUE_UPDATE_CHANNEL:
+ field->value[j] = b;
+ break;
+ case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
+ field->value[j] = intensity;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+ return 0;
+}
+
+static int lamparray_hw_get_state(struct lamparray_device *ldev, u8 *r, u8 *g,
+ u8 *b, enum led_brightness *brightness)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report *report = ldev->update_report;
+
+ if (!report || !ldev->red_field || !ldev->green_field ||
+ !ldev->blue_field || !ldev->intensity_field)
+ return -ENODEV;
+
+ if (!r || !g || !b || !brightness)
+ return -EINVAL;
+
+ /* Single-zone: Reading lamp 0 only suffices */
+ if (ldev->range_start_field && ldev->range_end_field) {
+ ldev->range_start_field->value[ldev->range_start_index] = 0;
+ ldev->range_end_field->value[ldev->range_end_index] = 0;
+ }
+
+ hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+
+ *r = ldev->red_field->value[ldev->red_index];
+ *g = ldev->green_field->value[ldev->green_index];
+ *b = ldev->blue_field->value[ldev->blue_index];
+ *brightness = ldev->intensity_field->value[ldev->intensity_index];
+
+ return 0;
+}
+
+/* Helper functions */
+
+static int lamparray_restore_state(struct lamparray_device *ldev)
+{
+ u8 r, g, b;
+ int ret;
+ enum led_brightness brightness;
+
+ mutex_lock(&ldev->lock);
+
+ if (!ldev->use_leds_uapi) {
+ mutex_unlock(&ldev->lock);
+ return 0;
+ }
+
+ r = ldev->last_r;
+ g = ldev->last_g;
+ b = ldev->last_b;
+ brightness = ldev->last_brightness;
+
+ ldev->mc_cdev.subled_info[0].brightness = r;
+ ldev->mc_cdev.subled_info[1].brightness = g;
+ ldev->mc_cdev.subled_info[2].brightness = b;
+ ldev->mc_cdev.led_cdev.brightness = brightness;
+
+ mutex_unlock(&ldev->lock);
+
+ ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
+ return ret;
+}
+
+/* LEDs API */
+
+static int lamparray_led_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lamparray_device *ldev =
+ container_of(mc, struct lamparray_device, mc_cdev);
+ struct lamparray *la = container_of(ldev, struct lamparray, ldev);
+ u8 r, g, b;
+ int ret;
+
+ if (!la)
+ return -ENODEV;
+ ldev = &la->ldev;
+
+ ret = led_mc_calc_color_components(mc, brightness);
+ if (ret)
+ return ret;
+
+ r = mc->subled_info[0].brightness;
+ g = mc->subled_info[1].brightness;
+ b = mc->subled_info[2].brightness;
+
+ ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
+ if (ret)
+ hid_err(ldev->hdev, "Failed to send LampArray update: %d\n",
+ ret);
+
+ mutex_lock(&ldev->lock);
+ ldev->last_r = r;
+ ldev->last_g = g;
+ ldev->last_b = b;
+ ldev->last_brightness = brightness;
+ mutex_unlock(&ldev->lock);
+
+ return 0;
+}
+
+static enum led_brightness
+lamparray_led_brightness_get(struct led_classdev *cdev)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lamparray_device *ldev =
+ container_of(mc, struct lamparray_device, mc_cdev);
+ enum led_brightness brightness;
+ struct lamparray *la = container_of(ldev, struct lamparray, ldev);
+ u8 rr, gg, bb;
+ enum led_brightness br;
+ int ret;
+
+ /* Default: cache (also used while registering LED classdev) */
+ mutex_lock(&ldev->lock);
+ brightness = ldev->last_brightness;
+ mutex_unlock(&ldev->lock);
+
+ /* Only do HID readback after registration completed */
+ if (READ_ONCE(ldev->led_registered)) {
+ if (!la)
+ return brightness;
+ ldev = &la->ldev;
+
+ ret = lamparray_hw_get_state(ldev, &rr, &gg, &bb, &br);
+ if (ret) {
+ hid_warn(ldev->hdev,
+ "Failed to read LampArray state (%d), using cached brightness %u\n",
+ ret, brightness);
+ return brightness;
+ }
+
+ mutex_lock(&ldev->lock);
+ if (ldev->last_r != rr || ldev->last_g != gg ||
+ ldev->last_b != bb || ldev->last_brightness != br) {
+ ldev->last_r = rr;
+ ldev->last_g = gg;
+ ldev->last_b = bb;
+ ldev->last_brightness = br;
+
+ if (ldev->led_registered && ldev->mc_cdev.subled_info) {
+ ldev->mc_cdev.subled_info[0].brightness = rr;
+ ldev->mc_cdev.subled_info[1].brightness = gg;
+ ldev->mc_cdev.subled_info[2].brightness = bb;
+ }
+ }
+ mutex_unlock(&ldev->lock);
+ return br;
+ }
+ return brightness;
+}
+
+static int lamparray_register_led(struct lamparray_device *ldev)
+{
+ struct device *dev = &ldev->hdev->dev;
+ struct led_classdev *cdev = &ldev->mc_cdev.led_cdev;
+ u8 r_i, g_i, b_i;
+ int ret;
+
+ mutex_lock(&ldev->lock);
+
+ if (ldev->led_registered) {
+ mutex_unlock(&ldev->lock);
+ return 0;
+ }
+
+ if (!cdev->name) {
+ cdev->name =
+ devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev));
+ if (!cdev->name) {
+ mutex_unlock(&ldev->lock);
+ return -ENOMEM;
+ }
+ }
+
+ cdev->max_brightness = 255;
+ cdev->brightness_set_blocking = lamparray_led_brightness_set;
+ cdev->brightness_get = lamparray_led_brightness_get;
+ cdev->brightness = ldev->last_brightness;
+
+ ldev->subleds[0].color_index = LED_COLOR_ID_RED;
+ ldev->subleds[1].color_index = LED_COLOR_ID_GREEN;
+ ldev->subleds[2].color_index = LED_COLOR_ID_BLUE;
+
+ /*
+ * Initialize the color mix (multi_intensity) from the last known HW/init
+ * state so that writing only /brightness scales the expected default color
+ * instead of white.
+ *
+ * If last_brightness is non-zero, treat last_r/g/b as per-channel
+ * brightness and normalize back to intensities (0..255).
+ * If last_brightness is zero, keep last_r/g/b as the intended mix.
+ */
+ if (ldev->last_brightness) {
+ r_i = (u8)min_t(unsigned int, 255,
+ (ldev->last_r * 255u) / ldev->last_brightness);
+ g_i = (u8)min_t(unsigned int, 255,
+ (ldev->last_g * 255u) / ldev->last_brightness);
+ b_i = (u8)min_t(unsigned int, 255,
+ (ldev->last_b * 255u) / ldev->last_brightness);
+ } else {
+ r_i = ldev->last_r;
+ g_i = ldev->last_g;
+ b_i = ldev->last_b;
+ }
+
+ ldev->subleds[0].intensity = r_i;
+ ldev->subleds[1].intensity = g_i;
+ ldev->subleds[2].intensity = b_i;
+
+ ldev->mc_cdev.subled_info = ldev->subleds;
+ ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
+
+ /* Ensure subled_info[].brightness matches intensity + brightness */
+ led_mc_calc_color_components(&ldev->mc_cdev, cdev->brightness);
+
+ ldev->mc_cdev.subled_info = ldev->subleds;
+ ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
+
+ mutex_unlock(&ldev->lock);
+
+ ret = led_classdev_multicolor_register(dev, &ldev->mc_cdev);
+ if (ret)
+ return ret;
+
+ mutex_lock(&ldev->lock);
+ ldev->led_registered = true;
+ mutex_unlock(&ldev->lock);
+
+ return 0;
+}
+
+static void lamparray_unregister_led(struct lamparray_device *ldev)
+{
+ bool was_registered;
+
+ mutex_lock(&ldev->lock);
+ was_registered = ldev->led_registered;
+ ldev->led_registered = false;
+ mutex_unlock(&ldev->lock);
+
+ if (!was_registered)
+ return;
+
+ led_classdev_multicolor_unregister(&ldev->mc_cdev);
+}
+
+/* Sysfs */
+
+static struct lamparray_device *
+lamparray_ldev_from_sysfs_dev(struct device *dev)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+
+ return xa_load(&lamparray_by_hdev, (unsigned long)hdev);
+}
+
+static ssize_t use_leds_uapi_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
+
+ if (!ldev)
+ return -ENODEV;
+
+ return sysfs_emit(buf, "%d\n", ldev->use_leds_uapi);
+}
+
+static ssize_t use_leds_uapi_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
+ int val;
+ int old_val;
+ int ret;
+
+ if (!ldev)
+ return -ENODEV;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val != 0 && val != 1)
+ return -EINVAL;
+
+ mutex_lock(&ldev->lock);
+ old_val = ldev->use_leds_uapi;
+
+ if (val == old_val) {
+ mutex_unlock(&ldev->lock);
+ return count;
+ }
+
+ ldev->use_leds_uapi = val;
+ mutex_unlock(&ldev->lock);
+
+ if (val == 1) {
+ ret = lamparray_register_led(ldev);
+ if (ret) {
+ mutex_lock(&ldev->lock);
+ ldev->use_leds_uapi = old_val;
+ mutex_unlock(&ldev->lock);
+ return ret;
+ }
+ ret = lamparray_restore_state(ldev);
+ if (ret) {
+ hid_err(ldev->hdev, "Could not restore state: %d", ret);
+ return ret;
+ }
+
+ } else {
+ lamparray_unregister_led(ldev);
+ }
+
+ return count;
+}
+static DEVICE_ATTR_RW(use_leds_uapi);
+
+static struct attribute *lamparray_attrs[] = {
+ &dev_attr_use_leds_uapi.attr,
+ NULL,
+};
+
+static const struct attribute_group lamparray_attr_group = {
+ .attrs = lamparray_attrs,
+};
+
+static int lamparray_register_sysfs(struct lamparray_device *ldev)
+{
+ struct device *dev = &ldev->hdev->dev;
+ int ret;
+
+ ret = sysfs_create_group(&dev->kobj, &lamparray_attr_group);
+ if (ret)
+ hid_err(ldev->hdev,
+ "Failed to create lamparray sysfs group: %d\n", ret);
+
+ return ret;
+}
+
+static void lamparray_remove_sysfs(struct lamparray_device *ldev)
+{
+ sysfs_remove_group(&ldev->hdev->dev.kobj, &lamparray_attr_group);
+}
+
+/* Public API */
+
+bool lamparray_is_supported_device(struct hid_device *hdev)
+{
+ unsigned int i;
+
+ hid_dbg(hdev, "lamparray: walking %u collections\n",
+ hdev->maxcollection);
+
+ for (i = 0; i < hdev->maxcollection; i++) {
+ struct hid_collection *col = &hdev->collection[i];
+ u16 page = (col->usage & HID_USAGE_PAGE) >> 16;
+ u16 code = col->usage & HID_USAGE;
+
+ hid_dbg(hdev,
+ "lamparray: collection[%u]: type=%u level=%u usage=0x%08x page=0x%04x code=0x%04x\n",
+ i, col->type, col->level, col->usage, page, code);
+
+ if (col->type == HID_COLLECTION_APPLICATION &&
+ page == HID_LIGHTING_ILLUMINATION_USAGE_PAGE &&
+ code == HID_APPLICATION_COLLECTION_USAGE_TYPE) {
+ return true;
+ }
+ }
+ return false;
+}
+EXPORT_SYMBOL_GPL(lamparray_is_supported_device);
+
+struct lamparray *
+lamparray_register(struct hid_device *hdev,
+ const struct lamparray_init_state *led_init_state)
+{
+ int ret;
+ struct lamparray *la;
+ struct lamparray_device *ldev;
+
+ if (!hdev)
+ return ERR_PTR(-ENODEV);
+
+ la = kzalloc(sizeof(*la), GFP_KERNEL);
+ if (!la)
+ return ERR_PTR(-ENOMEM);
+
+ ldev = &la->ldev;
+
+ mutex_init(&ldev->lock);
+ ldev->hdev = hdev;
+ ldev->quirks = lamparray_lookup_quirks(hdev);
+ ldev->use_leds_uapi = 1;
+ ldev->led_registered = false;
+ if (!led_init_state) {
+ ldev->last_r = 255;
+ ldev->last_g = 255;
+ ldev->last_b = 255;
+ ldev->last_brightness = LED_OFF;
+ } else {
+ ldev->last_r = led_init_state->r;
+ ldev->last_g = led_init_state->g;
+ ldev->last_b = led_init_state->b;
+ ldev->last_brightness = led_init_state->brightness;
+ }
+ ret = lamparray_parse_update_report(ldev);
+ if (ret) {
+ hid_err(hdev, "No LampArray update report found: %d\n", ret);
+ goto err_free;
+ }
+
+ ret = lamparray_read_lamp_count(ldev);
+ if (ret) {
+ hid_err(hdev,
+ "Could not determine LampCount. This device needs a quirk for a fixed LampCount: %d\n",
+ ret);
+ goto err_unregister_led;
+ }
+
+ ret = lamparray_register_led(ldev);
+ if (ret) {
+ hid_warn(hdev, "Failed to register LED UAPI: %d\n", ret);
+ mutex_lock(&ldev->lock);
+ ldev->use_leds_uapi = 0;
+ mutex_unlock(&ldev->lock);
+ }
+
+ ret = xa_err(xa_store(&lamparray_by_hdev, (unsigned long)hdev, ldev,
+ GFP_KERNEL));
+ if (ret)
+ goto err_unregister_led;
+
+ ret = lamparray_register_sysfs(ldev);
+ if (ret)
+ goto err_xa_erase;
+
+ ret = lamparray_hw_set_autonomous(ldev, false);
+ if (ret) {
+ hid_err(hdev, "Could not disable autonomous mode: %d", ret);
+ goto err_remove_sysfs;
+ }
+
+ hid_info(hdev, "LampArray device registered\n");
+
+ ret = lamparray_restore_state(ldev);
+ if (ret) {
+ hid_err(hdev, "Failed to set standard state: %d", ret);
+ goto err_remove_sysfs;
+ }
+ return la;
+
+err_remove_sysfs:
+ lamparray_remove_sysfs(ldev);
+err_xa_erase:
+ xa_erase(&lamparray_by_hdev, (unsigned long)hdev);
+err_unregister_led:
+ lamparray_unregister_led(ldev);
+err_free:
+ kfree(la);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(lamparray_register);
+
+void lamparray_unregister(struct lamparray *la)
+{
+ struct lamparray_device *ldev;
+
+ if (!la)
+ return;
+
+ ldev = &la->ldev;
+
+ lamparray_unregister_led(ldev);
+ lamparray_remove_sysfs(ldev);
+ xa_erase(&lamparray_by_hdev, (unsigned long)ldev->hdev);
+
+ kfree(la);
+}
+EXPORT_SYMBOL_GPL(lamparray_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HID LampArray helper module (single-zone RGB)");
diff --git a/drivers/hid/hid-lamparray.h b/drivers/hid/hid-lamparray.h
new file mode 100644
index 000000000000..ac3edd366a5b
--- /dev/null
+++ b/drivers/hid/hid-lamparray.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _HID_LAMPARRAY_H
+#define _HID_LAMPARRAY_H
+
+#include <linux/types.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+
+struct hid_device;
+struct lamparray;
+
+/*
+ * Optional initial LED state for lamparray_register().
+ * Used to define the initial state of a LampArray's LEDs.
+ */
+struct lamparray_init_state {
+ u8 r;
+ u8 g;
+ u8 b;
+ enum led_brightness brightness;
+};
+
+#if IS_ENABLED(CONFIG_HID_LAMPARRAY)
+
+/**
+ * lamparray_is_supported_device() - check whether a HID device supports LampArray
+ * @hdev: HID device to inspect
+ *
+ * Check whether the given HID device exposes a Lighting/LampArray application
+ * collection as defined by the HID Lighting specification.
+ *
+ * This helper can be used by HID drivers to determine whether LampArray
+ * functionality should be enabled for a device.
+ *
+ * Return: %true if LampArray support is detected, %false otherwise.
+ */
+bool lamparray_is_supported_device(struct hid_device *hdev);
+
+/**
+ * lamparray_register() - initialize LampArray support for a HID device
+ * @hdev: HID device
+ * @led_init_state: Optional LED state at init specification
+ *
+ * Allocate and initialize internal LampArray state for the given HID device.
+ * The function parses required HID reports and fields and registers the
+ * associated miscdevice and sysfs attributes.
+ *
+ * If enabled, a multicolor LED class device is also registered to expose the
+ * LampArray functionality via the LED subsystem. If specified, the desired
+ * initial LED state is applied. If led_init_state is NULL, a default state is
+ * applied.
+ *
+ * Return: pointer to a LampArray handle on success, or ERR_PTR() on failure.
+ */
+struct lamparray *lamparray_register(struct hid_device *hdev,
+ const struct lamparray_init_state *led_init_state);
+
+/**
+ * lamparray_unregister() - tear down LampArray support
+ * @la: LampArray handle returned by lamparray_register()
+ *
+ * Remove all resources associated with a LampArray instance.
+ *
+ * This unregisters the LED class device (if present), removes the miscdevice
+ * and sysfs interfaces and frees all internal state associated with @la.
+ */
+void lamparray_unregister(struct lamparray *la);
+
+#else /* !CONFIG_HID_LAMPARRAY */
+
+static inline bool lamparray_is_supported_device(struct hid_device *hdev)
+{
+ return false;
+}
+
+static inline struct lamparray *
+lamparray_register(struct hid_device *hdev,
+ const struct lamparray_init_state *led_init_state)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void lamparray_unregister(struct lamparray *la)
+{
+}
+
+#endif /* CONFIG_HID_LAMPARRAY */
+
+#endif /* _HID_LAMPARRAY_H */
--
2.43.0
^ permalink raw reply related
* [PATCH] HID: sony: add support for Rock Band 2 instruments
From: appsforartists @ 2026-02-20 16:50 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Sanjay Govind, Rosalie Wanders,
Antheas Kapenekakis, Vicki Pfau, Nícolas F . R . A . Prado,
Brenton Simpson
From: Brenton Simpson <appsforartists@google.com>
Rock Band 2 for the Nintendo Wii and for the Sony PlayStation 3 use the
same mapping as later games:
Green: SOUTH (A)
Red: EAST (B)
Yellow: NORTH (Y)
Blue: WEST (X)
Orange/pedal: TL (L1)
Solo flag: TL2 (L2)
Tilt: TR (R1)
Pad flag: THUMBL (L3)
Instrument button: MODE (Steam/Xbox)
Whammy bar: ABS_Z (Axis 4)
Effects switch: Z (Axis 5)
As documented at https://github.com/TheNathannator/PlasticBand/blob/main/Docs/.
The guitar and drums both use the same mapping. Tested using the Wii
versions of the instruments.
Signed-off-by: Brenton Simpson <appsforartists@google.com>
---
drivers/hid/hid-ids.h | 8 +++++++-
drivers/hid/hid-sony.c | 45 +++++++++++++++++++++++++++++-------------
2 files changed, 38 insertions(+), 15 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 3e299a30dcde..644d3c4df144 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -664,6 +664,10 @@
#define USB_DEVICE_ID_UGCI_FLYING 0x0020
#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
+#define USB_VENDOR_ID_HARMONIX 0x1bad
+#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE 0x3010
+#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE 0x3110
+
#define USB_VENDOR_ID_HP 0x03f0
#define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
@@ -1299,7 +1303,9 @@
#define USB_VENDOR_ID_SONY_RHYTHM 0x12ba
#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b
-#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100
+#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE 0x0100
+#define USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE 0x0200
+#define USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE 0x0210
#define USB_VENDOR_ID_SINO_LITE 0x1345
#define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index a89af14e4acc..f6975f6ae882 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -62,9 +62,10 @@
#define GH_GUITAR_CONTROLLER BIT(14)
#define GHL_GUITAR_PS3WIIU BIT(15)
#define GHL_GUITAR_PS4 BIT(16)
-#define RB4_GUITAR_PS4_USB BIT(17)
-#define RB4_GUITAR_PS4_BT BIT(18)
-#define RB4_GUITAR_PS5 BIT(19)
+#define RB2_INSTRUMENT BIT(17)
+#define RB4_GUITAR_PS4_USB BIT(18)
+#define RB4_GUITAR_PS4_BT BIT(19)
+#define RB4_GUITAR_PS5 BIT(20)
#define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
#define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
@@ -422,12 +423,12 @@ static const unsigned int sixaxis_keymap[] = {
[0x11] = BTN_MODE, /* PS */
};
-static const unsigned int rb4_absmap[] = {
+static const unsigned int rb_absmap[] = {
[0x30] = ABS_X,
[0x31] = ABS_Y,
};
-static const unsigned int rb4_keymap[] = {
+static const unsigned int rb_keymap[] = {
[0x1] = BTN_WEST, /* Square */
[0x2] = BTN_SOUTH, /* Cross */
[0x3] = BTN_EAST, /* Circle */
@@ -625,17 +626,17 @@ static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
-static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
+static int rb_instrument_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
unsigned int key = usage->hid & HID_USAGE;
- if (key >= ARRAY_SIZE(rb4_keymap))
+ if (key >= ARRAY_SIZE(rb_keymap))
return 0;
- key = rb4_keymap[key];
+ key = rb_keymap[key];
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
return 1;
} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
@@ -645,10 +646,10 @@ static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
if (usage->hid == HID_GD_HATSWITCH)
return 0;
- if (abs >= ARRAY_SIZE(rb4_absmap))
+ if (abs >= ARRAY_SIZE(rb_absmap))
return 0;
- abs = rb4_absmap[abs];
+ abs = rb_absmap[abs];
hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
return 1;
}
@@ -1101,11 +1102,14 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
if (sc->quirks & GH_GUITAR_CONTROLLER)
return gh_guitar_mapping(hdev, hi, field, usage, bit, max);
+ if (sc->quirks & RB2_INSTRUMENT)
+ return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
+
if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
- return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
+ return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
if (sc->quirks & RB4_GUITAR_PS5)
- return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
+ return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
/* Let hid-core decide for the others */
return 0;
@@ -2369,12 +2373,25 @@ static const struct hid_device_id sony_devices[] = {
/* Guitar Hero PC Guitar Dongle */
{ HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE),
.driver_data = GH_GUITAR_CONTROLLER },
- /* Guitar Hero PS3 World Tour Guitar Dongle */
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE),
+ /* Guitar Hero World Tour PS3 Guitar Dongle */
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE),
.driver_data = GH_GUITAR_CONTROLLER },
/* Guitar Hero Live PS4 guitar dongles */
{ HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE),
.driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER },
+ /* Rock Band 2 instruments
+ * Nintendo Wii instruments are included in `hid-sony` because `hid-nintendo`
+ * is for the newer Nintendo Switch, and the Wii instruments use the same
+ * protocol as their Sony PlayStation 3 cousins.
+ */
+ { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE),
+ .driver_data = RB2_INSTRUMENT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE),
+ .driver_data = RB2_INSTRUMENT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE),
+ .driver_data = RB2_INSTRUMENT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE),
+ .driver_data = RB2_INSTRUMENT },
/* Rock Band 4 PS4 guitars */
{ HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER),
.driver_data = RB4_GUITAR_PS4_USB },
--
2.53.0.414.gf7e9f6c205-goog
^ permalink raw reply related
* Re: Subject: [PATCH] Input: atkbd - validate scancode in firmware keymap entries
From: Dmitry Torokhov @ 2026-02-21 2:33 UTC (permalink / raw)
To: Ariel Silver; +Cc: linux-input, linux-kernel
In-Reply-To: <CACKMdfnPQoBnEhP0oCe1gXCYzJYyWBJsgHCHiMOn37XYYLAkVg@mail.gmail.com>
Hi Ariel,
On Fri, Feb 20, 2026 at 10:44:28AM +0200, Ariel Silver wrote:
> The SCANCODE() macro extracts a 16-bit value (0..65535) from firmware
> device property data, but atkbd_get_keymap_from_fwnode() uses it
> directly to index atkbd->keycode[], which only has ATKBD_KEYMAP_SIZE
> (512) elements. A firmware-supplied scancode >= 512 causes a heap
> out-of-bounds write that can corrupt adjacent struct atkbd fields and
> neighboring slab objects.
>
> Add a bounds check that rejects the entire firmware keymap if any entry
> contains an out-of-range scancode, consistent with the validation
> performed by matrix_keypad_parse_keymap() in drivers/input/matrix-keymap.c
> for the same "linux,keymap" property format. When rejected, the driver
> falls back to the default keycode table.
>
> Fixes: 9d17ad2369dc ("Input: atkbd - receive and use physcode->keycode
> mapping from FW")
> Reported-by: Ariel Silver <arielsilver77@gmail.com>
> Signed-off-by: Ariel Silver <arielsilver77@gmail.com>
> Cc: stable@vger.kernel.org
Was it observed on real hardware or this is theoretical? I do not think
this needs to go to stable, but otherwise I will apply it.
> ---
> drivers/input/keyboard/atkbd.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
> index XXXXXXX..XXXXXXX 100644
> --- a/drivers/input/keyboard/atkbd.c
> +++ b/drivers/input/keyboard/atkbd.c
> @@ -1111,6 +1111,13 @@ static int atkbd_get_keymap_from_fwnode(struct
> atkbd *atkbd)
> for (i = 0; i < n; i++) {
> scancode = SCANCODE(ptr[i]);
> keycode = KEYCODE(ptr[i]);
> + if (scancode >= ATKBD_KEYMAP_SIZE) {
> + dev_warn(dev,
> + "invalid scancode 0x%x in FW keymap entry %d\n",
> + scancode, i);
> + kfree(ptr);
> + return -EINVAL;
> + }
> atkbd->keycode[scancode] = keycode;
> }
I think you pasted this to gmail web interface which resulted in line
wrapping and messed up indentation. I untangled it but if you plan on
sending more kernel patches please try to not use web interface. Making
"git send-email" workig might be worth it, or look into "b4 send" with a
web endpoint.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] HID: sony: add support for Rock Band 2 instruments
From: Rosalie @ 2026-02-21 2:42 UTC (permalink / raw)
To: appsforartists, Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Sanjay Govind, Antheas Kapenekakis,
Vicki Pfau, Nícolas F . R . A . Prado
In-Reply-To: <20260220165047.2844568-1-appsforartists@google.com>
On 20/02/2026 17:50, appsforartists@google.com wrote:
> From: Brenton Simpson <appsforartists@google.com>
>
> Rock Band 2 for the Nintendo Wii and for the Sony PlayStation 3 use the
> same mapping as later games:
>
> Green: SOUTH (A)
> Red: EAST (B)
> Yellow: NORTH (Y)
> Blue: WEST (X)
> Orange/pedal: TL (L1)
> Solo flag: TL2 (L2)
> Tilt: TR (R1)
> Pad flag: THUMBL (L3)
> Instrument button: MODE (Steam/Xbox)
> Whammy bar: ABS_Z (Axis 4)
> Effects switch: Z (Axis 5)
>
> As documented at https://github.com/TheNathannator/PlasticBand/blob/main/Docs/.
>
> The guitar and drums both use the same mapping. Tested using the Wii
> versions of the instruments.
>
> Signed-off-by: Brenton Simpson <appsforartists@google.com>
> ---
> drivers/hid/hid-ids.h | 8 +++++++-
> drivers/hid/hid-sony.c | 45 +++++++++++++++++++++++++++++-------------
> 2 files changed, 38 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 3e299a30dcde..644d3c4df144 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -664,6 +664,10 @@
> #define USB_DEVICE_ID_UGCI_FLYING 0x0020
> #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
>
> +#define USB_VENDOR_ID_HARMONIX 0x1bad
> +#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE 0x3010
> +#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE 0x3110
> +
> #define USB_VENDOR_ID_HP 0x03f0
> #define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
> #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
> @@ -1299,7 +1303,9 @@
>
> #define USB_VENDOR_ID_SONY_RHYTHM 0x12ba
> #define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b
> -#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100
> +#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE 0x0100
> +#define USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE 0x0200
> +#define USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE 0x0210
>
> #define USB_VENDOR_ID_SINO_LITE 0x1345
> #define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008
> diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
> index a89af14e4acc..f6975f6ae882 100644
> --- a/drivers/hid/hid-sony.c
> +++ b/drivers/hid/hid-sony.c
> @@ -62,9 +62,10 @@
> #define GH_GUITAR_CONTROLLER BIT(14)
> #define GHL_GUITAR_PS3WIIU BIT(15)
> #define GHL_GUITAR_PS4 BIT(16)
> -#define RB4_GUITAR_PS4_USB BIT(17)
> -#define RB4_GUITAR_PS4_BT BIT(18)
> -#define RB4_GUITAR_PS5 BIT(19)
> +#define RB2_INSTRUMENT BIT(17)
> +#define RB4_GUITAR_PS4_USB BIT(18)
> +#define RB4_GUITAR_PS4_BT BIT(19)
> +#define RB4_GUITAR_PS5 BIT(20)
>
> #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
> #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
> @@ -422,12 +423,12 @@ static const unsigned int sixaxis_keymap[] = {
> [0x11] = BTN_MODE, /* PS */
> };
>
> -static const unsigned int rb4_absmap[] = {
> +static const unsigned int rb_absmap[] = {
> [0x30] = ABS_X,
> [0x31] = ABS_Y,
> };
Is the Whammy bar mapped correctly to ABS_Z with this patch, or do the
instruments not use the rb_absmap array, because the array does not
contain ABS_Z?
If it doesn't need the rb_absmap array, maybe it'd be better to make a
separate rb2_instrument_mapping function instead of re-using the
rb4_guitar_mapping function.
>
> -static const unsigned int rb4_keymap[] = {
> +static const unsigned int rb_keymap[] = {
> [0x1] = BTN_WEST, /* Square */
> [0x2] = BTN_SOUTH, /* Cross */
> [0x3] = BTN_EAST, /* Circle */
> @@ -625,17 +626,17 @@ static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> return 0;
> }
>
> -static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> +static int rb_instrument_mapping(struct hid_device *hdev, struct hid_input *hi,
> struct hid_field *field, struct hid_usage *usage,
> unsigned long **bit, int *max)
> {
> if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
> unsigned int key = usage->hid & HID_USAGE;
>
> - if (key >= ARRAY_SIZE(rb4_keymap))
> + if (key >= ARRAY_SIZE(rb_keymap))
> return 0;
>
> - key = rb4_keymap[key];
> + key = rb_keymap[key];
> hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
> return 1;
> } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
> @@ -645,10 +646,10 @@ static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> if (usage->hid == HID_GD_HATSWITCH)
> return 0;
>
> - if (abs >= ARRAY_SIZE(rb4_absmap))
> + if (abs >= ARRAY_SIZE(rb_absmap))
> return 0;
>
> - abs = rb4_absmap[abs];
> + abs = rb_absmap[abs];
> hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
> return 1;
> }
> @@ -1101,11 +1102,14 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
> if (sc->quirks & GH_GUITAR_CONTROLLER)
> return gh_guitar_mapping(hdev, hi, field, usage, bit, max);
>
> + if (sc->quirks & RB2_INSTRUMENT)
> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
> +
> if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
> - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
>
> if (sc->quirks & RB4_GUITAR_PS5)
> - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
>
> /* Let hid-core decide for the others */
> return 0;
> @@ -2369,12 +2373,25 @@ static const struct hid_device_id sony_devices[] = {
> /* Guitar Hero PC Guitar Dongle */
> { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE),
> .driver_data = GH_GUITAR_CONTROLLER },
> - /* Guitar Hero PS3 World Tour Guitar Dongle */
> - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE),
> + /* Guitar Hero World Tour PS3 Guitar Dongle */
> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE),
> .driver_data = GH_GUITAR_CONTROLLER },
> /* Guitar Hero Live PS4 guitar dongles */
> { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE),
> .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER },
> + /* Rock Band 2 instruments
> + * Nintendo Wii instruments are included in `hid-sony` because `hid-nintendo`
> + * is for the newer Nintendo Switch, and the Wii instruments use the same
> + * protocol as their Sony PlayStation 3 cousins.
> + */
> + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE),
> + .driver_data = RB2_INSTRUMENT },
> + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE),
> + .driver_data = RB2_INSTRUMENT },
> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE),
> + .driver_data = RB2_INSTRUMENT },
> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE),
> + .driver_data = RB2_INSTRUMENT },
> /* Rock Band 4 PS4 guitars */
> { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER),
> .driver_data = RB4_GUITAR_PS4_USB },
^ permalink raw reply
* Re: [PATCH v3] HID: generic: add LampArray support via hid-lamparray helper
From: kernel test robot @ 2026-02-21 4:22 UTC (permalink / raw)
To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
Cc: oe-kbuild-all, wse, Tim Guttzeit, linux-kernel, linux-input
In-Reply-To: <20260220135309.151487-1-tgu@tuxedocomputers.com>
Hi Tim,
kernel test robot noticed the following build errors:
[auto build test ERROR on hid/for-next]
[also build test ERROR on linus/master v6.19 next-20260220]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Tim-Guttzeit/HID-generic-add-LampArray-support-via-hid-lamparray-helper/20260220-215620
base: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link: https://lore.kernel.org/r/20260220135309.151487-1-tgu%40tuxedocomputers.com
patch subject: [PATCH v3] HID: generic: add LampArray support via hid-lamparray helper
config: arm64-randconfig-003-20260221 (https://download.01.org/0day-ci/archive/20260221/202602211232.5VR8uvU5-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260221/202602211232.5VR8uvU5-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602211232.5VR8uvU5-lkp@intel.com/
All errors (new ones prefixed by >>):
aarch64-linux-ld: Unexpected GOT/PLT entries detected!
aarch64-linux-ld: Unexpected run-time procedure linkages detected!
aarch64-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_led_brightness_get':
>> hid-lamparray.c:(.text+0x1fc): undefined reference to `hid_hw_request'
aarch64-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_hw_set_state':
hid-lamparray.c:(.text+0x3e0): undefined reference to `hid_hw_request'
aarch64-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_led_brightness_set':
hid-lamparray.c:(.text+0x5f0): undefined reference to `led_mc_calc_color_components'
aarch64-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_register_led':
hid-lamparray.c:(.text+0x804): undefined reference to `led_mc_calc_color_components'
>> aarch64-linux-ld: hid-lamparray.c:(.text+0x824): undefined reference to `led_classdev_multicolor_register_ext'
aarch64-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_unregister_led':
hid-lamparray.c:(.text+0x948): undefined reference to `led_classdev_multicolor_unregister'
aarch64-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_register':
hid-lamparray.c:(.text+0xec4): undefined reference to `hid_hw_request'
>> aarch64-linux-ld: hid-lamparray.c:(.text+0x100c): undefined reference to `hid_hw_request'
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH] HID: sony: add support for Rock Band 2 instruments
From: Brenton Simpson @ 2026-02-21 5:04 UTC (permalink / raw)
To: Rosalie
Cc: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel,
Sanjay Govind, Antheas Kapenekakis, Vicki Pfau,
Nícolas F . R . A . Prado
In-Reply-To: <f0d2a36a-5320-43c9-909b-3c41257c5e48@mailbox.org>
On Fri, Feb 20, 2026 at 9:42 PM Rosalie <rosalie@mailbox.org> wrote:
> On 20/02/2026 17:50, Brenton Simpson <appsforartists@google.com> wrote:
>
> > Rock Band 2 for the Nintendo Wii and for the Sony PlayStation 3 use the
> > same mapping as later games:
> >
> > Green: SOUTH (A)
> > Red: EAST (B)
> > Yellow: NORTH (Y)
> > Blue: WEST (X)
> > Orange/pedal: TL (L1)
> > Solo flag: TL2 (L2)
> > Tilt: TR (R1)
> > Pad flag: THUMBL (L3)
> > Instrument button: MODE (Steam/Xbox)
> > Whammy bar: ABS_Z (Axis 4)
> > Effects switch: Z (Axis 5)
> >
> > As documented at https://github.com/TheNathannator/PlasticBand/blob/main/Docs/.
> >
> > The guitar and drums both use the same mapping. Tested using the Wii
> > versions of the instruments.
> >
> > Signed-off-by: Brenton Simpson <appsforartists@google.com>
> > ---
> > drivers/hid/hid-ids.h | 8 +++++++-
> > drivers/hid/hid-sony.c | 45 +++++++++++++++++++++++++++++-------------
> > 2 files changed, 38 insertions(+), 15 deletions(-)
> >
The indentation also seemed inconsistent in these two hid- files. I
tried my best to follow the local style of the parts I edited. Happy
to do a style pass in a separate commit if desired.
> > diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> > index 3e299a30dcde..644d3c4df144 100644
> > --- a/drivers/hid/hid-ids.h
> > +++ b/drivers/hid/hid-ids.h
> > @@ -664,6 +664,10 @@
> > #define USB_DEVICE_ID_UGCI_FLYING 0x0020
> > #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
> >
> > +#define USB_VENDOR_ID_HARMONIX 0x1bad
> > +#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE 0x3010
> > +#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE 0x3110
> > +
> > #define USB_VENDOR_ID_HP 0x03f0
> > #define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
> > #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
> > @@ -1299,7 +1303,9 @@
> >
> > #define USB_VENDOR_ID_SONY_RHYTHM 0x12ba
> > #define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b
> > -#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100
> > +#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE 0x0100
> > +#define USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE 0x0200
> > +#define USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE 0x0210
> >
> > #define USB_VENDOR_ID_SINO_LITE 0x1345
> > #define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008
> > diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
> > index a89af14e4acc..f6975f6ae882 100644
> > --- a/drivers/hid/hid-sony.c
> > +++ b/drivers/hid/hid-sony.c
> > @@ -62,9 +62,10 @@
> > #define GH_GUITAR_CONTROLLER BIT(14)
> > #define GHL_GUITAR_PS3WIIU BIT(15)
> > #define GHL_GUITAR_PS4 BIT(16)
> > -#define RB4_GUITAR_PS4_USB BIT(17)
> > -#define RB4_GUITAR_PS4_BT BIT(18)
> > -#define RB4_GUITAR_PS5 BIT(19)
> > +#define RB2_INSTRUMENT BIT(17)
> > +#define RB4_GUITAR_PS4_USB BIT(18)
> > +#define RB4_GUITAR_PS4_BT BIT(19)
> > +#define RB4_GUITAR_PS5 BIT(20)
> >
> > #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
> > #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
> > @@ -422,12 +423,12 @@ static const unsigned int sixaxis_keymap[] = {
> > [0x11] = BTN_MODE, /* PS */
> > };
> >
> > -static const unsigned int rb4_absmap[] = {
> > +static const unsigned int rb_absmap[] = {
> > [0x30] = ABS_X,
> > [0x31] = ABS_Y,
> > };
>
>
> Is the Whammy bar mapped correctly to ABS_Z with this patch, or do the
> instruments not use the rb_absmap array, because the array does not
> contain ABS_Z?
>
> If it doesn't need the rb_absmap array, maybe it'd be better to make a
> separate rb2_instrument_mapping function instead of re-using the
> rb4_guitar_mapping function.
I renamed the absmap to be consistent, but unlike the CRKD guitars,
the Harmonix guitars don't have a joystick, so they ignore the absmap.
It's the keymap they need.
> > -static const unsigned int rb4_keymap[] = {
> > +static const unsigned int rb_keymap[] = {
> > [0x1] = BTN_WEST, /* Square */
> > [0x2] = BTN_SOUTH, /* Cross */
> > [0x3] = BTN_EAST, /* Circle */
> > @@ -625,17 +626,17 @@ static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> > return 0;
> > }
> >
> > -static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> > +static int rb_instrument_mapping(struct hid_device *hdev, struct hid_input *hi,
> > struct hid_field *field, struct hid_usage *usage,
> > unsigned long **bit, int *max)
> > {
Without the patch, the red button is not usable without a VDF:
0300037cad1b00001030000001010000,Licensed by Nintendo of America
Harmonix Guitar Controller for Nintendo
Wii,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,rightx:a2,righty:a3,platform:Linux
So all registered Rock Band instruments can use this
rb_instrument_mapping function. The ones that send the axes at weird
locations are augmented by the existing rb4_parse functions; the older
ones send them at the standard locations.
> > if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
> > unsigned int key = usage->hid & HID_USAGE;
> >
> > - if (key >= ARRAY_SIZE(rb4_keymap))
> > + if (key >= ARRAY_SIZE(rb_keymap))
> > return 0;
> >
> > - key = rb4_keymap[key];
> > + key = rb_keymap[key];
> > hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
> > return 1;
> > } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
> > @@ -645,10 +646,10 @@ static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> > if (usage->hid == HID_GD_HATSWITCH)
> > return 0;
> >
Like the D-pad, the whammy and the effects switch are correctly mapped
by default. I tested all the buttons on both the guitar and the
drums. With the patch, they're correct.
> > - if (abs >= ARRAY_SIZE(rb4_absmap))
> > + if (abs >= ARRAY_SIZE(rb_absmap))
> > return 0;
> >
> > - abs = rb4_absmap[abs];
> > + abs = rb_absmap[abs];
> > hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
> > return 1;
> > }
> > @@ -1101,11 +1102,14 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
> > if (sc->quirks & GH_GUITAR_CONTROLLER)
> > return gh_guitar_mapping(hdev, hi, field, usage, bit, max);
> >
> > + if (sc->quirks & RB2_INSTRUMENT)
> > + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
> > +
> > if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
> > - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
> > + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
> >
> > if (sc->quirks & RB4_GUITAR_PS5)
> > - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
> > + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
> >
> > /* Let hid-core decide for the others */
> > return 0;
> > @@ -2369,12 +2373,25 @@ static const struct hid_device_id sony_devices[] = {
> > /* Guitar Hero PC Guitar Dongle */
> > { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE),
> > .driver_data = GH_GUITAR_CONTROLLER },
> > - /* Guitar Hero PS3 World Tour Guitar Dongle */
> > - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE),
> > + /* Guitar Hero World Tour PS3 Guitar Dongle */
> > + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE),
> > .driver_data = GH_GUITAR_CONTROLLER },
> > /* Guitar Hero Live PS4 guitar dongles */
> > { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE),
> > .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER },
> > + /* Rock Band 2 instruments
> > + * Nintendo Wii instruments are included in `hid-sony` because `hid-nintendo`
> > + * is for the newer Nintendo Switch, and the Wii instruments use the same
> > + * protocol as their Sony PlayStation 3 cousins.
> > + */
> > + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE),
> > + .driver_data = RB2_INSTRUMENT },
> > + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE),
> > + .driver_data = RB2_INSTRUMENT },
> > + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE),
> > + .driver_data = RB2_INSTRUMENT },
> > + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE),
> > + .driver_data = RB2_INSTRUMENT },
> > /* Rock Band 4 PS4 guitars */
> > { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER),
> > .driver_data = RB4_GUITAR_PS4_USB },
Some of the Rock Band 3 instruments are outliers that try to merge
real stringed instruments with plastic ones, so I left those out of
scope for this commit. The instruments from other games (and the
common ones from Rock Band 3) should be compatible with
rb_instrument_mapping.
^ permalink raw reply
* Re: [PATCH] HID: sony: add support for Rock Band 2 instruments
From: Rosalie @ 2026-02-21 5:26 UTC (permalink / raw)
To: Brenton Simpson
Cc: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel,
Sanjay Govind, Antheas Kapenekakis, Vicki Pfau,
Nícolas F . R . A . Prado
In-Reply-To: <CAAL3-=-Cbi+xL2wXKG7vsjMgh8iX8chA+rzkXk_R80cpQF5vNw@mail.gmail.com>
On 2/21/26 06:04, Brenton Simpson wrote:
> On Fri, Feb 20, 2026 at 9:42 PM Rosalie <rosalie@mailbox.org> wrote:
>
>> On 20/02/2026 17:50, Brenton Simpson <appsforartists@google.com> wrote:
>>
>>> Rock Band 2 for the Nintendo Wii and for the Sony PlayStation 3 use the
>>> same mapping as later games:
>>>
>>> Green: SOUTH (A)
>>> Red: EAST (B)
>>> Yellow: NORTH (Y)
>>> Blue: WEST (X)
>>> Orange/pedal: TL (L1)
>>> Solo flag: TL2 (L2)
>>> Tilt: TR (R1)
>>> Pad flag: THUMBL (L3)
>>> Instrument button: MODE (Steam/Xbox)
>>> Whammy bar: ABS_Z (Axis 4)
>>> Effects switch: Z (Axis 5)
>>>
>>> As documented at https://github.com/TheNathannator/PlasticBand/blob/main/Docs/.
>>>
>>> The guitar and drums both use the same mapping. Tested using the Wii
>>> versions of the instruments.
>>>
>>> Signed-off-by: Brenton Simpson <appsforartists@google.com>
>>> ---
>>> drivers/hid/hid-ids.h | 8 +++++++-
>>> drivers/hid/hid-sony.c | 45 +++++++++++++++++++++++++++++-------------
>>> 2 files changed, 38 insertions(+), 15 deletions(-)
>>>
>
> The indentation also seemed inconsistent in these two hid- files. I
> tried my best to follow the local style of the parts I edited. Happy
> to do a style pass in a separate commit if desired.
>
You can use the ./scripts/checkpatch.pl script to check for any issues
with regards to style and indentation.
>>> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
>>> index 3e299a30dcde..644d3c4df144 100644
>>> --- a/drivers/hid/hid-ids.h
>>> +++ b/drivers/hid/hid-ids.h
>>> @@ -664,6 +664,10 @@
>>> #define USB_DEVICE_ID_UGCI_FLYING 0x0020
>>> #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
>>>
>>> +#define USB_VENDOR_ID_HARMONIX 0x1bad
>>> +#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE 0x3010
>>> +#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE 0x3110
>>> +
>>> #define USB_VENDOR_ID_HP 0x03f0
>>> #define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
>>> #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
>>> @@ -1299,7 +1303,9 @@
>>>
>>> #define USB_VENDOR_ID_SONY_RHYTHM 0x12ba
>>> #define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b
>>> -#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100
>>> +#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE 0x0100
>>> +#define USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE 0x0200
>>> +#define USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE 0x0210
>>>
>>> #define USB_VENDOR_ID_SINO_LITE 0x1345
>>> #define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008
>>> diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
>>> index a89af14e4acc..f6975f6ae882 100644
>>> --- a/drivers/hid/hid-sony.c
>>> +++ b/drivers/hid/hid-sony.c
>>> @@ -62,9 +62,10 @@
>>> #define GH_GUITAR_CONTROLLER BIT(14)
>>> #define GHL_GUITAR_PS3WIIU BIT(15)
>>> #define GHL_GUITAR_PS4 BIT(16)
>>> -#define RB4_GUITAR_PS4_USB BIT(17)
>>> -#define RB4_GUITAR_PS4_BT BIT(18)
>>> -#define RB4_GUITAR_PS5 BIT(19)
>>> +#define RB2_INSTRUMENT BIT(17)
>>> +#define RB4_GUITAR_PS4_USB BIT(18)
>>> +#define RB4_GUITAR_PS4_BT BIT(19)
>>> +#define RB4_GUITAR_PS5 BIT(20)
>>>
>>> #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
>>> #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
>>> @@ -422,12 +423,12 @@ static const unsigned int sixaxis_keymap[] = {
>>> [0x11] = BTN_MODE, /* PS */
>>> };
>>>
>>> -static const unsigned int rb4_absmap[] = {
>>> +static const unsigned int rb_absmap[] = {
>>> [0x30] = ABS_X,
>>> [0x31] = ABS_Y,
>>> };
>>
>>
>> Is the Whammy bar mapped correctly to ABS_Z with this patch, or do the
>> instruments not use the rb_absmap array, because the array does not
>> contain ABS_Z?
>>
>> If it doesn't need the rb_absmap array, maybe it'd be better to make a
>> separate rb2_instrument_mapping function instead of re-using the
>> rb4_guitar_mapping function.
>
> I renamed the absmap to be consistent, but unlike the CRKD guitars,
> the Harmonix guitars don't have a joystick, so they ignore the absmap.
> It's the keymap they need.
>
>>> -static const unsigned int rb4_keymap[] = {
>>> +static const unsigned int rb_keymap[] = {
>>> [0x1] = BTN_WEST, /* Square */
>>> [0x2] = BTN_SOUTH, /* Cross */
>>> [0x3] = BTN_EAST, /* Circle */
>>> @@ -625,17 +626,17 @@ static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
>>> return 0;
>>> }
>>>
>>> -static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
>>> +static int rb_instrument_mapping(struct hid_device *hdev, struct hid_input *hi,
>>> struct hid_field *field, struct hid_usage *usage,
>>> unsigned long **bit, int *max)
>>> {
>
> Without the patch, the red button is not usable without a VDF:
>
> 0300037cad1b00001030000001010000,Licensed by Nintendo of America
> Harmonix Guitar Controller for Nintendo
> Wii,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,rightx:a2,righty:a3,platform:Linux
>
Is everything mapped correctly except the red button without this patch?
If so, maybe a simplified rb_keymap would be more clear code wise,
alongside my suggestions below.
> So all registered Rock Band instruments can use this
> rb_instrument_mapping function. The ones that send the axes at weird
> locations are augmented by the existing rb4_parse functions; the older
> ones send them at the standard locations.
>
I think you misunderstood my initial reply, if the rb_absmap array isn't
required then it's rather confusing to use the same mapping function for
the wii/ps3 instruments, because when reading the code it implies that
it'll be used, I'd suggest creating a new mapping function named
rb2_instrument_mapping which uses a (maybe simplified, see above)
rb_keymap array but doesn't use the rb_absmap array, to clearly signal
to code readers that it's unused for those instruments.
My suggestion would also remove the need to rename the rb4_absmap
variable name.
>>> if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
>>> unsigned int key = usage->hid & HID_USAGE;
>>>
>>> - if (key >= ARRAY_SIZE(rb4_keymap))
>>> + if (key >= ARRAY_SIZE(rb_keymap))
>>> return 0;
>>>
>>> - key = rb4_keymap[key];
>>> + key = rb_keymap[key];
>>> hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
>>> return 1;
>>> } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
>>> @@ -645,10 +646,10 @@ static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
>>> if (usage->hid == HID_GD_HATSWITCH)
>>> return 0;
>>>
>
> Like the D-pad, the whammy and the effects switch are correctly mapped
> by default. I tested all the buttons on both the guitar and the
> drums. With the patch, they're correct.
>
>>> - if (abs >= ARRAY_SIZE(rb4_absmap))
>>> + if (abs >= ARRAY_SIZE(rb_absmap))
>>> return 0;
>>>
>>> - abs = rb4_absmap[abs];
>>> + abs = rb_absmap[abs];
>>> hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
>>> return 1;
>>> }
>>> @@ -1101,11 +1102,14 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
>>> if (sc->quirks & GH_GUITAR_CONTROLLER)
>>> return gh_guitar_mapping(hdev, hi, field, usage, bit, max);
>>>
>>> + if (sc->quirks & RB2_INSTRUMENT)
>>> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
>>> +
>>> if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
>>> - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
>>> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
>>>
>>> if (sc->quirks & RB4_GUITAR_PS5)
>>> - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
>>> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
>>>
>>> /* Let hid-core decide for the others */
>>> return 0;
>>> @@ -2369,12 +2373,25 @@ static const struct hid_device_id sony_devices[] = {
>>> /* Guitar Hero PC Guitar Dongle */
>>> { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE),
>>> .driver_data = GH_GUITAR_CONTROLLER },
>>> - /* Guitar Hero PS3 World Tour Guitar Dongle */
>>> - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE),
>>> + /* Guitar Hero World Tour PS3 Guitar Dongle */
>>> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE),
>>> .driver_data = GH_GUITAR_CONTROLLER },
>>> /* Guitar Hero Live PS4 guitar dongles */
>>> { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE),
>>> .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER },
>>> + /* Rock Band 2 instruments
>>> + * Nintendo Wii instruments are included in `hid-sony` because `hid-nintendo`
>>> + * is for the newer Nintendo Switch, and the Wii instruments use the same
>>> + * protocol as their Sony PlayStation 3 cousins.
>>> + */
>>> + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE),
>>> + .driver_data = RB2_INSTRUMENT },
>>> + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE),
>>> + .driver_data = RB2_INSTRUMENT },
>>> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE),
>>> + .driver_data = RB2_INSTRUMENT },
>>> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE),
>>> + .driver_data = RB2_INSTRUMENT },
>>> /* Rock Band 4 PS4 guitars */
>>> { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER),
>>> .driver_data = RB4_GUITAR_PS4_USB },
>
> Some of the Rock Band 3 instruments are outliers that try to merge
> real stringed instruments with plastic ones, so I left those out of
> scope for this commit. The instruments from other games (and the
> common ones from Rock Band 3) should be compatible with
> rb_instrument_mapping.
^ permalink raw reply
* Re: [PATCH] HID: sony: add support for Rock Band 2 instruments
From: Brenton Simpson @ 2026-02-21 5:52 UTC (permalink / raw)
To: Rosalie
Cc: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel,
Sanjay Govind, Antheas Kapenekakis, Vicki Pfau,
Nícolas F . R . A . Prado
In-Reply-To: <6bf7ba2f-bfe1-4988-b15f-9a42f4546b91@mailbox.org>
On Sat, Feb 21, 2026 at 12:26 AM Rosalie <rosalie@mailbox.org> wrote:
>
> On 2/21/26 06:04, Brenton Simpson wrote:
> > On Fri, Feb 20, 2026 at 9:42 PM Rosalie <rosalie@mailbox.org> wrote:
> >
> >> On 20/02/2026 17:50, Brenton Simpson <appsforartists@google.com> wrote:
> >>
> >>> Rock Band 2 for the Nintendo Wii and for the Sony PlayStation 3 use the
> >>> same mapping as later games:
> >>>
> >>> Green: SOUTH (A)
> >>> Red: EAST (B)
> >>> Yellow: NORTH (Y)
> >>> Blue: WEST (X)
> >>> Orange/pedal: TL (L1)
> >>> Solo flag: TL2 (L2)
> >>> Tilt: TR (R1)
> >>> Pad flag: THUMBL (L3)
> >>> Instrument button: MODE (Steam/Xbox)
> >>> Whammy bar: ABS_Z (Axis 4)
> >>> Effects switch: Z (Axis 5)
> >>>
> >>> As documented at https://github.com/TheNathannator/PlasticBand/blob/main/Docs/.
> >>>
> >>> The guitar and drums both use the same mapping. Tested using the Wii
> >>> versions of the instruments.
> >>>
> >>> Signed-off-by: Brenton Simpson <appsforartists@google.com>
> >>> ---
> >>> drivers/hid/hid-ids.h | 8 +++++++-
> >>> drivers/hid/hid-sony.c | 45 +++++++++++++++++++++++++++++-------------
> >>> 2 files changed, 38 insertions(+), 15 deletions(-)
> >>>
> >
> > The indentation also seemed inconsistent in these two hid- files. I
> > tried my best to follow the local style of the parts I edited. Happy
> > to do a style pass in a separate commit if desired.
> >
>
> You can use the ./scripts/checkpatch.pl script to check for any issues
> with regards to style and indentation.
Thanks. I ran that before I sent the patch, and it passes, but there
are still a lot of columns that aren't aligned in these files, and
look like they're meant to be.
> >>> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> >>> index 3e299a30dcde..644d3c4df144 100644
> >>> --- a/drivers/hid/hid-ids.h
> >>> +++ b/drivers/hid/hid-ids.h
> >>> @@ -664,6 +664,10 @@
> >>> #define USB_DEVICE_ID_UGCI_FLYING 0x0020
> >>> #define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
> >>>
> >>> +#define USB_VENDOR_ID_HARMONIX 0x1bad
> >>> +#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE 0x3010
> >>> +#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE 0x3110
> >>> +
> >>> #define USB_VENDOR_ID_HP 0x03f0
> >>> #define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
> >>> #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
> >>> @@ -1299,7 +1303,9 @@
> >>>
> >>> #define USB_VENDOR_ID_SONY_RHYTHM 0x12ba
> >>> #define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b
> >>> -#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100
> >>> +#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE 0x0100
> >>> +#define USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE 0x0200
> >>> +#define USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE 0x0210
> >>>
> >>> #define USB_VENDOR_ID_SINO_LITE 0x1345
> >>> #define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008
> >>> diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
> >>> index a89af14e4acc..f6975f6ae882 100644
> >>> --- a/drivers/hid/hid-sony.c
> >>> +++ b/drivers/hid/hid-sony.c
> >>> @@ -62,9 +62,10 @@
> >>> #define GH_GUITAR_CONTROLLER BIT(14)
> >>> #define GHL_GUITAR_PS3WIIU BIT(15)
> >>> #define GHL_GUITAR_PS4 BIT(16)
> >>> -#define RB4_GUITAR_PS4_USB BIT(17)
> >>> -#define RB4_GUITAR_PS4_BT BIT(18)
> >>> -#define RB4_GUITAR_PS5 BIT(19)
> >>> +#define RB2_INSTRUMENT BIT(17)
> >>> +#define RB4_GUITAR_PS4_USB BIT(18)
> >>> +#define RB4_GUITAR_PS4_BT BIT(19)
> >>> +#define RB4_GUITAR_PS5 BIT(20)
> >>>
> >>> #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
> >>> #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
> >>> @@ -422,12 +423,12 @@ static const unsigned int sixaxis_keymap[] = {
> >>> [0x11] = BTN_MODE, /* PS */
> >>> };
> >>>
> >>> -static const unsigned int rb4_absmap[] = {
> >>> +static const unsigned int rb_absmap[] = {
> >>> [0x30] = ABS_X,
> >>> [0x31] = ABS_Y,
> >>> };
> >>
> >>
> >> Is the Whammy bar mapped correctly to ABS_Z with this patch, or do the
> >> instruments not use the rb_absmap array, because the array does not
> >> contain ABS_Z?
> >>
> >> If it doesn't need the rb_absmap array, maybe it'd be better to make a
> >> separate rb2_instrument_mapping function instead of re-using the
> >> rb4_guitar_mapping function.
> >
> > I renamed the absmap to be consistent, but unlike the CRKD guitars,
> > the Harmonix guitars don't have a joystick, so they ignore the absmap.
> > It's the keymap they need.
> >
> >>> -static const unsigned int rb4_keymap[] = {
> >>> +static const unsigned int rb_keymap[] = {
> >>> [0x1] = BTN_WEST, /* Square */
> >>> [0x2] = BTN_SOUTH, /* Cross */
> >>> [0x3] = BTN_EAST, /* Circle */
> >>> @@ -625,17 +626,17 @@ static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> >>> return 0;
> >>> }
> >>>
> >>> -static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> >>> +static int rb_instrument_mapping(struct hid_device *hdev, struct hid_input *hi,
> >>> struct hid_field *field, struct hid_usage *usage,
> >>> unsigned long **bit, int *max)
> >>> {
> >
> > Without the patch, the red button is not usable without a VDF:
> >
> > 0300037cad1b00001030000001010000,Licensed by Nintendo of America
> > Harmonix Guitar Controller for Nintendo
> > Wii,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,rightx:a2,righty:a3,platform:Linux
> >
>
> Is everything mapped correctly except the red button without this patch?
> If so, maybe a simplified rb_keymap would be more clear code wise,
> alongside my suggestions below.
No, they're all jumbled. Red is BTN_C, so it doesn't even appear in
games. Other mismaps without this patch:
Green: EAST
Blue: SOUTH
Orange: WEST
Solo flag: L1
Start: R2
Select: L2
Tilt: Z
Drum pad flag: SELECT
> > So all registered Rock Band instruments can use this
> > rb_instrument_mapping function. The ones that send the axes at weird
> > locations are augmented by the existing rb4_parse functions; the older
> > ones send them at the standard locations.
> >
>
> I think you misunderstood my initial reply, if the rb_absmap array isn't
> required then it's rather confusing to use the same mapping function for
> the wii/ps3 instruments, because when reading the code it implies that
> it'll be used, I'd suggest creating a new mapping function named
> rb2_instrument_mapping which uses a (maybe simplified, see above)
> rb_keymap array but doesn't use the rb_absmap array, to clearly signal
> to code readers that it's unused for those instruments.
>
> My suggestion would also remove the need to rename the rb4_absmap
> variable name.
That handles joysticks, right? The Wii/PS3 instruments don't have
joysticks to map.
> >>> if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
> >>> unsigned int key = usage->hid & HID_USAGE;
> >>>
> >>> - if (key >= ARRAY_SIZE(rb4_keymap))
> >>> + if (key >= ARRAY_SIZE(rb_keymap))
> >>> return 0;
> >>>
> >>> - key = rb4_keymap[key];
> >>> + key = rb_keymap[key];
> >>> hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
> >>> return 1;
> >>> } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
> >>> @@ -645,10 +646,10 @@ static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
> >>> if (usage->hid == HID_GD_HATSWITCH)
> >>> return 0;
> >>>
> >
> > Like the D-pad, the whammy and the effects switch are correctly mapped
> > by default. I tested all the buttons on both the guitar and the
> > drums. With the patch, they're correct.
> >
> >>> - if (abs >= ARRAY_SIZE(rb4_absmap))
> >>> + if (abs >= ARRAY_SIZE(rb_absmap))
> >>> return 0;
> >>>
> >>> - abs = rb4_absmap[abs];
> >>> + abs = rb_absmap[abs];
> >>> hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
> >>> return 1;
> >>> }
> >>> @@ -1101,11 +1102,14 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
> >>> if (sc->quirks & GH_GUITAR_CONTROLLER)
> >>> return gh_guitar_mapping(hdev, hi, field, usage, bit, max);
> >>>
> >>> + if (sc->quirks & RB2_INSTRUMENT)
> >>> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
> >>> +
> >>> if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
> >>> - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
> >>> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
> >>>
> >>> if (sc->quirks & RB4_GUITAR_PS5)
> >>> - return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
> >>> + return rb_instrument_mapping(hdev, hi, field, usage, bit, max);
> >>>
> >>> /* Let hid-core decide for the others */
> >>> return 0;
> >>> @@ -2369,12 +2373,25 @@ static const struct hid_device_id sony_devices[] = {
> >>> /* Guitar Hero PC Guitar Dongle */
> >>> { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE),
> >>> .driver_data = GH_GUITAR_CONTROLLER },
> >>> - /* Guitar Hero PS3 World Tour Guitar Dongle */
> >>> - { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE),
> >>> + /* Guitar Hero World Tour PS3 Guitar Dongle */
> >>> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR_DONGLE),
> >>> .driver_data = GH_GUITAR_CONTROLLER },
> >>> /* Guitar Hero Live PS4 guitar dongles */
> >>> { HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE),
> >>> .driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER },
> >>> + /* Rock Band 2 instruments
> >>> + * Nintendo Wii instruments are included in `hid-sony` because `hid-nintendo`
> >>> + * is for the newer Nintendo Switch, and the Wii instruments use the same
> >>> + * protocol as their Sony PlayStation 3 cousins.
> >>> + */
> >>> + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR_DONGLE),
> >>> + .driver_data = RB2_INSTRUMENT },
> >>> + { HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS_DONGLE),
> >>> + .driver_data = RB2_INSTRUMENT },
> >>> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_GUITAR_DONGLE),
> >>> + .driver_data = RB2_INSTRUMENT },
> >>> + { HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB2_DRUMS_DONGLE),
> >>> + .driver_data = RB2_INSTRUMENT },
> >>> /* Rock Band 4 PS4 guitars */
> >>> { HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER),
> >>> .driver_data = RB4_GUITAR_PS4_USB },
> >
> > Some of the Rock Band 3 instruments are outliers that try to merge
> > real stringed instruments with plastic ones, so I left those out of
> > scope for this commit. The instruments from other games (and the
> > common ones from Rock Band 3) should be compatible with
> > rb_instrument_mapping.
>
^ permalink raw reply
* Re: [PATCH v3] HID: pidff: Fix condition effect bit clearing
From: Jiri Kosina @ 2026-02-21 9:47 UTC (permalink / raw)
To: Tomasz Pakuła; +Cc: bentiss, oleg, linux-input, linux-kernel
In-Reply-To: <20260204214455.68629-1-tomasz.pakula.oficjalny@gmail.com>
On Wed, 4 Feb 2026, Tomasz Pakuła wrote:
> As reported by MPDarkGuy on discord, NULL pointer dereferences were
> happening because not all the conditional effects bits were cleared.
>
> Properly clear all conditional effect bits from ffbit
>
> Fixes: 7f3d7bc0df4b ("HID: pidff: Better quirk assigment when searching for fields")
> Cc: <stable@vger.kernel.org> # 6.18.x
> Signed-off-by: Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>
Applied to hid.git#for-7.0/upstream-fixes, sorry for the delay.
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: quirks: really enable the intended work around for appledisplay
From: Jiri Kosina @ 2026-02-21 9:48 UTC (permalink / raw)
To: René Rebe
Cc: Lukas Bulwahn, Benjamin Tissoires, linux-input, kernel-janitors,
linux-kernel, Lukas Bulwahn
In-Reply-To: <944F8139-6528-442E-BFC2-9609A7DACA2D@exactco.de>
On Thu, 5 Feb 2026, René Rebe wrote:
> > From: Lukas Bulwahn <lukas.bulwahn@redhat.com>
> >
> > Commit c7fabe4ad921 ("HID: quirks: work around VID/PID conflict for
> > appledisplay") intends to add a quirk for kernels built with Apple Cinema
> > Display support, but it refers to the non-existing config option
> > CONFIG_APPLEDISPLAY, whereas the config option for Apple Cinema Display
> > support is named CONFIG_USB_APPLEDISPLAY.
> >
> > Refer to the intended config option CONFIG_USB_APPLEDISPLAY in the ifdef
> > directive.
> >
> > Fixes: c7fabe4ad921 ("HID: quirks: work around VID/PID conflict for appledisplay")
> > Signed-off-by: Lukas Bulwahn <lukas.bulwahn@redhat.com>
> > ---
> >
> > Note this fix suggests that the patch of commit c7fabe4ad921 in this form
> > was never effectively tested, checking the effect that the commit message
> > claims it would have.
>
> Thanks for spotting this. It was tested when I did this years ago. Though, maybe I
> tested before adding the #if IS_ENABLED, anciently hit some key in Vim, o
> something was renamed since then.
>
> Either way, I’ll get the display out of the basement and re-test it the coming days.
Is there any results of this testing, please?
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH 1/1] HID: uhid: Fix out-of-bounds write caused by raw events mismanagement
From: Jiri Kosina @ 2026-02-21 9:49 UTC (permalink / raw)
To: Lee Jones
Cc: David Rheinsberg, Benjamin Tissoires, linux-input, linux-kernel,
stable
In-Reply-To: <20260211164025.171242-1-lee@kernel.org>
On Wed, 11 Feb 2026, Lee Jones wrote:
> Since the report ID is located within the data buffer, overwriting it
> would mean that any subsequent matching could cause a disparity in
> assumed allocated buffer size. This in turn could trivially result in
> an out-of-bounds condition. To mitigate this issue, let's refuse to
> overwrite a given report's data area if the ID in get_report_reply
> doesn't match.
Applied to hid.git#for-7.0/upstream-fixes, thanks.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: magicmouse: fix battery reporting for Apple Magic Trackpad 2
From: Jiri Kosina @ 2026-02-21 9:50 UTC (permalink / raw)
To: Julius Lehmann; +Cc: Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20260214-magic-trackpad-usb-battery-v1-1-353bc63b56c0@devpi.de>
On Sat, 14 Feb 2026, Julius Lehmann wrote:
> Battery reporting does not work for the Apple Magic Trackpad 2 if it is
> connected via USB. The current hid descriptor fixup code checks for a
> hid descriptor length of exactly 83 bytes. If the hid descriptor is
> larger, which is the case for newer apple mice, the fixup is not
> applied.
>
> This fix checks for hid descriptor sizes greater/equal 83 bytes which
> applies the fixup for newer devices as well.
Applied to hid.git#for-7.0/upstream-fixes. Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: multitouch: new class MT_CLS_EGALAX_P80H84
From: Jiri Kosina @ 2026-02-21 9:54 UTC (permalink / raw)
To: Ian Ray; +Cc: Benjamin Tissoires, Sebastian Reichel, linux-input, linux-kernel
In-Reply-To: <20260217115152.35910-1-ian.ray@gehealthcare.com>
On Tue, 17 Feb 2026, Ian Ray wrote:
> Add class MT_CLS_EGALAX_P80H84 to describe eGalaxy P80H84 touchscreen.
>
> The eGalaxy P80H84 touchscreen reports 'HID_GROUP_MULTITOUCH_WIN_8' and
> this caused the generic 'MT_CLS_WIN_8' class to be detected.
>
> The new class does _not_ export all inputs, since doing so results in a
> visible mouse cursor when only the touchscreen is connected.
>
> The visible mouse cursor was exposed by c23e2043d5f7 ("HID: multitouch:
> do not filter mice nodes").
Applied to hid.git#for-7.0/upstream-fixes. Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox