* [PATCH RFC RFT 1/8] drm/amd/display: Rename the user brightness range to user_min and user_max
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 2/8] drm/amd/display: Interpolate between backlight LUT entries Hugo Sales
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
get_brightness_range() gives the range of the user brightness level,
and the code around it calls its two ends min and max. The custom curve
code later in this series adds an input signal range and an output
range next to it, so call this one user_min and user_max
No functional change
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Rename only. It also joins two lines in
amdgpu_dm_backlight_fill_props() that now fit in 100 columns, and wraps
two test comments that no longer do
amdgpu_dm_backlight.o disassembles the same with and without this
patch. amdgpu_dm_backlight_test.o differs only in the line numbers its
KUnit asserts record, since the two wrapped comments add two lines
KUnit: amdgpu_dm_backlight passes 74 of 74 with and without this
patch, and the tests from patches 5, 6 and 8 give the same results with
and without it
---
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 72 ++++----
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 4 +-
.../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 194 +++++++++++----------
3 files changed, 135 insertions(+), 135 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index cef2ad8fea74d..3b59699e74287 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -98,34 +98,34 @@ EXPORT_IF_KUNIT(amdgpu_dm_update_backlight_caps);
STATIC_IFN_KUNIT
int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int *min, unsigned int *max)
+ unsigned int *user_min, unsigned int *user_max)
{
if (!caps)
return 0;
if (caps->aux_support) {
/* Firmware limits are in nits, DC API wants millinits. */
- *max = 1000 * caps->aux_max_input_signal;
- *min = 1000 * caps->aux_min_input_signal;
+ *user_max = 1000 * caps->aux_max_input_signal;
+ *user_min = 1000 * caps->aux_min_input_signal;
} else {
/* Firmware limits are 8-bit, PWM control is 16-bit. */
- *max = 0x101 * caps->max_input_signal;
- *min = 0x101 * caps->min_input_signal;
+ *user_max = 0x101 * caps->max_input_signal;
+ *user_min = 0x101 * caps->min_input_signal;
}
return 1;
}
EXPORT_IF_KUNIT(get_brightness_range);
-/* Rescale userspace [0..max] to the firmware curve's [0..255]. */
-static inline u32 scale_input_to_fw(int max, u64 input)
+/* Rescale userspace [0..user_max] to the firmware curve's [0..255]. */
+static inline u32 scale_input_to_fw(int user_max, u64 input)
{
- return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max);
+ return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, user_max);
}
-/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [0..max] */
-static inline u32 scale_fw_to_input(int max, u64 input)
+/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [0..user_max] */
+static inline u32 scale_fw_to_input(int user_max, u64 input)
{
- return DIV_ROUND_CLOSEST_ULL(input * max, AMDGPU_MAX_BL_LEVEL);
+ return DIV_ROUND_CLOSEST_ULL(input * user_max, AMDGPU_MAX_BL_LEVEL);
}
static u32 backlight_signal_to_luminance(const struct amdgpu_dm_backlight_caps *caps,
@@ -173,18 +173,18 @@ static u32 backlight_signal_to_luminance(const struct amdgpu_dm_backlight_caps *
static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
u32 user_brightness)
{
- unsigned int min, max;
+ unsigned int user_min, user_max;
u32 signal, luminance;
- if (!get_brightness_range(caps, &min, &max))
+ if (!get_brightness_range(caps, &user_min, &user_max))
return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000,
MAX_BACKLIGHT_LEVEL);
- user_brightness = clamp(user_brightness, min, max);
+ user_brightness = clamp(user_brightness, user_min, user_max);
if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)
- return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000, max);
+ return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000, user_max);
- signal = scale_input_to_fw(max, user_brightness);
+ signal = scale_input_to_fw(user_max, user_brightness);
luminance = backlight_signal_to_luminance(caps, signal);
return min_t(u32, luminance * 1000, 100000);
@@ -192,9 +192,9 @@ static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_c
STATIC_IFN_KUNIT
void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int max, uint32_t *user_brightness)
+ unsigned int user_max, uint32_t *user_brightness)
{
- u32 brightness = scale_input_to_fw(max, *user_brightness);
+ u32 brightness = scale_input_to_fw(user_max, *user_brightness);
u8 lower_signal, upper_signal, upper_lum, lower_lum, lum;
int left, right;
@@ -250,7 +250,7 @@ void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
(brightness - lower_signal),
upper_signal - lower_signal);
scale:
- *user_brightness = scale_fw_to_input(max, DIV_ROUND_CLOSEST(lum * brightness, 101));
+ *user_brightness = scale_fw_to_input(user_max, DIV_ROUND_CLOSEST(lum * brightness, 101));
}
EXPORT_IF_KUNIT(convert_custom_brightness);
@@ -259,15 +259,15 @@ STATIC_IFN_KUNIT
u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
uint32_t brightness)
{
- unsigned int min, max;
+ unsigned int user_min, user_max;
- if (!get_brightness_range(caps, &min, &max))
+ if (!get_brightness_range(caps, &user_min, &user_max))
return brightness;
- convert_custom_brightness(caps, max, &brightness);
+ convert_custom_brightness(caps, user_max, &brightness);
- /* Rescale 0..max to min..max */
- return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max);
+ /* Rescale 0..user_max to user_min..user_max */
+ return user_min + DIV_ROUND_CLOSEST_ULL((u64)(user_max - user_min) * brightness, user_max);
}
EXPORT_IF_KUNIT(convert_brightness_from_user);
@@ -276,16 +276,16 @@ STATIC_IFN_KUNIT
u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
uint32_t brightness)
{
- unsigned int min, max;
+ unsigned int user_min, user_max;
- if (!get_brightness_range(caps, &min, &max))
+ if (!get_brightness_range(caps, &user_min, &user_max))
return brightness;
- if (brightness < min)
+ if (brightness < user_min)
return 0;
- /* Rescale min..max to 0..max */
- return DIV_ROUND_CLOSEST_ULL((u64)max * (brightness - min),
- max - min);
+ /* Rescale user_min..user_max to 0..user_max */
+ return DIV_ROUND_CLOSEST_ULL((u64)user_max * (brightness - user_min),
+ user_max - user_min);
}
EXPORT_IF_KUNIT(convert_brightness_to_user);
@@ -484,16 +484,14 @@ void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps,
bool custom_curve_enabled,
struct backlight_properties *props)
{
- unsigned int min, max;
+ unsigned int user_min, user_max;
- if (get_brightness_range(caps, &min, &max)) {
+ if (get_brightness_range(caps, &user_min, &user_max)) {
if (is_system_supplied)
- props->brightness = DIV_ROUND_CLOSEST(max * caps->ac_level,
- 100);
+ props->brightness = DIV_ROUND_CLOSEST(user_max * caps->ac_level, 100);
else
- props->brightness = DIV_ROUND_CLOSEST(max * caps->dc_level,
- 100);
- props->max_brightness = max;
+ props->brightness = DIV_ROUND_CLOSEST(user_max * caps->dc_level, 100);
+ props->max_brightness = user_max;
} else {
props->brightness = MAX_BACKLIGHT_LEVEL;
props->max_brightness = MAX_BACKLIGHT_LEVEL;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index 32ba2f8efb33b..c5400b6c98660 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -66,9 +66,9 @@ ssize_t panel_power_savings_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count);
int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int *min, unsigned int *max);
+ unsigned int *user_min, unsigned int *user_max);
void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int max, uint32_t *user_brightness);
+ unsigned int user_max, uint32_t *user_brightness);
u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
uint32_t brightness);
u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index 3d00cde8c77bc..d38f388961d69 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -482,7 +482,7 @@ static void dm_test_register_backlight_device_success(struct kunit *test)
struct amdgpu_dm_backlight_caps *caps;
struct dc_link *link = dm_kunit_alloc_link(test);
struct drm_minor *primary;
- unsigned int max;
+ unsigned int user_max;
setup_test_link_service(test, link);
link->dc->link_srv->edp_get_backlight_level = dm_test_get_backlight_level_error;
@@ -499,14 +499,14 @@ static void dm_test_register_backlight_device_success(struct kunit *test)
caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps->ac_level = 50;
caps->dc_level = 25;
- max = 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ user_max = 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
amdgpu_dm_register_backlight_device(aconnector);
KUNIT_ASSERT_NOT_NULL(test, adev->dm.backlight_dev[0]);
- KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.max_brightness, max);
+ KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.max_brightness, user_max);
KUNIT_EXPECT_EQ(test, adev->dm.backlight_dev[0]->props.brightness,
- DIV_ROUND_CLOSEST(max * caps->ac_level, 100));
+ DIV_ROUND_CLOSEST(user_max * caps->ac_level, 100));
KUNIT_EXPECT_EQ(test, adev->dm.brightness[0],
adev->dm.backlight_dev[0]->props.brightness);
KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
@@ -821,12 +821,12 @@ static void dm_test_validate_backlight_caps(struct kunit *test)
*/
static void dm_test_brightness_range_null_caps(struct kunit *test)
{
- unsigned int min = 99, max = 99;
+ unsigned int user_min = 99, user_max = 99;
- KUNIT_EXPECT_EQ(test, get_brightness_range(NULL, &min, &max), 0);
- /* min/max should remain untouched */
- KUNIT_EXPECT_EQ(test, min, 99U);
- KUNIT_EXPECT_EQ(test, max, 99U);
+ KUNIT_EXPECT_EQ(test, get_brightness_range(NULL, &user_min, &user_max), 0);
+ /* user_min/user_max should remain untouched */
+ KUNIT_EXPECT_EQ(test, user_min, 99U);
+ KUNIT_EXPECT_EQ(test, user_max, 99U);
}
/**
@@ -836,16 +836,16 @@ static void dm_test_brightness_range_null_caps(struct kunit *test)
static void dm_test_brightness_range_pwm(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.aux_support = false;
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1);
+ KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &user_min, &user_max), 1);
/* 0x101 * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, 0x101 * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT */
- KUNIT_EXPECT_EQ(test, min, 0x101U * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT);
- KUNIT_EXPECT_EQ(test, max, 0x101U * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT);
+ KUNIT_EXPECT_EQ(test, user_min, 0x101U * AMDGPU_DM_DEFAULT_MIN_BACKLIGHT);
+ KUNIT_EXPECT_EQ(test, user_max, 0x101U * AMDGPU_DM_DEFAULT_MAX_BACKLIGHT);
}
/**
@@ -855,16 +855,16 @@ static void dm_test_brightness_range_pwm(struct kunit *test)
static void dm_test_brightness_range_aux(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.aux_support = true;
caps.aux_min_input_signal = 1;
caps.aux_max_input_signal = 512;
- KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1);
+ KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &user_min, &user_max), 1);
/* millinits: 1000 * value */
- KUNIT_EXPECT_EQ(test, min, 1000U);
- KUNIT_EXPECT_EQ(test, max, 512000U);
+ KUNIT_EXPECT_EQ(test, user_min, 1000U);
+ KUNIT_EXPECT_EQ(test, user_max, 512000U);
}
/* Tests for convert_brightness_to_user() */
@@ -878,7 +878,7 @@ static void dm_test_brightness_to_user_null_caps(struct kunit *test)
/*
* With NULL caps, get_brightness_range fails → passthrough.
* We simulate this by passing a zeroed caps struct where
- * max_input_signal=0 makes max=0 and the function hits
+ * max_input_signal=0 makes user_max=0 and the function hits
* get_brightness_range returning 0 since caps is NULL.
*/
KUNIT_EXPECT_EQ(test, convert_brightness_to_user(NULL, 42), 42U);
@@ -896,7 +896,7 @@ static void dm_test_brightness_to_user_below_min(struct kunit *test)
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- /* brightness < min (0x101*AMDGPU_DM_DEFAULT_MIN_BACKLIGHT), should return 0 */
+ /* brightness < user_min (0x101*AMDGPU_DM_DEFAULT_MIN_BACKLIGHT), should return 0 */
KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, 100), 0U);
}
@@ -907,16 +907,16 @@ static void dm_test_brightness_to_user_below_min(struct kunit *test)
static void dm_test_brightness_to_user_at_max(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.aux_support = false;
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
- /* At max → should return max */
- KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, max), max);
+ /* At user_max → should return user_max */
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, user_max), user_max);
}
/**
@@ -926,16 +926,16 @@ static void dm_test_brightness_to_user_at_max(struct kunit *test)
static void dm_test_brightness_to_user_at_min(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.aux_support = false;
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
- /* At min → should return 0 */
- KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, min), 0U);
+ /* At user_min → should return 0 */
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, user_min), 0U);
}
/**
@@ -945,19 +945,19 @@ static void dm_test_brightness_to_user_at_min(struct kunit *test)
static void dm_test_brightness_to_user_midpoint_pwm(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max, mid_hw, result;
+ unsigned int user_min, user_max, mid_hw, result;
u64 expected;
caps.aux_support = false;
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/* midpoint of hw range */
- mid_hw = min + (max - min) / 2;
- /* expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min) */
- expected = DIV_ROUND_CLOSEST_ULL((u64)max * (mid_hw - min), max - min);
+ mid_hw = user_min + (user_max - user_min) / 2;
+ /* DIV_ROUND_CLOSEST_ULL((u64)user_max * (mid_hw - user_min), user_max - user_min) */
+ expected = DIV_ROUND_CLOSEST_ULL((u64)user_max * (mid_hw - user_min), user_max - user_min);
result = convert_brightness_to_user(&caps, mid_hw);
KUNIT_EXPECT_EQ(test, result, (u32)expected);
@@ -981,7 +981,7 @@ static void dm_test_brightness_from_user_null_caps(struct kunit *test)
static void dm_test_brightness_from_user_zero(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.aux_support = false;
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
@@ -989,10 +989,10 @@ static void dm_test_brightness_from_user_zero(struct kunit *test)
/* no custom curve */
caps.data_points = 0;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
- /* brightness=0 → min + 0 = min */
- KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min);
+ /* brightness=0 → user_min + 0 = user_min */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)user_min);
}
/**
@@ -1002,20 +1002,21 @@ static void dm_test_brightness_from_user_zero(struct kunit *test)
static void dm_test_brightness_from_user_max(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.aux_support = false;
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 0;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/*
- * brightness=max → min + DIV_ROUND_CLOSEST((max-min)*max, max)
- * = min + (max - min) = max
+ * brightness=user_max →
+ * user_min + DIV_ROUND_CLOSEST((user_max-user_min)*user_max, user_max)
+ * = user_min + (user_max - user_min) = user_max
*/
- KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, user_max), (u32)user_max);
}
/**
@@ -1025,19 +1026,19 @@ static void dm_test_brightness_from_user_max(struct kunit *test)
static void dm_test_brightness_from_user_aux(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.aux_support = true;
caps.aux_min_input_signal = 1;
caps.aux_max_input_signal = 512;
caps.data_points = 0;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
- /* brightness=0 → min */
- KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min);
- /* brightness=max → max */
- KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max);
+ /* brightness=0 → user_min */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)user_min);
+ /* brightness=user_max → user_max */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, user_max), (u32)user_max);
}
/* Tests for convert_custom_brightness() */
@@ -1094,7 +1095,7 @@ static void dm_test_custom_brightness_exact_match(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
uint32_t brightness;
- unsigned int min, max;
+ unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
@@ -1110,22 +1111,23 @@ static void dm_test_custom_brightness_exact_match(struct kunit *test)
caps.luminance_data[2].input_signal = 200;
caps.luminance_data[2].luminance = 90;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/*
* Set brightness so that scale_input_to_fw yields exactly 128.
- * scale_input_to_fw(min, max, x) = DIV_ROUND_CLOSEST(x * 255, max - min)
- * With min=0, max=0x101*255=65535:
+ * scale_input_to_fw(user_min, user_max, x) =
+ * DIV_ROUND_CLOSEST(x * 255, user_max - user_min)
+ * With user_min=0, user_max=0x101*255=65535:
* We need x such that DIV_ROUND_CLOSEST(x * 255, 65535) = 128
* → x = 128 * 65535 / 255 = 32896
*/
brightness = 32896;
- convert_custom_brightness(&caps, max, &brightness);
+ convert_custom_brightness(&caps, user_max, &brightness);
/*
* Exact match: lum=50, brightness_scaled=128
- * result = scale_fw_to_input(max, DIV_ROUND_CLOSEST(50*128, 101))
+ * result = scale_fw_to_input(user_max, DIV_ROUND_CLOSEST(50*128, 101))
* = scale_fw_to_input(65535, DIV_ROUND_CLOSEST(6400, 101))
* = scale_fw_to_input(65535, 63)
* = DIV_ROUND_CLOSEST(63 * 65535, 255) = 16191 (approx)
@@ -1144,7 +1146,7 @@ static void dm_test_custom_brightness_below_first(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
uint32_t brightness;
- unsigned int min, max;
+ unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
@@ -1158,7 +1160,7 @@ static void dm_test_custom_brightness_below_first(struct kunit *test)
caps.luminance_data[1].input_signal = 200;
caps.luminance_data[1].luminance = 80;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/*
* Set brightness low enough that scaled value < 100.
@@ -1167,7 +1169,7 @@ static void dm_test_custom_brightness_below_first(struct kunit *test)
*/
brightness = 12850;
- convert_custom_brightness(&caps, max, &brightness);
+ convert_custom_brightness(&caps, user_max, &brightness);
/*
* Below first data point: lum = DIV_ROUND_CLOSEST(40 * 50, 100) = 20
@@ -1189,7 +1191,7 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
uint32_t brightness;
- unsigned int min, max;
+ unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
@@ -1203,7 +1205,7 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test)
caps.luminance_data[1].input_signal = 200;
caps.luminance_data[1].luminance = 80;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/*
* Choose a value between data points 50 and 200.
@@ -1211,7 +1213,7 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test)
*/
brightness = 32125;
- convert_custom_brightness(&caps, max, &brightness);
+ convert_custom_brightness(&caps, user_max, &brightness);
/*
* The function should interpolate between data points and produce
@@ -1230,7 +1232,7 @@ static void dm_test_custom_brightness_above_last(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
uint32_t brightness;
- unsigned int min, max;
+ unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
@@ -1244,7 +1246,7 @@ static void dm_test_custom_brightness_above_last(struct kunit *test)
caps.luminance_data[1].input_signal = 150;
caps.luminance_data[1].luminance = 60;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/*
* Choose brightness above the last data point (150).
@@ -1254,7 +1256,7 @@ static void dm_test_custom_brightness_above_last(struct kunit *test)
*/
brightness = 56533;
- convert_custom_brightness(&caps, max, &brightness);
+ convert_custom_brightness(&caps, user_max, &brightness);
/* Output should differ from input (remapped via curve) */
KUNIT_EXPECT_TRUE(test, brightness != 56533);
@@ -1271,7 +1273,7 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
uint32_t brightness;
- unsigned int min, max;
+ unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
@@ -1283,7 +1285,7 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test)
caps.luminance_data[0].input_signal = 128;
caps.luminance_data[0].luminance = 50;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/*
* Brightness below the single data point triggers the
@@ -1292,7 +1294,7 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test)
*/
brightness = 16448;
- convert_custom_brightness(&caps, max, &brightness);
+ convert_custom_brightness(&caps, user_max, &brightness);
KUNIT_EXPECT_TRUE(test, brightness < 16448);
@@ -1307,7 +1309,7 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
uint32_t brightness;
- unsigned int min, max;
+ unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
@@ -1321,7 +1323,7 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
caps.luminance_data[1].input_signal = 200;
caps.luminance_data[1].luminance = 80;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
/*
* Choose brightness between data points to trigger interpolation.
@@ -1330,7 +1332,7 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
*/
brightness = 32125;
- convert_custom_brightness(&caps, max, &brightness);
+ convert_custom_brightness(&caps, user_max, &brightness);
/* Should remap; result should differ from input */
KUNIT_EXPECT_TRUE(test, brightness != 32125);
@@ -1345,18 +1347,18 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
static void dm_test_brightness_to_user_above_max(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max, result;
+ unsigned int user_min, user_max, result;
caps.aux_support = false;
caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
- /* brightness above max → result > max (linear extrapolation) */
- result = convert_brightness_to_user(&caps, max + 1000);
+ /* brightness above user_max → result > user_max (linear extrapolation) */
+ result = convert_brightness_to_user(&caps, user_max + 1000);
- KUNIT_EXPECT_GT(test, result, max);
+ KUNIT_EXPECT_GT(test, result, user_max);
}
/**
@@ -1366,7 +1368,7 @@ static void dm_test_brightness_to_user_above_max(struct kunit *test)
static void dm_test_brightness_from_user_midrange(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
u32 result;
caps.aux_support = false;
@@ -1374,13 +1376,13 @@ static void dm_test_brightness_from_user_midrange(struct kunit *test)
caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 0;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
- /* Mid-range brightness should map to between min and max */
- result = convert_brightness_from_user(&caps, max / 2);
+ /* Mid-range brightness should map to between user_min and user_max */
+ result = convert_brightness_from_user(&caps, user_max / 2);
- KUNIT_EXPECT_GE(test, result, min);
- KUNIT_EXPECT_LE(test, result, max);
+ KUNIT_EXPECT_GE(test, result, user_min);
+ KUNIT_EXPECT_LE(test, result, user_max);
}
/**
@@ -1429,7 +1431,7 @@ static void dm_test_brightness_from_user_curve_min(struct kunit *test)
static void dm_test_brightness_from_user_with_curve(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
u32 with_curve, without_curve;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
@@ -1444,13 +1446,13 @@ static void dm_test_brightness_from_user_with_curve(struct kunit *test)
caps.luminance_data[1].input_signal = 200;
caps.luminance_data[1].luminance = 80;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
- with_curve = convert_brightness_from_user(&caps, max / 2);
+ with_curve = convert_brightness_from_user(&caps, user_max / 2);
/* Now disable the curve and compare */
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
- without_curve = convert_brightness_from_user(&caps, max / 2);
+ without_curve = convert_brightness_from_user(&caps, user_max / 2);
/* Custom curve should produce a different mapping */
KUNIT_EXPECT_NE(test, with_curve, without_curve);
@@ -1465,16 +1467,16 @@ static void dm_test_brightness_from_user_with_curve(struct kunit *test)
static void dm_test_brightness_range_zero_signals(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min = 99, max = 99;
+ unsigned int user_min = 99, user_max = 99;
caps.aux_support = false;
caps.min_input_signal = 0;
caps.max_input_signal = 0;
- /* Both signals zero → min=max=0 */
- KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &min, &max), 1);
- KUNIT_EXPECT_EQ(test, min, 0U);
- KUNIT_EXPECT_EQ(test, max, 0U);
+ /* Both signals zero → user_min=user_max=0 */
+ KUNIT_EXPECT_EQ(test, get_brightness_range(&caps, &user_min, &user_max), 1);
+ KUNIT_EXPECT_EQ(test, user_min, 0U);
+ KUNIT_EXPECT_EQ(test, user_max, 0U);
}
/* Tests for amdgpu_dm_backlight_fill_props() */
@@ -1487,19 +1489,19 @@ static void dm_test_backlight_fill_props_ac_linear(struct kunit *test)
{
struct backlight_properties props = {};
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.min_input_signal = 12;
caps.max_input_signal = 255;
caps.ac_level = 40;
caps.dc_level = 20;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
amdgpu_dm_backlight_fill_props(&caps, true, false, &props);
KUNIT_EXPECT_EQ(test, props.brightness,
- DIV_ROUND_CLOSEST(max * caps.ac_level, 100));
- KUNIT_EXPECT_EQ(test, props.max_brightness, max);
+ DIV_ROUND_CLOSEST(user_max * caps.ac_level, 100));
+ KUNIT_EXPECT_EQ(test, props.max_brightness, user_max);
KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_LINEAR);
KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW);
}
@@ -1512,7 +1514,7 @@ static void dm_test_backlight_fill_props_dc_nonlinear(struct kunit *test)
{
struct backlight_properties props = {};
struct amdgpu_dm_backlight_caps caps = {};
- unsigned int min, max;
+ unsigned int user_min, user_max;
caps.min_input_signal = 12;
caps.max_input_signal = 255;
@@ -1520,12 +1522,12 @@ static void dm_test_backlight_fill_props_dc_nonlinear(struct kunit *test)
caps.dc_level = 20;
caps.data_points = 2;
- get_brightness_range(&caps, &min, &max);
+ get_brightness_range(&caps, &user_min, &user_max);
amdgpu_dm_backlight_fill_props(&caps, false, true, &props);
KUNIT_EXPECT_EQ(test, props.brightness,
- DIV_ROUND_CLOSEST(max * caps.dc_level, 100));
- KUNIT_EXPECT_EQ(test, props.max_brightness, max);
+ DIV_ROUND_CLOSEST(user_max * caps.dc_level, 100));
+ KUNIT_EXPECT_EQ(test, props.max_brightness, user_max);
KUNIT_EXPECT_EQ(test, props.scale, BACKLIGHT_SCALE_NON_LINEAR);
KUNIT_EXPECT_EQ(test, props.type, BACKLIGHT_RAW);
}
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH RFC RFT 2/8] drm/amd/display: Interpolate between backlight LUT entries
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 1/8] drm/amd/display: Rename the user brightness range to user_min and user_max Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
2026-11-11 7:49 ` Ray Wu
2026-11-04 9:55 ` [PATCH RFC RFT 3/8] drm/amd/display: Interpolate when reading PWM back as a percentage Hugo Sales
` (5 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
backlight_millipercent_to_pwm_legacy() takes the floor index into
backlight_lut, which has one entry per percent of luminance. Any request
between two whole percents gets the PWM of the lower one, so a PWM panel
has at most 101 brightness levels however fine the request is
Interpolate linearly between the entries on each side of the request.
Whole percents still give the LUT entry. The result never decreases as
the request grows, since validate_ext_backlight_caps() only accepts a
curve whose signals and luminances strictly increase, so the LUT never
decreases either. The linear curve bypass and the 100% case are
unchanged
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
amdgpu_dm only hands the power module whole percents until patch 4, so
on its own this patch changes no PWM for it. Coming first keeps patch 4
from dropping a level to the whole percent below
With patch 4 too, on the GV301QH curve with minimum 12, the 62452 levels
from user_min to user_max reach the panel as 57821 different PWM values.
Without the series they give 99, and patch 4 without this patch would
give 100. The bottom tenth of the scale goes from 18 PWM values to 5434
BACKLIGHT_MILLIPERCENT_MAX is defined at the top of power_abm.c as
100 * 1000. mod_power_create() sets num_backlight_levels to at least
101 and allocates backlight_lut with that many entries, and nothing
changes it afterwards, so index + 1 stays within the LUT. The function
still returns early with fewer than 2 levels, as
backlight_pwm_to_millipercent_legacy() already does
KUnit: dm_test_pwm_lut_between_percents, dm_test_pwm_lut_monotonic and
dm_test_pwm_lut_min_input_signal from patch 6 fail without this patch
and pass with it
---
.../gpu/drm/amd/display/modules/power/power_abm.c | 29 ++++++++++++++++++----
1 file changed, 24 insertions(+), 5 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 5e86889eaa841..d9c1f1401b604 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
@@ -524,6 +524,9 @@ static unsigned int backlight_millipercent_to_pwm_legacy(
{
unsigned int pwm = (unsigned int)-1;
unsigned int index = 0;
+ unsigned int lower, upper;
+ unsigned long long scaled;
+ unsigned int remainder;
if (core_power == NULL)
return 0;
@@ -541,17 +544,33 @@ static unsigned int backlight_millipercent_to_pwm_legacy(
return pwm;
}
- if (millipercent >= (100 * 1000))
+ if (millipercent >= BACKLIGHT_MILLIPERCENT_MAX)
return core_power->bl_prop[inst].backlight_lut[core_power->bl_prop[inst].num_backlight_levels - 1];
- /* This will give the floor index. */
- index = ((core_power->bl_prop[inst].num_backlight_levels - 1) *
- millipercent) / 100000;
/* Null check otherwise eDP doesn't lightup when connected to DP1 */
if (core_power->bl_prop[inst].backlight_lut == NULL)
return pwm;
- pwm = core_power->bl_prop[inst].backlight_lut[index];
+ if (core_power->bl_prop[inst].num_backlight_levels < 2)
+ return pwm;
+
+ /*
+ * Interpolate between the two LUT entries around millipercent.
+ * backlight_lut has num_backlight_levels entries, and millipercent is
+ * below BACKLIGHT_MILLIPERCENT_MAX here, so index + 1 stays within it
+ */
+ scaled = (unsigned long long)(core_power->bl_prop[inst].num_backlight_levels - 1) *
+ millipercent;
+ index = (unsigned int)div_u64_rem(scaled, BACKLIGHT_MILLIPERCENT_MAX, &remainder);
+ lower = core_power->bl_prop[inst].backlight_lut[index];
+ upper = core_power->bl_prop[inst].backlight_lut[index + 1];
+
+ if (upper <= lower)
+ return lower;
+
+ pwm = lower + (unsigned int)div_u64((unsigned long long)(upper - lower) * remainder +
+ BACKLIGHT_MILLIPERCENT_MAX / 2,
+ BACKLIGHT_MILLIPERCENT_MAX);
return pwm;
}
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH RFC RFT 2/8] drm/amd/display: Interpolate between backlight LUT entries
2026-11-04 9:55 ` [PATCH RFC RFT 2/8] drm/amd/display: Interpolate between backlight LUT entries Hugo Sales
@ 2026-11-11 7:49 ` Ray Wu
0 siblings, 0 replies; 11+ messages in thread
From: Ray Wu @ 2026-11-11 7:49 UTC (permalink / raw)
To: Hugo Sales, alexander.deucher, sunpeng.li, harry.wentland,
siqueira, amd-gfx
Cc: simona, alex.hung, mario.limonciello, linux-kernel,
lohita.mudimela, bhawanpreet.lakha, dri-devel, roman.li, airlied,
christian.koenig
Reviewed-by: Ray Wu <ray.wu@amd.com>
On 11/4/2026 5:55 PM, Hugo Sales wrote:
> backlight_millipercent_to_pwm_legacy() takes the floor index into
> backlight_lut, which has one entry per percent of luminance. Any request
> between two whole percents gets the PWM of the lower one, so a PWM panel
> has at most 101 brightness levels however fine the request is
>
> Interpolate linearly between the entries on each side of the request.
> Whole percents still give the LUT entry. The result never decreases as
> the request grows, since validate_ext_backlight_caps() only accepts a
> curve whose signals and luminances strictly increase, so the LUT never
> decreases either. The linear curve bypass and the 100% case are
> unchanged
>
> Signed-off-by: Hugo Sales <hugo@hsal.es>
> ---
> amdgpu_dm only hands the power module whole percents until patch 4, so
> on its own this patch changes no PWM for it. Coming first keeps patch 4
> from dropping a level to the whole percent below
>
> With patch 4 too, on the GV301QH curve with minimum 12, the 62452 levels
> from user_min to user_max reach the panel as 57821 different PWM values.
> Without the series they give 99, and patch 4 without this patch would
> give 100. The bottom tenth of the scale goes from 18 PWM values to 5434
>
> BACKLIGHT_MILLIPERCENT_MAX is defined at the top of power_abm.c as
> 100 * 1000. mod_power_create() sets num_backlight_levels to at least
> 101 and allocates backlight_lut with that many entries, and nothing
> changes it afterwards, so index + 1 stays within the LUT. The function
> still returns early with fewer than 2 levels, as
> backlight_pwm_to_millipercent_legacy() already does
>
> KUnit: dm_test_pwm_lut_between_percents, dm_test_pwm_lut_monotonic and
> dm_test_pwm_lut_min_input_signal from patch 6 fail without this patch
> and pass with it
> ---
> .../gpu/drm/amd/display/modules/power/power_abm.c | 29 ++++++++++++++++++----
> 1 file changed, 24 insertions(+), 5 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 5e86889eaa841..d9c1f1401b604 100644
> --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
> +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
> @@ -524,6 +524,9 @@ static unsigned int backlight_millipercent_to_pwm_legacy(
> {
> unsigned int pwm = (unsigned int)-1;
> unsigned int index = 0;
> + unsigned int lower, upper;
> + unsigned long long scaled;
> + unsigned int remainder;
>
> if (core_power == NULL)
> return 0;
> @@ -541,17 +544,33 @@ static unsigned int backlight_millipercent_to_pwm_legacy(
> return pwm;
> }
>
> - if (millipercent >= (100 * 1000))
> + if (millipercent >= BACKLIGHT_MILLIPERCENT_MAX)
> return core_power->bl_prop[inst].backlight_lut[core_power->bl_prop[inst].num_backlight_levels - 1];
>
> - /* This will give the floor index. */
> - index = ((core_power->bl_prop[inst].num_backlight_levels - 1) *
> - millipercent) / 100000;
> /* Null check otherwise eDP doesn't lightup when connected to DP1 */
> if (core_power->bl_prop[inst].backlight_lut == NULL)
> return pwm;
>
> - pwm = core_power->bl_prop[inst].backlight_lut[index];
> + if (core_power->bl_prop[inst].num_backlight_levels < 2)
> + return pwm;
> +
> + /*
> + * Interpolate between the two LUT entries around millipercent.
> + * backlight_lut has num_backlight_levels entries, and millipercent is
> + * below BACKLIGHT_MILLIPERCENT_MAX here, so index + 1 stays within it
> + */
> + scaled = (unsigned long long)(core_power->bl_prop[inst].num_backlight_levels - 1) *
> + millipercent;
> + index = (unsigned int)div_u64_rem(scaled, BACKLIGHT_MILLIPERCENT_MAX, &remainder);
> + lower = core_power->bl_prop[inst].backlight_lut[index];
> + upper = core_power->bl_prop[inst].backlight_lut[index + 1];
> +
> + if (upper <= lower)
> + return lower;
> +
> + pwm = lower + (unsigned int)div_u64((unsigned long long)(upper - lower) * remainder +
> + BACKLIGHT_MILLIPERCENT_MAX / 2,
> + BACKLIGHT_MILLIPERCENT_MAX);
>
> return pwm;
> }
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC RFT 3/8] drm/amd/display: Interpolate when reading PWM back as a percentage
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 1/8] drm/amd/display: Rename the user brightness range to user_min and user_max Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 2/8] drm/amd/display: Interpolate between backlight LUT entries Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
2026-11-11 7:50 ` Ray Wu
2026-11-04 9:55 ` [PATCH RFC RFT 4/8] drm/amd/display: Stop rounding PWM brightness to whole percents Hugo Sales
` (4 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
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@hsal.es>
---
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
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH RFC RFT 3/8] drm/amd/display: Interpolate when reading PWM back as a percentage
2026-11-04 9:55 ` [PATCH RFC RFT 3/8] drm/amd/display: Interpolate when reading PWM back as a percentage Hugo Sales
@ 2026-11-11 7:50 ` Ray Wu
0 siblings, 0 replies; 11+ messages in thread
From: Ray Wu @ 2026-11-11 7:50 UTC (permalink / raw)
To: Hugo Sales, alexander.deucher, sunpeng.li, harry.wentland,
siqueira, amd-gfx
Cc: simona, alex.hung, mario.limonciello, linux-kernel,
lohita.mudimela, bhawanpreet.lakha, dri-devel, roman.li, airlied,
christian.koenig
Reviewed-by: Ray Wu <ray.wu@amd.com>
On 11/4/2026 5:55 PM, Hugo Sales wrote:
> 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@hsal.es>
> ---
> 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;
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC RFT 4/8] drm/amd/display: Stop rounding PWM brightness to whole percents
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
` (2 preceding siblings ...)
2026-11-04 9:55 ` [PATCH RFC RFT 3/8] drm/amd/display: Interpolate when reading PWM back as a percentage Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 5/8] drm/amd/display: Add KUnit tests for the custom brightness curve Hugo Sales
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
Since commit d422a66847a9 ("drm/amd/display: map PWM brightness through
custom backlight curve"), convert_brightness_to_millipercent() rounds the
16-bit user level to an 8-bit signal and the luminance to a whole
percent, so the power module gets at most 101 levels
Interpolate on the unrounded user level and round once, to millipercent.
The curve and its segments stay the same, and a result over 100% is
still capped
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
On the GV301QH curve, minimum 12 and 49 points, the 62452 levels from
user_min to user_max gave 99 different millipercent values and now give
62112
The curve's first point is 2% at signal 14, and below it the curve is a
line from 0, so the minimum, signal 12, should get 2% * 12 / 14, which
is 1714 millipercent. It now gets exactly that instead of 2000, and
every other level also gets the curve's value instead of the nearest
whole percent
Since patches 2 and 3 the power module's LUT keeps those steps. On
the same curve the levels reach the panel as 57821 different PWM values
instead of 99
backlight_custom_curve_scale() maps 0..user_max to 0..output_max, with
the curve reaching 100% at signal_max. The two weights of the
interpolation can't go negative. A luminance is a u8, so before the cap
the result is at most 2.55 times output_max. The divisor can pass 32
bits, hence mul_u64_add_u64_div_u64()
With no data points the mapping stays linear. Above the last point the
curve still runs to 100% at max_input_signal, and points above
max_input_signal are still interpolated as before. Past a last point
above max_input_signal the curve runs on to 100% at signal 255. Before,
the divisor wrapped there and the level stayed near that point's
luminance. The top level still gives 100% when a point at
max_input_signal claims less
KUnit: the PWM tests from patch 5 fail without this patch and pass
with it
---
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 79 ++++++++++++----------
1 file changed, 42 insertions(+), 37 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index 3b59699e74287..900c17e0285c9 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -39,6 +39,7 @@
#include "modules/inc/mod_power.h"
#include <linux/backlight.h>
+#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/power_supply.h>
#include <drm/drm_edid.h>
@@ -128,53 +129,59 @@ static inline u32 scale_fw_to_input(int user_max, u64 input)
return DIV_ROUND_CLOSEST_ULL(input * user_max, AMDGPU_MAX_BL_LEVEL);
}
-static u32 backlight_signal_to_luminance(const struct amdgpu_dm_backlight_caps *caps,
- u32 signal)
+/*
+ * Map user level 0..user_max to 0..output_max through the custom curve,
+ * which ends at 100% at signal_max, or at AMDGPU_MAX_BL_LEVEL when the
+ * last point lies past signal_max. A point at signal s sits at user
+ * level s * user_max / AMDGPU_MAX_BL_LEVEL, so compare
+ * user * AMDGPU_MAX_BL_LEVEL against s * user_max rather than rounding the
+ * user level to 8 bits
+ */
+static u32 backlight_custom_curve_scale(const struct amdgpu_dm_backlight_caps *caps,
+ u32 user, u32 user_max, u32 signal_max,
+ u32 output_max)
{
- u32 lower_signal, upper_signal;
- u32 lower_lum, upper_lum;
+ u64 x, x0 = 0, x1, num, den;
+ u32 l0 = 0, l1 = 100;
int i;
+ user = min(user, user_max);
if (!caps->data_points)
- return DIV_ROUND_CLOSEST(signal * 100, AMDGPU_MAX_BL_LEVEL);
-
- if (signal < caps->luminance_data[0].input_signal) {
- lower_signal = 0;
- lower_lum = 0;
- upper_signal = caps->luminance_data[0].input_signal;
- upper_lum = caps->luminance_data[0].luminance;
- } else if (signal >=
- caps->luminance_data[caps->data_points - 1].input_signal) {
- lower_signal = caps->luminance_data[caps->data_points - 1].input_signal;
- lower_lum = caps->luminance_data[caps->data_points - 1].luminance;
- upper_signal = caps->max_input_signal;
- upper_lum = 100;
+ return DIV_ROUND_CLOSEST_ULL((u64)user * output_max, user_max);
+
+ x = (u64)user * AMDGPU_MAX_BL_LEVEL;
+ for (i = 0; i < caps->data_points; i++) {
+ u64 xi = (u64)caps->luminance_data[i].input_signal * user_max;
+
+ if (x < xi)
+ break;
+ x0 = xi;
+ l0 = caps->luminance_data[i].luminance;
+ }
+
+ if (i < caps->data_points) {
+ x1 = (u64)caps->luminance_data[i].input_signal * user_max;
+ l1 = caps->luminance_data[i].luminance;
} else {
- for (i = 1;
- i < caps->data_points &&
- signal > caps->luminance_data[i].input_signal;
- i++)
- ;
-
- lower_signal = caps->luminance_data[i - 1].input_signal;
- upper_signal = caps->luminance_data[i].input_signal;
- lower_lum = caps->luminance_data[i - 1].luminance;
- upper_lum = caps->luminance_data[i].luminance;
+ x1 = (u64)signal_max * user_max;
+ if (x0 > x1)
+ x1 = (u64)AMDGPU_MAX_BL_LEVEL * user_max;
+ if (x >= x1)
+ return output_max;
}
- if (upper_signal == lower_signal)
- return upper_lum;
+ /* x0 <= x < x1, so neither weight wraps */
+ num = (u64)l0 * (x1 - x) + (u64)l1 * (x - x0);
+ den = 100 * (x1 - x0);
- return lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) *
- (signal - lower_signal),
- upper_signal - lower_signal);
+ return min_t(u64, mul_u64_add_u64_div_u64(output_max, num, den / 2, den),
+ output_max);
}
static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
u32 user_brightness)
{
unsigned int user_min, user_max;
- u32 signal, luminance;
if (!get_brightness_range(caps, &user_min, &user_max))
return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000,
@@ -184,10 +191,8 @@ static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_c
if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)
return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000, user_max);
- signal = scale_input_to_fw(user_max, user_brightness);
- luminance = backlight_signal_to_luminance(caps, signal);
-
- return min_t(u32, luminance * 1000, 100000);
+ return backlight_custom_curve_scale(caps, user_brightness, user_max,
+ caps->max_input_signal, 100000);
}
STATIC_IFN_KUNIT
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH RFC RFT 5/8] drm/amd/display: Add KUnit tests for the custom brightness curve
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
` (3 preceding siblings ...)
2026-11-04 9:55 ` [PATCH RFC RFT 4/8] drm/amd/display: Stop rounding PWM brightness to whole percents Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 6/8] drm/amd/display: Add KUnit tests for backlight LUT interpolation Hugo Sales
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
Add KUnit tests for the PWM path, using the curve from the ATIF reply of
an ASUS ROG Flow X13 GV301QH: minimum 12 and 49 points. They check
values worked out by hand, and walk every request to check that the
millipercent never goes down and changes on most steps. Two more curves
cover points above max_input_signal and a luminance over 100%
Export convert_brightness_to_millipercent() when the KUnit tests are
enabled, so the tests can call it
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
KUnit on UML with PROVE_LOCKING: amdgpu_dm_backlight passes 74 of 74
without this patch and 78 of 78 with it. Patch 4 says which of the new
tests fail without it
The other amdgpu_dm suites behave the same with and without the series.
Both runs stop in amdgpu_dm_crtc, at the BUG in drm_vblank_restore()
that dm_test_crtc_enable_vblank_ips_restore hits. With that test
skipped, the crtc, helpers, quirks and services suites pass
W=1 C=1 on power_abm, amdgpu_dm, amdgpu_dm_backlight,
amdgpu_dm_connector, amdgpu_dm_services and the backlight and connector
test objects adds no warnings at any patch, and checkpatch --strict is
clean
---
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 7 +-
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 2 +
.../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 212 +++++++++++++++++++++
3 files changed, 219 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index 900c17e0285c9..2c1da5cbf5d7f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -178,8 +178,9 @@ static u32 backlight_custom_curve_scale(const struct amdgpu_dm_backlight_caps *c
output_max);
}
-static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
- u32 user_brightness)
+STATIC_IFN_KUNIT
+u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
+ u32 user_brightness)
{
unsigned int user_min, user_max;
@@ -195,6 +196,8 @@ static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_c
caps->max_input_signal, 100000);
}
+EXPORT_IF_KUNIT(convert_brightness_to_millipercent);
+
STATIC_IFN_KUNIT
void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
unsigned int user_max, uint32_t *user_brightness)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index c5400b6c98660..9eec805809158 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -69,6 +69,8 @@ int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
unsigned int *user_min, unsigned int *user_max);
void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
unsigned int user_max, uint32_t *user_brightness);
+u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
+ u32 user_brightness);
u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
uint32_t brightness);
u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index d38f388961d69..ba8ffb92ae7af 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -1460,6 +1460,214 @@ static void dm_test_brightness_from_user_with_curve(struct kunit *test)
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
+/*
+ * ATIF QBTC reply of an ASUS ROG Flow X13 GV301QH (BIOS 418), with
+ * min_input_signal 12 and max_input_signal 255
+ */
+static const struct amdgpu_dm_luminance_data gv301qh_curve[] = {
+ { .input_signal = 14, .luminance = 2 },
+ { .input_signal = 16, .luminance = 4 },
+ { .input_signal = 18, .luminance = 6 },
+ { .input_signal = 21, .luminance = 8 },
+ { .input_signal = 23, .luminance = 10 },
+ { .input_signal = 26, .luminance = 12 },
+ { .input_signal = 29, .luminance = 14 },
+ { .input_signal = 32, .luminance = 16 },
+ { .input_signal = 35, .luminance = 18 },
+ { .input_signal = 38, .luminance = 20 },
+ { .input_signal = 41, .luminance = 22 },
+ { .input_signal = 44, .luminance = 24 },
+ { .input_signal = 48, .luminance = 26 },
+ { .input_signal = 52, .luminance = 28 },
+ { .input_signal = 55, .luminance = 30 },
+ { .input_signal = 59, .luminance = 32 },
+ { .input_signal = 62, .luminance = 34 },
+ { .input_signal = 67, .luminance = 36 },
+ { .input_signal = 71, .luminance = 38 },
+ { .input_signal = 75, .luminance = 40 },
+ { .input_signal = 80, .luminance = 42 },
+ { .input_signal = 84, .luminance = 44 },
+ { .input_signal = 88, .luminance = 46 },
+ { .input_signal = 93, .luminance = 48 },
+ { .input_signal = 98, .luminance = 50 },
+ { .input_signal = 103, .luminance = 52 },
+ { .input_signal = 108, .luminance = 54 },
+ { .input_signal = 113, .luminance = 56 },
+ { .input_signal = 118, .luminance = 58 },
+ { .input_signal = 123, .luminance = 60 },
+ { .input_signal = 129, .luminance = 62 },
+ { .input_signal = 135, .luminance = 64 },
+ { .input_signal = 140, .luminance = 66 },
+ { .input_signal = 146, .luminance = 68 },
+ { .input_signal = 152, .luminance = 70 },
+ { .input_signal = 158, .luminance = 72 },
+ { .input_signal = 164, .luminance = 74 },
+ { .input_signal = 171, .luminance = 76 },
+ { .input_signal = 177, .luminance = 78 },
+ { .input_signal = 183, .luminance = 80 },
+ { .input_signal = 190, .luminance = 82 },
+ { .input_signal = 197, .luminance = 84 },
+ { .input_signal = 204, .luminance = 86 },
+ { .input_signal = 211, .luminance = 88 },
+ { .input_signal = 218, .luminance = 90 },
+ { .input_signal = 225, .luminance = 92 },
+ { .input_signal = 232, .luminance = 94 },
+ { .input_signal = 240, .luminance = 96 },
+ { .input_signal = 247, .luminance = 98 },
+};
+
+static void dm_test_restore_dc_debug_mask(void *data)
+{
+ amdgpu_dm_set_dc_debug_mask((uintptr_t)data);
+}
+
+/* Turn the custom curve on, and back off when the test ends, even if it aborts */
+static void dm_test_enable_custom_curve(struct kunit *test)
+{
+ uint mask = amdgpu_dm_get_dc_debug_mask();
+
+ KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dm_test_restore_dc_debug_mask,
+ (void *)(uintptr_t)mask), 0);
+ amdgpu_dm_set_dc_debug_mask(mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+}
+
+/*
+ * Over AUX, use the 1..512 nits range amdgpu_dm_backlight falls back to when
+ * the EDID gives no luminance range
+ */
+static void set_gv301qh_caps(struct amdgpu_dm_backlight_caps *caps, bool aux)
+{
+ caps->aux_support = aux;
+ caps->aux_min_input_signal = 1;
+ caps->aux_max_input_signal = 512;
+ caps->min_input_signal = 12;
+ caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ caps->data_points = ARRAY_SIZE(gv301qh_curve);
+ memcpy(caps->luminance_data, gv301qh_curve, sizeof(gv301qh_curve));
+}
+
+/*
+ * A PWM range of 0..65535 with no minimum, so the result is the curve's
+ * output on 0..65535
+ */
+static void set_custom_curve_caps(struct amdgpu_dm_backlight_caps *caps,
+ const struct amdgpu_dm_luminance_data *points,
+ int n)
+{
+ caps->aux_support = false;
+ caps->min_input_signal = 0;
+ caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ caps->data_points = n;
+ memcpy(caps->luminance_data, points, n * sizeof(*points));
+}
+
+/**
+ * dm_test_brightness_curve_pwm_values - Test PWM millipercent on the GV301QH curve
+ * @test: The KUnit test context
+ *
+ * Worked out by hand on the PWM range of 3084..65535. A request below the
+ * minimum is raised to 3084, signal 12, which is 12 / 14 of the way to the
+ * first point (14, 2), so 1714 millipercent. 32000 is signal 124.51,
+ * between (123, 60) and (129, 62), so 60505. 65535 is 100000
+ */
+static void dm_test_brightness_curve_pwm_values(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ dm_test_enable_custom_curve(test);
+ set_gv301qh_caps(&caps, false);
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 0), 1714U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 3084), 1714U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 32000), 60505U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 65535), 100000U);
+}
+
+/**
+ * dm_test_brightness_curve_pwm_steps - Test PWM millipercent never drops and keeps its steps
+ * @test: The KUnit test context
+ *
+ * Walk every request from user_min to user_max. The millipercent must
+ * never go down, and most requests must change it. There are 62452
+ * requests and 100001 possible millipercent values, against 101 with
+ * whole percents
+ */
+static void dm_test_brightness_curve_pwm_steps(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int user_min, user_max, user, steps = 0;
+ u32 prev, cur;
+
+ dm_test_enable_custom_curve(test);
+ set_gv301qh_caps(&caps, false);
+ get_brightness_range(&caps, &user_min, &user_max);
+
+ prev = convert_brightness_to_millipercent(&caps, user_min);
+ for (user = user_min + 1; user <= user_max; user++) {
+ cur = convert_brightness_to_millipercent(&caps, user);
+ if (cur < prev || cur > 100000) {
+ KUNIT_FAIL(test, "%u gives %u after %u", user, cur, prev);
+ return;
+ }
+ steps += cur != prev;
+ prev = cur;
+ }
+
+ KUNIT_EXPECT_GT(test, steps, (user_max - user_min) / 2);
+}
+
+/**
+ * dm_test_brightness_curve_pwm_points_above_max - Test PWM curve points above max_input_signal
+ * @test: The KUnit test context
+ *
+ * With max_input_signal 200 and points (180, 70) and (220, 90), the curve
+ * still runs between the two points. 42330 is signal 210.003, so 85001
+ * millipercent. Past the last point the curve runs on to 100% at signal
+ * 255: 47873 is signal 237.502, so 95001
+ */
+static void dm_test_brightness_curve_pwm_points_above_max(struct kunit *test)
+{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 180, .luminance = 70 },
+ { .input_signal = 220, .luminance = 90 },
+ };
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ dm_test_enable_custom_curve(test);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
+ caps.max_input_signal = 200;
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 42330), 85001U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 47873), 95001U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 0x101 * 200), 100000U);
+}
+
+/**
+ * dm_test_brightness_curve_pwm_luminance_over_100 - Test PWM with an ATIF luminance over 100%
+ * @test: The KUnit test context
+ *
+ * Points (100, 50), (200, 150) and (250, 120) claim more than 100%. The
+ * curve uses them as given and caps only the result, as before.
+ * Level 125 is 50 + 100 * 25 / 100 = 75%, and the curve reaches full
+ * brightness at level 150, halfway to the second point, and stays there
+ */
+static void dm_test_brightness_curve_pwm_luminance_over_100(struct kunit *test)
+{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 100, .luminance = 50 },
+ { .input_signal = 200, .luminance = 150 },
+ { .input_signal = 250, .luminance = 120 },
+ };
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ dm_test_enable_custom_curve(test);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 125 * 0x101), 75000U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 150 * 0x101), 100000U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 225 * 0x101), 100000U);
+}
+
/**
* dm_test_brightness_range_zero_signals - Test Brightness range with zero min and max signals
* @test: The KUnit test context
@@ -2018,6 +2226,10 @@ static struct kunit_case dm_backlight_test_cases[] = {
KUNIT_CASE(dm_test_brightness_from_user_midrange),
KUNIT_CASE(dm_test_brightness_from_user_with_curve),
KUNIT_CASE(dm_test_brightness_from_user_curve_min),
+ KUNIT_CASE(dm_test_brightness_curve_pwm_values),
+ KUNIT_CASE(dm_test_brightness_curve_pwm_steps),
+ KUNIT_CASE(dm_test_brightness_curve_pwm_points_above_max),
+ KUNIT_CASE(dm_test_brightness_curve_pwm_luminance_over_100),
KUNIT_CASE(dm_test_brightness_range_zero_signals),
/* amdgpu_dm_backlight_fill_props */
KUNIT_CASE(dm_test_backlight_fill_props_ac_linear),
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH RFC RFT 6/8] drm/amd/display: Add KUnit tests for backlight LUT interpolation
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
` (4 preceding siblings ...)
2026-11-04 9:55 ` [PATCH RFC RFT 5/8] drm/amd/display: Add KUnit tests for the custom brightness curve Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 7/8] drm/amd/display: Map AUX brightness through the custom curve like PWM Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 8/8] drm/amd/display: Add KUnit tests for the AUX custom brightness curve Hugo Sales
7 siblings, 0 replies; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
Build the power module's LUT with initialize_backlight_caps() from the
ATIF reply of an ASUS ROG Flow X13 GV301QH, with min_input_signal 0, and
test backlight_millipercent_to_pwm() on it. A whole percent gives its
LUT entry, and a request in between lands between the two entries, at
values worked out by hand. The PWM never goes down as the request goes
up, and below the first curve point every millipercent gets its own
PWM, where the floor index gave only 0 and 0x707
Test backlight_pwm_to_millipercent() by hand at two points too, and
check that every PWM turned into a percentage and back lands within 1
of where it started. A LUT built with the real min_input_signal of 12
gets the same hand checks in both directions
Export these functions when the KUnit tests are enabled, so the tests
can call them
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
The tests use gv301qh_curve and set_gv301qh_caps() from patch 5
KUnit on UML with PROVE_LOCKING: amdgpu_dm_backlight passes 78 of 78
without this patch and 83 of 83 with it. dm_test_pwm_lut_whole_percents
passes at every patch, since a whole percent always gave the LUT entry.
dm_test_pwm_lut_between_percents, dm_test_pwm_lut_monotonic and
dm_test_pwm_lut_min_input_signal pass from patch 2, and
dm_test_pwm_lut_readback from patch 3
The other amdgpu_dm suites behave the same with and without the series,
as in patch 5
checkpatch --strict flags two prototypes whose line ends in '('. They
follow the DC style of the prototypes around them in power_abm.c and
power_helpers.h
---
.../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 146 +++++++++++++++++++++
.../gpu/drm/amd/display/modules/power/power_abm.c | 10 +-
.../drm/amd/display/modules/power/power_helpers.h | 4 +
3 files changed, 159 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index ba8ffb92ae7af..e9da2c46779fd 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -24,6 +24,7 @@
#include "amd_shared.h"
#include "link_service.h"
#include "dc/inc/hw/panel_cntl.h"
+#include "power_helpers.h"
struct dm_backlight_connector_fixture {
struct amdgpu_device *adev;
@@ -2163,6 +2164,145 @@ static void dm_test_setup_backlight_device_attaches_abm_property(struct kunit *t
amdgpu_dm_set_abm_level_param(saved_abm_level);
}
+/* Tests for the legacy backlight LUT conversion, in both directions */
+
+/*
+ * Build the power module's backlight LUT from the GV301QH curve the same
+ * way the driver does, through the ATIF caps cached in the display manager
+ */
+static struct core_power *gv301qh_power_module(struct kunit *test, int min_input_signal)
+{
+ struct amdgpu_device *adev = dm_kunit_alloc_adev(test);
+ struct dc *dc = dm_kunit_alloc_dc_with_ctx(test);
+ struct core_power *core_power;
+
+ core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, core_power);
+
+ set_gv301qh_caps(&adev->dm.backlight_caps[0], false);
+ adev->dm.backlight_caps[0].min_input_signal = min_input_signal;
+ adev->dm.backlight_caps[0].caps_valid = true;
+ dc->ctx->driver_context = adev;
+ core_power->dc = dc;
+ core_power->bl_prop[0].num_backlight_levels = 101;
+ core_power->bl_prop[0].backlight_lut = kunit_kcalloc(test, 101, sizeof(unsigned int),
+ GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, core_power->bl_prop[0].backlight_lut);
+
+ initialize_backlight_caps(core_power, 0);
+ KUNIT_ASSERT_TRUE(test, core_power->bl_prop[0].backlight_caps_valid);
+
+ return core_power;
+}
+
+/**
+ * dm_test_pwm_lut_whole_percents - Test Whole percents give the LUT entry
+ * @test: The KUnit test context
+ *
+ * With min_input_signal 0 the LUT starts at 0, reaches the first curve
+ * point (signal 14, 2%) at index 2 and has 0x707 halfway at index 1. Index
+ * 50 is the point (98, 50%)
+ */
+static void dm_test_pwm_lut_whole_percents(struct kunit *test)
+{
+ struct core_power *core_power = gv301qh_power_module(test, 0);
+
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 0, 0), 0U);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 1000, 0), 0x707U);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 2000, 0), 0xe0eU);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 50000, 0), 98U * 0x101);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 100000, 0), 0xffffU);
+}
+
+/**
+ * dm_test_pwm_lut_between_percents - Test Requests between percents interpolate
+ * @test: The KUnit test context
+ *
+ * The code rounds the step between two entries before adding it. 0.5% is
+ * halfway from 0 to 0x707, so 1799 / 2 = 899.5, which rounds to 900. 1.5%
+ * is 1799 + 900 = 2699. 2.5% is halfway from 0xe0e to 0xf0f, so 257 / 2 =
+ * 128.5 rounds to 129 and 3598 + 129 = 3727. 99.999% is 1028 * 999 / 1000
+ * = 1026.97 past 64507, which rounds to 1027 and gives 65534
+ */
+static void dm_test_pwm_lut_between_percents(struct kunit *test)
+{
+ struct core_power *core_power = gv301qh_power_module(test, 0);
+
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 500, 0), 900U);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 1500, 0), 2699U);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 2500, 0), 3727U);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 99999, 0), 65534U);
+}
+
+/**
+ * dm_test_pwm_lut_readback - Test PWM reads back as the percentage that set it
+ * @test: The KUnit test context
+ *
+ * 900 is halfway from 0 to 0x707, so it reads back as 0.5%. Every PWM from
+ * 0 to 0xffff turned into a percentage and back must land within 1 of
+ * where it started
+ */
+static void dm_test_pwm_lut_readback(struct kunit *test)
+{
+ struct core_power *core_power = gv301qh_power_module(test, 0);
+ unsigned int pwm, mp;
+
+ KUNIT_EXPECT_EQ(test, backlight_pwm_to_millipercent(core_power, 0x707, 0), 1000U);
+ KUNIT_EXPECT_EQ(test, backlight_pwm_to_millipercent(core_power, 900, 0), 500U);
+
+ for (pwm = 0; pwm <= 0xffff; pwm++) {
+ mp = backlight_pwm_to_millipercent(core_power, pwm, 0);
+ KUNIT_ASSERT_LE_MSG(test,
+ abs((int)backlight_millipercent_to_pwm(core_power, mp, 0) -
+ (int)pwm), 1,
+ "pwm %u", pwm);
+ }
+}
+
+/**
+ * dm_test_pwm_lut_monotonic - Test PWM never goes down as the request goes up
+ * @test: The KUnit test context
+ *
+ * Every millipercent below 2% must also get its own PWM, where the floor
+ * index gave only 0 and 0x707
+ */
+static void dm_test_pwm_lut_monotonic(struct kunit *test)
+{
+ struct core_power *core_power = gv301qh_power_module(test, 0);
+ unsigned int mp, pwm, prev = 0, steps = 0;
+
+ for (mp = 0; mp <= 100000; mp++) {
+ pwm = backlight_millipercent_to_pwm(core_power, mp, 0);
+ KUNIT_ASSERT_GE_MSG(test, pwm, prev, "millipercent %u", mp);
+ if (mp < 2000 && (mp == 0 || pwm != prev))
+ steps++;
+ prev = pwm;
+ }
+
+ KUNIT_EXPECT_EQ(test, steps, 2000U);
+}
+
+/**
+ * dm_test_pwm_lut_min_input_signal - Test the LUT with the real minimum of 12
+ * @test: The KUnit test context
+ *
+ * With min_input_signal 12 the LUT starts at 0xc0c, has 3341 at index 1 and
+ * reaches the first curve point (signal 14, 2%) at index 2, 0xe0e. 1.714%,
+ * the curve's value at the minimum, is 0.714 of the way from 3341 to 0xe0e,
+ * so 3341 + 183 = 3524. A PWM at or below 0xc0c reads back as 0%
+ */
+static void dm_test_pwm_lut_min_input_signal(struct kunit *test)
+{
+ struct core_power *core_power = gv301qh_power_module(test, 12);
+
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 0, 0), 0xc0cU);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 1000, 0), 3341U);
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(core_power, 1714, 0), 3524U);
+ KUNIT_EXPECT_EQ(test, backlight_pwm_to_millipercent(core_power, 3341, 0), 1000U);
+ KUNIT_EXPECT_EQ(test, backlight_pwm_to_millipercent(core_power, 0xc0c, 0), 0U);
+ KUNIT_EXPECT_EQ(test, backlight_pwm_to_millipercent(core_power, 1000, 0), 0U);
+}
+
static struct kunit_case dm_backlight_test_cases[] = {
/* dm_find_stream_with_link */
KUNIT_CASE(dm_test_find_stream_with_link_returns_match),
@@ -2255,6 +2395,12 @@ static struct kunit_case dm_backlight_test_cases[] = {
KUNIT_CASE(dm_test_setup_backlight_device_max_edps),
KUNIT_CASE(dm_test_setup_backlight_device_oled_success),
KUNIT_CASE(dm_test_setup_backlight_device_attaches_abm_property),
+ /* legacy backlight LUT conversion */
+ KUNIT_CASE(dm_test_pwm_lut_whole_percents),
+ KUNIT_CASE(dm_test_pwm_lut_between_percents),
+ KUNIT_CASE(dm_test_pwm_lut_readback),
+ KUNIT_CASE(dm_test_pwm_lut_monotonic),
+ KUNIT_CASE(dm_test_pwm_lut_min_input_signal),
{}
};
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 83d79c20281ec..11efe59b9f96b 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
@@ -3,6 +3,7 @@
// Copyright 2026 Advanced Micro Devices, Inc.
#include "dm_services.h"
+#include "dm_helpers.h"
#include "dc.h"
#include "mod_power.h"
#include "core_types.h"
@@ -436,7 +437,8 @@ static unsigned int backlight_pwm_to_millipercent_legacy(
* 0 PWM = 0% and max_backlight_pwm = 100%.
* Otherwise, falls back to the legacy LUT based mapping.
*/
-static unsigned int backlight_pwm_to_millipercent(
+STATIC_IFN_KUNIT
+unsigned int backlight_pwm_to_millipercent(
struct core_power *core_power, unsigned int pwm, unsigned int inst)
{
if (!is_vesa_abc(core_power, inst))
@@ -456,6 +458,8 @@ static unsigned int backlight_pwm_to_millipercent(
core_power->bl_prop[inst].max_backlight_pwm);
}
+EXPORT_IF_KUNIT(backlight_pwm_to_millipercent);
+
static unsigned int backlight_pwm_to_millinit_legacy(
struct core_power *core_power, unsigned int pwm, unsigned int inst)
{
@@ -586,6 +590,8 @@ unsigned int backlight_millipercent_to_pwm(
BACKLIGHT_MILLIPERCENT_MAX);
}
+EXPORT_IF_KUNIT(backlight_millipercent_to_pwm);
+
/* Legacy millinit→PWM conversion: scales linearly between
* [min_brightness_millinits, max_brightness_millinits] → [min_backlight_pwm, max_backlight_pwm].
*/
@@ -844,6 +850,8 @@ void initialize_backlight_caps(struct core_power *core_power, unsigned int inst)
core_power->bl_prop[inst].backlight_caps_valid = true;
}
+EXPORT_IF_KUNIT(initialize_backlight_caps);
+
static void varibright_set_level(struct core_power *core_power)
{
if (!core_power->varibright_prop.varibright_active ||
diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
index 68679fa109460..9c07f1c92a781 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
+++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
@@ -201,6 +201,10 @@ void mod_power_set_backlight_control_type(struct core_power *core_power,
unsigned int inst, enum backlight_control_type backlight_control_type);
unsigned int backlight_millipercent_to_pwm(
struct core_power *core_power, unsigned int millipercent, unsigned int inst);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+unsigned int backlight_pwm_to_millipercent(
+ struct core_power *core_power, unsigned int pwm, unsigned int inst);
+#endif
unsigned int backlight_millipercent_to_millinit(
struct core_power *core_power, unsigned int millipercent, unsigned int inst);
void fill_backlight_level_params(struct core_power *core_power,
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH RFC RFT 7/8] drm/amd/display: Map AUX brightness through the custom curve like PWM
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
` (5 preceding siblings ...)
2026-11-04 9:55 ` [PATCH RFC RFT 6/8] drm/amd/display: Add KUnit tests for backlight LUT interpolation Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
2026-11-04 9:55 ` [PATCH RFC RFT 8/8] drm/amd/display: Add KUnit tests for the AUX custom brightness curve Hugo Sales
7 siblings, 0 replies; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
convert_custom_brightness() rounds the user level to 8 bits and the
luminance to a whole percent, then multiplies that percentage by the
level again
Use the backlight_custom_curve_scale() helper from the PWM path instead.
It places the level between user_min and user_max by the curve's
luminance. This changes the AUX mapping on purpose. A level now asks for
the luminance the curve gives it, as on PWM, and no longer for that
luminance times the level
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
On the 1..512 nits fallback range with the GV301QH curve, the 512001
levels gave 182 different values from 1000 to 495968 millinits. Now they
give 433295 different values from 1000 to 512000, and only levels 0 and
1 give 1000, instead of the bottom 6.1% of the scale. Half the scale
moves from 159310 to 315265 millinits
The convert_custom_brightness() tests now check exact values through
convert_brightness_from_user(), and
dm_test_brightness_from_user_curve_min expects 261610 for half the AUX
range now that the level goes in once. The early returns for
DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE and for no data points keep the
linear mapping. convert_brightness_to_user() never inverted the curve
and doesn't change
As before, the AUX curve ends at 100% at signal 255. It doesn't read
max_input_signal, which is a PWM limit. A point at signal 255 with less
than 100% no longer holds the top level below max, as it already
doesn't on PWM
KUnit: the four tests in patch 8, and the brightness values it adds to
the luminance over 100% test, fail without this patch and pass with it
---
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 88 +-----
.../amd/display/amdgpu_dm/amdgpu_dm_backlight.h | 2 -
.../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 314 +++++++--------------
3 files changed, 115 insertions(+), 289 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index 2c1da5cbf5d7f..358ba0c73d795 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -117,18 +117,6 @@ int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
}
EXPORT_IF_KUNIT(get_brightness_range);
-/* Rescale userspace [0..user_max] to the firmware curve's [0..255]. */
-static inline u32 scale_input_to_fw(int user_max, u64 input)
-{
- return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, user_max);
-}
-
-/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [0..user_max] */
-static inline u32 scale_fw_to_input(int user_max, u64 input)
-{
- return DIV_ROUND_CLOSEST_ULL(input * user_max, AMDGPU_MAX_BL_LEVEL);
-}
-
/*
* Map user level 0..user_max to 0..output_max through the custom curve,
* which ends at 100% at signal_max, or at AMDGPU_MAX_BL_LEVEL when the
@@ -198,71 +186,6 @@ u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *ca
EXPORT_IF_KUNIT(convert_brightness_to_millipercent);
-STATIC_IFN_KUNIT
-void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int user_max, uint32_t *user_brightness)
-{
- u32 brightness = scale_input_to_fw(user_max, *user_brightness);
- u8 lower_signal, upper_signal, upper_lum, lower_lum, lum;
- int left, right;
-
- if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE)
- return;
-
- if (!caps->data_points)
- return;
-
- /*
- * Handle the case where brightness is below the first data point
- * Interpolate between (0,0) and (first_signal, first_lum)
- */
- if (brightness < caps->luminance_data[0].input_signal) {
- lum = DIV_ROUND_CLOSEST(caps->luminance_data[0].luminance * brightness,
- caps->luminance_data[0].input_signal);
- goto scale;
- }
-
- left = 0;
- right = caps->data_points - 1;
- while (left <= right) {
- int mid = left + (right - left) / 2;
- u8 signal = caps->luminance_data[mid].input_signal;
-
- /* Exact match found */
- if (signal == brightness) {
- lum = caps->luminance_data[mid].luminance;
- goto scale;
- }
-
- if (signal < brightness)
- left = mid + 1;
- else
- right = mid - 1;
- }
-
- /* verify bound */
- if (left >= caps->data_points)
- left = caps->data_points - 1;
-
- /* At this point, left > right */
- lower_signal = caps->luminance_data[right].input_signal;
- upper_signal = caps->luminance_data[left].input_signal;
- lower_lum = caps->luminance_data[right].luminance;
- upper_lum = caps->luminance_data[left].luminance;
-
- /* interpolate */
- if (right == left || !lower_lum)
- lum = upper_lum;
- else
- lum = lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) *
- (brightness - lower_signal),
- upper_signal - lower_signal);
-scale:
- *user_brightness = scale_fw_to_input(user_max, DIV_ROUND_CLOSEST(lum * brightness, 101));
-}
-
-EXPORT_IF_KUNIT(convert_custom_brightness);
-
STATIC_IFN_KUNIT
u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
uint32_t brightness)
@@ -272,10 +195,15 @@ u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
if (!get_brightness_range(caps, &user_min, &user_max))
return brightness;
- convert_custom_brightness(caps, user_max, &brightness);
+ if ((amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) ||
+ !caps->data_points)
+ return user_min + DIV_ROUND_CLOSEST_ULL((u64)(user_max - user_min) * brightness,
+ user_max);
- /* Rescale 0..user_max to user_min..user_max */
- return user_min + DIV_ROUND_CLOSEST_ULL((u64)(user_max - user_min) * brightness, user_max);
+ /* max_input_signal is a PWM limit, so the AUX curve ends at signal 255 */
+ return user_min + backlight_custom_curve_scale(caps, brightness, user_max,
+ AMDGPU_MAX_BL_LEVEL,
+ user_max - user_min);
}
EXPORT_IF_KUNIT(convert_brightness_from_user);
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index 9eec805809158..d3e64d74d81c0 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -67,8 +67,6 @@ ssize_t panel_power_savings_store(struct device *device,
const char *buf, size_t count);
int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
unsigned int *user_min, unsigned int *user_max);
-void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int user_max, uint32_t *user_brightness);
u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
u32 user_brightness);
u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index e9da2c46779fd..06c1e084357ff 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -1042,48 +1042,69 @@ static void dm_test_brightness_from_user_aux(struct kunit *test)
KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, user_max), (u32)user_max);
}
-/* Tests for convert_custom_brightness() */
+/* Tests for the custom curve in convert_brightness_from_user() */
+
+/*
+ * A PWM range of 0..65535 with no minimum, so the result is the curve's
+ * output on 0..65535
+ */
+static void set_custom_curve_caps(struct amdgpu_dm_backlight_caps *caps,
+ const struct amdgpu_dm_luminance_data *points,
+ int n)
+{
+ caps->aux_support = false;
+ caps->min_input_signal = 0;
+ caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ caps->data_points = n;
+ memcpy(caps->luminance_data, points, n * sizeof(*points));
+}
/**
* dm_test_custom_brightness_no_data_points - Test Custom brightness no data points
* @test: The KUnit test context
+ *
+ * With no data points the request maps linearly onto user_min..user_max
*/
static void dm_test_custom_brightness_no_data_points(struct kunit *test)
{
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness = 128;
- uint32_t saved = brightness;
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
caps.data_points = 0;
- convert_custom_brightness(&caps, 65535, &brightness);
+ /* 3084 + DIV_ROUND_CLOSEST(62451 * 32768, 65535) */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 32768), 34310U);
- /* No data points → no-op */
- KUNIT_EXPECT_EQ(test, brightness, saved);
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
}
/**
* dm_test_custom_brightness_debug_mask_disables - Test Custom brightness debug mask disables
* @test: The KUnit test context
+ *
+ * DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE skips the curve, so the request maps
+ * linearly onto user_min..user_max
*/
static void dm_test_custom_brightness_debug_mask_disables(struct kunit *test)
{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 50, .luminance = 10 },
+ { .input_signal = 128, .luminance = 50 },
+ { .input_signal = 200, .luminance = 90 },
+ };
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness = 128;
- uint32_t saved = brightness;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
- caps.data_points = 3;
- caps.luminance_data[0].input_signal = 50;
- caps.luminance_data[0].luminance = 10;
-
- /* Set the disable flag */
- amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
- convert_custom_brightness(&caps, 65535, &brightness);
+ amdgpu_dm_set_dc_debug_mask(saved_mask | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
- /* Should be no-op due to debug mask */
- KUNIT_EXPECT_EQ(test, brightness, saved);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 32768), 34310U);
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
@@ -1091,50 +1112,24 @@ static void dm_test_custom_brightness_debug_mask_disables(struct kunit *test)
/**
* dm_test_custom_brightness_exact_match - Test Custom brightness exact match
* @test: The KUnit test context
+ *
+ * 32896 is signal 128 exactly, the point (128, 50), so the result is 50% of
+ * 65535, rounded to 32768
*/
static void dm_test_custom_brightness_exact_match(struct kunit *test)
{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 50, .luminance = 20 },
+ { .input_signal = 128, .luminance = 50 },
+ { .input_signal = 200, .luminance = 90 },
+ };
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness;
- unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
- amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
-
- caps.aux_support = false;
- caps.min_input_signal = 0;
- caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps.data_points = 3;
- caps.luminance_data[0].input_signal = 50;
- caps.luminance_data[0].luminance = 20;
- caps.luminance_data[1].input_signal = 128;
- caps.luminance_data[1].luminance = 50;
- caps.luminance_data[2].input_signal = 200;
- caps.luminance_data[2].luminance = 90;
-
- get_brightness_range(&caps, &user_min, &user_max);
-
- /*
- * Set brightness so that scale_input_to_fw yields exactly 128.
- * scale_input_to_fw(user_min, user_max, x) =
- * DIV_ROUND_CLOSEST(x * 255, user_max - user_min)
- * With user_min=0, user_max=0x101*255=65535:
- * We need x such that DIV_ROUND_CLOSEST(x * 255, 65535) = 128
- * → x = 128 * 65535 / 255 = 32896
- */
- brightness = 32896;
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
- convert_custom_brightness(&caps, user_max, &brightness);
-
- /*
- * Exact match: lum=50, brightness_scaled=128
- * result = scale_fw_to_input(user_max, DIV_ROUND_CLOSEST(50*128, 101))
- * = scale_fw_to_input(65535, DIV_ROUND_CLOSEST(6400, 101))
- * = scale_fw_to_input(65535, 63)
- * = DIV_ROUND_CLOSEST(63 * 65535, 255) = 16191 (approx)
- */
- KUNIT_EXPECT_TRUE(test, brightness != 32896);
- KUNIT_EXPECT_TRUE(test, brightness < 32896);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 32896), 32768U);
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
@@ -1142,44 +1137,23 @@ static void dm_test_custom_brightness_exact_match(struct kunit *test)
/**
* dm_test_custom_brightness_below_first - Test Custom brightness below first
* @test: The KUnit test context
+ *
+ * 12850 is signal 50, halfway from (0, 0) to the first point (100, 40), so
+ * the result is 20% of 65535
*/
static void dm_test_custom_brightness_below_first(struct kunit *test)
{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 100, .luminance = 40 },
+ { .input_signal = 200, .luminance = 80 },
+ };
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness;
- unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
- amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
-
- caps.aux_support = false;
- caps.min_input_signal = 0;
- caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps.data_points = 2;
- caps.luminance_data[0].input_signal = 100;
- caps.luminance_data[0].luminance = 40;
- caps.luminance_data[1].input_signal = 200;
- caps.luminance_data[1].luminance = 80;
-
- get_brightness_range(&caps, &user_min, &user_max);
-
- /*
- * Set brightness low enough that scaled value < 100.
- * scale_input_to_fw(0, 65535, x) = DIV_ROUND_CLOSEST(x*255, 65535)
- * For result=50: x = 50*65535/255 = 12850
- */
- brightness = 12850;
-
- convert_custom_brightness(&caps, user_max, &brightness);
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
- /*
- * Below first data point: lum = DIV_ROUND_CLOSEST(40 * 50, 100) = 20
- * Then: scale_fw_to_input(65535, DIV_ROUND_CLOSEST(20 * 50, 101))
- * = scale_fw_to_input(65535, DIV_ROUND_CLOSEST(1000, 101))
- * = scale_fw_to_input(65535, 10)
- * The output should be significantly less than input.
- */
- KUNIT_EXPECT_TRUE(test, brightness < 12850);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 12850), 13107U);
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
@@ -1187,40 +1161,23 @@ static void dm_test_custom_brightness_below_first(struct kunit *test)
/**
* dm_test_custom_brightness_interpolation - Test Custom brightness interpolation
* @test: The KUnit test context
+ *
+ * 32125 is signal 125, halfway from (50, 20) to (200, 80), so the result is
+ * 50% of 65535, rounded to 32768
*/
static void dm_test_custom_brightness_interpolation(struct kunit *test)
{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 50, .luminance = 20 },
+ { .input_signal = 200, .luminance = 80 },
+ };
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness;
- unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
- amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
-
- caps.aux_support = false;
- caps.min_input_signal = 0;
- caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps.data_points = 2;
- caps.luminance_data[0].input_signal = 50;
- caps.luminance_data[0].luminance = 20;
- caps.luminance_data[1].input_signal = 200;
- caps.luminance_data[1].luminance = 80;
-
- get_brightness_range(&caps, &user_min, &user_max);
-
- /*
- * Choose a value between data points 50 and 200.
- * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125
- */
- brightness = 32125;
-
- convert_custom_brightness(&caps, user_max, &brightness);
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
- /*
- * The function should interpolate between data points and produce
- * a remapped value different from the input.
- */
- KUNIT_EXPECT_TRUE(test, brightness != 32125);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 32125), 32768U);
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
@@ -1228,40 +1185,25 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test)
/**
* dm_test_custom_brightness_above_last - Test Custom brightness above last data point
* @test: The KUnit test context
+ *
+ * Above the last point (150, 60) the curve runs to (255, 100). 56540 is
+ * signal 220, so the luminance is 60 + 40 * 70 / 105, 86.67%, and the
+ * result is 56797. The top of the range gives max
*/
static void dm_test_custom_brightness_above_last(struct kunit *test)
{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 50, .luminance = 20 },
+ { .input_signal = 150, .luminance = 60 },
+ };
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness;
- unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
- amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
-
- caps.aux_support = false;
- caps.min_input_signal = 0;
- caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps.data_points = 2;
- caps.luminance_data[0].input_signal = 50;
- caps.luminance_data[0].luminance = 20;
- caps.luminance_data[1].input_signal = 150;
- caps.luminance_data[1].luminance = 60;
-
- get_brightness_range(&caps, &user_min, &user_max);
-
- /*
- * Choose brightness above the last data point (150).
- * scale_input_to_fw(0, 65535, x) = 220 when x = 220*65535/255 = 56533
- * After binary search, left >= data_points, clamped → right==left,
- * so lum = upper_lum = 60.
- */
- brightness = 56533;
-
- convert_custom_brightness(&caps, user_max, &brightness);
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
- /* Output should differ from input (remapped via curve) */
- KUNIT_EXPECT_TRUE(test, brightness != 56533);
- KUNIT_EXPECT_TRUE(test, brightness < 56533);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 56540), 56797U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 65535), 65535U);
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
@@ -1269,35 +1211,22 @@ static void dm_test_custom_brightness_above_last(struct kunit *test)
/**
* dm_test_custom_brightness_single_data_point - Test Custom brightness with single data point
* @test: The KUnit test context
+ *
+ * 16448 is signal 64, halfway from (0, 0) to the only point (128, 50), so
+ * the result is 25% of 65535, rounded to 16384
*/
static void dm_test_custom_brightness_single_data_point(struct kunit *test)
{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 128, .luminance = 50 },
+ };
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness;
- unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
- amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
-
- caps.aux_support = false;
- caps.min_input_signal = 0;
- caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps.data_points = 1;
- caps.luminance_data[0].input_signal = 128;
- caps.luminance_data[0].luminance = 50;
-
- get_brightness_range(&caps, &user_min, &user_max);
-
- /*
- * Brightness below the single data point triggers the
- * "below first" path: lum = DIV_ROUND_CLOSEST(50 * scaled, 128).
- * scale_input_to_fw(0, 65535, x) = 64 when x = 64*65535/255 = 16448
- */
- brightness = 16448;
-
- convert_custom_brightness(&caps, user_max, &brightness);
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
- KUNIT_EXPECT_TRUE(test, brightness < 16448);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 16448), 16384U);
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
@@ -1305,38 +1234,24 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test)
/**
* dm_test_custom_brightness_lower_lum_zero - Test Custom brightness with zero lower luminance
* @test: The KUnit test context
+ *
+ * A point with 0% luminance is interpolated like any other. 32125 is
+ * signal 125, halfway from (50, 0) to (200, 80), so the result is 40% of
+ * 65535
*/
static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
{
+ static const struct amdgpu_dm_luminance_data points[] = {
+ { .input_signal = 50, .luminance = 0 },
+ { .input_signal = 200, .luminance = 80 },
+ };
struct amdgpu_dm_backlight_caps caps = {};
- uint32_t brightness;
- unsigned int user_min, user_max;
uint saved_mask = amdgpu_dm_get_dc_debug_mask();
- amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
-
- caps.aux_support = false;
- caps.min_input_signal = 0;
- caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps.data_points = 2;
- caps.luminance_data[0].input_signal = 50;
- caps.luminance_data[0].luminance = 0; /* zero lower luminance */
- caps.luminance_data[1].input_signal = 200;
- caps.luminance_data[1].luminance = 80;
-
- get_brightness_range(&caps, &user_min, &user_max);
-
- /*
- * Choose brightness between data points to trigger interpolation.
- * scale_input_to_fw(0, 65535, x) = 125 when x = 125*65535/255 = 32125
- * With lower_lum == 0, code takes shortcut: lum = upper_lum = 80.
- */
- brightness = 32125;
-
- convert_custom_brightness(&caps, user_max, &brightness);
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
- /* Should remap; result should differ from input */
- KUNIT_EXPECT_TRUE(test, brightness != 32125);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 32125), 26214U);
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
@@ -1392,10 +1307,10 @@ static void dm_test_brightness_from_user_midrange(struct kunit *test)
*
* On the AUX range of 1..512 nits with points (50, 20%) and (200, 80%), a
* request for 0 must give the 1000 millinit minimum, where adding the
- * minimum twice gave 1998. Half the scale is signal 128, luminance 51%,
- * so 51 * 128 / 101 = 65 of 255, which is 1000 + 511000 * 65 / 255 =
- * 131255. A request for 0 on the PWM range with min_input_signal 12 must
- * give 12 * 0x101
+ * minimum twice gave 1998. Half the scale is signal 127.5, so luminance
+ * 20 + 60 * 77.5 / 150 = 51%, which is 1000 + 511000 * 51% = 261610. A
+ * request for 0 on the PWM range with min_input_signal 12 must give
+ * 12 * 0x101
*/
static void dm_test_brightness_from_user_curve_min(struct kunit *test)
{
@@ -1417,7 +1332,7 @@ static void dm_test_brightness_from_user_curve_min(struct kunit *test)
caps.luminance_data[1].luminance = 80;
KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), 1000U);
- KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 256000), 131255U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 256000), 261610U);
caps.aux_support = false;
KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), 12U * 0x101);
@@ -1547,21 +1462,6 @@ static void set_gv301qh_caps(struct amdgpu_dm_backlight_caps *caps, bool aux)
memcpy(caps->luminance_data, gv301qh_curve, sizeof(gv301qh_curve));
}
-/*
- * A PWM range of 0..65535 with no minimum, so the result is the curve's
- * output on 0..65535
- */
-static void set_custom_curve_caps(struct amdgpu_dm_backlight_caps *caps,
- const struct amdgpu_dm_luminance_data *points,
- int n)
-{
- caps->aux_support = false;
- caps->min_input_signal = 0;
- caps->max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
- caps->data_points = n;
- memcpy(caps->luminance_data, points, n * sizeof(*points));
-}
-
/**
* dm_test_brightness_curve_pwm_values - Test PWM millipercent on the GV301QH curve
* @test: The KUnit test context
@@ -2353,7 +2253,7 @@ static struct kunit_case dm_backlight_test_cases[] = {
KUNIT_CASE(dm_test_brightness_from_user_zero),
KUNIT_CASE(dm_test_brightness_from_user_max),
KUNIT_CASE(dm_test_brightness_from_user_aux),
- /* convert_custom_brightness */
+ /* custom curve in convert_brightness_from_user */
KUNIT_CASE(dm_test_custom_brightness_no_data_points),
KUNIT_CASE(dm_test_custom_brightness_debug_mask_disables),
KUNIT_CASE(dm_test_custom_brightness_exact_match),
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH RFC RFT 8/8] drm/amd/display: Add KUnit tests for the AUX custom brightness curve
2026-11-04 9:55 [PATCH RFC RFT 0/8] drm/amd/display: Stop rounding backlight levels to whole percents Hugo Sales
` (6 preceding siblings ...)
2026-11-04 9:55 ` [PATCH RFC RFT 7/8] drm/amd/display: Map AUX brightness through the custom curve like PWM Hugo Sales
@ 2026-11-04 9:55 ` Hugo Sales
7 siblings, 0 replies; 11+ messages in thread
From: Hugo Sales @ 2026-11-04 9:55 UTC (permalink / raw)
To: alexander.deucher, sunpeng.li, harry.wentland, siqueira, amd-gfx
Cc: Hugo Sales, ray.wu, simona, alex.hung, mario.limonciello,
linux-kernel, lohita.mudimela, bhawanpreet.lakha, dri-devel,
roman.li, airlied, christian.koenig
Test the AUX mapping with the GV301QH curve on the 1..512 nits fallback
range. A request for 0 and for max must give user_min and user_max on
both paths, and values must match ones worked out by hand. A walk over
every request must never go down, must leave the minimum within 1% of
the scale and must change the output on most steps. An AUX curve whose
max_input_signal is below 255 must still run to 100% at signal 255
Check convert_brightness_from_user() on the PWM and AUX ranges in the
test with a luminance over 100% too
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
KUnit on UML with PROVE_LOCKING: amdgpu_dm_backlight passes 83 of 83
without this patch and 87 of 87 with it. Without patch 7 the four new
tests fail, and so do the brightness checks added to the luminance over
100% test
This and patch 7 are the AUX half of the series. My laptop's panel
ignores AMD's AUX brightness, so I tested them only in KUnit and with
the amdgpu_dm_brightness trace on a forced AUX path. Patches 1 to 6
don't need them
---
.../amdgpu_dm/tests/amdgpu_dm_backlight_test.c | 133 ++++++++++++++++++++-
1 file changed, 132 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
index 06c1e084357ff..b3e6a96320164 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_backlight_test.c
@@ -1517,6 +1517,75 @@ static void dm_test_brightness_curve_pwm_steps(struct kunit *test)
KUNIT_EXPECT_GT(test, steps, (user_max - user_min) / 2);
}
+/**
+ * dm_test_brightness_from_user_curve_floor - Test Custom curve ends are the minimum and max
+ * @test: The KUnit test context
+ *
+ * A request for 0 must reach the panel as the minimum, not as the minimum
+ * applied twice, and a request for max must reach max, on both the PWM and
+ * the AUX range
+ */
+static void dm_test_brightness_from_user_curve_floor(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int user_min, user_max;
+
+ dm_test_enable_custom_curve(test);
+
+ set_gv301qh_caps(&caps, false);
+ get_brightness_range(&caps, &user_min, &user_max);
+ KUNIT_EXPECT_EQ(test, user_min, 3084U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)user_min);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, user_max), (u32)user_max);
+
+ set_gv301qh_caps(&caps, true);
+ get_brightness_range(&caps, &user_min, &user_max);
+ KUNIT_EXPECT_EQ(test, user_min, 1000U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)user_min);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, user_max), (u32)user_max);
+}
+
+/**
+ * dm_test_brightness_from_user_curve_values - Test Custom curve output on the AUX range
+ * @test: The KUnit test context
+ *
+ * Worked out by hand on the AUX range of 1000..512000 millinits. 1000 is
+ * signal 0.498, so 0.0711% from (0, 0) to the first point (14, 2), and
+ * 1000 + 511000 * 0.000711 gives 1364. 200000 is signal 99.61, between
+ * (98, 50) and (103, 52), so 50.644% and 259790
+ */
+static void dm_test_brightness_from_user_curve_values(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ dm_test_enable_custom_curve(test);
+ set_gv301qh_caps(&caps, true);
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 1000), 1364U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 200000), 259790U);
+}
+
+/**
+ * dm_test_brightness_from_user_curve_aux_top - Test AUX curve ignores the PWM max_input_signal
+ * @test: The KUnit test context
+ *
+ * On AUX the curve runs to 100% at signal 255 even when max_input_signal,
+ * a PWM limit, is lower. With max_input_signal 251, 499953 is signal
+ * 249.00003, between the last point (247, 98) and 100% at 255, so 98.5%
+ * and 504335. Ending the curve at 251 would give 99% and 506890
+ */
+static void dm_test_brightness_from_user_curve_aux_top(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ dm_test_enable_custom_curve(test);
+ set_gv301qh_caps(&caps, true);
+ caps.max_input_signal = 251;
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 499953), 504335U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 512000), 512000U);
+}
+
/**
* dm_test_brightness_curve_pwm_points_above_max - Test PWM curve points above max_input_signal
* @test: The KUnit test context
@@ -1543,6 +1612,51 @@ static void dm_test_brightness_curve_pwm_points_above_max(struct kunit *test)
KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 0x101 * 200), 100000U);
}
+static void check_curve_steps(struct kunit *test,
+ const struct amdgpu_dm_backlight_caps *caps)
+{
+ unsigned int user_min, user_max, user, steps = 0;
+ u32 prev, cur;
+
+ get_brightness_range(caps, &user_min, &user_max);
+ prev = user_min;
+ for (user = 0; user <= user_max; user++) {
+ cur = convert_brightness_from_user(caps, user);
+ if (cur < prev || cur > user_max) {
+ KUNIT_FAIL(test, "%u gives %u after %u, range %u..%u",
+ user, cur, prev, user_min, user_max);
+ return;
+ }
+ steps += cur != prev;
+ prev = cur;
+ }
+
+ KUNIT_EXPECT_GT(test, convert_brightness_from_user(caps, user_max / 100), (u32)user_min);
+ KUNIT_EXPECT_GT(test, steps, (user_max - user_min) / 2);
+}
+
+/**
+ * dm_test_brightness_from_user_curve_monotonic - Test Custom curve never drops and keeps its steps
+ * @test: The KUnit test context
+ *
+ * Every request from 0 to max, on both the PWM and the AUX range, must give
+ * a value in user_min..user_max that is no lower than the one for the request
+ * before. 1% of the range must already be above the minimum, and most
+ * requests must change the output
+ */
+static void dm_test_brightness_from_user_curve_monotonic(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ dm_test_enable_custom_curve(test);
+
+ set_gv301qh_caps(&caps, false);
+ check_curve_steps(test, &caps);
+
+ set_gv301qh_caps(&caps, true);
+ check_curve_steps(test, &caps);
+}
+
/**
* dm_test_brightness_curve_pwm_luminance_over_100 - Test PWM with an ATIF luminance over 100%
* @test: The KUnit test context
@@ -1550,7 +1664,9 @@ static void dm_test_brightness_curve_pwm_points_above_max(struct kunit *test)
* Points (100, 50), (200, 150) and (250, 120) claim more than 100%. The
* curve uses them as given and caps only the result, as before.
* Level 125 is 50 + 100 * 25 / 100 = 75%, and the curve reaches full
- * brightness at level 150, halfway to the second point, and stays there
+ * brightness at level 150, halfway to the second point, and stays there.
+ * On the AUX range of 1..255 nits, level 125000 is signal 125, so 1000 +
+ * 75% of 254000
*/
static void dm_test_brightness_curve_pwm_luminance_over_100(struct kunit *test)
{
@@ -1564,9 +1680,20 @@ static void dm_test_brightness_curve_pwm_luminance_over_100(struct kunit *test)
dm_test_enable_custom_curve(test);
set_custom_curve_caps(&caps, points, ARRAY_SIZE(points));
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 125 * 0x101), 49151U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 150 * 0x101), 65535U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 225 * 0x101), 65535U);
KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 125 * 0x101), 75000U);
KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 150 * 0x101), 100000U);
KUNIT_EXPECT_EQ(test, convert_brightness_to_millipercent(&caps, 225 * 0x101), 100000U);
+
+ caps.aux_support = true;
+ caps.aux_min_input_signal = 1;
+ caps.aux_max_input_signal = 255;
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 125000), 191500U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 150000), 255000U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 225000), 255000U);
}
/**
@@ -2268,7 +2395,11 @@ static struct kunit_case dm_backlight_test_cases[] = {
KUNIT_CASE(dm_test_brightness_from_user_curve_min),
KUNIT_CASE(dm_test_brightness_curve_pwm_values),
KUNIT_CASE(dm_test_brightness_curve_pwm_steps),
+ KUNIT_CASE(dm_test_brightness_from_user_curve_floor),
+ KUNIT_CASE(dm_test_brightness_from_user_curve_values),
+ KUNIT_CASE(dm_test_brightness_from_user_curve_aux_top),
KUNIT_CASE(dm_test_brightness_curve_pwm_points_above_max),
+ KUNIT_CASE(dm_test_brightness_from_user_curve_monotonic),
KUNIT_CASE(dm_test_brightness_curve_pwm_luminance_over_100),
KUNIT_CASE(dm_test_brightness_range_zero_signals),
/* amdgpu_dm_backlight_fill_props */
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread