From: Andrei Rusu de Castro <arc@empyreal.works>
To: amd-gfx@lists.freedesktop.org
Cc: harry.wentland@amd.com, sunpeng.li@amd.com, siqueira@igalia.com,
alexander.deucher@amd.com, christian.koenig@amd.com,
airlied@gmail.com, simona@ffwll.ch, alex.hung@amd.com,
roman.li@amd.com, mario.limonciello@amd.com,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
chen-yu.chen@amd.com, ray.wu@amd.com
Subject: [PATCH 2/4] drm/amd/display: test custom brightness with non-zero minimum
Date: Wed, 02 Sep 2026 12:31:52 +0000 [thread overview]
Message-ID: <20260902-brightness-2-39f2d73d@empyreal.works> (raw)
In-Reply-To: <20260902-brightness-cover-abf809f2@empyreal.works>
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),
next prev parent reply other threads:[~2026-09-03 7:33 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
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:57 ` sashiko-bot
2026-09-02 12:33 ` [PATCH 4/4] drm/amd/display: test power module brightness input domain Andrei Rusu de Castro
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 ` [PATCH v2 3/4] drm/amd/display: pass userspace brightness to power module 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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260902-brightness-2-39f2d73d@empyreal.works \
--to=arc@empyreal.works \
--cc=airlied@gmail.com \
--cc=alex.hung@amd.com \
--cc=alexander.deucher@amd.com \
--cc=amd-gfx@lists.freedesktop.org \
--cc=chen-yu.chen@amd.com \
--cc=christian.koenig@amd.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=harry.wentland@amd.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mario.limonciello@amd.com \
--cc=ray.wu@amd.com \
--cc=roman.li@amd.com \
--cc=simona@ffwll.ch \
--cc=siqueira@igalia.com \
--cc=sunpeng.li@amd.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.