Linux Input/HID development
 help / color / mirror / Atom feed
* [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

* RE: [PATCH v3] selftests: hid: tests: test_wacom_generic: add tests for display devices and opaque devices
From: Jiri Kosina @ 2026-02-21  9:56 UTC (permalink / raw)
  To: Skomra, Erin
  Cc: Alex Tran, Benjamin Tissoires, Shuah Khan, Cheng, Ping,
	Gerecke, Jason, Erin Skomra, linux-input@vger.kernel.org,
	linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <AS8PR07MB8373592F3C7C20ACE67808EDF598A@AS8PR07MB8373.eurprd07.prod.outlook.com>

On Wed, 4 Feb 2026, Skomra, Erin wrote:

> Tested by Erin Skomra <erin.skomra@wacom.com>
> Reviewed-by Erin Skomra <erin.skomra@wacom.com>

Erin,

thanks for testing and reviewing the patch.
Next time please make sure that you send the tags in an appropriate format 
so that it can be automatically picked by the tools like b4 when applying.

I've fixed it manually this time and applied.

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: Benjamin Tissoires @ 2026-02-21 13:03 UTC (permalink / raw)
  To: Lee Jones
  Cc: David Rheinsberg, Jiri Kosina, linux-input, linux-kernel, stable
In-Reply-To: <20260211164025.171242-1-lee@kernel.org>

On Feb 11 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.

That's a strong assumption and a breakage of the userspace FWIW. The CI
is now full of errors:
https://gitlab.freedesktop.org/bentiss/hid/-/commits/for-7.0/upstream-fixes

It is pretty common to allocate the buffer and not initialize it in
get_report operations.

It was a bad API choice to have rnum and data[0] for all HID requests
(internally, externally), but we should stick to it. The CI breakage in
itself is not a big issue TBH, but if it breaks here, it will probably
break existing users.

Cheers,
Benjamin

> 
> Cc: stable@vger.kernel.org
> Fixes: fcfcf0deb89ec ("HID: uhid: implement feature requests")
> Signed-off-by: Lee Jones <lee@kernel.org>
> ---
>  drivers/hid/uhid.c | 4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
> index 21a70420151e..a0ee4e86656f 100644
> --- a/drivers/hid/uhid.c
> +++ b/drivers/hid/uhid.c
> @@ -262,6 +262,10 @@ static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum,
>  	req = &uhid->report_buf.u.get_report_reply;
>  	if (req->err) {
>  		ret = -EIO;
> +	} else if (rnum != req->data[0]) {
> +		hid_err(hid, "Report ID mismatch - refusing to overwrite the data buffer\n");
> +		ret = -EINVAL;
> +		goto unlock;
>  	} else {
>  		ret = min3(count, (size_t)req->size, (size_t)UHID_DATA_MAX);
>  		memcpy(buf, req->data, ret);
> -- 
> 2.53.0.273.g2a3d683680-goog
> 

^ permalink raw reply

* Re: [PATCH v3] HID: generic: add LampArray support via hid-lamparray helper
From: kernel test robot @ 2026-02-21 15:49 UTC (permalink / raw)
  To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
  Cc: llvm, 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: i386-randconfig-002-20260221 (https://download.01.org/0day-ci/archive/20260221/202602212327.QsyQtOFJ-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260221/202602212327.QsyQtOFJ-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/202602212327.QsyQtOFJ-lkp@intel.com/

All errors (new ones prefixed by >>):

>> ld.lld: error: undefined symbol: hid_hw_request
   >>> referenced by hid-lamparray.c:210 (drivers/hid/hid-lamparray.c:210)
   >>>               drivers/hid/hid-lamparray.o:(lamparray_register) in archive vmlinux.a
   >>> referenced by hid-lamparray.c:316 (drivers/hid/hid-lamparray.c:316)
   >>>               drivers/hid/hid-lamparray.o:(lamparray_hw_set_autonomous) in archive vmlinux.a
   >>> referenced by hid-lamparray.c:392 (drivers/hid/hid-lamparray.c:392)
   >>>               drivers/hid/hid-lamparray.o:(lamparray_led_brightness_get) in archive vmlinux.a
   >>> referenced 1 more times

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ 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 19:46 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Lee Jones, David Rheinsberg, linux-input, linux-kernel, stable
In-Reply-To: <aZmsTQeeGf26FqvY@plouf>

On Sat, 21 Feb 2026, Benjamin Tissoires 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.
> 
> That's a strong assumption and a breakage of the userspace FWIW. The CI
> is now full of errors:
> https://gitlab.freedesktop.org/bentiss/hid/-/commits/for-7.0/upstream-fixes
> 
> It is pretty common to allocate the buffer and not initialize it in
> get_report operations.
> 
> It was a bad API choice to have rnum and data[0] for all HID requests
> (internally, externally), but we should stick to it. The CI breakage in
> itself is not a big issue TBH, but if it breaks here, it will probably
> break existing users.

Lee,

was this found via code inspection, fuzzing, or is there some real-world 
report behind it?

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* [PATCH 1/7] Input: export input_default_setkeycode
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel

From: Fabio Baltieri <fabiobaltieri@chromium.org>

Export input_default_setkeycode so that a driver can set a custom
setkeycode handler to take some driver specific action but still call
the default handler at some point.

Signed-off-by: Fabio Baltieri <fabiobaltieri@chromium.org>
Link: https://patch.msgid.link/20260211173421.1206478-2-fabiobaltieri@chromium.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/input.c | 23 ++++++++++++++++++++---
 include/linux/input.h |  4 ++++
 2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/input/input.c b/drivers/input/input.c
index ceb134044175..9336980ee320 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -861,14 +861,30 @@ static int input_default_getkeycode(struct input_dev *dev,
 	return 0;
 }
 
-static int input_default_setkeycode(struct input_dev *dev,
-				    const struct input_keymap_entry *ke,
-				    unsigned int *old_keycode)
+/**
+ * input_default_setkeycode - default setkeycode method
+ * @dev: input device which keymap is being updated.
+ * @ke: new keymap entry.
+ * @old_keycode: pointer to the location where old keycode should be stored.
+ *
+ * This function is the default implementation of &input_dev.setkeycode()
+ * method. It is typically used when a driver does not provide its own
+ * implementation, but it is also exported so drivers can extend it.
+ *
+ * The function must be called with &input_dev.event_lock held.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int input_default_setkeycode(struct input_dev *dev,
+			     const struct input_keymap_entry *ke,
+			     unsigned int *old_keycode)
 {
 	unsigned int index;
 	int error;
 	int i;
 
+	lockdep_assert_held(&dev->event_lock);
+
 	if (!dev->keycodesize)
 		return -EINVAL;
 
@@ -922,6 +938,7 @@ static int input_default_setkeycode(struct input_dev *dev,
 	__set_bit(ke->keycode, dev->keybit);
 	return 0;
 }
+EXPORT_SYMBOL(input_default_setkeycode);
 
 /**
  * input_get_keycode - retrieve keycode currently mapped to a given scancode
diff --git a/include/linux/input.h b/include/linux/input.h
index 7d7cb0593a63..06ca62328db1 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -517,6 +517,10 @@ INPUT_GENERATE_ABS_ACCESSORS(res, resolution)
 int input_scancode_to_scalar(const struct input_keymap_entry *ke,
 			     unsigned int *scancode);
 
+int input_default_setkeycode(struct input_dev *dev,
+			     const struct input_keymap_entry *ke,
+			     unsigned int *old_keycode);
+
 int input_get_keycode(struct input_dev *dev, struct input_keymap_entry *ke);
 int input_set_keycode(struct input_dev *dev,
 		      const struct input_keymap_entry *ke);
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 2/7] Input: cros_ec_keyb - add function key support
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

From: Fabio Baltieri <fabiobaltieri@chromium.org>

Add support for handling an Fn button and sending separate keycodes for
a subset of keys in the matrix defined in the upper half of the keymap.

Signed-off-by: Fabio Baltieri <fabiobaltieri@chromium.org>
Reviewed-by: Simon Glass <sjg@chromium.org>
Link: https://patch.msgid.link/20260211173421.1206478-3-fabiobaltieri@chromium.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 182 +++++++++++++++++++++++---
 1 file changed, 166 insertions(+), 16 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index 066aaa87d93f..bdbfd219f2e8 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -29,6 +29,12 @@
 
 #include <linux/unaligned.h>
 
+/*
+ * Maximum size of the normal key matrix, this is limited by the host command
+ * key_matrix field defined in ec_response_get_next_data_v3
+ */
+#define CROS_EC_KEYBOARD_COLS_MAX 18
+
 /**
  * struct cros_ec_keyb - Structure representing EC keyboard device
  *
@@ -44,6 +50,9 @@
  * @bs_idev: The input device for non-matrix buttons and switches (or NULL).
  * @notifier: interrupt event notifier for transport devices
  * @vdata: vivaldi function row data
+ * @has_fn_map: whether the driver uses an fn function-map layer
+ * @fn_active: tracks whether the function key is currently pressed
+ * @fn_combo_active: tracks whether another key was pressed while fn is active
  */
 struct cros_ec_keyb {
 	unsigned int rows;
@@ -61,6 +70,10 @@ struct cros_ec_keyb {
 	struct notifier_block notifier;
 
 	struct vivaldi_data vdata;
+
+	bool has_fn_map;
+	bool fn_active;
+	bool fn_combo_active;
 };
 
 /**
@@ -166,16 +179,89 @@ static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, uint8_t *buf)
 	return false;
 }
 
+static void cros_ec_emit_fn_key(struct input_dev *input, unsigned int pos)
+{
+	input_event(input, EV_MSC, MSC_SCAN, pos);
+	input_report_key(input, KEY_FN, true);
+	input_sync(input);
+
+	input_event(input, EV_MSC, MSC_SCAN, pos);
+	input_report_key(input, KEY_FN, false);
+}
+
+static void cros_ec_keyb_process_key_plain(struct cros_ec_keyb *ckdev,
+					   int row, int col, bool state)
+{
+	struct input_dev *idev = ckdev->idev;
+	const unsigned short *keycodes = idev->keycode;
+	int pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
+
+	input_event(idev, EV_MSC, MSC_SCAN, pos);
+	input_report_key(idev, keycodes[pos], state);
+}
+
+static void cros_ec_keyb_process_key_fn_map(struct cros_ec_keyb *ckdev,
+					    int row, int col, bool state)
+{
+	struct input_dev *idev = ckdev->idev;
+	const unsigned short *keycodes = idev->keycode;
+	unsigned int pos, fn_pos;
+	unsigned int code, fn_code;
+
+	pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
+	code = keycodes[pos];
+
+	if (code == KEY_FN) {
+		ckdev->fn_active = state;
+		if (state) {
+			ckdev->fn_combo_active = false;
+		} else if (!ckdev->fn_combo_active) {
+			/*
+			 * Send both Fn press and release events if nothing
+			 * else has been pressed together with Fn.
+			 */
+			cros_ec_emit_fn_key(idev, pos);
+		}
+		return;
+	}
+
+	fn_pos = MATRIX_SCAN_CODE(row + ckdev->rows, col, ckdev->row_shift);
+	fn_code = keycodes[fn_pos];
+
+	if (state) {
+		if (ckdev->fn_active) {
+			ckdev->fn_combo_active = true;
+			if (!fn_code)
+				return; /* Discard if no Fn mapping exists */
+
+			code = fn_code;
+			pos = fn_pos;
+		}
+	} else {
+		/*
+		 * If the Fn-remapped code is currently pressed, release it.
+		 * Otherwise, release the standard code (if it was pressed).
+		 */
+		if (fn_code && test_bit(fn_code, idev->key)) {
+			code = fn_code;
+			pos = fn_pos;
+		} else if (!test_bit(code, idev->key)) {
+			return; /* Discard, key press code was not sent */
+		}
+	}
+
+	input_event(idev, EV_MSC, MSC_SCAN, pos);
+	input_report_key(idev, code, state);
+}
 
 /*
  * Compares the new keyboard state to the old one and produces key
- * press/release events accordingly.  The keyboard state is 13 bytes (one byte
- * per column)
+ * press/release events accordingly.  The keyboard state is one byte
+ * per column.
  */
 static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev,
 			 uint8_t *kb_state, int len)
 {
-	struct input_dev *idev = ckdev->idev;
 	int col, row;
 	int new_state;
 	int old_state;
@@ -192,20 +278,19 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev,
 
 	for (col = 0; col < ckdev->cols; col++) {
 		for (row = 0; row < ckdev->rows; row++) {
-			int pos = MATRIX_SCAN_CODE(row, col, ckdev->row_shift);
-			const unsigned short *keycodes = idev->keycode;
-
 			new_state = kb_state[col] & (1 << row);
 			old_state = ckdev->old_kb_state[col] & (1 << row);
-			if (new_state != old_state) {
-				dev_dbg(ckdev->dev,
-					"changed: [r%d c%d]: byte %02x\n",
-					row, col, new_state);
-
-				input_event(idev, EV_MSC, MSC_SCAN, pos);
-				input_report_key(idev, keycodes[pos],
-						 new_state);
-			}
+
+			if (new_state == old_state)
+				continue;
+
+			dev_dbg(ckdev->dev, "changed: [r%d c%d]: byte %02x\n",
+				row, col, new_state);
+
+			if (ckdev->has_fn_map)
+				cros_ec_keyb_process_key_fn_map(ckdev, row, col, new_state);
+			else
+				cros_ec_keyb_process_key_plain(ckdev, row, col, new_state);
 		}
 		ckdev->old_kb_state[col] = kb_state[col];
 	}
@@ -583,6 +668,62 @@ static void cros_ec_keyb_parse_vivaldi_physmap(struct cros_ec_keyb *ckdev)
 	ckdev->vdata.num_function_row_keys = n_physmap;
 }
 
+/* Returns true if there is a KEY_FN code defined in the normal keymap */
+static bool cros_ec_keyb_has_fn_key(struct cros_ec_keyb *ckdev)
+{
+	const unsigned short *keycodes = ckdev->idev->keycode;
+	int i;
+
+	for (i = 0; i < MATRIX_SCAN_CODE(ckdev->rows, 0, ckdev->row_shift); i++) {
+		if (keycodes[i] == KEY_FN)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Returns true if there is a KEY_FN defined and at least one key in the fn
+ * layer keymap
+ */
+static bool cros_ec_keyb_has_fn_map(struct cros_ec_keyb *ckdev)
+{
+	struct input_dev *idev = ckdev->idev;
+	const unsigned short *keycodes = ckdev->idev->keycode;
+	int i;
+
+	if (!cros_ec_keyb_has_fn_key(ckdev))
+		return false;
+
+	for (i = MATRIX_SCAN_CODE(ckdev->rows, 0, ckdev->row_shift);
+	     i < idev->keycodemax; i++) {
+		if (keycodes[i] != KEY_RESERVED)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Custom handler for the set keycode ioctl, calls the default handler and
+ * recomputes has_fn_map.
+ */
+static int cros_ec_keyb_setkeycode(struct input_dev *idev,
+				   const struct input_keymap_entry *ke,
+				   unsigned int *old_keycode)
+{
+	struct cros_ec_keyb *ckdev = input_get_drvdata(idev);
+	int ret;
+
+	ret = input_default_setkeycode(idev, ke, old_keycode);
+	if (ret)
+		return ret;
+
+	ckdev->has_fn_map = cros_ec_keyb_has_fn_map(ckdev);
+
+	return 0;
+}
+
 /**
  * cros_ec_keyb_register_matrix - Register matrix keys
  *
@@ -604,6 +745,12 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	if (err)
 		return err;
 
+	if (ckdev->cols > CROS_EC_KEYBOARD_COLS_MAX) {
+		dev_err(dev, "keypad,num-columns too large: %d (max: %d)\n",
+			ckdev->cols, CROS_EC_KEYBOARD_COLS_MAX);
+		return -EINVAL;
+	}
+
 	ckdev->valid_keys = devm_kzalloc(dev, ckdev->cols, GFP_KERNEL);
 	if (!ckdev->valid_keys)
 		return -ENOMEM;
@@ -632,11 +779,12 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	idev->id.version = 1;
 	idev->id.product = 0;
 	idev->dev.parent = dev;
+	idev->setkeycode = cros_ec_keyb_setkeycode;
 
 	ckdev->ghost_filter = device_property_read_bool(dev,
 					"google,needs-ghost-filter");
 
-	err = matrix_keypad_build_keymap(NULL, ckdev->rows, ckdev->cols,
+	err = matrix_keypad_build_keymap(NULL, ckdev->rows * 2, ckdev->cols,
 					 NULL, idev);
 	if (err) {
 		dev_err(dev, "cannot build key matrix\n");
@@ -651,6 +799,8 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	cros_ec_keyb_compute_valid_keys(ckdev);
 	cros_ec_keyb_parse_vivaldi_physmap(ckdev);
 
+	ckdev->has_fn_map = cros_ec_keyb_has_fn_map(ckdev);
+
 	err = input_register_device(ckdev->idev);
 	if (err) {
 		dev_err(dev, "cannot register input device\n");
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 3/7] Input: cros_ec_keyb - use u8 instead of uint8_t
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

In the kernel u8/u16/u32 are preferred to the uint*_t variants.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index bdbfd219f2e8..2f78a19521ab 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -59,8 +59,8 @@ struct cros_ec_keyb {
 	unsigned int cols;
 	int row_shift;
 	bool ghost_filter;
-	uint8_t *valid_keys;
-	uint8_t *old_kb_state;
+	u8 *valid_keys;
+	u8 *old_kb_state;
 
 	struct device *dev;
 	struct cros_ec_device *ec;
@@ -145,11 +145,11 @@ static const struct cros_ec_bs_map cros_ec_keyb_bs[] = {
  * Returns true when there is at least one combination of pressed keys that
  * results in ghosting.
  */
-static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, uint8_t *buf)
+static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, u8 *buf)
 {
 	int col1, col2, buf1, buf2;
 	struct device *dev = ckdev->dev;
-	uint8_t *valid_keys = ckdev->valid_keys;
+	u8 *valid_keys = ckdev->valid_keys;
 
 	/*
 	 * Ghosting happens if for any pressed key X there are other keys
@@ -259,8 +259,7 @@ static void cros_ec_keyb_process_key_fn_map(struct cros_ec_keyb *ckdev,
  * press/release events accordingly.  The keyboard state is one byte
  * per column.
  */
-static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev,
-			 uint8_t *kb_state, int len)
+static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, u8 *kb_state, int len)
 {
 	int col, row;
 	int new_state;
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related

* [PATCH 4/7] Input: cros_ec_keyb - use BIT() macro instead of open-coding shifts
From: Dmitry Torokhov @ 2026-02-22  0:37 UTC (permalink / raw)
  To: Fabio Baltieri, Benson Leung
  Cc: Guenter Roeck, Simon Glass, Tzung-Bi Shih, linux-input,
	chrome-platform, linux-kernel
In-Reply-To: <20260222003717.471977-1-dmitry.torokhov@gmail.com>

Using the macro clarifies the code.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/cros_ec_keyb.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c
index 2f78a19521ab..fabbdbc22785 100644
--- a/drivers/input/keyboard/cros_ec_keyb.c
+++ b/drivers/input/keyboard/cros_ec_keyb.c
@@ -277,8 +277,8 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, u8 *kb_state, int l
 
 	for (col = 0; col < ckdev->cols; col++) {
 		for (row = 0; row < ckdev->rows; row++) {
-			new_state = kb_state[col] & (1 << row);
-			old_state = ckdev->old_kb_state[col] & (1 << row);
+			new_state = kb_state[col] & BIT(row);
+			old_state = ckdev->old_kb_state[col] & BIT(row);
 
 			if (new_state == old_state)
 				continue;
@@ -411,7 +411,7 @@ static void cros_ec_keyb_compute_valid_keys(struct cros_ec_keyb *ckdev)
 		for (row = 0; row < ckdev->rows; row++) {
 			code = keymap[MATRIX_SCAN_CODE(row, col, row_shift)];
 			if (code && (code != KEY_BATTERY))
-				ckdev->valid_keys[col] |= 1 << row;
+				ckdev->valid_keys[col] |= BIT(row);
 		}
 		dev_dbg(ckdev->dev, "valid_keys[%02d] = 0x%02x\n",
 			col, ckdev->valid_keys[col]);
@@ -780,8 +780,7 @@ static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev)
 	idev->dev.parent = dev;
 	idev->setkeycode = cros_ec_keyb_setkeycode;
 
-	ckdev->ghost_filter = device_property_read_bool(dev,
-					"google,needs-ghost-filter");
+	ckdev->ghost_filter = device_property_read_bool(dev, "google,needs-ghost-filter");
 
 	err = matrix_keypad_build_keymap(NULL, ckdev->rows * 2, ckdev->cols,
 					 NULL, idev);
-- 
2.53.0.345.g96ddfc5eaa-goog


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox