* [PATCH v2 1/4] drm/amd/display: keep custom brightness curve in userspace domain
2026-09-02 22:31 ` [PATCH v2 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
@ 2026-09-02 22:31 ` Andrei Rusu de Castro
2026-09-02 22:31 ` [PATCH v2 2/4] drm/amd/display: test custom brightness with non-zero minimum Andrei Rusu de Castro
` (2 subsequent siblings)
3 siblings, 0 replies; 12+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 22: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
Assisted-by: LLM
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);
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v2 2/4] drm/amd/display: test custom brightness with non-zero minimum
2026-09-02 22:31 ` [PATCH v2 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
2026-09-02 22:31 ` [PATCH v2 1/4] drm/amd/display: keep custom brightness curve in userspace domain Andrei Rusu de Castro
@ 2026-09-02 22:31 ` Andrei Rusu de Castro
2026-09-02 22:31 ` [PATCH v2 3/4] drm/amd/display: pass userspace brightness to power module Andrei Rusu de Castro
2026-09-02 22:32 ` [PATCH v2 4/4] drm/amd/display: test power module brightness input domain Andrei Rusu de Castro
3 siblings, 0 replies; 12+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 22: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.
Assisted-by: LLM
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),
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v2 3/4] drm/amd/display: pass userspace brightness to power module
2026-09-02 22:31 ` [PATCH v2 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
2026-09-02 22:31 ` [PATCH v2 1/4] drm/amd/display: keep custom brightness curve in userspace domain Andrei Rusu de Castro
2026-09-02 22:31 ` [PATCH v2 2/4] drm/amd/display: test custom brightness with non-zero minimum Andrei Rusu de Castro
@ 2026-09-02 22:31 ` Andrei Rusu de Castro
2026-09-03 7:50 ` sashiko-bot
2026-09-02 22:32 ` [PATCH v2 4/4] drm/amd/display: test power module brightness input domain Andrei Rusu de Castro
3 siblings, 1 reply; 12+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 22: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
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 for AUX panels, but keep the
resulting millinit value unmasked while the power module derives cached
percentages and PWM levels.
The brightness mask is a final DP source-level quirk. Carry it into the
power module and apply it immediately before an AMD AUX write or after
PWM derivation when the effective hardware handoff is PWM. This
preserves ordinary PWM, forced-PWM OLED, AUX fallback-to-PWM, live AMD
AUX, and mode-change replay without perturbing an intermediate unit.
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.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260902-brightness-cover-abf809f2@empyreal.works?part=3
Fixes: 3c108046e1d6 ("drm/amd/display: Add power module on Linux")
Cc: stable@vger.kernel.org # 7.2.x
Assisted-by: LLM
Signed-off-by: Andrei Rusu de Castro <arc@empyreal.works>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +-
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 +-
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 44 ++++++++++++---
.../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 | 55 +++++++++++++++----
.../amd/display/modules/power/power_helpers.h | 14 +++++
9 files changed, 107 insertions(+), 22 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.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
index 3524931451c8..5969c5fbe480 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h
@@ -229,7 +229,7 @@ struct amdgpu_dm_backlight_caps {
*/
bool aux_support;
/**
- * @brightness_mask: After deriving brightness, OR it with this mask.
+ * @brightness_mask: OR this with the final source backlight value.
* Workaround for panels with issues with certain brightness values.
*/
u32 brightness_mask;
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..adeb4a7ab7c9 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,39 @@ 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);
+
+ 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 +295,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 +317,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 +343,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..0e00042bced4 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"
@@ -266,6 +267,16 @@ void mod_power_set_backlight_control_type(struct core_power *core_power,
core_power->bl_prop[inst].backlight_control_type = backlight_control_type;
}
+STATIC_IFN_KUNIT
+unsigned int backlight_apply_source_mask(struct core_power *core_power,
+ unsigned int backlight,
+ unsigned int inst)
+{
+ return backlight | core_power->bl_prop[inst].brightness_mask;
+}
+
+EXPORT_IF_KUNIT(backlight_apply_source_mask);
+
/* Returns true when the panel uses the VESA AUX backlight control path, which
* requires zero-anchored linear brightness interpolation.
*/
@@ -529,7 +540,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,
@@ -615,8 +626,10 @@ static unsigned int backlight_millinit_to_pwm_legacy(
* 0 nits = 0 PWM and max_brightness_millinits = max_backlight_pwm.
* Otherwise, falls back to the legacy min→max nits range mapping.
*/
-static unsigned int backlight_millinit_to_pwm(
- struct core_power *core_power, unsigned int millinit, unsigned int inst)
+STATIC_IFN_KUNIT
+unsigned int backlight_millinit_to_pwm(struct core_power *core_power,
+ unsigned int millinit,
+ unsigned int inst)
{
if (!is_vesa_abc(core_power, inst))
return backlight_millinit_to_pwm_legacy(core_power, millinit, inst);
@@ -639,6 +652,8 @@ static unsigned int backlight_millinit_to_pwm(
core_power->bl_prop[inst].max_brightness_millinits);
}
+EXPORT_IF_KUNIT(backlight_millinit_to_pwm);
+
static bool validate_ext_backlight_caps(
struct dm_acpi_atif_backlight_caps *ext_backlight_caps)
{
@@ -894,6 +909,7 @@ void mod_power_update_backlight_on_mode_change(
bool is_hdr)
{
struct set_backlight_level_params backlight_level_params = { 0 };
+ unsigned int backlight_millinit;
/* Cache the panel's backlight control type once at mode-change/init
* time. It is a stable per-panel property (decided in the OS shim
@@ -905,9 +921,15 @@ void mod_power_update_backlight_on_mode_change(
if ((link->dpcd_sink_ext_caps.bits.hdr_aux_backlight_control == 1 ||
link->dpcd_sink_ext_caps.bits.sdr_aux_backlight_control == 1) &&
- link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX)
+ link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX) {
+ backlight_millinit =
+ core_power->bl_state[panel_inst].backlight_millinit;
+ backlight_millinit =
+ backlight_apply_source_mask(core_power, backlight_millinit,
+ panel_inst);
dc_link_set_backlight_level_nits(link, core_power->bl_state[panel_inst].isHDR,
- core_power->bl_state[panel_inst].backlight_millinit, 0);
+ backlight_millinit, 0);
+ }
backlight_level_params.frame_ramp = 0;
@@ -918,11 +940,12 @@ void mod_power_update_backlight_on_mode_change(
dc_link_set_backlight_level(link, &backlight_level_params);
}
-static bool set_backlight_millinits_aux(struct core_power *core_power,
- struct dc_stream_state *stream,
- unsigned int backlight_millinits,
- unsigned int transition_time_millisec,
- unsigned int inst)
+STATIC_IFN_KUNIT
+bool set_backlight_millinits_aux(struct core_power *core_power,
+ struct dc_stream_state *stream,
+ unsigned int backlight_millinits,
+ unsigned int transition_time_millisec,
+ unsigned int inst)
{
struct dc_link *link = NULL;
@@ -939,10 +962,17 @@ static bool set_backlight_millinits_aux(struct core_power *core_power,
link->dc->caps.dmub_caps.aux_backlight_support)
return true;
+ if (link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX)
+ backlight_millinits =
+ backlight_apply_source_mask(core_power, backlight_millinits,
+ inst);
+
return dc_link_set_backlight_level_nits(link, core_power->bl_state[inst].isHDR,
backlight_millinits, transition_time_millisec);
}
+EXPORT_IF_KUNIT(set_backlight_millinits_aux);
+
static bool set_backlight(struct core_power *core_power,
struct dc_stream_state *stream,
struct set_backlight_level_params *backlight_level_params,
@@ -1020,6 +1050,11 @@ 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 =
+ backlight_apply_source_mask(core_power, backlight_pwm,
+ panel_inst);
}
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..d4d8ebee1cde 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.
@@ -203,6 +204,19 @@ unsigned int backlight_millipercent_to_pwm(
struct core_power *core_power, unsigned int millipercent, unsigned int inst);
unsigned int backlight_millipercent_to_millinit(
struct core_power *core_power, unsigned int millipercent, unsigned int inst);
+#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST)
+unsigned int backlight_millinit_to_pwm(struct core_power *core_power,
+ unsigned int millinit,
+ unsigned int inst);
+unsigned int backlight_apply_source_mask(struct core_power *core_power,
+ unsigned int backlight,
+ unsigned int inst);
+bool set_backlight_millinits_aux(struct core_power *core_power,
+ struct dc_stream_state *stream,
+ unsigned int backlight_millinits,
+ unsigned int transition_time_millisec,
+ unsigned int inst);
+#endif
void fill_backlight_level_params(struct core_power *core_power,
struct set_backlight_level_params *backlight_level_params,
int panel_inst, uint8_t aux_inst, unsigned int backlight_pwm,
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v2 3/4] drm/amd/display: pass userspace brightness to power module
2026-09-02 22:31 ` [PATCH v2 3/4] drm/amd/display: pass userspace brightness to power module Andrei Rusu de Castro
@ 2026-09-03 7:50 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-09-03 7:50 UTC (permalink / raw)
To: Andrei Rusu de Castro; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [Medium] The brightness quirk mask is not applied to `backlight_millinits` for AMD AUX panels, causing firmware to write unmasked values during autonomous operations
Pre-existing issues:
- [High] Unprotected traversal of drm_mode_config.connector_list
- [High] Unprotected access to `dm->dc->current_state` leading to potential Use-After-Free
- [High] Potential division by zero in brightness conversion functions when firmware capabilities are anomalous
- [High] Incomplete bounds validation during power module creation allows multi-display setups to trigger a division by zero
--
commit 8608a59a12eb05fd14ffc0e7cb1af582e44dade9
Author: Andrei Rusu de Castro <arc@empyreal.works>
Date: Wed Sep 2 22:31:59 2026 +0000
drm/amd/display: pass userspace brightness to power module
The patch adjusts how brightness is passed to the power module in amdgpu_dm,
keeping it as userspace percentage (millipercent) instead of converting to
PWM domain first. This fixes domain mismatch issues and handles panel-specific
brightness quirks for AMD AUX writes.
Closes: https://sashiko.dev/#/patchset/20260902-brightness-cover-abf809f2@empyreal.works?part=3
> --- 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,39 @@ 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);
[Severity: High]
This is a pre-existing issue, but while convert_brightness_to_millipercent()
properly guards against max <= min to avoid division by zero, the
convert_brightness_from_user() function called here lacks these same guards.
Does convert_brightness_from_user() unconditionally divide by max, risking a
kernel panic if anomalous firmware sets max to zero? Similarly, does
convert_brightness_to_user() risk dividing by zero when evaluating max - min?
> +
> + return brightness;
> +}
[ ... ]
> @@ -262,7 +295,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) {
[Severity: High]
This isn't a bug introduced by this patch, but is it safe to traverse the DRM
connector_list using the raw list_for_each_entry macro without holding
mode_config.mutex or using the drm_connector_list_iter API? Can a concurrent
hotplug event removing a connector leave this loop traversing a freed pointer,
resulting in a use-after-free?
[ ... ]
> @@ -285,12 +317,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]);
[Severity: High]
This is a pre-existing issue, but further down in this function,
dm_find_stream_with_link() is called before acquiring dc_lock. Inside
dm_find_stream_with_link(), the dm->dc->current_state pointer is dereferenced
and iterated over. Because this sysfs callback runs concurrently with atomic
commits (which free the old state), is there a risk of a use-after-free crash
by reading a stale current_state pointer before the lock is held?
[ ... ]
> --- 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)
[Severity: High]
This isn't a bug introduced by this patch, but further down in
mod_power_create(), a loop validates the brightness ranges for eDP instances:
for (inst = 0; inst < edp_num; inst++) {
if (core_power->bl_prop[inst].nits_range != 0 && ...) {
is_brightness_range_valid = true;
break;
}
}
Does this premature break cause the loop to exit without verifying remaining
instances? If panel 1 has a zero nits_range while panel 0 is valid, could
this lead to a division by zero later in backlight_millinit_to_pwm_legacy()
when it evaluates panel 1?
[ ... ]
> --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
> +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
[ ... ]
> @@ -1020,6 +1050,11 @@ 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 =
> + backlight_apply_source_mask(core_power, backlight_pwm,
> + panel_inst);
> }
[Severity: Medium]
When backlight_level_params->control_type remains BACKLIGHT_CONTROL_AMD_AUX
(for instance, during HDR playback), the brightness_mask is not applied to
backlight_level_params->backlight_millinits here. Because these parameters
are subsequently passed to the DMCUB firmware for autonomous operations like
PSR/Replay exits, will this allow the firmware to write unmasked brightness
values directly to the panel, bypassing the quirk? Should
backlight_millinits also be masked here when the control type is AMD AUX?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1788388049.git.arc@empyreal.works?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 4/4] drm/amd/display: test power module brightness input domain
2026-09-02 22:31 ` [PATCH v2 0/4] drm/amd/display: fix brightness ownership through power module Andrei Rusu de Castro
` (2 preceding siblings ...)
2026-09-02 22:31 ` [PATCH v2 3/4] drm/amd/display: pass userspace brightness to power module Andrei Rusu de Castro
@ 2026-09-02 22:32 ` Andrei Rusu de Castro
3 siblings, 0 replies; 12+ messages in thread
From: Andrei Rusu de Castro @ 2026-09-02 22: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
Exercise the exact values selected for the power module and the final
PWM and AMD AUX handoffs. Cover non-zero and zero PWM minimums, both
endpoints, an interior value, clamping, invalid caps, custom-curve
retention, multiple panels, and the source-level 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 final hardware domain: the mathematical
AUX millinit remains unmasked, direct AMD AUX receives the mask, and an
AUX fallback derives PWM from the clean millinit before applying the
final PWM mask.
The composition case uses an input for which masking the intermediate
millinit changes the derived PWM value. It fails against the v1
implementation and passes when the mask is applied only at the final
hardware handoffs. Callback captures independently verify the live AMD
AUX value, the unchanged VESA AUX value, and the mode-change replay
value. The selected panel's linear-curve bypass is covered independently.
The cases were verified under UML KUnit.
Assisted-by: LLM
Signed-off-by: Andrei Rusu de Castro <arc@empyreal.works>
---
.../tests/amdgpu_dm_backlight_test.c | 334 +++++++++++++++++-
1 file changed, 331 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..7ab47a66c915 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 {
@@ -31,6 +32,33 @@ struct dm_backlight_connector_fixture {
struct dc_link *link;
};
+struct dm_backlight_aux_capture {
+ unsigned int calls;
+ unsigned int backlight_millinits;
+ unsigned int transition_time_millisec;
+ bool is_hdr;
+};
+
+static struct dm_backlight_aux_capture *dm_backlight_aux_capture;
+
+static bool dm_test_capture_backlight_nits(struct dc_link *link, bool is_hdr,
+ u32 backlight_millinits,
+ u32 transition_time_in_ms)
+{
+ dm_backlight_aux_capture->calls++;
+ dm_backlight_aux_capture->backlight_millinits = backlight_millinits;
+ dm_backlight_aux_capture->transition_time_millisec = transition_time_in_ms;
+ dm_backlight_aux_capture->is_hdr = is_hdr;
+
+ return true;
+}
+
+static bool dm_test_capture_backlight_level(const struct dc_link *link,
+ struct set_backlight_level_params *params)
+{
+ return true;
+}
+
static const struct drm_connector_funcs dm_backlight_test_connector_funcs = {
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
@@ -1019,6 +1047,296 @@ 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_unmasked - Test AUX conversion path
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_aux_keeps_curve_unmasked(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),
+ 81157U);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_power_module_aux_mask_composition - Compare both final handoffs
+ * @test: The KUnit test context
+ *
+ * AMD AUX writes millinits directly to DP_SOURCE_BACKLIGHT_LEVEL, while its
+ * non-HDR fallback derives a PWM value from the cached millinits. Applying
+ * the source-level mask before that conversion can therefore change the PWM
+ * before the PWM-domain mask is applied.
+ */
+static void dm_test_power_module_aux_mask_composition(struct kunit *test)
+{
+ struct set_backlight_level_params params = {};
+ struct amdgpu_dm_backlight_caps caps = {};
+ struct core_power core_power = {};
+ uint saved_mask = amdgpu_dm_get_dc_debug_mask();
+ u32 aux_level, millinit, pwm;
+
+ 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;
+
+ core_power.bl_prop[0].min_backlight_pwm = 3084;
+ core_power.bl_prop[0].max_backlight_pwm = 65535;
+ core_power.bl_prop[0].backlight_range = 62451;
+ core_power.bl_prop[0].min_brightness_millinits = 1000;
+ core_power.bl_prop[0].max_brightness_millinits = 270000;
+ core_power.bl_prop[0].nits_range = 269000;
+ core_power.bl_prop[0].brightness_mask = 3;
+ core_power.bl_prop[0].backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX;
+
+ millinit = convert_brightness_for_power_module(&caps, 51200);
+ aux_level = backlight_apply_source_mask(&core_power, millinit, 0);
+ pwm = backlight_millinit_to_pwm(&core_power, millinit, 0);
+ fill_backlight_level_params(&core_power, ¶ms, 0, 0, pwm,
+ BACKLIGHT_CONTROL_AMD_AUX, millinit, 0,
+ false);
+
+ KUNIT_EXPECT_EQ(test, millinit, 7012U);
+ KUNIT_EXPECT_EQ(test, aux_level, 7015U);
+ KUNIT_EXPECT_EQ(test, pwm, 4479U);
+ KUNIT_EXPECT_EQ(test, params.control_type, BACKLIGHT_CONTROL_PWM);
+ KUNIT_EXPECT_EQ(test, params.backlight_pwm_u16_16, 4479U);
+
+ amdgpu_dm_set_dc_debug_mask(saved_mask);
+}
+
+/**
+ * dm_test_power_module_live_aux_handoff_mask - Test the live AUX callback
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_live_aux_handoff_mask(struct kunit *test)
+{
+ struct dm_backlight_aux_capture *capture;
+ struct link_service *link_srv;
+ struct core_power *core_power;
+ struct dc_stream_state *stream;
+ struct dc_link *link;
+ struct dc *dc;
+
+ capture = kunit_kzalloc(test, sizeof(*capture), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL);
+ stream = kunit_kzalloc(test, sizeof(*stream), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, capture);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, core_power);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, stream);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
+
+ dm_backlight_aux_capture = capture;
+ link_srv->edp_set_backlight_level_nits = dm_test_capture_backlight_nits;
+ dc->link_srv = link_srv;
+ link->dc = dc;
+ link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX;
+ stream->link = link;
+ core_power->bl_prop[0].brightness_mask = 3;
+
+ KUNIT_ASSERT_TRUE(test, set_backlight_millinits_aux(core_power, stream,
+ 7012, 50, 0));
+ KUNIT_EXPECT_EQ(test, capture->calls, 1U);
+ KUNIT_EXPECT_EQ(test, capture->backlight_millinits, 7015U);
+ KUNIT_EXPECT_EQ(test, capture->transition_time_millisec, 50U);
+ KUNIT_EXPECT_FALSE(test, capture->is_hdr);
+
+ memset(capture, 0, sizeof(*capture));
+ link->backlight_control_type = BACKLIGHT_CONTROL_VESA_AUX;
+ KUNIT_ASSERT_TRUE(test, set_backlight_millinits_aux(core_power, stream,
+ 7012, 50, 0));
+ KUNIT_EXPECT_EQ(test, capture->calls, 1U);
+ KUNIT_EXPECT_EQ(test, capture->backlight_millinits, 7012U);
+
+ dm_backlight_aux_capture = NULL;
+}
+
+/**
+ * dm_test_power_module_mode_change_aux_mask - Test replay AUX callback
+ * @test: The KUnit test context
+ */
+static void dm_test_power_module_mode_change_aux_mask(struct kunit *test)
+{
+ struct dm_backlight_aux_capture *capture;
+ struct link_service *link_srv;
+ struct core_power *core_power;
+ struct dc_link *link;
+ struct dc *dc;
+
+ capture = kunit_kzalloc(test, sizeof(*capture), GFP_KERNEL);
+ link_srv = kunit_kzalloc(test, sizeof(*link_srv), GFP_KERNEL);
+ core_power = kunit_kzalloc(test, sizeof(*core_power), GFP_KERNEL);
+ link = kunit_kzalloc(test, sizeof(*link), GFP_KERNEL);
+ dc = kunit_kzalloc(test, sizeof(*dc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, capture);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link_srv);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, core_power);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, link);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dc);
+
+ dm_backlight_aux_capture = capture;
+ link_srv->edp_set_backlight_level_nits = dm_test_capture_backlight_nits;
+ link_srv->edp_set_backlight_level = dm_test_capture_backlight_level;
+ dc->link_srv = link_srv;
+ link->dc = dc;
+ link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX;
+ link->dpcd_sink_ext_caps.bits.sdr_aux_backlight_control = 1;
+ core_power->bl_state[0].backlight_millinit = 7012;
+ core_power->bl_prop[0].brightness_mask = 3;
+
+ mod_power_update_backlight_on_mode_change(core_power, link, 0, 0, false);
+ KUNIT_EXPECT_EQ(test, capture->calls, 1U);
+ KUNIT_EXPECT_EQ(test, capture->backlight_millinits, 7015U);
+ KUNIT_EXPECT_EQ(test, capture->transition_time_millisec, 0U);
+
+ dm_backlight_aux_capture = NULL;
+}
+
/* Tests for convert_custom_brightness() */
/**
@@ -1590,7 +1908,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 +1937,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 +2516,16 @@ 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_unmasked),
+ KUNIT_CASE(dm_test_power_module_aux_mask_composition),
+ KUNIT_CASE(dm_test_power_module_live_aux_handoff_mask),
+ KUNIT_CASE(dm_test_power_module_mode_change_aux_mask),
/* convert_custom_brightness */
KUNIT_CASE(dm_test_custom_brightness_no_data_points),
KUNIT_CASE(dm_test_custom_brightness_debug_mask_disables),
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread