Le ven. 14 août 2026 à 21:15, David Lechner <[email protected]> a écrit :
>
> 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)
we should include directly include/limits.h for 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]>
>
Except the little nitpick that can be fixed while applying:
Reviewed-by: Julien Stephan <[email protected]>