* [PATCH 0/4] drm/amd/display: fix brightness ownership through power module
@ 2026-09-02 12:31 Andrei Rusu de Castro
2026-09-02 12:31 ` [PATCH 1/4] drm/amd/display: keep custom brightness curve in userspace domain Andrei Rusu de Castro
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 12:31 UTC (permalink / raw)
To: amd-gfx
Cc: harry.wentland, sunpeng.li, siqueira, alexander.deucher,
christian.koenig, airlied, simona, alex.hung, roman.li,
mario.limonciello, dri-devel, linux-kernel, chen-yu.chen, ray.wu
Linux passes PWM eDP brightness through two owners. The display manager
maps the request into the firmware range and applies the ATIF custom
curve. The power module then derives a percentage from that hardware
value and applies the same curve and range again. A non-zero firmware
minimum consequently prevents zero from reaching the panel minimum.
The split also leaves the custom-curve disable policy and final PWM mask
attached to the wrong owner.
Patch 1 keeps pre-power-module custom-curve output in the userspace
domain, which also fixes affected stable kernels. Patch 2 covers that
conversion with a non-zero firmware minimum. Patch 3 passes zero-anchored
millipercent into the power module and moves the panel policy, curve,
range, trace units, and final effective-PWM mask to that owner. Patch 4
covers ordinary PWM, forced PWM, AMD-AUX fallback, true AUX, multiple
panels, endpoints, interior values, clamping, and invalid ranges.
The complete series passed all 85 AMD backlight UML KUnit cases on the
current base and after clean application to Linux 7.3-rc1. It was built
into one kernel, booted on two Strix Halo systems, and produced the same
AMDGPU module on both. An instrumented physical panel test observed:
request 0 -> 0 millipercent -> PWM 3084 -> success
request 65535 -> 100000 millipercent -> PWM 65535 -> success
The panel visibly reached minimum and maximum brightness, then returned
to its starting level. No fatal kernel event followed. The true AUX path
was covered by KUnit but was not available for a physical test. The
existing LUT-unaware hardware readback inverse is unchanged.
Andrei Rusu de Castro (4):
drm/amd/display: keep custom brightness curve in userspace domain
drm/amd/display: test custom brightness with non-zero minimum
drm/amd/display: pass userspace brightness to power module
drm/amd/display: test power module brightness input domain
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +-
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 59 ++-
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 5 +-
.../amd/display/amdgpu_dm/amdgpu_dm_trace.h | 3 +-
.../tests/amdgpu_dm_backlight_test.c | 447 +++++++++++++++++-
.../drm/amd/display/modules/inc/mod_power.h | 1 +
.../gpu/drm/amd/display/modules/power/power.c | 2 +
.../drm/amd/display/modules/power/power_abm.c | 6 +-
.../amd/display/modules/power/power_helpers.h | 1 +
9 files changed, 494 insertions(+), 36 deletions(-)
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/4] drm/amd/display: keep custom brightness curve in userspace domain
2026-09-02 12:31 [PATCH 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
@ 2026-09-02 12:31 ` Andrei Rusu de Castro
2026-09-02 12:31 ` [PATCH 2/4] drm/amd/display: test custom brightness with non-zero minimum Andrei Rusu de Castro
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 12:31 UTC (permalink / raw)
To: amd-gfx
Cc: harry.wentland, sunpeng.li, siqueira, alexander.deucher,
christian.koenig, airlied, simona, alex.hung, roman.li,
mario.limonciello, dri-devel, linux-kernel, chen-yu.chen, ray.wu,
stable
convert_brightness_from_user() expects convert_custom_brightness() to
reshape a value in the userspace [0..max] domain before the caller maps
it once into the firmware [min..max] domain.
Commit 8dbd72cb7900 ("drm/amd/display: Export full brightness range to
userspace") instead made convert_custom_brightness() call
scale_fw_to_input(min, max, ...). That helper adds min and scales by the
firmware span, entering the firmware domain before the caller applies
the same span and minimum again.
With the default PWM range, min is 3084 and max is 65535. A zero request
through any custom curve consequently returns 6023 instead of 3084, so
the darkest 2939 firmware levels are unreachable.
Keep the curve result in [0..max]. scale_fw_to_input() then becomes the
inverse of scale_input_to_fw(), and the caller remains the sole owner of
the userspace-to-firmware conversion. The linear path is unchanged.
A later patch removes this helper from the PWM power-module path because
the power module owns that curve. AUX panels continue to use it, and
stable kernels predating the power-module refactor still require this
correction on PWM panels.
Commit 6fd83a1c2cde ("drm/amd/display: Scale custom brightness curve from
full range") fixed the corresponding input-side domain error. This
completes the output side of the same conversion.
Source and arithmetic analysis identified the duplicate
domain conversion. KUnit coverage for a non-zero firmware minimum is
added separately.
Fixes: 8dbd72cb7900 ("drm/amd/display: Export full brightness range to userspace")
Cc: stable@vger.kernel.org # 6.17.x: 6fd83a1c2cde: drm/amd/display: Scale custom brightness curve from full range
Cc: stable@vger.kernel.org # 6.17.x
Cc: stable@vger.kernel.org # before 7.3 this code is in amdgpu_dm.c; drop the header and test hunks
Signed-off-by: Andrei Rusu de Castro <arc@empyreal.works>
---
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 13 ++++----
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 3 +-
.../tests/amdgpu_dm_backlight_test.c | 30 +++++++++----------
3 files changed, 22 insertions(+), 24 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 e61bbc310f33..424b33573a73 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
@@ -112,16 +112,15 @@ static inline u32 scale_input_to_fw(int max, u64 input)
return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max);
}
-/* Rescale from [0..AMDGPU_MAX_BL_LEVEL] to [min..max] */
-static inline u32 scale_fw_to_input(int min, int max, u64 input)
+/* Rescale the firmware curve's [0..AMDGPU_MAX_BL_LEVEL] back to userspace [0..max]. */
+static inline u32 scale_fw_to_input(int max, u64 input)
{
- return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL);
+ return DIV_ROUND_CLOSEST_ULL(input * max, AMDGPU_MAX_BL_LEVEL);
}
STATIC_IFN_KUNIT
void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int min, unsigned int max,
- uint32_t *user_brightness)
+ unsigned int max, uint32_t *user_brightness)
{
u32 brightness = scale_input_to_fw(max, *user_brightness);
u8 lower_signal, upper_signal, upper_lum, lower_lum, lum;
@@ -179,7 +178,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(min, max,
+ *user_brightness = scale_fw_to_input(max,
DIV_ROUND_CLOSEST(lum * brightness, 101));
}
@@ -194,7 +193,7 @@ u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
if (!get_brightness_range(caps, &min, &max))
return brightness;
- convert_custom_brightness(caps, min, max, &brightness);
+ convert_custom_brightness(caps, max, &brightness);
/* Rescale 0..max to min..max */
return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness, max);
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 07b75064847c..90bed0ea5d00 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,7 @@ ssize_t panel_power_savings_store(struct device *device,
int get_brightness_range(const struct amdgpu_dm_backlight_caps *caps,
unsigned int *min, unsigned int *max);
void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps,
- unsigned int min, unsigned int max,
- uint32_t *user_brightness);
+ unsigned int 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 7ca17f803f9d..1fb171fdbc3c 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
@@ -1033,7 +1033,7 @@ static void dm_test_custom_brightness_no_data_points(struct kunit *test)
caps.data_points = 0;
- convert_custom_brightness(&caps, 3084, 65535, &brightness);
+ convert_custom_brightness(&caps, 65535, &brightness);
/* No data points → no-op */
KUNIT_EXPECT_EQ(test, brightness, saved);
@@ -1057,7 +1057,7 @@ static void dm_test_custom_brightness_debug_mask_disables(struct kunit *test)
/* Set the disable flag */
amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
- convert_custom_brightness(&caps, 3084, 65535, &brightness);
+ convert_custom_brightness(&caps, 65535, &brightness);
/* Should be no-op due to debug mask */
KUNIT_EXPECT_EQ(test, brightness, saved);
@@ -1100,14 +1100,14 @@ static void dm_test_custom_brightness_exact_match(struct kunit *test)
*/
brightness = 32896;
- convert_custom_brightness(&caps, min, max, &brightness);
+ convert_custom_brightness(&caps, max, &brightness);
/*
* Exact match: lum=50, brightness_scaled=128
- * result = scale_fw_to_input(min, max, DIV_ROUND_CLOSEST(50*128, 101))
- * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(6400, 101))
- * = scale_fw_to_input(0, 65535, 63)
- * = 0 + DIV_ROUND_CLOSEST(63 * 65535, 255) = 16191 (approx)
+ * result = scale_fw_to_input(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);
@@ -1146,13 +1146,13 @@ static void dm_test_custom_brightness_below_first(struct kunit *test)
*/
brightness = 12850;
- convert_custom_brightness(&caps, min, max, &brightness);
+ convert_custom_brightness(&caps, max, &brightness);
/*
* Below first data point: lum = DIV_ROUND_CLOSEST(40 * 50, 100) = 20
- * Then: scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(20 * 50, 101))
- * = scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(1000, 101))
- * = scale_fw_to_input(0, 65535, 10)
+ * 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);
@@ -1190,7 +1190,7 @@ static void dm_test_custom_brightness_interpolation(struct kunit *test)
*/
brightness = 32125;
- convert_custom_brightness(&caps, min, max, &brightness);
+ convert_custom_brightness(&caps, max, &brightness);
/*
* The function should interpolate between data points and produce
@@ -1233,7 +1233,7 @@ static void dm_test_custom_brightness_above_last(struct kunit *test)
*/
brightness = 56533;
- convert_custom_brightness(&caps, min, max, &brightness);
+ convert_custom_brightness(&caps, max, &brightness);
/* Output should differ from input (remapped via curve) */
KUNIT_EXPECT_TRUE(test, brightness != 56533);
@@ -1271,7 +1271,7 @@ static void dm_test_custom_brightness_single_data_point(struct kunit *test)
*/
brightness = 16448;
- convert_custom_brightness(&caps, min, max, &brightness);
+ convert_custom_brightness(&caps, max, &brightness);
KUNIT_EXPECT_TRUE(test, brightness < 16448);
@@ -1309,7 +1309,7 @@ static void dm_test_custom_brightness_lower_lum_zero(struct kunit *test)
*/
brightness = 32125;
- convert_custom_brightness(&caps, min, max, &brightness);
+ convert_custom_brightness(&caps, max, &brightness);
/* Should remap; result should differ from input */
KUNIT_EXPECT_TRUE(test, brightness != 32125);
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/4] drm/amd/display: test custom brightness with non-zero minimum
2026-09-02 12:31 [PATCH 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
2026-09-02 12:31 ` [PATCH 1/4] drm/amd/display: keep custom brightness curve in userspace domain Andrei Rusu de Castro
@ 2026-09-02 12:31 ` Andrei Rusu de Castro
2026-09-02 12:32 ` [PATCH 3/4] drm/amd/display: pass userspace brightness to power module Andrei Rusu de Castro
2026-09-02 12:33 ` [PATCH 4/4] drm/amd/display: test power module brightness input domain Andrei Rusu de Castro
3 siblings, 0 replies; 5+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 12:31 UTC (permalink / raw)
To: amd-gfx
Cc: harry.wentland, sunpeng.li, siqueira, alexander.deucher,
christian.koenig, airlied, simona, alex.hung, roman.li,
mario.limonciello, dri-devel, linux-kernel, chen-yu.chen, ray.wu
The existing custom-curve tests use min_input_signal == 0. In that
configuration the firmware minimum is zero and an extra [0..max] to
[min..max] conversion is an identity, so the output-side double scaling
cannot be observed.
Add a curve using the default non-zero PWM minimum. Cover the helper's
userspace-domain result, zero and maximum endpoints, interpolation,
monotonicity, range bounds, zero round-trip, agreement with the linear
path at zero, and the unchanged no-curve path.
Six of the nine new cases fail against the pre-fix arithmetic. The other
three retain endpoint and linear-path invariants around the repair.
The cases were verified under UML KUnit.
Signed-off-by: Andrei Rusu de Castro <arc@empyreal.works>
---
.../tests/amdgpu_dm_backlight_test.c | 264 ++++++++++++++++++
1 file changed, 264 insertions(+)
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 1fb171fdbc3c..a6fa052a8272 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
@@ -1398,6 +1398,261 @@ static void dm_test_brightness_from_user_with_curve(struct kunit *test)
amdgpu_dm_set_dc_debug_mask(saved_mask);
}
+/*
+ * The custom curve cases above all use min_input_signal == 0. There the
+ * firmware minimum is zero and the [0..max] to [min..max] span is the identity,
+ * so an extra application of that mapping cannot be observed. The cases below
+ * use the default firmware range instead, where min is 0x101 * 12 == 3084 and
+ * max is 0x101 * 255 == 65535.
+ */
+static void dm_test_curve_caps_init(struct amdgpu_dm_backlight_caps *caps)
+{
+ 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 = 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 = 255;
+ caps->luminance_data[2].luminance = 100;
+}
+
+/**
+ * dm_test_custom_brightness_user_domain - Curve output is a userspace value
+ * @test: The KUnit test context
+ *
+ * convert_custom_brightness() reshapes a value inside the userspace [0..max]
+ * domain. Its caller owns the single conversion to the firmware [min..max]
+ * domain, so the firmware minimum must not appear in this result.
+ */
+static void dm_test_custom_brightness_user_domain(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ unsigned int min, max;
+ u32 brightness;
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ dm_test_curve_caps_init(&caps);
+ get_brightness_range(&caps, &min, &max);
+
+ /* Zero stays zero; it is not lifted to the firmware minimum here. */
+ brightness = 0;
+ convert_custom_brightness(&caps, max, &brightness);
+ KUNIT_EXPECT_EQ(test, brightness, (u32)0);
+
+ /* The top of the curve stays inside the userspace range. */
+ brightness = max;
+ convert_custom_brightness(&caps, max, &brightness);
+ KUNIT_EXPECT_LE(test, brightness, (u32)max);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_curve_from_user_zero_is_min - Zero maps to the firmware minimum
+ * @test: The KUnit test context
+ *
+ * Zero is the darkest level userspace can ask for and must reach the darkest
+ * level the firmware accepts.
+ */
+static void dm_test_curve_from_user_zero_is_min(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ unsigned int min, max;
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ dm_test_curve_caps_init(&caps);
+ get_brightness_range(&caps, &min, &max);
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)min);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_curve_from_user_matches_linear_at_zero - Curve keeps the lower endpoint
+ * @test: The KUnit test context
+ *
+ * The curve reshapes the interior of the range. It does not move either
+ * endpoint, so the curved and linear paths must agree at zero.
+ */
+static void dm_test_curve_from_user_matches_linear_at_zero(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ u32 with_curve, without_curve;
+
+ dm_test_curve_caps_init(&caps);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ with_curve = convert_brightness_from_user(&caps, 0);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask | DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ without_curve = convert_brightness_from_user(&caps, 0);
+
+ KUNIT_EXPECT_EQ(test, with_curve, without_curve);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_curve_from_user_round_trip_zero - Zero survives a conversion round trip
+ * @test: The KUnit test context
+ */
+static void dm_test_curve_from_user_round_trip_zero(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ u32 level;
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ dm_test_curve_caps_init(&caps);
+
+ level = convert_brightness_from_user(&caps, 0);
+ KUNIT_EXPECT_EQ(test, convert_brightness_to_user(&caps, level), (u32)0);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_curve_from_user_interpolation - Interpolated point with a non-zero min
+ * @test: The KUnit test context
+ */
+static void dm_test_curve_from_user_interpolation(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ dm_test_curve_caps_init(&caps);
+
+ /*
+ * scale_input_to_fw(65535, 25700) = DIV_ROUND_CLOSEST(25700 * 255, 65535)
+ * = 100, which falls between the (50, 20) and (128, 50) points:
+ * lum = 20 + DIV_ROUND_CLOSEST((50 - 20) * (100 - 50), 128 - 50) = 39
+ * The curved firmware level is DIV_ROUND_CLOSEST(39 * 100, 101) = 39,
+ * which is DIV_ROUND_CLOSEST(39 * 65535, 255) = 10023 in the userspace
+ * domain and 3084 + DIV_ROUND_CLOSEST(62451 * 10023, 65535) = 12635
+ * once converted to the firmware domain.
+ */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 25700), (u32)12635);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_curve_from_user_max - The top of the range stays inside the range
+ * @test: The KUnit test context
+ */
+static void dm_test_curve_from_user_max(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ unsigned int min, max;
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ dm_test_curve_caps_init(&caps);
+ get_brightness_range(&caps, &min, &max);
+
+ /*
+ * scale_input_to_fw(65535, 65535) = 255 matches the last point exactly,
+ * so lum = 100 and the curved firmware level is
+ * DIV_ROUND_CLOSEST(100 * 255, 101) = 252. That is
+ * DIV_ROUND_CLOSEST(252 * 65535, 255) = 64764 in the userspace domain
+ * and 3084 + DIV_ROUND_CLOSEST(62451 * 64764, 65535) = 64800 in the
+ * firmware domain.
+ */
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)64800);
+ KUNIT_EXPECT_LE(test, convert_brightness_from_user(&caps, max), (u32)max);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_curve_from_user_monotonic - A rising curve gives a rising level
+ * @test: The KUnit test context
+ */
+static void dm_test_curve_from_user_monotonic(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ unsigned int min, max, i;
+ u32 previous = 0;
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ dm_test_curve_caps_init(&caps);
+ get_brightness_range(&caps, &min, &max);
+
+ for (i = 0; i <= max; i += 1023) {
+ u32 level = convert_brightness_from_user(&caps, i);
+
+ KUNIT_ASSERT_GE(test, level, previous);
+ previous = level;
+ }
+
+ KUNIT_EXPECT_GE(test, convert_brightness_from_user(&caps, max), previous);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_curve_from_user_within_range - Curved levels never leave [min..max]
+ * @test: The KUnit test context
+ *
+ * The firmware level is programmed through a 16-bit path, so a converted value
+ * above max would wrap and darken the panel.
+ */
+static void dm_test_curve_from_user_within_range(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ unsigned int min, max, i;
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ dm_test_curve_caps_init(&caps);
+ get_brightness_range(&caps, &min, &max);
+
+ for (i = 0; i <= max; i += 1023) {
+ u32 level = convert_brightness_from_user(&caps, i);
+
+ KUNIT_ASSERT_GE(test, level, (u32)min);
+ KUNIT_ASSERT_LE(test, level, (u32)max);
+ }
+
+ KUNIT_EXPECT_LE(test, convert_brightness_from_user(&caps, max), (u32)max);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_from_user_no_curve_unchanged - The linear path is untouched
+ * @test: The KUnit test context
+ *
+ * Without luminance data the conversion is the plain
+ * min + DIV_ROUND_CLOSEST((max - min) * brightness, max) mapping.
+ */
+static void dm_test_from_user_no_curve_unchanged(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, 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);
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 0), (u32)3084);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 16383), (u32)18696);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 32767), (u32)34309);
+ KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)65535);
+}
+
/**
* dm_test_brightness_range_zero_signals - Test Brightness range with zero min and max signals
* @test: The KUnit test context
@@ -1955,6 +2210,15 @@ static struct kunit_case dm_backlight_test_cases[] = {
KUNIT_CASE(dm_test_brightness_to_user_above_max),
KUNIT_CASE(dm_test_brightness_from_user_midrange),
KUNIT_CASE(dm_test_brightness_from_user_with_curve),
+ KUNIT_CASE(dm_test_custom_brightness_user_domain),
+ KUNIT_CASE(dm_test_curve_from_user_zero_is_min),
+ KUNIT_CASE(dm_test_curve_from_user_matches_linear_at_zero),
+ KUNIT_CASE(dm_test_curve_from_user_round_trip_zero),
+ KUNIT_CASE(dm_test_curve_from_user_interpolation),
+ KUNIT_CASE(dm_test_curve_from_user_max),
+ KUNIT_CASE(dm_test_curve_from_user_monotonic),
+ KUNIT_CASE(dm_test_curve_from_user_within_range),
+ KUNIT_CASE(dm_test_from_user_no_curve_unchanged),
KUNIT_CASE(dm_test_brightness_range_zero_signals),
/* amdgpu_dm_backlight_fill_props */
KUNIT_CASE(dm_test_backlight_fill_props_ac_linear),
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 3/4] drm/amd/display: pass userspace brightness to power module
2026-09-02 12:31 [PATCH 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
2026-09-02 12:31 ` [PATCH 1/4] drm/amd/display: keep custom brightness curve in userspace domain Andrei Rusu de Castro
2026-09-02 12:31 ` [PATCH 2/4] drm/amd/display: test custom brightness with non-zero minimum Andrei Rusu de Castro
@ 2026-09-02 12:32 ` Andrei Rusu de Castro
2026-09-02 12:33 ` [PATCH 4/4] drm/amd/display: test power module brightness input domain Andrei Rusu de Castro
3 siblings, 0 replies; 5+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 12:32 UTC (permalink / raw)
To: amd-gfx
Cc: harry.wentland, sunpeng.li, siqueira, alexander.deucher,
christian.koenig, airlied, simona, alex.hung, roman.li,
mario.limonciello, dri-devel, linux-kernel, chen-yu.chen, ray.wu,
stable
The power module consumes millipercent and builds its own PWM lookup
table from the ATIF brightness transfer characteristics.
The Linux display manager instead converts the userspace request to a
firmware level, then derives a percentage from that hardware-domain
value. For a non-zero PWM minimum this maps the minimum above zero and
the maximum above 100 percent. It also applies the ATIF curve once in
the display manager and again in the power module.
Pass the original userspace percentage to the power module on PWM
panels. Keep the existing nits conversion and source-unit brightness
mask for AUX panels.
The brightness mask is a final DP source-level quirk. Carry it into the
power module and apply it when the effective hardware handoff is PWM,
preserving ordinary PWM, forced-PWM OLED, fallback PWM, and mode-change
replay without altering a true AUX handoff.
DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE previously bypassed only the display
manager's copy of the curve. Wire it to the power module's existing
linear bypass and use the selected panel's policy rather than panel
zero's. Label the brightness trace value as millipercent or millinits so
the corrected input domain is explicit.
A live DCN 3.5.1 trace exposed the domain mismatch. KUnit coverage is
added separately.
Fixes: 3c108046e1d6 ("drm/amd/display: Add power module on Linux")
Cc: stable@vger.kernel.org # 7.2.x
Signed-off-by: Andrei Rusu de Castro <arc@empyreal.works>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 ++-
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 46 +++++++++++++++----
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 2 +
.../amd/display/amdgpu_dm/amdgpu_dm_trace.h | 3 +-
.../drm/amd/display/modules/inc/mod_power.h | 1 +
.../gpu/drm/amd/display/modules/power/power.c | 2 +
.../drm/amd/display/modules/power/power_abm.c | 6 ++-
.../amd/display/modules/power/power_helpers.h | 1 +
8 files changed, 55 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index ec483276d753..4e730527be4a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -425,7 +425,9 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm)
!(amdgpu_dc_feature_mask & DC_DISABLE_FRACTIONAL_PWM_MASK);
init_data[i].use_custom_backlight_caps = false;
init_data[i].custom_backlight_caps_config_no = 0;
- init_data[i].use_linear_backlight_curve = false;
+ init_data[i].use_linear_backlight_curve =
+ !!(amdgpu_dc_debug_mask &
+ DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
init_data[i].def_varibright_enable = 0;
init_data[i].varibright_level = 0;
/*
@@ -436,6 +438,8 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm)
dm->backlight_caps[i].min_input_signal * 0x101;
init_data[i].max_backlight_pwm =
dm->backlight_caps[i].max_input_signal * 0x101;
+ init_data[i].brightness_mask =
+ dm->backlight_caps[i].brightness_mask;
init_data[i].min_abm_backlight =
dm->backlight_caps[i].min_input_signal * 0x101;
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 424b33573a73..04e810af4623 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
@@ -218,6 +218,41 @@ u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
}
EXPORT_IF_KUNIT(convert_brightness_to_user);
+static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
+ u32 brightness)
+{
+ unsigned int min, max;
+
+ if (!get_brightness_range(caps, &min, &max) || max <= min)
+ return 0;
+
+ if (brightness >= max)
+ return 100 * 1000;
+
+ return DIV_ROUND_CLOSEST_ULL((u64)brightness * 100 * 1000, max);
+}
+
+STATIC_IFN_KUNIT
+u32 convert_brightness_for_power_module(const struct amdgpu_dm_backlight_caps *caps,
+ u32 user_brightness)
+{
+ u32 brightness;
+
+ if (!caps)
+ return user_brightness;
+
+ if (!caps->aux_support)
+ return convert_brightness_to_millipercent(caps, user_brightness);
+
+ brightness = convert_brightness_from_user(caps, user_brightness);
+ if (caps->brightness_mask)
+ brightness |= caps->brightness_mask;
+
+ return brightness;
+}
+
+EXPORT_IF_KUNIT(convert_brightness_for_power_module);
+
STATIC_IFN_KUNIT
struct dc_stream_state *dm_find_stream_with_link(
struct amdgpu_display_manager *dm,
@@ -262,7 +297,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
bool rc = false, reallow_idle = false;
struct drm_connector *connector;
struct dc_stream_state *stream;
- unsigned int min, max;
list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) {
struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
@@ -285,12 +319,9 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
/* update scratch register */
if (bl_idx == 0)
amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]);
- brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]);
link = (struct dc_link *)dm->backlight_link[bl_idx];
-
- /* Apply brightness quirk */
- if (caps->brightness_mask)
- brightness |= caps->brightness_mask;
+ brightness = convert_brightness_for_power_module(caps,
+ dm->brightness[bl_idx]);
if (trace_amdgpu_dm_brightness_enabled()) {
trace_amdgpu_dm_brightness(__builtin_return_address(0),
@@ -314,9 +345,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness,
AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true);
} else {
- /* power module uses millipercent */
- get_brightness_range(caps, &min, &max);
- brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000;
rc = mod_power_set_backlight_percent(dm->power_module, stream,
brightness, 0, false);
}
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 90bed0ea5d00..396e7654e299 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
@@ -72,6 +72,8 @@ 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,
uint32_t brightness);
+u32 convert_brightness_for_power_module(const struct amdgpu_dm_backlight_caps *caps,
+ u32 user_brightness);
int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm,
struct backlight_device *bd);
void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h
index f33a2c1e0da5..5e7782f9e89f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h
@@ -744,10 +744,11 @@ TRACE_EVENT(amdgpu_dm_brightness,
__entry->aux = aux;
__entry->ac = ac;
),
- TP_printk("%ps: brightness requested=%u converted=%u aux=%s power=%s",
+ TP_printk("%ps: brightness requested=%u converted=%u unit=%s aux=%s power=%s",
(void *)__entry->function,
(u32)__entry->user_brightness,
(u32)__entry->converted_brightness,
+ (__entry->aux) ? "millinits" : "millipercent",
(__entry->aux) ? "true" : "false",
(__entry->ac) ? "AC" : "DC"
)
diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h
index 02bee3b1956d..56b41861e164 100644
--- a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h
+++ b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h
@@ -19,6 +19,7 @@ struct mod_power_init_params {
unsigned int min_backlight_pwm;
unsigned int max_backlight_pwm;
+ unsigned int brightness_mask;
unsigned int min_abm_backlight;
unsigned int num_backlight_levels;
diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c
index ee15c14a899e..1d5e94893e36 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power.c
@@ -157,6 +157,8 @@ struct mod_power *mod_power_create(struct dc *dc,
init_params[inst].use_custom_backlight_caps;
core_power->bl_prop[inst].custom_backlight_caps_config_no =
init_params[inst].custom_backlight_caps_config_no;
+ core_power->bl_prop[inst].brightness_mask =
+ init_params[inst].brightness_mask;
// Do not allow less than 101 backlight levels
if (init_params[inst].num_backlight_levels < 101)
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 5e86889eaa84..4d7d80ac60c9 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
@@ -529,7 +529,7 @@ static unsigned int backlight_millipercent_to_pwm_legacy(
return 0;
// Bypass the brightness mapping LUT
- if (core_power->bl_prop->use_linear_backlight_curve) {
+ if (core_power->bl_prop[inst].use_linear_backlight_curve) {
pwm = core_power->bl_prop[inst].min_backlight_pwm +
(unsigned int) div_u64((unsigned long long) millipercent *
core_power->bl_prop[inst].backlight_range,
@@ -1020,6 +1020,10 @@ void fill_backlight_level_params(struct core_power *core_power,
if (backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX && !is_hdr)
backlight_level_params->control_type = BACKLIGHT_CONTROL_PWM;
+
+ if (backlight_level_params->control_type == BACKLIGHT_CONTROL_PWM)
+ backlight_level_params->backlight_pwm_u16_16 |=
+ bl_prop->brightness_mask;
}
bool mod_power_set_backlight_nits(struct mod_power *mod_power,
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 68679fa10946..1eeacbfb8d91 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
+++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
@@ -94,6 +94,7 @@ struct pwr_backlight_properties {
unsigned int min_backlight_pwm;
unsigned int max_backlight_pwm;
unsigned int backlight_range;
+ unsigned int brightness_mask;
/* Describes the panel's min and max luminance in millinits measured
* on full white screen, in min and max backlight settings.
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 4/4] drm/amd/display: test power module brightness input domain
2026-09-02 12:31 [PATCH 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
` (2 preceding siblings ...)
2026-09-02 12:32 ` [PATCH 3/4] drm/amd/display: pass userspace brightness to power module Andrei Rusu de Castro
@ 2026-09-02 12:33 ` Andrei Rusu de Castro
3 siblings, 0 replies; 5+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 12:33 UTC (permalink / raw)
To: amd-gfx
Cc: harry.wentland, sunpeng.li, siqueira, alexander.deucher,
christian.koenig, airlied, simona, alex.hung, roman.li,
mario.limonciello, dri-devel, linux-kernel, chen-yu.chen, ray.wu
Exercise the exact value selected for the power module on PWM and AUX
panels. Cover non-zero and zero PWM minimums, both endpoints, an
interior value, clamping, invalid caps, and retention of the AUX custom
curve and source-unit brightness mask.
The PWM case carries valid ATIF points to prove that the display manager
passes the userspace percentage without applying that curve before the
power module applies its own lookup table. Additional cases prove that
the brightness mask follows the effective control type: true AUX remains
unchanged, while ordinary PWM and AUX fallback-to-PWM are masked. The
selected panel's linear-curve bypass is covered independently.
The cases were verified under UML KUnit.
Signed-off-by: Andrei Rusu de Castro <arc@empyreal.works>
---
.../tests/amdgpu_dm_backlight_test.c | 159 +++++++++++++++++-
1 file changed, 156 insertions(+), 3 deletions(-)
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 a6fa052a8272..e0d46635130b 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
@@ -23,6 +23,7 @@
#include "amdgpu_dm_kunit_test_helpers.h"
#include "amd_shared.h"
#include "link_service.h"
+#include "modules/power/power_helpers.h"
#include "dc/inc/hw/panel_cntl.h"
struct dm_backlight_connector_fixture {
@@ -1019,6 +1020,151 @@ static void dm_test_brightness_from_user_aux(struct kunit *test)
KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)max);
}
+/* Tests for convert_brightness_for_power_module() */
+
+/**
+ * dm_test_power_module_brightness_invalid_caps - Test invalid PWM range
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_brightness_invalid_caps(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(NULL, 100), 100U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, 100), 0U);
+}
+
+/**
+ * dm_test_power_module_pwm_uses_user_domain - Test PWM input domain
+ * @test: The KUnit test context
+ *
+ * The power module owns the ATIF luminance-to-PWM curve. The display manager
+ * must therefore pass the original userspace percentage rather than first
+ * converting it to a firmware PWM level.
+ */
+static void dm_test_power_module_pwm_uses_user_domain(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+ 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 = 230;
+ caps.luminance_data[2].luminance = 90;
+ get_brightness_range(&caps, &min, &max);
+
+ KUNIT_EXPECT_EQ(test, min, 3084U);
+ KUNIT_EXPECT_EQ(test, max, 65535U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, 0), 0U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, 25700), 39216U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, 32768), 50001U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, max), 100000U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, max + 1), 100000U);
+}
+
+/**
+ * dm_test_power_module_pwm_zero_min - Test zero-minimum range
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_pwm_zero_min(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ unsigned int min, max;
+
+ caps.min_input_signal = 0;
+ caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+ get_brightness_range(&caps, &min, &max);
+
+ KUNIT_EXPECT_EQ(test, min, 0U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, 0), 0U);
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, max), 100000U);
+}
+
+/**
+ * dm_test_power_module_mask_follows_effective_control - Test mask handoff
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_mask_follows_effective_control(struct kunit *test)
+{
+ struct set_backlight_level_params params = {};
+ struct core_power core_power = {};
+
+ core_power.bl_prop[0].brightness_mask = 3;
+ fill_backlight_level_params(&core_power, ¶ms, 0, 0, 3084,
+ BACKLIGHT_CONTROL_PWM, 0, 0, false);
+ KUNIT_EXPECT_EQ(test, params.backlight_pwm_u16_16, 3087U);
+
+ fill_backlight_level_params(&core_power, ¶ms, 0, 0, 32896,
+ BACKLIGHT_CONTROL_PWM, 0, 0, false);
+ KUNIT_EXPECT_EQ(test, params.backlight_pwm_u16_16, 32899U);
+
+ fill_backlight_level_params(&core_power, ¶ms, 0, 0, 65535,
+ BACKLIGHT_CONTROL_PWM, 0, 0, false);
+ KUNIT_EXPECT_EQ(test, params.backlight_pwm_u16_16, 65535U);
+
+ fill_backlight_level_params(&core_power, ¶ms, 0, 0, 3084,
+ BACKLIGHT_CONTROL_AMD_AUX, 0, 0, true);
+ KUNIT_EXPECT_EQ(test, params.control_type, BACKLIGHT_CONTROL_AMD_AUX);
+ KUNIT_EXPECT_EQ(test, params.backlight_pwm_u16_16, 3084U);
+
+ fill_backlight_level_params(&core_power, ¶ms, 0, 0, 3084,
+ BACKLIGHT_CONTROL_AMD_AUX, 0, 0, false);
+ KUNIT_EXPECT_EQ(test, params.control_type, BACKLIGHT_CONTROL_PWM);
+ KUNIT_EXPECT_EQ(test, params.backlight_pwm_u16_16, 3087U);
+}
+
+/**
+ * dm_test_power_module_linear_curve_uses_panel_instance - Test linear bypass
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_linear_curve_uses_panel_instance(struct kunit *test)
+{
+ struct core_power core_power = {};
+ unsigned int backlight_lut[101] = {};
+
+ core_power.bl_prop[0].backlight_lut = backlight_lut;
+ core_power.bl_prop[0].num_backlight_levels = ARRAY_SIZE(backlight_lut);
+ core_power.bl_prop[1].min_backlight_pwm = 3084;
+ core_power.bl_prop[1].max_backlight_pwm = 65535;
+ core_power.bl_prop[1].backlight_range = 62451;
+ core_power.bl_prop[1].use_linear_backlight_curve = true;
+ backlight_lut[50] = 12345;
+
+ KUNIT_EXPECT_EQ(test, backlight_millipercent_to_pwm(&core_power, 50000, 1),
+ 34309U);
+}
+
+/**
+ * dm_test_power_module_aux_keeps_curve_and_mask - Test AUX conversion path
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_aux_keeps_curve_and_mask(struct kunit *test)
+{
+ struct amdgpu_dm_backlight_caps caps = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
+ caps.aux_support = true;
+ caps.aux_min_input_signal = 1;
+ caps.aux_max_input_signal = 512;
+ caps.brightness_mask = 3;
+ 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;
+
+ KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(&caps, 200000),
+ 81159U);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
/* Tests for convert_custom_brightness() */
/**
@@ -1590,7 +1736,7 @@ static void dm_test_curve_from_user_monotonic(struct kunit *test)
for (i = 0; i <= max; i += 1023) {
u32 level = convert_brightness_from_user(&caps, i);
- KUNIT_ASSERT_GE(test, level, previous);
+ KUNIT_EXPECT_GE(test, level, previous);
previous = level;
}
@@ -1619,8 +1765,8 @@ static void dm_test_curve_from_user_within_range(struct kunit *test)
for (i = 0; i <= max; i += 1023) {
u32 level = convert_brightness_from_user(&caps, i);
- KUNIT_ASSERT_GE(test, level, (u32)min);
- KUNIT_ASSERT_LE(test, level, (u32)max);
+ KUNIT_EXPECT_GE(test, level, (u32)min);
+ KUNIT_EXPECT_LE(test, level, (u32)max);
}
KUNIT_EXPECT_LE(test, convert_brightness_from_user(&caps, max), (u32)max);
@@ -2198,6 +2344,13 @@ 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_brightness_for_power_module */
+ KUNIT_CASE(dm_test_power_module_brightness_invalid_caps),
+ KUNIT_CASE(dm_test_power_module_pwm_uses_user_domain),
+ KUNIT_CASE(dm_test_power_module_pwm_zero_min),
+ KUNIT_CASE(dm_test_power_module_mask_follows_effective_control),
+ KUNIT_CASE(dm_test_power_module_linear_curve_uses_panel_instance),
+ KUNIT_CASE(dm_test_power_module_aux_keeps_curve_and_mask),
/* convert_custom_brightness */
KUNIT_CASE(dm_test_custom_brightness_no_data_points),
KUNIT_CASE(dm_test_custom_brightness_debug_mask_disables),
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-02 12:33 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-02 12:31 [PATCH 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
2026-09-02 12:31 ` [PATCH 1/4] drm/amd/display: keep custom brightness curve in userspace domain Andrei Rusu de Castro
2026-09-02 12:31 ` [PATCH 2/4] drm/amd/display: test custom brightness with non-zero minimum Andrei Rusu de Castro
2026-09-02 12:32 ` [PATCH 3/4] drm/amd/display: pass userspace brightness to power module Andrei Rusu de Castro
2026-09-02 12:33 ` [PATCH 4/4] drm/amd/display: test power module brightness input domain Andrei Rusu de Castro
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®