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]>

Reply via email to