Linux LED subsystem development
 help / color / mirror / Atom feed
* [PATCH v3] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode
@ 2026-06-29  9:51 Fenglin Wu
  2026-06-29 10:01 ` sashiko-bot
  2026-06-29 15:05 ` kernel test robot
  0 siblings, 2 replies; 3+ messages in thread
From: Fenglin Wu @ 2026-06-29  9:51 UTC (permalink / raw)
  To: linux-arm-msm, Lee Jones, Pavel Machek, Bjorn Andersson,
	Marijn Suijten, Anjelique Melendez, Guru Das Srinagesh
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
	Pavel Machek, linux-leds, linux-kernel, Fenglin Wu

Currently, when the LED is configured as a RGB LED or a multi-color
LED device, the same pattern is programmed for all LED channels
regardless of the sub-led intensities when triggered by HW pattern.
It results that the LED device is always working in a white-balanced
mode regardless of the intensity settings.

To fix this, scale the pattern data according to the sub-led intensity
and program the HW pattern separately for each LPG channel.

Fixes: 24e2d05d1b68 ("leds: Add driver for Qualcomm LPG")
Fixes: 6ab1f766a80a ("leds: rgb: leds-qcom-lpg: Add support for PPG through single SDAM")
Fixes: 5e9ff626861a ("leds: rgb: leds-qcom-lpg: Include support for PPG with dedicated LUT SDAM")
Assisted-by: Claude:claude-4-6-sonnet
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
Changes in v3:
- update to use __free() and guard(mutex) for easy cleanup
- Link to v2: https://patch.msgid.link/20260624-lpg-rgb-color-balance-fix-v2-1-c01b0e50caf6@oss.qualcomm.com

Changes in v2:
- Change to use tab for the indention in the comments of 'struct lpg_pattern'
- Remove the comment in lpg_prepare_pattern() as the function name is
  self-explantory.
- Link to v1: https://patch.msgid.link/20260605-lpg-rgb-color-balance-fix-v1-1-3233644a3385@oss.qualcomm.com
---
 drivers/leds/rgb/leds-qcom-lpg.c | 173 ++++++++++++++++++++++++++++++---------
 1 file changed, 133 insertions(+), 40 deletions(-)

diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
index d7d6518de30f..c3fe8de33242 100644
--- a/drivers/leds/rgb/leds-qcom-lpg.c
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -148,6 +148,24 @@ struct lpg_channel {
 	unsigned int pattern_hi_idx;
 };
 
+/**
+ * struct lpg_pattern - The LPG pattern normalized from the LED pattern
+ * @data:	The pattern data array (caller must kfree)
+ * @len:	number of entries to write to the LUT
+ * @delta_t:	common step duration in ms
+ * @lo_pause:	low-pause duration in ms
+ * @hi_pause:	high-pause duration in ms
+ * @ping_pong:	true if the pattern support reverse
+ */
+struct lpg_pattern {
+	struct led_pattern *data;
+	unsigned int len;
+	unsigned int delta_t;
+	unsigned int lo_pause;
+	unsigned int hi_pause;
+	bool ping_pong;
+};
+
 /**
  * struct lpg_led - logical LED object
  * @lpg:		lpg context reference
@@ -959,23 +977,15 @@ static int lpg_blink_mc_set(struct led_classdev *cdev,
 	return ret;
 }
 
-static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
-			   u32 len, int repeat)
+static int lpg_prepare_pattern(struct lpg *lpg, struct led_pattern *led_pattern,
+				u32 len, int repeat, struct lpg_pattern *prep)
 {
-	struct lpg_channel *chan;
-	struct lpg *lpg = led->lpg;
-	struct led_pattern *pattern;
 	unsigned int brightness_a;
 	unsigned int brightness_b;
-	unsigned int hi_pause = 0;
-	unsigned int lo_pause = 0;
 	unsigned int actual_len;
 	unsigned int delta_t;
-	unsigned int lo_idx;
-	unsigned int hi_idx;
 	unsigned int i;
 	bool ping_pong = true;
-	int ret = -EINVAL;
 
 	/* Hardware only support oneshot or indefinite loops */
 	if (repeat != -1 && repeat != 1)
@@ -995,15 +1005,16 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
 	if (len % 2)
 		return -EINVAL;
 
-	pattern = kzalloc_objs(*pattern, len / 2);
+	struct led_pattern *pattern __free(kfree) = kzalloc_objs(*pattern, len / 2);
+
 	if (!pattern)
 		return -ENOMEM;
 
 	for (i = 0; i < len; i += 2) {
 		if (led_pattern[i].brightness != led_pattern[i + 1].brightness)
-			goto out_free_pattern;
+			return -EINVAL;
 		if (led_pattern[i + 1].delta_t != 0)
-			goto out_free_pattern;
+			return -EINVAL;
 
 		pattern[i / 2].brightness = led_pattern[i].brightness;
 		pattern[i / 2].delta_t = led_pattern[i].delta_t;
@@ -1016,7 +1027,7 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
 	 * through the entire LUT, so prohibit this.
 	 */
 	if (len < 2)
-		goto out_free_pattern;
+		return -EINVAL;
 
 	/*
 	 * The LPG plays patterns with at a fixed pace, a "low pause" can be
@@ -1073,13 +1084,13 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
 			 * specify hi pause. Reject other variations.
 			 */
 			if (i != actual_len - 1)
-				goto out_free_pattern;
+				return -EINVAL;
 		}
 	}
 
 	/* LPG_RAMP_DURATION_REG is a 9bit */
 	if (delta_t >= BIT(9))
-		goto out_free_pattern;
+		return -EINVAL;
 
 	/*
 	 * Find "low pause" and "high pause" in the pattern in the LUT case.
@@ -1087,43 +1098,64 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
 	 * duration of all steps.
 	 */
 	if (lpg->lut_base || lpg->lut_sdam) {
-		lo_pause = pattern[0].delta_t;
-		hi_pause = pattern[actual_len - 1].delta_t;
+		prep->lo_pause = pattern[0].delta_t;
+		prep->hi_pause = pattern[actual_len - 1].delta_t;
 	} else {
 		if (delta_t != pattern[0].delta_t || delta_t != pattern[actual_len - 1].delta_t)
-			goto out_free_pattern;
+			return -EINVAL;
+		prep->lo_pause = 0;
+		prep->hi_pause = 0;
 	}
 
+	prep->data = pattern;
+	prep->len = actual_len;
+	prep->delta_t = delta_t;
+	prep->ping_pong = ping_pong;
 
-	mutex_lock(&lpg->lock);
+	return 0;
+}
+
+static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
+			   u32 len, int repeat)
+{
+	struct lpg_channel *chan;
+	struct lpg *lpg = led->lpg;
+	struct lpg_pattern pattern;
+	unsigned int lo_idx;
+	unsigned int hi_idx;
+	unsigned int i;
+	int ret;
+
+	ret = lpg_prepare_pattern(lpg, led_pattern, len, repeat, &pattern);
+	if (ret < 0)
+		return ret;
+
+	guard(mutex)(&lpg->lock);
 
 	if (lpg->lut_base)
-		ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx);
+		ret = lpg_lut_store(lpg, pattern.data, pattern.len, &lo_idx, &hi_idx);
 	else
-		ret = lpg_lut_store_sdam(lpg, pattern, actual_len, &lo_idx, &hi_idx);
+		ret = lpg_lut_store_sdam(lpg, pattern.data, pattern.len, &lo_idx, &hi_idx);
 
 	if (ret < 0)
-		goto out_unlock;
+		goto free_pattern;
 
 	for (i = 0; i < led->num_channels; i++) {
 		chan = led->channels[i];
 
-		chan->ramp_tick_ms = delta_t;
-		chan->ramp_ping_pong = ping_pong;
+		chan->ramp_tick_ms = pattern.delta_t;
+		chan->ramp_ping_pong = pattern.ping_pong;
 		chan->ramp_oneshot = repeat != -1;
 
-		chan->ramp_lo_pause_ms = lo_pause;
-		chan->ramp_hi_pause_ms = hi_pause;
+		chan->ramp_lo_pause_ms = pattern.lo_pause;
+		chan->ramp_hi_pause_ms = pattern.hi_pause;
 
 		chan->pattern_lo_idx = lo_idx;
 		chan->pattern_hi_idx = hi_idx;
 	}
 
-out_unlock:
-	mutex_unlock(&lpg->lock);
-out_free_pattern:
-	kfree(pattern);
-
+free_pattern:
+	kfree(pattern.data);
 	return ret;
 }
 
@@ -1144,23 +1176,86 @@ static int lpg_pattern_single_set(struct led_classdev *cdev,
 }
 
 static int lpg_pattern_mc_set(struct led_classdev *cdev,
-			      struct led_pattern *pattern, u32 len,
+			      struct led_pattern *led_pattern, u32 len,
 			      int repeat)
 {
 	struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
 	struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
+	struct lpg *lpg = led->lpg;
+	struct lpg_channel *chan;
+	struct lpg_pattern pattern;
 	unsigned int triled_mask = 0;
-	int ret, i;
+	unsigned int lo_idx;
+	unsigned int hi_idx;
+	unsigned int scale;
+	unsigned int i, j;
+	int ret;
+
+	ret = lpg_prepare_pattern(lpg, led_pattern, len, repeat, &pattern);
+	if (ret < 0)
+		return ret;
+
+	/* Allocate buffer for the per-channel scaled pattern copy */
+	struct led_pattern *scaled __free(kfree) =
+			   kmalloc_array(pattern.len, sizeof(*scaled), GFP_KERNEL);
+	if (!scaled) {
+		ret = -ENOMEM;
+		goto free_pattern;
+	}
 
 	for (i = 0; i < led->num_channels; i++)
 		triled_mask |= led->channels[i]->triled_mask;
-	triled_set(led->lpg, triled_mask, 0);
+	triled_set(lpg, triled_mask, 0);
 
-	ret = lpg_pattern_set(led, pattern, len, repeat);
+	led_mc_calc_color_components(mc, LED_FULL);
+
+	/*
+	 * Each channel gets its own LUT block scaled by subled_info[i].brightness
+	 * so the pattern respects the configured colour balance.
+	 */
+	guard(mutex)(&lpg->lock);
+
+	for (i = 0; i < led->num_channels; i++) {
+		chan = led->channels[i];
+		scale = mc->subled_info[i].brightness;
+
+		for (j = 0; j < pattern.len; j++) {
+			scaled[j].brightness = DIV_ROUND_CLOSEST(
+				(u32)pattern.data[j].brightness * scale, LED_FULL);
+			scaled[j].delta_t = pattern.data[j].delta_t;
+		}
+
+		if (lpg->lut_base)
+			ret = lpg_lut_store(lpg, scaled, pattern.len, &lo_idx, &hi_idx);
+		else
+			ret = lpg_lut_store_sdam(lpg, scaled, pattern.len, &lo_idx, &hi_idx);
+
+		if (ret < 0) {
+			/* Free LUT slots already allocated for previous channels */
+			while (i-- > 0) {
+				chan = led->channels[i];
+				lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx);
+				chan->pattern_lo_idx = 0;
+				chan->pattern_hi_idx = 0;
+			}
+
+			goto free_pattern;
+		}
+
+		chan->ramp_tick_ms = pattern.delta_t;
+		chan->ramp_ping_pong = pattern.ping_pong;
+		chan->ramp_oneshot = repeat != -1;
+		chan->ramp_lo_pause_ms = pattern.lo_pause;
+		chan->ramp_hi_pause_ms = pattern.hi_pause;
+		chan->pattern_lo_idx = lo_idx;
+		chan->pattern_hi_idx = hi_idx;
+	}
+
+free_pattern:
+	kfree(pattern.data);
 	if (ret < 0)
 		return ret;
 
-	led_mc_calc_color_components(mc, LED_FULL);
 	lpg_brightness_set(led, cdev, mc->subled_info);
 
 	return 0;
@@ -1174,11 +1269,9 @@ static int lpg_pattern_clear(struct lpg_led *led)
 
 	mutex_lock(&lpg->lock);
 
-	chan = led->channels[0];
-	lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx);
-
 	for (i = 0; i < led->num_channels; i++) {
 		chan = led->channels[i];
+		lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx);
 		lpg_sdam_configure_triggers(chan, 0);
 		lpg_clear_pbs_trigger(chan->lpg, chan->lut_mask);
 		chan->pattern_lo_idx = 0;

---
base-commit: 1795fd2dbe84ef4d393b69a0b2a3b371f810bde5
change-id: 20260605-lpg-rgb-color-balance-fix-82436649abf3

Best regards,
--  
Fenglin Wu <fenglin.wu@oss.qualcomm.com>


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH v3] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode
  2026-06-29  9:51 [PATCH v3] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode Fenglin Wu
@ 2026-06-29 10:01 ` sashiko-bot
  2026-06-29 15:05 ` kernel test robot
  1 sibling, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-06-29 10:01 UTC (permalink / raw)
  To: Fenglin Wu; +Cc: lee, linux-leds

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Failing to inhibit cleanup of the __free() 'pattern' variable in lpg_prepare_pattern() causes it to be freed on success, leading to Use-After-Free and Double-Free in callers.
- [Low] Mixing goto-based error handling with scope-based cleanup (__free and guard) violates subsystem guidelines and causes a compilation error.
--

commit 6d61dc847ec65d99f5fb5a8faf11368f527f4b5c
Author: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode

This patch updates the LED pattern programming to scale pattern data
according to sub-LED intensities. This ensures proper white balancing
when using hardware patterns.

> diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
> --- a/drivers/leds/rgb/leds-qcom-lpg.c
> +++ b/drivers/leds/rgb/leds-qcom-lpg.c
[ ... ]
> @@ -959,23 +977,15 @@ static int lpg_blink_mc_set(struct led_classdev *cdev,
> +static int lpg_prepare_pattern(struct lpg *lpg, struct led_pattern *led_pattern,
> +				u32 len, int repeat, struct lpg_pattern *prep)
>  {
[ ... ]
> -	pattern = kzalloc_objs(*pattern, len / 2);
> +	struct led_pattern *pattern __free(kfree) = kzalloc_objs(*pattern, len / 2);
> +
>  	if (!pattern)
>  		return -ENOMEM;
[ ... ]
> +	prep->data = pattern;
> +	prep->len = actual_len;
> +	prep->delta_t = delta_t;
> +	prep->ping_pong = ping_pong;
>  
> -	mutex_lock(&lpg->lock);
> +	return 0;
> +}

[Severity: Critical]
Since pattern is declared with __free(kfree), does returning 0 without
inhibiting cleanup cause pattern to be freed immediately upon function
exit? Could this be resolved by using no_free_ptr(pattern) when assigning
it to prep->data?

> +static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
> +			   u32 len, int repeat)
> +{
[ ... ]
> +	guard(mutex)(&lpg->lock);
>  
>  	if (lpg->lut_base)
> -		ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx);
> +		ret = lpg_lut_store(lpg, pattern.data, pattern.len, &lo_idx, &hi_idx);
>  	else
> -		ret = lpg_lut_store_sdam(lpg, pattern, actual_len, &lo_idx, &hi_idx);
> +		ret = lpg_lut_store_sdam(lpg, pattern.data, pattern.len, &lo_idx, &hi_idx);
>  
>  	if (ret < 0)
> -		goto out_unlock;
> +		goto free_pattern;

[Severity: Low]
Does mixing goto-based cleanup labels with scope-based cleanup like
guard(mutex) violate the kernel's cleanup guidelines? The guidelines
recommend either converting all resources to scope-based cleanup or not
using it at all in the same function.

[ ... ]
> -out_unlock:
> -	mutex_unlock(&lpg->lock);
> -out_free_pattern:
> -	kfree(pattern);
> -
> +free_pattern:
> +	kfree(pattern.data);
>  	return ret;
>  }

[ ... ]
>  static int lpg_pattern_mc_set(struct led_classdev *cdev,
> -			      struct led_pattern *pattern, u32 len,
> +			      struct led_pattern *led_pattern, u32 len,
>  			      int repeat)
>  {
[ ... ]
> +	/* Allocate buffer for the per-channel scaled pattern copy */
> +	struct led_pattern *scaled __free(kfree) =
> +			   kmalloc_array(pattern.len, sizeof(*scaled), GFP_KERNEL);
> +	if (!scaled) {
> +		ret = -ENOMEM;
> +		goto free_pattern;
> +	}

[Severity: Low]
Does jumping to free_pattern on this error path skip the declaration of
guard(mutex)(&lpg->lock) further down in the function? In C, jumping
forward over a variable initialized with a cleanup attribute results in a
compilation error.

[ ... ]
> +	guard(mutex)(&lpg->lock);
> +
> +	for (i = 0; i < led->num_channels; i++) {
> +		chan = led->channels[i];
> +		scale = mc->subled_info[i].brightness;
> +
> +		for (j = 0; j < pattern.len; j++) {
> +			scaled[j].brightness = DIV_ROUND_CLOSEST(
> +				(u32)pattern.data[j].brightness * scale, LED_FULL);

[Severity: Critical]
If pattern.data was already freed at the end of lpg_prepare_pattern(),
can this loop dereference freed memory?

> +			scaled[j].delta_t = pattern.data[j].delta_t;
> +		}
[ ... ]
> +free_pattern:
> +	kfree(pattern.data);

[Severity: Critical]
Would this unconditional kfree() trigger a double-free, since pattern.data
was already freed upon exiting lpg_prepare_pattern()?

>  	if (ret < 0)
>  		return ret;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260629-lpg-rgb-color-balance-fix-v3-1-17796a06d799@oss.qualcomm.com?part=1

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v3] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode
  2026-06-29  9:51 [PATCH v3] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode Fenglin Wu
  2026-06-29 10:01 ` sashiko-bot
@ 2026-06-29 15:05 ` kernel test robot
  1 sibling, 0 replies; 3+ messages in thread
From: kernel test robot @ 2026-06-29 15:05 UTC (permalink / raw)
  To: Fenglin Wu, linux-arm-msm, Lee Jones, Pavel Machek,
	Bjorn Andersson, Marijn Suijten, Anjelique Melendez,
	Guru Das Srinagesh
  Cc: llvm, oe-kbuild-all, David Collins, Subbaraman Narayanamurthy,
	Kamal Wadhwa, kernel, linux-leds, linux-kernel, Fenglin Wu

Hi Fenglin,

kernel test robot noticed the following build errors:

[auto build test ERROR on 1795fd2dbe84ef4d393b69a0b2a3b371f810bde5]

url:    https://github.com/intel-lab-lkp/linux/commits/Fenglin-Wu/leds-rgb-leds-qcom-lpg-Fix-LED-color-balancing-in-HW-pattern-mode/20260629-180250
base:   1795fd2dbe84ef4d393b69a0b2a3b371f810bde5
patch link:    https://lore.kernel.org/r/20260629-lpg-rgb-color-balance-fix-v3-1-17796a06d799%40oss.qualcomm.com
patch subject: [PATCH v3] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode
config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20260629/202606292317.3IsR1hhQ-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project 6cc609bb250b21b47fc7d394b4019101e9983597)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260629/202606292317.3IsR1hhQ-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/202606292317.3IsR1hhQ-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/leds/rgb/leds-qcom-lpg.c:1203:3: error: cannot jump from this goto statement to its label
    1203 |                 goto free_pattern;
         |                 ^
   drivers/leds/rgb/leds-qcom-lpg.c:1216:2: note: jump bypasses initialization of variable with __attribute__((cleanup))
    1216 |         guard(mutex)(&lpg->lock);
         |         ^
   include/linux/cleanup.h:422:2: note: expanded from macro 'guard'
     422 |         CLASS(_name, __UNIQUE_ID(guard))
         |         ^
   include/linux/cleanup.h:303:3: note: expanded from macro 'CLASS'
     303 |                 class_##_name##_constructor
         |                 ^
   <scratch space>:39:1: note: expanded from here
      39 | class_mutex_constructor
         | ^
   note: (skipping 3 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
   include/linux/compiler_types.h:16:23: note: expanded from macro '__PASTE'
      16 | #define __PASTE(a, b) ___PASTE(a, b)
         |                       ^
   include/linux/compiler_types.h:15:24: note: expanded from macro '___PASTE'
      15 | #define ___PASTE(a, b) a##b
         |                        ^
   <scratch space>:45:1: note: expanded from here
      45 | __UNIQUE_ID_unlock_324
         | ^
   drivers/leds/rgb/leds-qcom-lpg.c:1216:2: note: jump bypasses initialization of variable with __attribute__((cleanup))
   include/linux/cleanup.h:422:15: note: expanded from macro 'guard'
     422 |         CLASS(_name, __UNIQUE_ID(guard))
         |                      ^
   include/linux/compiler.h:165:2: note: expanded from macro '__UNIQUE_ID'
     165 |         __PASTE(__UNIQUE_ID_,                                   \
         |         ^
   include/linux/compiler_types.h:16:23: note: expanded from macro '__PASTE'
      16 | #define __PASTE(a, b) ___PASTE(a, b)
         |                       ^
   include/linux/compiler_types.h:15:24: note: expanded from macro '___PASTE'
      15 | #define ___PASTE(a, b) a##b
         |                        ^
   <scratch space>:33:1: note: expanded from here
      33 | __UNIQUE_ID_guard_323
         | ^
   1 error generated.


vim +1203 drivers/leds/rgb/leds-qcom-lpg.c

  1177	
  1178	static int lpg_pattern_mc_set(struct led_classdev *cdev,
  1179				      struct led_pattern *led_pattern, u32 len,
  1180				      int repeat)
  1181	{
  1182		struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
  1183		struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
  1184		struct lpg *lpg = led->lpg;
  1185		struct lpg_channel *chan;
  1186		struct lpg_pattern pattern;
  1187		unsigned int triled_mask = 0;
  1188		unsigned int lo_idx;
  1189		unsigned int hi_idx;
  1190		unsigned int scale;
  1191		unsigned int i, j;
  1192		int ret;
  1193	
  1194		ret = lpg_prepare_pattern(lpg, led_pattern, len, repeat, &pattern);
  1195		if (ret < 0)
  1196			return ret;
  1197	
  1198		/* Allocate buffer for the per-channel scaled pattern copy */
  1199		struct led_pattern *scaled __free(kfree) =
  1200				   kmalloc_array(pattern.len, sizeof(*scaled), GFP_KERNEL);
  1201		if (!scaled) {
  1202			ret = -ENOMEM;
> 1203			goto free_pattern;
  1204		}
  1205	
  1206		for (i = 0; i < led->num_channels; i++)
  1207			triled_mask |= led->channels[i]->triled_mask;
  1208		triled_set(lpg, triled_mask, 0);
  1209	
  1210		led_mc_calc_color_components(mc, LED_FULL);
  1211	
  1212		/*
  1213		 * Each channel gets its own LUT block scaled by subled_info[i].brightness
  1214		 * so the pattern respects the configured colour balance.
  1215		 */
  1216		guard(mutex)(&lpg->lock);
  1217	
  1218		for (i = 0; i < led->num_channels; i++) {
  1219			chan = led->channels[i];
  1220			scale = mc->subled_info[i].brightness;
  1221	
  1222			for (j = 0; j < pattern.len; j++) {
  1223				scaled[j].brightness = DIV_ROUND_CLOSEST(
  1224					(u32)pattern.data[j].brightness * scale, LED_FULL);
  1225				scaled[j].delta_t = pattern.data[j].delta_t;
  1226			}
  1227	
  1228			if (lpg->lut_base)
  1229				ret = lpg_lut_store(lpg, scaled, pattern.len, &lo_idx, &hi_idx);
  1230			else
  1231				ret = lpg_lut_store_sdam(lpg, scaled, pattern.len, &lo_idx, &hi_idx);
  1232	
  1233			if (ret < 0) {
  1234				/* Free LUT slots already allocated for previous channels */
  1235				while (i-- > 0) {
  1236					chan = led->channels[i];
  1237					lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx);
  1238					chan->pattern_lo_idx = 0;
  1239					chan->pattern_hi_idx = 0;
  1240				}
  1241	
  1242				goto free_pattern;
  1243			}
  1244	
  1245			chan->ramp_tick_ms = pattern.delta_t;
  1246			chan->ramp_ping_pong = pattern.ping_pong;
  1247			chan->ramp_oneshot = repeat != -1;
  1248			chan->ramp_lo_pause_ms = pattern.lo_pause;
  1249			chan->ramp_hi_pause_ms = pattern.hi_pause;
  1250			chan->pattern_lo_idx = lo_idx;
  1251			chan->pattern_hi_idx = hi_idx;
  1252		}
  1253	
  1254	free_pattern:
  1255		kfree(pattern.data);
  1256		if (ret < 0)
  1257			return ret;
  1258	
  1259		lpg_brightness_set(led, cdev, mc->subled_info);
  1260	
  1261		return 0;
  1262	}
  1263	

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

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-06-29 15:05 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-29  9:51 [PATCH v3] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode Fenglin Wu
2026-06-29 10:01 ` sashiko-bot
2026-06-29 15:05 ` 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