[PATCH] video: pwm_backlight: support num-interpolated-steps

David Lechner <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <20260814-video-pwm-backlight-num-interpolated-steps-v1-1-140b06287868@baylibre.com>
Add support for the num-interpolated-steps devicetree property to the
pwm_backlight driver. This is used in cases where there are many points
and we don't want to list them all in brightness-levels.

Signed-off-by: David Lechner <[email protected]>
---
This will be used by the MediaTek Genio 360 EVK that we are currently
working to bring up.
---
 drivers/video/pwm_backlight.c | 124 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 112 insertions(+), 12 deletions(-)

diff --git a/drivers/video/pwm_backlight.c b/drivers/video/pwm_backlight.c
index 1470eaf4cab..cd469417552 100644
--- a/drivers/video/pwm_backlight.c
+++ b/drivers/video/pwm_backlight.c
@@ -16,6 +16,65 @@
 #include <linux/math64.h>
 #include <power/regulator.h>
 
+/**
+ * build_interpolated_levels() - Build a linearly interpolated levels table
+ *
+ * Some "brightness-levels" tables only list a few anchor points and rely on
+ * "num-interpolated-steps" to fill in the values between them, so that a
+ * high resolution PWM duty cycle can be used without listing every value.
+ *
+ * @raw_levels: Anchor point values read from "brightness-levels"
+ * @count:	Number of anchor points in @raw_levels
+ * @num_steps:	Number of interpolated steps between each anchor point
+ * @levels:	Returns newly allocated table of (count - 1) * num_steps + 1
+ *		entries
+ * @num_levels: Returns the number of entries in @levels
+ * Return: 0 on success, -EINVAL if @count or @num_steps are invalid, -ENOMEM
+ *	   on allocation failure
+ */
+static int build_interpolated_levels(const u32 *raw_levels, u32 count,
+				     u32 num_steps, u32 **levels,
+				     u32 *num_levels)
+{
+	u64 count_out64;
+	u32 count_out, *table, i, x;
+
+	/*
+	 * num_steps must fit in the s32 divisor of div_s64(), and count_out64
+	 * must fit both u32 (it is stored as such) and the byte size passed
+	 * to malloc().
+	 */
+	if (count < 2 || num_steps == 0 || num_steps > S32_MAX)
+		return -EINVAL;
+
+	count_out64 = (u64)(count - 1) * num_steps + 1;
+	if (count_out64 > U32_MAX || count_out64 > SIZE_MAX / sizeof(u32))
+		return -EINVAL;
+
+	count_out = count_out64;
+
+	table = malloc(count_out * sizeof(u32));
+	if (!table)
+		return -ENOMEM;
+
+	for (i = 0; i < count - 1; i++) {
+		u32 x1 = i * num_steps;
+		u32 x2 = x1 + num_steps;
+		u32 y1 = raw_levels[i];
+		u32 y2 = raw_levels[i + 1];
+		s64 dy = (s64)y2 - y1;
+
+		for (x = x1; x < x2; x++)
+			table[x] = y1 + div_s64(dy * (x - x1), num_steps);
+	}
+
+	table[count_out - 1] = raw_levels[count - 1];
+	*levels = table;
+	*num_levels = count_out;
+
+	return 0;
+}
+
 /**
  * Private information for the PWM backlight
  *
@@ -49,7 +108,7 @@ struct pwm_backlight_priv {
 	 */
 	bool polarity;
 	u32 *levels;
-	int num_levels;
+	u32 num_levels;
 	uint default_level;
 	int cur_level;
 	uint min_level;
@@ -196,6 +255,7 @@ static int pwm_backlight_of_to_plat(struct udevice *dev)
 	struct ofnode_phandle_args args;
 	int index, ret, count, len;
 	const u32 *cell;
+	u32 num_steps;
 
 	log_debug("start\n");
 	ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
@@ -232,20 +292,60 @@ static int pwm_backlight_of_to_plat(struct udevice *dev)
 	index = dev_read_u32_default(dev, "default-brightness-level", 255);
 	cell = dev_read_prop(dev, "brightness-levels", &len);
 	count = len / sizeof(u32);
-	if (cell && count > index) {
-		priv->levels = malloc(len);
-		if (!priv->levels)
+
+	/*
+	 * If present, "num-interpolated-steps" means the levels above are
+	 * just anchor points, and the actual table used for
+	 * default-brightness-level and PWM duty cycle is the interpolated
+	 * table built from those anchor points. Interpolating needs at
+	 * least two anchor points.
+	 */
+	num_steps = dev_read_u32_default(dev, "num-interpolated-steps", 0);
+
+	priv->default_level = index;
+	priv->max_level = 255;
+
+	if (cell && count >= (num_steps ? 2 : 1)) {
+		u32 *raw_levels;
+
+		raw_levels = malloc(len);
+		if (!raw_levels)
 			return log_ret(-ENOMEM);
-		ret = dev_read_u32_array(dev, "brightness-levels", priv->levels,
+
+		ret = dev_read_u32_array(dev, "brightness-levels", raw_levels,
 					 count);
-		if (ret)
+		if (ret) {
+			free(raw_levels);
 			return log_msg_ret("levels", ret);
-		priv->num_levels = count;
-		priv->default_level = priv->levels[index];
-		priv->max_level = priv->levels[count - 1];
-	} else {
-		priv->default_level = index;
-		priv->max_level = 255;
+		}
+
+		if (num_steps) {
+			ret = build_interpolated_levels(raw_levels, count, num_steps,
+							&priv->levels,
+							&priv->num_levels);
+			free(raw_levels);
+			if (ret)
+				return log_ret(ret);
+		} else {
+			priv->levels = raw_levels;
+			priv->num_levels = count;
+		}
+
+		if (index < priv->num_levels) {
+			priv->default_level = priv->levels[index];
+			priv->max_level = priv->levels[priv->num_levels - 1];
+		} else {
+			/*
+			 * default-brightness-level is out of range for the
+			 * table: fall back to raw 0-255 PWM scaling instead
+			 * of using the table at all.
+			 */
+			log_warning("default-brightness-level %d out of range for %u-entry brightness-levels table, ignoring table\n",
+				    index, priv->num_levels);
+			free(priv->levels);
+			priv->levels = NULL;
+			priv->num_levels = 0;
+		}
 	}
 	priv->cur_level = priv->default_level;
 	log_debug("done\n");

---
base-commit: d3e8597e46b635ec556a057bc42f0b0859654bdf
change-id: 20260814-video-pwm-backlight-num-interpolated-steps-7d63c7728db1

Best regards,
--  
David Lechner <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.