* [PATCH] HID: generic: add LampArray support via hid-lamparray helper
@ 2026-02-19 13:02 Tim Guttzeit
2026-02-19 18:44 ` kernel test robot
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Tim Guttzeit @ 2026-02-19 13:02 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>
---
drivers/hid/Makefile | 1 +
drivers/hid/hid-generic.c | 41 +-
drivers/hid/hid-lamparray.c | 855 ++++++++++++++++++++++++++++++++++++
drivers/hid/hid-lamparray.h | 68 +++
4 files changed, 963 insertions(+), 2 deletions(-)
create mode 100644 drivers/hid/hid-lamparray.c
create mode 100644 drivers/hid/hid-lamparray.h
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 361a7daedeb8..e6903be9c4df 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_HID) += hid.o
obj-$(CONFIG_UHID) += uhid.o
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
+obj-$(CONFIG_HID_GENERIC) += hid-lamparray.o
hid-$(CONFIG_HIDRAW) += hidraw.o
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index c2de916747de..650c2121b403 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 (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 (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..b786ca00c404
--- /dev/null
+++ b/drivers/hid/hid-lamparray.h
@@ -0,0 +1,68 @@
+/* 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>
+
+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;
+};
+
+/**
+ * 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);
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
2026-02-19 13:02 [PATCH] HID: generic: add LampArray support via hid-lamparray helper Tim Guttzeit
@ 2026-02-19 18:44 ` kernel test robot
2026-02-19 18:55 ` kernel test robot
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: kernel test robot @ 2026-02-19 18:44 UTC (permalink / raw)
To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
Cc: oe-kbuild-all, wse, Tim Guttzeit, linux-kernel, linux-input
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-20260219]
[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/20260219-211040
base: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link: https://lore.kernel.org/r/20260219130217.2042972-1-tgu%40tuxedocomputers.com
patch subject: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
config: nios2-defconfig (https://download.01.org/0day-ci/archive/20260220/202602200233.9Bwav9tZ-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260220/202602200233.9Bwav9tZ-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/202602200233.9Bwav9tZ-lkp@intel.com/
All errors (new ones prefixed by >>):
nios2-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_led_brightness_set':
drivers/hid/hid-lamparray.c:449:(.text+0x7b4): undefined reference to `led_mc_calc_color_components'
>> drivers/hid/hid-lamparray.c:449:(.text+0x7b4): relocation truncated to fit: R_NIOS2_CALL26 against `led_mc_calc_color_components'
nios2-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_register_led':
drivers/hid/hid-lamparray.c:585:(.text+0x96c): undefined reference to `led_mc_calc_color_components'
drivers/hid/hid-lamparray.c:585:(.text+0x96c): relocation truncated to fit: R_NIOS2_CALL26 against `led_mc_calc_color_components'
nios2-linux-ld: drivers/hid/hid-lamparray.o: in function `led_classdev_multicolor_register':
include/linux/led-class-multicolor.h:70:(.text+0x98c): undefined reference to `led_classdev_multicolor_register_ext'
>> include/linux/led-class-multicolor.h:70:(.text+0x98c): relocation truncated to fit: R_NIOS2_CALL26 against `led_classdev_multicolor_register_ext'
nios2-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_unregister_led':
drivers/hid/hid-lamparray.c:615:(.text+0xd28): undefined reference to `led_classdev_multicolor_unregister'
>> drivers/hid/hid-lamparray.c:615:(.text+0xd28): relocation truncated to fit: R_NIOS2_CALL26 against `led_classdev_multicolor_unregister'
>> nios2-linux-ld: drivers/hid/hid-lamparray.c:615:(.text+0xf48): undefined reference to `led_classdev_multicolor_unregister'
drivers/hid/hid-lamparray.c:615:(.text+0xf48): relocation truncated to fit: R_NIOS2_CALL26 against `led_classdev_multicolor_unregister'
nios2-linux-ld: drivers/hid/hid-lamparray.c:615:(.text+0x1094): undefined reference to `led_classdev_multicolor_unregister'
drivers/hid/hid-lamparray.c:615:(.text+0x1094): relocation truncated to fit: R_NIOS2_CALL26 against `led_classdev_multicolor_unregister'
vim +449 drivers/hid/hid-lamparray.c
434
435 static int lamparray_led_brightness_set(struct led_classdev *cdev,
436 enum led_brightness brightness)
437 {
438 struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
439 struct lamparray_device *ldev =
440 container_of(mc, struct lamparray_device, mc_cdev);
441 struct lamparray *la = container_of(ldev, struct lamparray, ldev);
442 u8 r, g, b;
443 int ret;
444
445 if (!la)
446 return -ENODEV;
447 ldev = &la->ldev;
448
> 449 ret = led_mc_calc_color_components(mc, brightness);
450 if (ret)
451 return ret;
452
453 r = mc->subled_info[0].brightness;
454 g = mc->subled_info[1].brightness;
455 b = mc->subled_info[2].brightness;
456
457 ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
458 if (ret)
459 hid_err(ldev->hdev, "Failed to send LampArray update: %d\n",
460 ret);
461
462 mutex_lock(&ldev->lock);
463 ldev->last_r = r;
464 ldev->last_g = g;
465 ldev->last_b = b;
466 ldev->last_brightness = brightness;
467 mutex_unlock(&ldev->lock);
468
469 return 0;
470 }
471
472 static enum led_brightness
473 lamparray_led_brightness_get(struct led_classdev *cdev)
474 {
475 struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
476 struct lamparray_device *ldev =
477 container_of(mc, struct lamparray_device, mc_cdev);
478 enum led_brightness brightness;
479 struct lamparray *la = container_of(ldev, struct lamparray, ldev);
480 u8 rr, gg, bb;
481 enum led_brightness br;
482 int ret;
483
484 /* Default: cache (also used while registering LED classdev) */
485 mutex_lock(&ldev->lock);
486 brightness = ldev->last_brightness;
487 mutex_unlock(&ldev->lock);
488
489 /* Only do HID readback after registration completed */
490 if (READ_ONCE(ldev->led_registered)) {
491 if (!la)
492 return brightness;
493 ldev = &la->ldev;
494
495 ret = lamparray_hw_get_state(ldev, &rr, &gg, &bb, &br);
496 if (ret) {
497 hid_warn(ldev->hdev,
498 "Failed to read LampArray state (%d), using cached brightness %u\n",
499 ret, brightness);
500 return brightness;
501 }
502
503 mutex_lock(&ldev->lock);
504 if (ldev->last_r != rr || ldev->last_g != gg ||
505 ldev->last_b != bb || ldev->last_brightness != br) {
506 ldev->last_r = rr;
507 ldev->last_g = gg;
508 ldev->last_b = bb;
509 ldev->last_brightness = br;
510
511 if (ldev->led_registered && ldev->mc_cdev.subled_info) {
512 ldev->mc_cdev.subled_info[0].brightness = rr;
513 ldev->mc_cdev.subled_info[1].brightness = gg;
514 ldev->mc_cdev.subled_info[2].brightness = bb;
515 }
516 }
517 mutex_unlock(&ldev->lock);
518 return br;
519 }
520 return brightness;
521 }
522
523 static int lamparray_register_led(struct lamparray_device *ldev)
524 {
525 struct device *dev = &ldev->hdev->dev;
526 struct led_classdev *cdev = &ldev->mc_cdev.led_cdev;
527 u8 r_i, g_i, b_i;
528 int ret;
529
530 mutex_lock(&ldev->lock);
531
532 if (ldev->led_registered) {
533 mutex_unlock(&ldev->lock);
534 return 0;
535 }
536
537 if (!cdev->name) {
538 cdev->name =
539 devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev));
540 if (!cdev->name) {
541 mutex_unlock(&ldev->lock);
542 return -ENOMEM;
543 }
544 }
545
546 cdev->max_brightness = 255;
547 cdev->brightness_set_blocking = lamparray_led_brightness_set;
548 cdev->brightness_get = lamparray_led_brightness_get;
549 cdev->brightness = ldev->last_brightness;
550
551 ldev->subleds[0].color_index = LED_COLOR_ID_RED;
552 ldev->subleds[1].color_index = LED_COLOR_ID_GREEN;
553 ldev->subleds[2].color_index = LED_COLOR_ID_BLUE;
554
555 /*
556 * Initialize the color mix (multi_intensity) from the last known HW/init
557 * state so that writing only /brightness scales the expected default color
558 * instead of white.
559 *
560 * If last_brightness is non-zero, treat last_r/g/b as per-channel
561 * brightness and normalize back to intensities (0..255).
562 * If last_brightness is zero, keep last_r/g/b as the intended mix.
563 */
564 if (ldev->last_brightness) {
565 r_i = (u8)min_t(unsigned int, 255,
566 (ldev->last_r * 255u) / ldev->last_brightness);
567 g_i = (u8)min_t(unsigned int, 255,
568 (ldev->last_g * 255u) / ldev->last_brightness);
569 b_i = (u8)min_t(unsigned int, 255,
570 (ldev->last_b * 255u) / ldev->last_brightness);
571 } else {
572 r_i = ldev->last_r;
573 g_i = ldev->last_g;
574 b_i = ldev->last_b;
575 }
576
577 ldev->subleds[0].intensity = r_i;
578 ldev->subleds[1].intensity = g_i;
579 ldev->subleds[2].intensity = b_i;
580
581 ldev->mc_cdev.subled_info = ldev->subleds;
582 ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
583
584 /* Ensure subled_info[].brightness matches intensity + brightness */
> 585 led_mc_calc_color_components(&ldev->mc_cdev, cdev->brightness);
586
587 ldev->mc_cdev.subled_info = ldev->subleds;
588 ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
589
590 mutex_unlock(&ldev->lock);
591
592 ret = led_classdev_multicolor_register(dev, &ldev->mc_cdev);
593 if (ret)
594 return ret;
595
596 mutex_lock(&ldev->lock);
597 ldev->led_registered = true;
598 mutex_unlock(&ldev->lock);
599
600 return 0;
601 }
602
603 static void lamparray_unregister_led(struct lamparray_device *ldev)
604 {
605 bool was_registered;
606
607 mutex_lock(&ldev->lock);
608 was_registered = ldev->led_registered;
609 ldev->led_registered = false;
610 mutex_unlock(&ldev->lock);
611
612 if (!was_registered)
613 return;
614
> 615 led_classdev_multicolor_unregister(&ldev->mc_cdev);
616 }
617
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
2026-02-19 13:02 [PATCH] HID: generic: add LampArray support via hid-lamparray helper Tim Guttzeit
2026-02-19 18:44 ` kernel test robot
@ 2026-02-19 18:55 ` kernel test robot
2026-02-19 19:14 ` kernel test robot
2026-02-19 20:28 ` kernel test robot
3 siblings, 0 replies; 5+ messages in thread
From: kernel test robot @ 2026-02-19 18:55 UTC (permalink / raw)
To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
Cc: oe-kbuild-all, wse, Tim Guttzeit, linux-kernel, linux-input
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-20260219]
[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/20260219-211040
base: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link: https://lore.kernel.org/r/20260219130217.2042972-1-tgu%40tuxedocomputers.com
patch subject: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
config: csky-defconfig (https://download.01.org/0day-ci/archive/20260220/202602200241.6ypuWvE5-lkp@intel.com/config)
compiler: csky-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260220/202602200241.6ypuWvE5-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/202602200241.6ypuWvE5-lkp@intel.com/
All errors (new ones prefixed by >>):
csky-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_led_brightness_set':
hid-lamparray.c:(.text+0x550): undefined reference to `led_mc_calc_color_components'
>> csky-linux-ld: hid-lamparray.c:(.text+0x5c0): undefined reference to `led_mc_calc_color_components'
csky-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_register_led':
hid-lamparray.c:(.text+0x6b2): undefined reference to `led_mc_calc_color_components'
>> csky-linux-ld: hid-lamparray.c:(.text+0x6ce): undefined reference to `led_classdev_multicolor_register_ext'
csky-linux-ld: hid-lamparray.c:(.text+0x750): undefined reference to `led_mc_calc_color_components'
csky-linux-ld: hid-lamparray.c:(.text+0x758): undefined reference to `led_classdev_multicolor_register_ext'
csky-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_register.part.0':
hid-lamparray.c:(.text+0x94e): undefined reference to `led_classdev_multicolor_unregister'
csky-linux-ld: drivers/hid/hid-lamparray.o: in function `lamparray_unregister':
hid-lamparray.c:(.text+0xad4): undefined reference to `led_classdev_multicolor_unregister'
>> csky-linux-ld: hid-lamparray.c:(.text+0xb1c): undefined reference to `led_classdev_multicolor_unregister'
csky-linux-ld: hid-lamparray.c:(.text+0xb24): undefined reference to `led_classdev_multicolor_unregister'
csky-linux-ld: drivers/hid/hid-lamparray.o: in function `use_leds_uapi_store':
hid-lamparray.c:(.text+0xc06): undefined reference to `led_classdev_multicolor_unregister'
csky-linux-ld: drivers/hid/hid-lamparray.o:hid-lamparray.c:(.text+0xc5c): more undefined references to `led_classdev_multicolor_unregister' follow
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
2026-02-19 13:02 [PATCH] HID: generic: add LampArray support via hid-lamparray helper Tim Guttzeit
2026-02-19 18:44 ` kernel test robot
2026-02-19 18:55 ` kernel test robot
@ 2026-02-19 19:14 ` kernel test robot
2026-02-19 20:28 ` kernel test robot
3 siblings, 0 replies; 5+ messages in thread
From: kernel test robot @ 2026-02-19 19:14 UTC (permalink / raw)
To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
Cc: llvm, oe-kbuild-all, wse, Tim Guttzeit, linux-kernel, linux-input
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-20260219]
[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/20260219-211040
base: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link: https://lore.kernel.org/r/20260219130217.2042972-1-tgu%40tuxedocomputers.com
patch subject: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
config: x86_64-kexec (https://download.01.org/0day-ci/archive/20260219/202602192025.xrvVo680-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/20260219/202602192025.xrvVo680-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/202602192025.xrvVo680-lkp@intel.com/
All errors (new ones prefixed by >>):
>> ld.lld: error: undefined symbol: led_mc_calc_color_components
>>> referenced by hid-lamparray.c:585 (drivers/hid/hid-lamparray.c:585)
>>> vmlinux.o:(lamparray_register_led)
>>> referenced by hid-lamparray.c:449 (drivers/hid/hid-lamparray.c:449)
>>> vmlinux.o:(lamparray_led_brightness_set)
--
>> ld.lld: error: undefined symbol: led_classdev_multicolor_register_ext
>>> referenced by led-class-multicolor.h:70 (include/linux/led-class-multicolor.h:70)
>>> vmlinux.o:(lamparray_register_led)
--
>> ld.lld: error: undefined symbol: led_classdev_multicolor_unregister
>>> referenced by hid-lamparray.c:615 (drivers/hid/hid-lamparray.c:615)
>>> vmlinux.o:(lamparray_unregister_led)
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
2026-02-19 13:02 [PATCH] HID: generic: add LampArray support via hid-lamparray helper Tim Guttzeit
` (2 preceding siblings ...)
2026-02-19 19:14 ` kernel test robot
@ 2026-02-19 20:28 ` kernel test robot
3 siblings, 0 replies; 5+ messages in thread
From: kernel test robot @ 2026-02-19 20:28 UTC (permalink / raw)
To: Tim Guttzeit, Jiri Kosina, Benjamin Tissoires
Cc: oe-kbuild-all, wse, Tim Guttzeit, linux-kernel, linux-input
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-20260219]
[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/20260219-211040
base: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-next
patch link: https://lore.kernel.org/r/20260219130217.2042972-1-tgu%40tuxedocomputers.com
patch subject: [PATCH] HID: generic: add LampArray support via hid-lamparray helper
config: x86_64-rhel-9.4 (https://download.01.org/0day-ci/archive/20260219/202602192131.Q9y8Kqvt-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260219/202602192131.Q9y8Kqvt-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/202602192131.Q9y8Kqvt-lkp@intel.com/
All errors (new ones prefixed by >>):
ld: vmlinux.o: in function `lamparray_led_brightness_set':
>> drivers/hid/hid-lamparray.c:449:(.text+0xcb5bbd): undefined reference to `led_mc_calc_color_components'
ld: vmlinux.o: in function `lamparray_register_led':
drivers/hid/hid-lamparray.c:585:(.text+0xcb5db3): undefined reference to `led_mc_calc_color_components'
ld: vmlinux.o: in function `led_classdev_multicolor_register':
>> include/linux/led-class-multicolor.h:70:(.text+0xcb5dd9): undefined reference to `led_classdev_multicolor_register_ext'
ld: vmlinux.o: in function `lamparray_unregister_led':
>> drivers/hid/hid-lamparray.c:615:(.text+0xcb60eb): undefined reference to `led_classdev_multicolor_unregister'
>> ld: drivers/hid/hid-lamparray.c:615:(.text+0xcb620b): undefined reference to `led_classdev_multicolor_unregister'
ld: drivers/hid/hid-lamparray.c:615:(.text+0xcb636d): undefined reference to `led_classdev_multicolor_unregister'
vim +449 drivers/hid/hid-lamparray.c
434
435 static int lamparray_led_brightness_set(struct led_classdev *cdev,
436 enum led_brightness brightness)
437 {
438 struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
439 struct lamparray_device *ldev =
440 container_of(mc, struct lamparray_device, mc_cdev);
441 struct lamparray *la = container_of(ldev, struct lamparray, ldev);
442 u8 r, g, b;
443 int ret;
444
445 if (!la)
446 return -ENODEV;
447 ldev = &la->ldev;
448
> 449 ret = led_mc_calc_color_components(mc, brightness);
450 if (ret)
451 return ret;
452
453 r = mc->subled_info[0].brightness;
454 g = mc->subled_info[1].brightness;
455 b = mc->subled_info[2].brightness;
456
457 ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
458 if (ret)
459 hid_err(ldev->hdev, "Failed to send LampArray update: %d\n",
460 ret);
461
462 mutex_lock(&ldev->lock);
463 ldev->last_r = r;
464 ldev->last_g = g;
465 ldev->last_b = b;
466 ldev->last_brightness = brightness;
467 mutex_unlock(&ldev->lock);
468
469 return 0;
470 }
471
472 static enum led_brightness
473 lamparray_led_brightness_get(struct led_classdev *cdev)
474 {
475 struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
476 struct lamparray_device *ldev =
477 container_of(mc, struct lamparray_device, mc_cdev);
478 enum led_brightness brightness;
479 struct lamparray *la = container_of(ldev, struct lamparray, ldev);
480 u8 rr, gg, bb;
481 enum led_brightness br;
482 int ret;
483
484 /* Default: cache (also used while registering LED classdev) */
485 mutex_lock(&ldev->lock);
486 brightness = ldev->last_brightness;
487 mutex_unlock(&ldev->lock);
488
489 /* Only do HID readback after registration completed */
490 if (READ_ONCE(ldev->led_registered)) {
491 if (!la)
492 return brightness;
493 ldev = &la->ldev;
494
495 ret = lamparray_hw_get_state(ldev, &rr, &gg, &bb, &br);
496 if (ret) {
497 hid_warn(ldev->hdev,
498 "Failed to read LampArray state (%d), using cached brightness %u\n",
499 ret, brightness);
500 return brightness;
501 }
502
503 mutex_lock(&ldev->lock);
504 if (ldev->last_r != rr || ldev->last_g != gg ||
505 ldev->last_b != bb || ldev->last_brightness != br) {
506 ldev->last_r = rr;
507 ldev->last_g = gg;
508 ldev->last_b = bb;
509 ldev->last_brightness = br;
510
511 if (ldev->led_registered && ldev->mc_cdev.subled_info) {
512 ldev->mc_cdev.subled_info[0].brightness = rr;
513 ldev->mc_cdev.subled_info[1].brightness = gg;
514 ldev->mc_cdev.subled_info[2].brightness = bb;
515 }
516 }
517 mutex_unlock(&ldev->lock);
518 return br;
519 }
520 return brightness;
521 }
522
523 static int lamparray_register_led(struct lamparray_device *ldev)
524 {
525 struct device *dev = &ldev->hdev->dev;
526 struct led_classdev *cdev = &ldev->mc_cdev.led_cdev;
527 u8 r_i, g_i, b_i;
528 int ret;
529
530 mutex_lock(&ldev->lock);
531
532 if (ldev->led_registered) {
533 mutex_unlock(&ldev->lock);
534 return 0;
535 }
536
537 if (!cdev->name) {
538 cdev->name =
539 devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev));
540 if (!cdev->name) {
541 mutex_unlock(&ldev->lock);
542 return -ENOMEM;
543 }
544 }
545
546 cdev->max_brightness = 255;
547 cdev->brightness_set_blocking = lamparray_led_brightness_set;
548 cdev->brightness_get = lamparray_led_brightness_get;
549 cdev->brightness = ldev->last_brightness;
550
551 ldev->subleds[0].color_index = LED_COLOR_ID_RED;
552 ldev->subleds[1].color_index = LED_COLOR_ID_GREEN;
553 ldev->subleds[2].color_index = LED_COLOR_ID_BLUE;
554
555 /*
556 * Initialize the color mix (multi_intensity) from the last known HW/init
557 * state so that writing only /brightness scales the expected default color
558 * instead of white.
559 *
560 * If last_brightness is non-zero, treat last_r/g/b as per-channel
561 * brightness and normalize back to intensities (0..255).
562 * If last_brightness is zero, keep last_r/g/b as the intended mix.
563 */
564 if (ldev->last_brightness) {
565 r_i = (u8)min_t(unsigned int, 255,
566 (ldev->last_r * 255u) / ldev->last_brightness);
567 g_i = (u8)min_t(unsigned int, 255,
568 (ldev->last_g * 255u) / ldev->last_brightness);
569 b_i = (u8)min_t(unsigned int, 255,
570 (ldev->last_b * 255u) / ldev->last_brightness);
571 } else {
572 r_i = ldev->last_r;
573 g_i = ldev->last_g;
574 b_i = ldev->last_b;
575 }
576
577 ldev->subleds[0].intensity = r_i;
578 ldev->subleds[1].intensity = g_i;
579 ldev->subleds[2].intensity = b_i;
580
581 ldev->mc_cdev.subled_info = ldev->subleds;
582 ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
583
584 /* Ensure subled_info[].brightness matches intensity + brightness */
585 led_mc_calc_color_components(&ldev->mc_cdev, cdev->brightness);
586
587 ldev->mc_cdev.subled_info = ldev->subleds;
588 ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
589
590 mutex_unlock(&ldev->lock);
591
592 ret = led_classdev_multicolor_register(dev, &ldev->mc_cdev);
593 if (ret)
594 return ret;
595
596 mutex_lock(&ldev->lock);
597 ldev->led_registered = true;
598 mutex_unlock(&ldev->lock);
599
600 return 0;
601 }
602
603 static void lamparray_unregister_led(struct lamparray_device *ldev)
604 {
605 bool was_registered;
606
607 mutex_lock(&ldev->lock);
608 was_registered = ldev->led_registered;
609 ldev->led_registered = false;
610 mutex_unlock(&ldev->lock);
611
612 if (!was_registered)
613 return;
614
> 615 led_classdev_multicolor_unregister(&ldev->mc_cdev);
616 }
617
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-02-19 20:28 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-19 13:02 [PATCH] HID: generic: add LampArray support via hid-lamparray helper Tim Guttzeit
2026-02-19 18:44 ` kernel test robot
2026-02-19 18:55 ` kernel test robot
2026-02-19 19:14 ` kernel test robot
2026-02-19 20:28 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox