From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-oo2-f42.google.com (mail-oo2-f42.google.com [74.125.231.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4CFD23AAF63 for ; Thu, 10 Sep 2026 03:21:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.231.170 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789010504; cv=none; b=nFZ7Y0CAxGnZv7CHb0dwuSVCGrp4ChI1gULsOGU4OQiBA0iwYi7G1iyvaPZfzxEu3cy9X0iLGVvXF79/dVSU+lZZLa//DD98Hb/AEQvG2LxbnVZojqoYvIshnYUxyxqSx1sY2IAHJTr+CQ+VNrcVdTrTufnO1qWA7j9u8g7uvoY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789010504; c=relaxed/simple; bh=n7PxzJwow6goQxqFXofo7b7F5m0GVCyGNZusi68VtWs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=O2SuCKxNezHX16NKhMfu6FsIzwyo0qOcUV2hSCBX0hVcqGdtCpZfr/HXjjcZD7ItDClbEElqMo01G/mrP4ZYZGHc7Ob6eDJQa6xlCBrNB39zkbscKWKOPkuc69f75ItQWdaCWWu4zN0qrRvOeaHQcHLKuEttV6kMbyiezGNdAQM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=e05Cq2OQ; arc=none smtp.client-ip=74.125.231.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="e05Cq2OQ" Received: by mail-oo2-f42.google.com with SMTP id 46e09a7af769-801d2f7a00eso164365a34.0 for ; Wed, 09 Sep 2026 20:21:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789010500; x=1789615300; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=SYHmtxUSY9vp5nzuqjNrcemqcLc6QeYPUNm9qo4Y6tI=; b=e05Cq2OQs9OpA/6BOxW4QYp8cqeZkZ7goOkx3lgQehXQax8iQw9jqJ9/QAiPf93aYQ Rh2y5waHJY7ksVqa44YnabyN5VziOEPhGXWxKNLDrLWYxKxxoPWl4Ol6DJ+mV1RTmOrC n9rgn8LNmgB0L70yXJoFUYFkfCCbQV9s1sBgRnMYWT6icuVQXaGzuwC8M+gr9BTGqVIe ayH5PrFNGy/GzCHr3NwUWvVSUc8mI30EeyHFx91S1vtfxNlzMJ4YpO/1dMoAwO4jmCgG QaFTH33DeTaQohmwBVh8Bfx+hg58LrqdJFT/Lfm8Ew+/JGo6vMAKRn33AUKe3LWQCKMh EbrA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789010500; x=1789615300; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=SYHmtxUSY9vp5nzuqjNrcemqcLc6QeYPUNm9qo4Y6tI=; b=CLskW8lIGsMZQh3qhxN4esBtFOee0K8cmKGw3vfa82bMwnQKoKQBf3Pzh4GmzGdyLZ 40b6Xy1ms2AzH9XEhp6Z188EhJDS15xNqoQIYSUK4nfPsQOa1UdXX7qzaKs10gXOYmPb aWHc6X0z7TuWac3J+9xD/0hAqNEqySBs4UxbicuB8f0n9tOmR70PZAMuXRK1Is9V7nXR gb43X2G2TdfqUr8M4Jn03Toh54uGuZVEzZ6VQOXohmipB+2Bjx1ubL83FY6hzMrULNZi 7n/VLqgqLZ7/kLioOX5tYhFMeieJb+5B3PfdykXaBdZ3XL3Z0iblvDf1znO9yeN7heFK l3ew== X-Forwarded-Encrypted: i=1; AKwUvBw3o1Hfv94u9B4kzWkZYdw9atrtcfW7MwYvQHkXRRsGTzp4PQDNgUzyDQfdd8IgWd0ZUea+6kcn6XtR@vger.kernel.org X-Gm-Message-State: AFuF++mC2GqiPfNUd4EtqGtKGJSFB8X3m2E0HqAYwx9aPWV4F/dkbO41 QffV62cWtMU2Yk1nPbH8Zpi//4pNSxJEqvo135P8QIa+Q8V5CGIncY6Nj+befY0yODBpWipQ X-Gm-Gg: AYBFou3eec9n42Kuc28P1ZGR79Sl/snJha5Px/TVmciIcZP7jMiTmrLD12I7PnWPxXE Hos80ZLBNSp9duFojiOS4QJqKYfP4q2v2vHK1NbX1W+pwVo6W1HqZDHb1KZoxLLqpY1MV0C0vy6 cg4xtQgO9EMkodO1+LNmi0SkZeKG7g1cx+AlnvU+y5YlevMbN5JqI1UzYOESvOniMgFvlmM3C5e oHI+BpcDM4wd4ZEomBidkaUAUNVL+apmfXePqvZim6UiWssaRnOqLmaRyO0I18OnRS5Y1aoc9Mu aYVZKHugY3Y98/R3aT/pWvZr5yCLNrAYGN46lG/jdd5iM21cbDC5DGsXqNpXbRkVM5M//7YMbTj 0FrIPjm/pESFt0eySGuCW6W11/ed4uGzcJk4MOCpry1N7SEV4p+3nUh4ea78w7XrgTN2fFzbPME cbnjsuydgasiftzQAkprDDrOK/sn3vTUiNrffCnx5eylCrXb1rHOOe0C7qsIhWzIuQvGJs/Gp2a xjkAVYCmO9taRmU8XPlDSZnZud40gUGGfydI0UfsDEJ9u8mK/fQU65dfA== X-Received: by 2002:a05:6830:3495:b0:801:b3e8:25ab with SMTP id 46e09a7af769-801b3e8a6c8mr4014082a34.2.1789010500192; Wed, 09 Sep 2026 20:21:40 -0700 (PDT) Received: from localhost ([76.187.192.44]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7f9f6da9a49sm19758315a34.13.2026.09.09.20.21.39 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 09 Sep 2026 20:21:39 -0700 (PDT) From: Andrei Aldea To: Jiri Kosina , Benjamin Tissoires , "Derek J. Clark" Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Lee Jones , Pavel Machek , 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 Message-ID: <20260910032115.28669-14-andrei1998@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260910032115.28669-1-andrei1998@gmail.com> References: <20260910032115.28669-1-andrei1998@gmail.com> Precedence: bulk X-Mailing-List: linux-leds@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Andrei Aldea --- 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);