[PATCH RFC RFT 3/8] drm/amd/display: Interpolate when reading PWM back as a percentage

From: Hugo Sales

Date: Wed Nov 04 2026 - 04:56:32 EST


backlight_pwm_to_millipercent_legacy() finds the closest backlight_lut
entry and returns its whole percent. That was the exact inverse while
backlight_millipercent_to_pwm_legacy() only returned LUT entries. Now
that it interpolates, a request of 1.5% programs a PWM halfway between
two entries, which reads back as 1% or 2%, and setting the value read
back moves the backlight by up to half a LUT step

Interpolate between the two entries around the PWM instead. A PWM equal
to a LUT entry still reads back as that entry's whole percent

Signed-off-by: Hugo Sales <hugo@xxxxxxx>
---
Converting a percentage to PWM, back, and to PWM again now moves the
PWM by about a two-thousandth of the LUT step around it, plus rounding.
On the GV301QH LUT, whose steps are at most 1799, that keeps every PWM
within 1 of where it started. A curve with much wider steps can move
further, but by far less than the half step it could before

KUnit: dm_test_pwm_lut_readback from patch 6 fails without this patch
and passes with it
---
.../gpu/drm/amd/display/modules/power/power_abm.c | 33 ++++++++--------------
1 file changed, 11 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
index d9c1f1401b604..83d79c20281ec 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
@@ -409,34 +409,23 @@ static unsigned int backlight_pwm_to_millipercent_legacy(
break;
}

- /* In this case, exact match is not found. Check if mid/min/max
- * value is actually closer.
- */
+ /* No exact match, so on a sorted LUT lut[max] < pwm < lut[min] */
if (max < min) {
- unsigned int min_delta;
- unsigned int mid_delta;
- unsigned int max_delta;
-
- min_delta = (core_power->bl_prop[inst].backlight_lut[min] > pwm) ?
- core_power->bl_prop[inst].backlight_lut[min] - pwm :
- pwm - core_power->bl_prop[inst].backlight_lut[min];
-
- mid_delta = (core_power->bl_prop[inst].backlight_lut[mid] > pwm) ?
- core_power->bl_prop[inst].backlight_lut[mid] - pwm :
- pwm - core_power->bl_prop[inst].backlight_lut[mid];
+ unsigned int lower = core_power->bl_prop[inst].backlight_lut[max];
+ unsigned int upper = core_power->bl_prop[inst].backlight_lut[min];
+ unsigned long long span, den;

- max_delta = (core_power->bl_prop[inst].backlight_lut[max] > pwm) ?
- core_power->bl_prop[inst].backlight_lut[max] - pwm :
- pwm - core_power->bl_prop[inst].backlight_lut[max];
+ if (pwm <= lower || pwm >= upper)
+ return BACKLIGHT_MILLIPERCENT_MAX * mid / max_index;

- if ((min_delta < mid_delta) && (min_delta < max_delta))
- mid = min;
+ span = upper - lower;
+ den = span * max_index;

- if ((max_delta < mid_delta) && (max_delta < min_delta))
- mid = max;
+ /* LUT entries fit in 16 bits, so this stays far below 2^64 */
+ return (unsigned int)div64_u64((max * span + pwm - lower) *
+ BACKLIGHT_MILLIPERCENT_MAX + den / 2, den);
}

- /* No interpolation, just take closest index */
millipercent = BACKLIGHT_MILLIPERCENT_MAX * mid / max_index;

return millipercent;

--
2.56.0