Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

backlight: pwm_bl: Fix interpolation

The previous behavior was a little unexpected, its properties/problems:
1. It was designed to generate strictly increasing values (no repeats)
2. It had quantization errors when calculating step size. Resulting in
unexpected jumps near the end of some segments.

Example settings:
brightness-levels = <0 1 2 4 8 16 32 64 128 256>;
num-interpolated-steps = <16>;

Whenever num-interpolated-steps was larger than the distance
between 2 consecutive brightness levels the table would get really
discontinuous. The slope of the interpolation would stick with
integers only and if it was 0 the whole line segment would get skipped.

The distances between 1 2 4 and 8 would be 1 (property #1 fighting us),
and only starting with 16 it would start to interpolate properly.

Property #1 is not enough. The goal here is more than just monotonically
increasing. We should still care about the shape of the curve. Repeated
points might be desired if we're in the part of the curve where we want
to go slow (aka slope near 0).

Problem #2 is plainly a bug. Imagine if the 64 entry was 63 instead,
the calculated slope on the 32-63 segment will be almost half as it
should be.

The most expected and simplest algorithm for interpolation is linear
interpolation, which would handle both problems.
Let's just implement that!

Take pairs of points from the brightness-levels array and linearly
interpolate between them. On the X axis (what userspace sees) we'll
now have equally sized intervals (num-interpolated-steps sized,
as opposed to before where we were at the mercy of quantization).

Signed-off-by: Alexandru Stan <amstan@chromium.org>
Reviewed-by: Daniel Thompson <daniel.thompson@linaro.org>
Signed-off-by: Lee Jones <lee.jones@linaro.org>

authored by

Alexandru Stan and committed by
Lee Jones
789eb04b 3650b228

+30 -38
+30 -38
drivers/video/backlight/pwm_bl.c
··· 230 230 struct platform_pwm_backlight_data *data) 231 231 { 232 232 struct device_node *node = dev->of_node; 233 - unsigned int num_levels = 0; 234 - unsigned int levels_count; 233 + unsigned int num_levels; 235 234 unsigned int num_steps = 0; 236 235 struct property *prop; 237 236 unsigned int *table; ··· 259 260 if (!prop) 260 261 return 0; 261 262 262 - data->max_brightness = length / sizeof(u32); 263 + num_levels = length / sizeof(u32); 263 264 264 265 /* read brightness levels from DT property */ 265 - if (data->max_brightness > 0) { 266 - size_t size = sizeof(*data->levels) * data->max_brightness; 267 - unsigned int i, j, n = 0; 266 + if (num_levels > 0) { 267 + size_t size = sizeof(*data->levels) * num_levels; 268 268 269 269 data->levels = devm_kzalloc(dev, size, GFP_KERNEL); 270 270 if (!data->levels) ··· 271 273 272 274 ret = of_property_read_u32_array(node, "brightness-levels", 273 275 data->levels, 274 - data->max_brightness); 276 + num_levels); 275 277 if (ret < 0) 276 278 return ret; 277 279 ··· 296 298 * between two points. 297 299 */ 298 300 if (num_steps) { 299 - if (data->max_brightness < 2) { 301 + unsigned int num_input_levels = num_levels; 302 + unsigned int i; 303 + u32 x1, x2, x, dx; 304 + u32 y1, y2; 305 + s64 dy; 306 + 307 + if (num_input_levels < 2) { 300 308 dev_err(dev, "can't interpolate\n"); 301 309 return -EINVAL; 302 310 } ··· 312 308 * taking in consideration the number of interpolated 313 309 * steps between two levels. 314 310 */ 315 - for (i = 0; i < data->max_brightness - 1; i++) { 316 - if ((data->levels[i + 1] - data->levels[i]) / 317 - num_steps) 318 - num_levels += num_steps; 319 - else 320 - num_levels++; 321 - } 322 - num_levels++; 311 + num_levels = (num_input_levels - 1) * num_steps + 1; 323 312 dev_dbg(dev, "new number of brightness levels: %d\n", 324 313 num_levels); 325 314 ··· 324 327 table = devm_kzalloc(dev, size, GFP_KERNEL); 325 328 if (!table) 326 329 return -ENOMEM; 330 + /* 331 + * Fill the interpolated table[x] = y 332 + * by draw lines between each (x1, y1) to (x2, y2). 333 + */ 334 + dx = num_steps; 335 + for (i = 0; i < num_input_levels - 1; i++) { 336 + x1 = i * dx; 337 + x2 = x1 + dx; 338 + y1 = data->levels[i]; 339 + y2 = data->levels[i + 1]; 340 + dy = (s64)y2 - y1; 327 341 328 - /* Fill the interpolated table. */ 329 - levels_count = 0; 330 - for (i = 0; i < data->max_brightness - 1; i++) { 331 - value = data->levels[i]; 332 - n = (data->levels[i + 1] - value) / num_steps; 333 - if (n > 0) { 334 - for (j = 0; j < num_steps; j++) { 335 - table[levels_count] = value; 336 - value += n; 337 - levels_count++; 338 - } 339 - } else { 340 - table[levels_count] = data->levels[i]; 341 - levels_count++; 342 + for (x = x1; x < x2; x++) { 343 + table[x] = y1 + 344 + div_s64(dy * (x - x1), dx); 342 345 } 343 346 } 344 - table[levels_count] = data->levels[i]; 347 + /* Fill in the last point, since no line starts here. */ 348 + table[x2] = y2; 345 349 346 350 /* 347 351 * As we use interpolation lets remove current ··· 351 353 */ 352 354 devm_kfree(dev, data->levels); 353 355 data->levels = table; 354 - 355 - /* 356 - * Reassign max_brightness value to the new total number 357 - * of brightness levels. 358 - */ 359 - data->max_brightness = num_levels; 360 356 } 361 357 362 - data->max_brightness--; 358 + data->max_brightness = num_levels - 1; 363 359 } 364 360 365 361 return 0;