drm/i915: intel_backlight scale() math WA
Improper truncated integer division in the scale() function causes
actual_brightness != brightness. This (partial) work-around should be
sufficient for a majority of use-cases, but it is by no means a complete
solution.
TODO: Determine how best to scale "user" values to "hw" values, and
vice-versa, when the ranges are of different sizes. That would be a
buggy scenario even with this work-around.
The issue was introduced in the following (v3.17-rc1) commit:
6dda730
drm/i915: respect the VBT minimum backlight brightness
Note that for easier backporting this commit adds a duplicated macro.
A follow-up cleanup patch rectifies this for 3.18+
v2: (thanks to Chris Wilson) clarify commit message, use rounded division
macro
v3: -DIV_ROUND_CLOSEST() fails to build with CONFIG_X86_32=y. (Jani)
-Use DIV_ROUND_CLOSEST_ULL() instead. (Damien)
-v1 and v2 originally authored by Joe Konno.
Signed-off-by: U. Artie Eoff <ullysses.a.eoff@intel.com>
Cc: stable@vger.kernel.org
Reviewed-By: Joe Konno <joe.konno@intel.com>
[danvet: Add backporting note.]
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
This commit is contained in:
parent
ebb69c9517
commit
673e7bbdb3
|
@ -398,6 +398,9 @@ intel_panel_detect(struct drm_device *dev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DIV_ROUND_CLOSEST_ULL(ll, d) \
|
||||||
|
({ unsigned long long _tmp = (ll)+(d)/2; do_div(_tmp, d); _tmp; })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* scale - scale values from one range to another
|
* scale - scale values from one range to another
|
||||||
*
|
*
|
||||||
|
@ -419,9 +422,8 @@ static uint32_t scale(uint32_t source_val,
|
||||||
source_val = clamp(source_val, source_min, source_max);
|
source_val = clamp(source_val, source_min, source_max);
|
||||||
|
|
||||||
/* avoid overflows */
|
/* avoid overflows */
|
||||||
target_val = (uint64_t)(source_val - source_min) *
|
target_val = DIV_ROUND_CLOSEST_ULL((uint64_t)(source_val - source_min) *
|
||||||
(target_max - target_min);
|
(target_max - target_min), source_max - source_min);
|
||||||
do_div(target_val, source_max - source_min);
|
|
||||||
target_val += target_min;
|
target_val += target_min;
|
||||||
|
|
||||||
return target_val;
|
return target_val;
|
||||||
|
|
Loading…
Reference in a new issue