From: Andrei Aldea <andrei1998@gmail.com>
To: Jiri Kosina <jikos@kernel.org>,
Benjamin Tissoires <bentiss@kernel.org>,
"Derek J. Clark" <derekjohn.clark@gmail.com>
Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
Lee Jones <lee@kernel.org>, Pavel Machek <pavel@kernel.org>,
linux-leds@vger.kernel.org
Subject: [PATCH 13/15] HID: hid-oxp: represent RGB LEDs with a common array
Date: Wed, 9 Sep 2026 22:21:13 -0500 [thread overview]
Message-ID: <20260910032115.28669-14-andrei1998@gmail.com> (raw)
In-Reply-To: <20260910032115.28669-1-andrei1998@gmail.com>
Move the existing joystick-ring class device, color components, delayed
work, and cached settings into a per-LED wrapper owned by each HID
configuration. Use a tagged state pointer so later LED types can share
registration and work management without duplicating the lifecycle.
Track only fully initialized work items and walk that count when
quiescing, suspending, or resuming the configuration. Resolve LED callbacks
through their containing wrapper, preserving the LED core's drvdata.
Keep a single FULL joystick-ring LED and retain the existing Gen1/Gen2
55/57-byte RGB payloads, controls, and defaults. This commit adds no new
hardware protocol or lighting zones.
Assisted-by: LLM
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Signed-off-by: Andrei Aldea <andrei1998@gmail.com>
---
drivers/hid/hid-oxp.c | 433 +++++++++++++++++++++++++++++-------------
1 file changed, 297 insertions(+), 136 deletions(-)
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 48fa916..55f8b47 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -198,6 +198,8 @@ struct oxp_bmap_page_2 {
struct oxp_button_idx btn_m2;
} __packed;
+struct oxp_rgb_led;
+
/* Hybrid devices expose RGB and controller configuration on separate HIDs. */
struct oxp_hid_cfg {
/* General HID state */
@@ -218,20 +220,13 @@ struct oxp_hid_cfg {
u8 bmap_format;
/* RGB state */
- struct delayed_work oxp_rgb_queue;
- struct mc_subled subled_info[3];
- struct led_classdev_mc *led_mc;
- struct led_classdev_mc cdev;
+ struct oxp_rgb_led *rgb_leds;
spinlock_t rgb_reply_lock;
struct mutex rgb_mutex; /*serialize complete RGB transactions*/
- bool rgb_work_initialized;
bool rgb_reply_pending;
u8 rgb_reply_command;
u8 rgb_reply_zone;
- u8 rgb_brightness;
- u8 rgb_effect;
- u8 rgb_speed;
- u8 rgb_en;
+ u8 rgb_led_count;
};
enum oxp_gamepad_mode_index {
@@ -336,6 +331,31 @@ struct oxp_gen_2_rgb_report {
u8 effect;
} __packed;
+enum oxp_rgb_type {
+ OXP_RGB_FULL,
+};
+
+struct oxp_rgb_full_state {
+ u8 brightness;
+ u8 enabled;
+ u8 effect;
+ u8 speed;
+};
+
+struct oxp_rgb_led {
+ struct mc_subled subled_info[3];
+ struct led_classdev_mc mc_cdev;
+ struct delayed_work work;
+ struct oxp_hid_cfg *cfg;
+ void *state;
+ u8 type;
+};
+
+struct oxp_rgb_led_desc {
+ const char *name;
+ u8 type;
+};
+
struct oxp_attr {
u8 index;
};
@@ -352,29 +372,55 @@ static u16 get_usage_page(struct hid_device *hdev)
return hdev->collection[0].usage >> 16;
}
+static struct oxp_rgb_led *oxp_rgb_led_by_type(struct oxp_hid_cfg *cfg, u8 type)
+{
+ int i;
+
+ for (i = 0; i < cfg->rgb_led_count; i++)
+ if (cfg->rgb_leds[i].type == type)
+ return &cfg->rgb_leds[i];
+
+ return NULL;
+}
+
+static struct oxp_rgb_full_state *oxp_rgb_full_state(struct oxp_rgb_led *led)
+{
+ if (!led)
+ return NULL;
+
+ switch (led->type) {
+ case OXP_RGB_FULL:
+ return led->state;
+ }
+
+ return NULL;
+}
+
static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
struct hid_report *report, u8 *data,
int size)
{
struct oxp_hid_cfg *cfg = hid_get_drvdata(hdev);
- struct led_classdev_mc *led_mc = cfg->led_mc;
+ struct oxp_rgb_led *led = oxp_rgb_led_by_type(cfg, OXP_RGB_FULL);
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
struct oxp_gen_1_rgb_report *rgb_rep;
+ struct led_classdev_mc *led_mc;
- if (size < sizeof(*rgb_rep) || !led_mc)
+ if (size < sizeof(*rgb_rep) || !state)
return 0;
if (data[1] != OXP_FID_GEN1_RGB_REPLY)
return 0;
+ led_mc = &led->mc_cdev;
rgb_rep = (struct oxp_gen_1_rgb_report *)data;
/* Ensure we save monocolor as the list value */
- cfg->rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
- OXP_EFFECT_MONO_LIST :
- rgb_rep->effect;
- cfg->rgb_speed = rgb_rep->speed;
- cfg->rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
- OXP_FEAT_ENABLED;
- cfg->rgb_brightness = rgb_rep->brightness;
+ state->effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+ OXP_EFFECT_MONO_LIST : rgb_rep->effect;
+ state->speed = rgb_rep->speed;
+ state->enabled = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+ OXP_FEAT_ENABLED;
+ state->brightness = rgb_rep->brightness;
led_mc->led_cdev.brightness = rgb_rep->brightness *
led_mc->led_cdev.max_brightness / 4;
/* If monocolor had less than 100% brightness on the previous boot,
@@ -441,8 +487,10 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
int size)
{
struct oxp_hid_cfg *cfg = hid_get_drvdata(hdev);
- struct led_classdev_mc *led_mc = cfg->led_mc;
+ struct oxp_rgb_led *led = oxp_rgb_led_by_type(cfg, OXP_RGB_FULL);
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
struct oxp_gen_2_rgb_report *rgb_rep;
+ struct led_classdev_mc *led_mc;
bool solicited = false;
if (size < OXP_STATUS_HEADER_SIZE)
@@ -479,22 +527,22 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
if (data[3] != OXP_GET_PROPERTY)
return 0;
- if (size < sizeof(*rgb_rep) || !led_mc)
+ if (size < sizeof(*rgb_rep) || !state)
return 0;
+ led_mc = &led->mc_cdev;
rgb_rep = (struct oxp_gen_2_rgb_report *)data;
if (rgb_rep->enabled > OXP_FEAT_ENABLED || rgb_rep->speed > 9 ||
rgb_rep->brightness > 4)
return 0;
/* Ensure we save monocolor as the list value */
- cfg->rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
- OXP_EFFECT_MONO_LIST :
- rgb_rep->effect;
- cfg->rgb_speed = rgb_rep->speed;
- cfg->rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
- OXP_FEAT_ENABLED;
- cfg->rgb_brightness = rgb_rep->brightness;
+ state->effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+ OXP_EFFECT_MONO_LIST : rgb_rep->effect;
+ state->speed = rgb_rep->speed;
+ state->enabled = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+ OXP_FEAT_ENABLED;
+ state->brightness = rgb_rep->brightness;
led_mc->led_cdev.brightness = rgb_rep->brightness *
led_mc->led_cdev.max_brightness / 4;
/* If monocolor had less than 100% brightness on the previous boot,
@@ -1158,9 +1206,11 @@ static const struct attribute_group oxp_cfg_attrs_group = {
.attrs = oxp_cfg_attrs,
};
-static int oxp_rgb_status_store(struct oxp_hid_cfg *cfg, u8 enabled,
- u8 speed, u8 brightness)
+static int oxp_rgb_status_store(struct oxp_rgb_led *led, u8 enabled, u8 speed,
+ u8 brightness)
{
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
+ struct oxp_hid_cfg *cfg = led->cfg;
u16 up = get_usage_page(cfg->hdev);
u8 *data;
@@ -1170,12 +1220,12 @@ static int oxp_rgb_status_store(struct oxp_hid_cfg *cfg, u8 enabled,
switch (up) {
case GEN1_USAGE_PAGE:
data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness };
- if (cfg->rgb_effect == OXP_EFFECT_MONO_LIST)
+ if (state->effect == OXP_EFFECT_MONO_LIST)
data[3] = 0x04;
return oxp_gen_1_property_out(cfg, OXP_FID_GEN1_RGB_SET, data, 4);
case GEN2_USAGE_PAGE:
data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness };
- if (cfg->rgb_effect == OXP_EFFECT_MONO_LIST)
+ if (state->effect == OXP_EFFECT_MONO_LIST)
data[5] = 0x04;
return oxp_gen_2_property_out(cfg, OXP_FID_GEN2_STATUS_EVENT, data, 6);
default:
@@ -1183,8 +1233,9 @@ static int oxp_rgb_status_store(struct oxp_hid_cfg *cfg, u8 enabled,
}
}
-static ssize_t oxp_rgb_status_show(struct oxp_hid_cfg *cfg)
+static ssize_t oxp_rgb_status_show(struct oxp_rgb_led *led)
{
+ struct oxp_hid_cfg *cfg = led->cfg;
u16 up = get_usage_page(cfg->hdev);
u8 *data;
@@ -1202,19 +1253,21 @@ static ssize_t oxp_rgb_status_show(struct oxp_hid_cfg *cfg)
}
}
-static int oxp_rgb_color_set(struct oxp_hid_cfg *cfg)
+static int oxp_rgb_color_set(struct oxp_rgb_led *led)
{
- u8 br = cfg->led_mc->led_cdev.brightness;
+ struct led_classdev_mc *led_mc = &led->mc_cdev;
+ struct oxp_hid_cfg *cfg = led->cfg;
u16 up = get_usage_page(cfg->hdev);
+ u8 br = led_mc->led_cdev.brightness;
u8 green, red, blue;
size_t size;
u8 *data;
int i;
- led_mc_calc_color_components(cfg->led_mc, br);
- red = cfg->led_mc->subled_info[0].brightness;
- green = cfg->led_mc->subled_info[1].brightness;
- blue = cfg->led_mc->subled_info[2].brightness;
+ led_mc_calc_color_components(led_mc, br);
+ red = led_mc->subled_info[0].brightness;
+ green = led_mc->subled_info[1].brightness;
+ blue = led_mc->subled_info[2].brightness;
switch (up) {
case GEN1_USAGE_PAGE:
@@ -1242,8 +1295,10 @@ static int oxp_rgb_color_set(struct oxp_hid_cfg *cfg)
}
}
-static int oxp_rgb_effect_set(struct oxp_hid_cfg *cfg, u8 effect)
+static int oxp_rgb_effect_set(struct oxp_rgb_led *led, u8 effect)
{
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
+ struct oxp_hid_cfg *cfg = led->cfg;
u16 up = get_usage_page(cfg->hdev);
u8 *data;
int ret;
@@ -1282,7 +1337,7 @@ static int oxp_rgb_effect_set(struct oxp_hid_cfg *cfg, u8 effect)
}
break;
case OXP_EFFECT_MONO_LIST:
- ret = oxp_rgb_color_set(cfg);
+ ret = oxp_rgb_color_set(led);
break;
default:
return -EINVAL;
@@ -1291,26 +1346,31 @@ static int oxp_rgb_effect_set(struct oxp_hid_cfg *cfg, u8 effect)
if (ret)
return ret;
- cfg->rgb_effect = effect;
+ state->effect = effect;
return 0;
}
-static struct oxp_hid_cfg *oxp_rgb_cfg_from_dev(struct device *dev)
+static struct oxp_rgb_led *oxp_rgb_led_from_dev(struct device *dev)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
- return container_of(mc_cdev, struct oxp_hid_cfg, cdev);
+ return container_of(mc_cdev, struct oxp_rgb_led, mc_cdev);
}
static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
- struct oxp_hid_cfg *cfg = oxp_rgb_cfg_from_dev(dev);
+ struct oxp_rgb_led *led = oxp_rgb_led_from_dev(dev);
+ struct oxp_hid_cfg *cfg = led->cfg;
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
int ret;
u8 val;
+ if (!state)
+ return -ENODEV;
+
ret = sysfs_match_string(oxp_feature_en_text, buf);
if (ret < 0)
return ret;
@@ -1318,29 +1378,32 @@ static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
guard(mutex)(&cfg->rgb_mutex);
- ret = oxp_rgb_status_store(cfg, val, cfg->rgb_speed,
- cfg->rgb_brightness);
+ ret = oxp_rgb_status_store(led, val, state->speed, state->brightness);
if (ret)
return ret;
- cfg->rgb_en = val;
+ state->enabled = val;
return count;
}
static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct oxp_hid_cfg *cfg = oxp_rgb_cfg_from_dev(dev);
+ struct oxp_rgb_led *led = oxp_rgb_led_from_dev(dev);
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
int ret;
- ret = oxp_rgb_status_show(cfg);
+ if (!state)
+ return -ENODEV;
+
+ ret = oxp_rgb_status_show(led);
if (ret)
return ret;
- if (cfg->rgb_en >= ARRAY_SIZE(oxp_feature_en_text))
+ if (state->enabled >= ARRAY_SIZE(oxp_feature_en_text))
return -EINVAL;
- return sysfs_emit(buf, "%s\n", oxp_feature_en_text[cfg->rgb_en]);
+ return sysfs_emit(buf, "%s\n", oxp_feature_en_text[state->enabled]);
}
static DEVICE_ATTR_RW(enabled);
@@ -1363,11 +1426,16 @@ static DEVICE_ATTR_RO(enabled_index);
static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
- struct oxp_hid_cfg *cfg = oxp_rgb_cfg_from_dev(dev);
+ struct oxp_rgb_led *led = oxp_rgb_led_from_dev(dev);
+ struct oxp_hid_cfg *cfg = led->cfg;
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
u8 old_effect;
int ret;
u8 val;
+ if (!state)
+ return -ENODEV;
+
ret = sysfs_match_string(oxp_rgb_effect_text, buf);
if (ret < 0)
return ret;
@@ -1375,19 +1443,19 @@ static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
val = ret;
guard(mutex)(&cfg->rgb_mutex);
- old_effect = cfg->rgb_effect;
- cfg->rgb_effect = val;
+ old_effect = state->effect;
+ state->effect = val;
- ret = oxp_rgb_status_store(cfg, cfg->rgb_en, cfg->rgb_speed,
- cfg->rgb_brightness);
+ ret = oxp_rgb_status_store(led, state->enabled, state->speed,
+ state->brightness);
if (ret) {
- cfg->rgb_effect = old_effect;
+ state->effect = old_effect;
return ret;
}
- ret = oxp_rgb_effect_set(cfg, val);
+ ret = oxp_rgb_effect_set(led, val);
if (ret) {
- cfg->rgb_effect = old_effect;
+ state->effect = old_effect;
return ret;
}
@@ -1397,17 +1465,21 @@ static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
static ssize_t effect_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct oxp_hid_cfg *cfg = oxp_rgb_cfg_from_dev(dev);
+ struct oxp_rgb_led *led = oxp_rgb_led_from_dev(dev);
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
int ret;
- ret = oxp_rgb_status_show(cfg);
+ if (!state)
+ return -ENODEV;
+
+ ret = oxp_rgb_status_show(led);
if (ret)
return ret;
- if (cfg->rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text))
+ if (state->effect >= ARRAY_SIZE(oxp_rgb_effect_text))
return -EINVAL;
- return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[cfg->rgb_effect]);
+ return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[state->effect]);
}
static DEVICE_ATTR_RW(effect);
@@ -1431,10 +1503,15 @@ static DEVICE_ATTR_RO(effect_index);
static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
- struct oxp_hid_cfg *cfg = oxp_rgb_cfg_from_dev(dev);
+ struct oxp_rgb_led *led = oxp_rgb_led_from_dev(dev);
+ struct oxp_hid_cfg *cfg = led->cfg;
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
int ret;
u8 val;
+ if (!state)
+ return -ENODEV;
+
ret = kstrtou8(buf, 10, &val);
if (ret)
return ret;
@@ -1444,28 +1521,32 @@ static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
guard(mutex)(&cfg->rgb_mutex);
- ret = oxp_rgb_status_store(cfg, cfg->rgb_en, val, cfg->rgb_brightness);
+ ret = oxp_rgb_status_store(led, state->enabled, val, state->brightness);
if (ret)
return ret;
- cfg->rgb_speed = val;
+ state->speed = val;
return count;
}
static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct oxp_hid_cfg *cfg = oxp_rgb_cfg_from_dev(dev);
+ struct oxp_rgb_led *led = oxp_rgb_led_from_dev(dev);
+ struct oxp_rgb_full_state *state = oxp_rgb_full_state(led);
int ret;
- ret = oxp_rgb_status_show(cfg);
+ if (!state)
+ return -ENODEV;
+
+ ret = oxp_rgb_status_show(led);
if (ret)
return ret;
- if (cfg->rgb_speed > 9)
+ if (state->speed > 9)
return -EINVAL;
- return sysfs_emit(buf, "%hhu\n", cfg->rgb_speed);
+ return sysfs_emit(buf, "%hhu\n", state->speed);
}
static DEVICE_ATTR_RW(speed);
@@ -1476,49 +1557,64 @@ static ssize_t speed_range_show(struct device *dev,
}
static DEVICE_ATTR_RO(speed_range);
-static void oxp_rgb_queue_fn(struct work_struct *work)
+static void oxp_rgb_full_queue(struct oxp_rgb_led *led,
+ struct oxp_rgb_full_state *state)
{
- struct oxp_hid_cfg *cfg = container_of(to_delayed_work(work),
- struct oxp_hid_cfg, oxp_rgb_queue);
- unsigned int max_brightness = cfg->led_mc->led_cdev.max_brightness;
- unsigned int brightness = cfg->led_mc->led_cdev.brightness;
+ unsigned int max_brightness = led->mc_cdev.led_cdev.max_brightness;
+ unsigned int brightness = led->mc_cdev.led_cdev.brightness;
+ struct oxp_hid_cfg *cfg = led->cfg;
u8 val = 4 * brightness / max_brightness;
int ret;
- if (READ_ONCE(cfg->suspended) || READ_ONCE(cfg->removing))
- return;
-
guard(mutex)(&cfg->rgb_mutex);
- if (cfg->rgb_brightness != val) {
- ret = oxp_rgb_status_store(cfg, cfg->rgb_en, cfg->rgb_speed, val);
+ if (state->brightness != val) {
+ ret = oxp_rgb_status_store(led, state->enabled, state->speed, val);
if (ret)
- dev_err(cfg->led_mc->led_cdev.dev,
+ dev_err(led->mc_cdev.led_cdev.dev,
"Error: Failed to write RGB Status: %i\n", ret);
- cfg->rgb_brightness = val;
+ state->brightness = val;
}
- if (cfg->rgb_effect != OXP_EFFECT_MONO_LIST)
+ if (state->effect != OXP_EFFECT_MONO_LIST)
return;
- ret = oxp_rgb_effect_set(cfg, cfg->rgb_effect);
+ ret = oxp_rgb_effect_set(led, state->effect);
if (ret)
- dev_err(cfg->led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n",
- ret);
+ dev_err(led->mc_cdev.led_cdev.dev,
+ "Error: Failed to write RGB color: %i\n", ret);
+}
+
+static void oxp_rgb_queue_fn(struct work_struct *work)
+{
+ struct oxp_rgb_led *led = container_of(to_delayed_work(work),
+ struct oxp_rgb_led, work);
+ struct oxp_hid_cfg *cfg = led->cfg;
+
+ if (READ_ONCE(cfg->suspended) || READ_ONCE(cfg->removing))
+ return;
+
+ switch (led->type) {
+ case OXP_RGB_FULL:
+ oxp_rgb_full_queue(led, oxp_rgb_full_state(led));
+ break;
+ }
}
static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
- struct oxp_hid_cfg *cfg = container_of(mc_cdev, struct oxp_hid_cfg, cdev);
+ struct oxp_rgb_led *led = container_of(mc_cdev, struct oxp_rgb_led,
+ mc_cdev);
+ struct oxp_hid_cfg *cfg = led->cfg;
if (READ_ONCE(cfg->suspended) || READ_ONCE(cfg->removing))
return;
led_cdev->brightness = brightness;
- mod_delayed_work(system_dfl_wq, &cfg->oxp_rgb_queue, msecs_to_jiffies(50));
+ mod_delayed_work(system_dfl_wq, &led->work, msecs_to_jiffies(50));
}
static struct attribute *oxp_rgb_attrs[] = {
@@ -1535,37 +1631,121 @@ static const struct attribute_group oxp_rgb_attr_group = {
.attrs = oxp_rgb_attrs,
};
-static const struct mc_subled oxp_rgb_subled_info[] = {
+static const struct oxp_rgb_led_desc oxp_rgb_led_descs[] = {
{
+ .name = "oxp:rgb:joystick_rings",
+ .type = OXP_RGB_FULL,
+ },
+};
+
+static int oxp_rgb_led_init(struct oxp_hid_cfg *cfg, struct oxp_rgb_led *led,
+ const struct oxp_rgb_led_desc *desc)
+{
+ struct oxp_rgb_full_state *full_state;
+ struct hid_device *hdev = cfg->hdev;
+ u8 green;
+ u8 blue;
+ u8 red;
+
+ led->cfg = cfg;
+ led->type = desc->type;
+
+ switch (led->type) {
+ case OXP_RGB_FULL:
+ full_state = devm_kzalloc(&hdev->dev, sizeof(*full_state),
+ GFP_KERNEL);
+ if (!full_state)
+ return -ENOMEM;
+ led->state = full_state;
+ led->mc_cdev.led_cdev.brightness = 0x64;
+ red = 0x24;
+ green = 0x22;
+ blue = 0x99;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ led->subled_info[0] = (struct mc_subled) {
.color_index = LED_COLOR_ID_RED,
- .intensity = 0x24,
+ .intensity = red,
.max_intensity = 0xff,
.channel = 0x1,
- },
- {
+ };
+ led->subled_info[1] = (struct mc_subled) {
.color_index = LED_COLOR_ID_GREEN,
- .intensity = 0x22,
+ .intensity = green,
.max_intensity = 0xff,
.channel = 0x2,
- },
- {
+ };
+ led->subled_info[2] = (struct mc_subled) {
.color_index = LED_COLOR_ID_BLUE,
- .intensity = 0x99,
+ .intensity = blue,
.max_intensity = 0xff,
.channel = 0x3,
- },
-};
+ };
+ led->mc_cdev.led_cdev.name = desc->name;
+ led->mc_cdev.led_cdev.color = LED_COLOR_ID_RGB;
+ led->mc_cdev.led_cdev.max_brightness = 0x64;
+ led->mc_cdev.led_cdev.brightness_set = oxp_rgb_brightness_set;
+ led->mc_cdev.num_colors = ARRAY_SIZE(led->subled_info);
+ led->mc_cdev.subled_info = led->subled_info;
+ INIT_DELAYED_WORK(&led->work, oxp_rgb_queue_fn);
-static const struct led_classdev_mc oxp_cdev_rgb = {
- .led_cdev = {
- .name = "oxp:rgb:joystick_rings",
- .color = LED_COLOR_ID_RGB,
- .brightness = 0x64,
- .max_brightness = 0x64,
- .brightness_set = oxp_rgb_brightness_set,
- },
- .num_colors = ARRAY_SIZE(oxp_rgb_subled_info),
-};
+ return 0;
+}
+
+static int oxp_rgb_leds_register(struct oxp_hid_cfg *cfg)
+{
+ int led_count = ARRAY_SIZE(oxp_rgb_led_descs);
+ struct hid_device *hdev = cfg->hdev;
+ struct oxp_rgb_led *led;
+ int ret;
+ int i;
+
+ cfg->rgb_leds = devm_kcalloc(&hdev->dev, led_count,
+ sizeof(*cfg->rgb_leds), GFP_KERNEL);
+ if (!cfg->rgb_leds)
+ return -ENOMEM;
+
+ for (i = 0; i < led_count; i++) {
+ led = &cfg->rgb_leds[i];
+ ret = oxp_rgb_led_init(cfg, led, &oxp_rgb_led_descs[i]);
+ if (ret)
+ return ret;
+ cfg->rgb_led_count++;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev,
+ &led->mc_cdev);
+ if (ret)
+ return dev_err_probe(&hdev->dev, ret,
+ "Failed to create RGB device\n");
+
+ ret = devm_device_add_group(led->mc_cdev.led_cdev.dev,
+ &oxp_rgb_attr_group);
+ if (ret)
+ return dev_err_probe(led->mc_cdev.led_cdev.dev, ret,
+ "Failed to create RGB configuration attributes\n");
+ }
+
+ return 0;
+}
+
+static void oxp_rgb_disable_works(struct oxp_hid_cfg *cfg)
+{
+ int i;
+
+ for (i = 0; i < cfg->rgb_led_count; i++)
+ disable_delayed_work_sync(&cfg->rgb_leds[i].work);
+}
+
+static void oxp_rgb_enable_works(struct oxp_hid_cfg *cfg)
+{
+ int i;
+
+ for (i = 0; i < cfg->rgb_led_count; i++)
+ enable_delayed_work(&cfg->rgb_leds[i].work);
+}
static struct quirk_entry quirk_hybrid_mcu = {
.hybrid_mcu = true,
@@ -1644,8 +1824,7 @@ static void oxp_quiesce_work(struct oxp_hid_cfg *cfg)
scoped_guard(spinlock_irqsave, &cfg->rgb_reply_lock) {
cfg->rgb_reply_pending = false;
}
- if (cfg->rgb_work_initialized)
- disable_delayed_work_sync(&cfg->oxp_rgb_queue);
+ oxp_rgb_disable_works(cfg);
if (cfg->gen2_work_initialized) {
disable_delayed_work_sync(&cfg->oxp_btn_queue);
disable_delayed_work_sync(&cfg->oxp_mcu_init);
@@ -1680,6 +1859,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up,
{
struct oxp_bmap_page_1 *bmap_1;
struct oxp_bmap_page_2 *bmap_2;
+ struct oxp_rgb_led *rgb_led;
struct oxp_hid_cfg *cfg;
int ret;
@@ -1701,31 +1881,14 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up,
if (up == GEN2_USAGE_PAGE && quirks && quirks->hybrid_mcu)
goto skip_rgb;
- cfg->cdev = oxp_cdev_rgb;
- memcpy(cfg->subled_info, oxp_rgb_subled_info, sizeof(cfg->subled_info));
- cfg->cdev.subled_info = cfg->subled_info;
- cfg->led_mc = &cfg->cdev;
-
- INIT_DELAYED_WORK(&cfg->oxp_rgb_queue, oxp_rgb_queue_fn);
- cfg->rgb_work_initialized = true;
- ret = devm_led_classdev_multicolor_register(&hdev->dev, cfg->led_mc);
- if (ret) {
- dev_err_probe(&hdev->dev, ret,
- "Failed to create RGB device\n");
- goto err_quiesce;
- }
-
- ret = devm_device_add_group(cfg->led_mc->led_cdev.dev,
- &oxp_rgb_attr_group);
- if (ret) {
- dev_err_probe(cfg->led_mc->led_cdev.dev, ret,
- "Failed to create RGB configuration attributes\n");
+ ret = oxp_rgb_leds_register(cfg);
+ if (ret)
goto err_quiesce;
- }
- ret = oxp_rgb_status_show(cfg);
+ rgb_led = oxp_rgb_led_by_type(cfg, OXP_RGB_FULL);
+ ret = oxp_rgb_status_show(rgb_led);
if (ret)
- dev_warn(cfg->led_mc->led_cdev.dev,
+ dev_warn(rgb_led->mc_cdev.led_cdev.dev,
"Failed to query RGB initial state: %i\n", ret);
/* Below features are only implemented in gen 2 */
@@ -1841,8 +2004,7 @@ static int __maybe_unused oxp_hid_suspend(struct hid_device *hdev,
scoped_guard(spinlock_irqsave, &cfg->rgb_reply_lock) {
cfg->rgb_reply_pending = false;
}
- if (cfg->rgb_work_initialized)
- disable_delayed_work_sync(&cfg->oxp_rgb_queue);
+ oxp_rgb_disable_works(cfg);
if (cfg->gen2_work_initialized) {
disable_delayed_work_sync(&cfg->oxp_btn_queue);
disable_delayed_work_sync(&cfg->oxp_mcu_init);
@@ -1860,8 +2022,7 @@ static int __maybe_unused oxp_hid_resume(struct hid_device *hdev)
READ_ONCE(cfg->removing))
return 0;
- if (cfg->rgb_work_initialized)
- enable_delayed_work(&cfg->oxp_rgb_queue);
+ oxp_rgb_enable_works(cfg);
if (cfg->gen2_work_initialized) {
enable_delayed_work(&cfg->oxp_btn_queue);
enable_delayed_work(&cfg->oxp_mcu_init);
next prev parent reply other threads:[~2026-09-10 3:21 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 3:21 [PATCH 00/15] HID: hid-oxp: fix and extend X2-family controller support Andrei Aldea
2026-09-10 3:21 ` [PATCH 01/15] HID: hid-oxp: fix default M1 and M2 key mappings Andrei Aldea
2026-09-10 3:21 ` [PATCH 02/15] HID: hid-oxp: validate input report lengths before decoding Andrei Aldea
2026-09-10 3:34 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 03/15] HID: hid-oxp: retain fractional brightness when reading RGB status Andrei Aldea
2026-09-10 3:29 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 04/15] HID: hid-oxp: reject invalid Gen2 RGB status values Andrei Aldea
2026-09-10 3:32 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 05/15] HID: hid-oxp: fix multicolor LED intensity scaling Andrei Aldea
2026-09-10 3:33 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 06/15] HID: hid-oxp: serialize complete RGB updates Andrei Aldea
2026-09-10 3:32 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 07/15] HID: hid-oxp: select brightness policy for the new RGB effect Andrei Aldea
2026-09-10 3:33 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 08/15] HID: hid-oxp: stop configuration work during teardown Andrei Aldea
2026-09-10 3:32 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 09/15] HID: hid-oxp: keep configuration state per HID interface Andrei Aldea
2026-09-10 3:35 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 10/15] HID: hid-oxp: handle controller reinitialization across suspend Andrei Aldea
2026-09-10 3:34 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 11/15] HID: hid-oxp: group declarations and protocol definitions Andrei Aldea
2026-09-10 3:40 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 12/15] HID: hid-oxp: support three-page button maps on X2 controllers Andrei Aldea
2026-09-10 3:40 ` sashiko-bot
2026-09-10 3:21 ` Andrei Aldea [this message]
2026-09-10 3:44 ` [PATCH 13/15] HID: hid-oxp: represent RGB LEDs with a common array sashiko-bot
2026-09-10 3:21 ` [PATCH 14/15] HID: hid-oxp: add Gen3 joystick ring RGB support Andrei Aldea
2026-09-10 3:43 ` sashiko-bot
2026-09-10 3:21 ` [PATCH 15/15] HID: hid-oxp: add X2 auxiliary RGB zones Andrei Aldea
2026-09-10 3:44 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260910032115.28669-14-andrei1998@gmail.com \
--to=andrei1998@gmail.com \
--cc=bentiss@kernel.org \
--cc=derekjohn.clark@gmail.com \
--cc=jikos@kernel.org \
--cc=lee@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=pavel@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.