From nobody Sat Sep 26 10:02:06 2026 Received: from mail-4397.protonmail.ch (mail-4397.protonmail.ch [185.70.43.97]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9693247FB1C for ; Wed, 2 Sep 2026 12:31:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.43.97 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352289; cv=none; b=sGOcDJhu1wa50LGjguIAI3LUdoh8ZANIf1J6ofkMz/tTqCFr8jyy9Oth+2cyw2dUjABxyjN9LqyYD5Iq2yHcDmqAlE5QjHOag1Rj7Aa61r288F0QQN5pyFeLlQgJZsud34Dh08H0kxFN07bdIXNVNuRNDCgkTi9Jsi8DGbZjSXQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352289; c=relaxed/simple; bh=ffr9F+zdAaXdbRtETH1x0W5b8++Do3zcr9KpWm4QvpY=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=iSflb9CXDj5H4d00+1IAUDRHu97Qs/zeJIMxYPGyQJcgw3qYEyg4q+XaFcPBbKJwu1Nn/lY7bd5VDvhi3BTNwG8KkgUio2fDXxV1wdMf+/EAAxKZi47qD88Ya0y+QyL3gjTCWYZMIUmy8lsq+WaI/nqf1xOH5HjYwGlUEwQT00Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works; spf=pass smtp.mailfrom=empyreal.works; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b=NLcjPR/e; arc=none smtp.client-ip=185.70.43.97 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=empyreal.works Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b="NLcjPR/e" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=empyreal.works; s=protonmail2; t=1788352283; x=1788611483; bh=CIXG5k8Z42Dvwo3n4OTF/HGNzdZ5644vv8fHSBN0grA=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=NLcjPR/eJiF/NWaCblNVUCHSx42EIQ+gVQE5iBYmjcMD8iqT1gXzRvZgMTC8I8t/p OSwwhIQtizzDJ/7oIoC9dEj2cbu3uJ8rZ8uBrJ6hMtHVVMg45o/g5+33tLcKyCuooq 4/MyeS6+RPvBFmpWHDc99YqYCS1KP3tuLvD8H8fMuASOeTwDwtw+sQDiT3A/hAU5T+ /tEgGi8civO5EUoTipF4ZLnpTBP7YXyNiCksIcLAb7nzJBabTBQfXy4uFZyY3nFj1Q m1z33G9gx/FOqlgFKcG8surWSxMAsfCyBcDz9RMFQ0aUa/y9qT1ytJrOrmH/Tkucri 8jPzRJVKA30Iw== Date: Wed, 02 Sep 2026 12:31:17 +0000 To: amd-gfx@lists.freedesktop.org From: Andrei Rusu de Castro 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, stable@vger.kernel.org Subject: [PATCH 1/4] drm/amd/display: keep custom brightness curve in userspace domain Message-ID: <20260902-brightness-1-fd468d7a@empyreal.works> In-Reply-To: <20260902-brightness-cover-abf809f2@empyreal.works> References: <20260902-brightness-cover-abf809f2@empyreal.works> Feedback-ID: 182420409:user:proton X-Pm-Message-ID: d3c1787a7ff7f8453ae90f24a3b3e0855c107f25 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 user= space") Cc: stable@vger.kernel.org # 6.17.x: 6fd83a1c2cde: drm/amd/display: Scale c= ustom 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 t= he header and test hunks Signed-off-by: Andrei Rusu de Castro --- .../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 inpu= t) return DIV_ROUND_CLOSEST_ULL(input * AMDGPU_MAX_BL_LEVEL, max); } =20 -/* 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_LEV= EL); + return DIV_ROUND_CLOSEST_ULL(input * max, AMDGPU_MAX_BL_LEVEL); } =20 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 =3D 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_b= acklight_caps *caps, (brightness - lower_signal), upper_signal - lower_signal); scale: - *user_brightness =3D scale_fw_to_input(min, max, + *user_brightness =3D scale_fw_to_input(max, DIV_ROUND_CLOSEST(lum * brightness, 101)); } =20 @@ -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; =20 - convert_custom_brightness(caps, min, max, &brightness); + convert_custom_brightness(caps, max, &brightness); =20 /* 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 *ca= ps, 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_backligh= t_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) =20 caps.data_points =3D 0; =20 - convert_custom_brightness(&caps, 3084, 65535, &brightness); + convert_custom_brightness(&caps, 65535, &brightness); =20 /* No data points =E2=86=92 no-op */ KUNIT_EXPECT_EQ(test, brightness, saved); @@ -1057,7 +1057,7 @@ static void dm_test_custom_brightness_debug_mask_disa= bles(struct kunit *test) /* Set the disable flag */ amdgpu_dm_set_dc_debug_mask(amdgpu_dm_get_dc_debug_mask() | DC_DISABLE_CU= STOM_BRIGHTNESS_CURVE); =20 - convert_custom_brightness(&caps, 3084, 65535, &brightness); + convert_custom_brightness(&caps, 65535, &brightness); =20 /* 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(s= truct kunit *test) */ brightness =3D 32896; =20 - convert_custom_brightness(&caps, min, max, &brightness); + convert_custom_brightness(&caps, max, &brightness); =20 /* * Exact match: lum=3D50, brightness_scaled=3D128 - * result =3D scale_fw_to_input(min, max, DIV_ROUND_CLOSEST(50*128, 101)) - * =3D scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(6400, 101)) - * =3D scale_fw_to_input(0, 65535, 63) - * =3D 0 + DIV_ROUND_CLOSEST(63 * 65535, 255) =3D 16191 (approx) + * result =3D scale_fw_to_input(max, DIV_ROUND_CLOSEST(50*128, 101)) + * =3D scale_fw_to_input(65535, DIV_ROUND_CLOSEST(6400, 101)) + * =3D scale_fw_to_input(65535, 63) + * =3D DIV_ROUND_CLOSEST(63 * 65535, 255) =3D 16191 (approx) */ KUNIT_EXPECT_TRUE(test, brightness !=3D 32896); KUNIT_EXPECT_TRUE(test, brightness < 32896); @@ -1146,13 +1146,13 @@ static void dm_test_custom_brightness_below_first(s= truct kunit *test) */ brightness =3D 12850; =20 - convert_custom_brightness(&caps, min, max, &brightness); + convert_custom_brightness(&caps, max, &brightness); =20 /* * Below first data point: lum =3D DIV_ROUND_CLOSEST(40 * 50, 100) =3D 20 - * Then: scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(20 * 50, 101)) - * =3D scale_fw_to_input(0, 65535, DIV_ROUND_CLOSEST(1000, 101)) - * =3D scale_fw_to_input(0, 65535, 10) + * Then: scale_fw_to_input(65535, DIV_ROUND_CLOSEST(20 * 50, 101)) + * =3D scale_fw_to_input(65535, DIV_ROUND_CLOSEST(1000, 101)) + * =3D 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(s= truct kunit *test) */ brightness =3D 32125; =20 - convert_custom_brightness(&caps, min, max, &brightness); + convert_custom_brightness(&caps, max, &brightness); =20 /* * The function should interpolate between data points and produce @@ -1233,7 +1233,7 @@ static void dm_test_custom_brightness_above_last(stru= ct kunit *test) */ brightness =3D 56533; =20 - convert_custom_brightness(&caps, min, max, &brightness); + convert_custom_brightness(&caps, max, &brightness); =20 /* Output should differ from input (remapped via curve) */ KUNIT_EXPECT_TRUE(test, brightness !=3D 56533); @@ -1271,7 +1271,7 @@ static void dm_test_custom_brightness_single_data_poi= nt(struct kunit *test) */ brightness =3D 16448; =20 - convert_custom_brightness(&caps, min, max, &brightness); + convert_custom_brightness(&caps, max, &brightness); =20 KUNIT_EXPECT_TRUE(test, brightness < 16448); =20 @@ -1309,7 +1309,7 @@ static void dm_test_custom_brightness_lower_lum_zero(= struct kunit *test) */ brightness =3D 32125; =20 - convert_custom_brightness(&caps, min, max, &brightness); + convert_custom_brightness(&caps, max, &brightness); =20 /* Should remap; result should differ from input */ KUNIT_EXPECT_TRUE(test, brightness !=3D 32125); From nobody Sat Sep 26 10:02:06 2026 Received: from mail-24420.protonmail.ch (mail-24420.protonmail.ch [109.224.244.20]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 03E6264 for ; Wed, 2 Sep 2026 12:32:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=109.224.244.20 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352327; cv=none; b=iauW/494U1QVUMxab2Sv1spvA/ZPHCf258t/B9h7oVAvRjVQq/DNWrz6pEOHHkblEvMphLc50Aoq0zK3AEY6dhfAIRl68oSdD56byKAjSkEDMjuTTvecisVokqvD9n6PC6214b1qsYSsabskHNcvAuNf6hE3VSaHcILR51GNbEw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352327; c=relaxed/simple; bh=qmg3RHfvjuR5YbzGSPWCjLsbbkANHINK1l9yvdeRrNk=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=H//Xq0FINiMZ9kOn5NGWuUZYVU8vz2ROlH7ev/uiyf3Rph7tS8e9yIeLnY+B8uvetNYAtr2h0lAWKNHD1zhsUIJoIvpvqstFy8yVDGSJ6gu2XRZ6Cl15Hij0tehC7jdR8iNIhKunFywXULpAwz21JkcJoX6+Fhl6Jw15aBMJ7/s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works; spf=pass smtp.mailfrom=empyreal.works; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b=B0ZFLiPk; arc=none smtp.client-ip=109.224.244.20 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=empyreal.works Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b="B0ZFLiPk" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=empyreal.works; s=protonmail2; t=1788352319; x=1788611519; bh=E/MTxBM/Wq97hg1dHWi2yjxxZ8SlNycGCO7uJpp86ok=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=B0ZFLiPkyF4EhNjazMrtRx+QRMofuEG50O9QXOgaA0VA7L87oTotwHXEf0iPQuZX1 UsUB/+M+GipHDyTpZOmvjiDkhsx3VWDk9NKeVcH/lwJtV7Q52aZSglkQGVnbYRFWSz vzVOdeJ81JKcDUrYDo1O4FjdFxEVb0iubrwEVqB05Z/sudvgx4txDNFIcbYjXZ9nM+ fQTHt3Iq8Jf+J0sumOlqBZDcjqsP7aN+lwu5/dyh+K/2L8nZlsbPbG4c4AvL027LHr 7ZzJt+Lu82VXWKr+DgUvJsUDniKj64MB2gCUQGKvzzB/AU8d9eBFfDNVFCYV2d2fQu t2Q6EnyxZTp/Q== Date: Wed, 02 Sep 2026 12:31:52 +0000 To: amd-gfx@lists.freedesktop.org From: Andrei Rusu de Castro 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 Message-ID: <20260902-brightness-2-39f2d73d@empyreal.works> In-Reply-To: <20260902-brightness-cover-abf809f2@empyreal.works> References: <20260902-brightness-cover-abf809f2@empyreal.works> Feedback-ID: 182420409:user:proton X-Pm-Message-ID: dfa439e4593297fdd2e4ab5e24f1b828a03f1fdc Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The existing custom-curve tests use min_input_signal =3D=3D 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 --- .../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_backligh= t_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); } =20 +/* + * The custom curve cases above all use min_input_signal =3D=3D 0. There = the + * firmware minimum is zero and the [0..max] to [min..max] span is the ide= ntity, + * so an extra application of that mapping cannot be observed. The cases = below + * use the default firmware range instead, where min is 0x101 * 12 =3D=3D = 3084 and + * max is 0x101 * 255 =3D=3D 65535. + */ +static void dm_test_curve_caps_init(struct amdgpu_dm_backlight_caps *caps) +{ + caps->aux_support =3D false; + caps->min_input_signal =3D AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps->max_input_signal =3D AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps->data_points =3D 3; + caps->luminance_data[0].input_signal =3D 50; + caps->luminance_data[0].luminance =3D 20; + caps->luminance_data[1].input_signal =3D 128; + caps->luminance_data[1].luminance =3D 50; + caps->luminance_data[2].input_signal =3D 255; + caps->luminance_data[2].luminance =3D 100; +} + +/** + * dm_test_custom_brightness_user_domain - Curve output is a userspace val= ue + * @test: The KUnit test context + * + * convert_custom_brightness() reshapes a value inside the userspace [0..m= ax] + * domain. Its caller owns the single conversion to the firmware [min..ma= x] + * 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 =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + unsigned int min, max; + u32 brightness; + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + 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 =3D 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 =3D 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 dark= est + * level the firmware accepts. + */ +static void dm_test_curve_from_user_zero_is_min(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + unsigned int min, max; + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + 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 *t= est) +{ + struct amdgpu_dm_backlight_caps caps =3D {}; + uint saved_mask =3D 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_CU= RVE); + with_curve =3D convert_brightness_from_user(&caps, 0); + + amdgpu_dm_set_dc_debug_mask(saved_mask | DC_DISABLE_CUSTOM_BRIGHTNESS_CUR= VE); + without_curve =3D 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 ro= und 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 =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + u32 level; + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + dm_test_curve_caps_init(&caps); + + level =3D 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-z= ero min + * @test: The KUnit test context + */ +static void dm_test_curve_from_user_interpolation(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + dm_test_curve_caps_init(&caps); + + /* + * scale_input_to_fw(65535, 25700) =3D DIV_ROUND_CLOSEST(25700 * 255, 655= 35) + * =3D 100, which falls between the (50, 20) and (128, 50) points: + * lum =3D 20 + DIV_ROUND_CLOSEST((50 - 20) * (100 - 50), 128 - 50) =3D= 39 + * The curved firmware level is DIV_ROUND_CLOSEST(39 * 100, 101) =3D 39, + * which is DIV_ROUND_CLOSEST(39 * 65535, 255) =3D 10023 in the userspace + * domain and 3084 + DIV_ROUND_CLOSEST(62451 * 10023, 65535) =3D 12635 + * once converted to the firmware domain. + */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 25700), (u32)12= 635); + + amdgpu_dm_set_dc_debug_mask(saved_mask); +} + +/** + * dm_test_curve_from_user_max - The top of the range stays inside the ran= ge + * @test: The KUnit test context + */ +static void dm_test_curve_from_user_max(struct kunit *test) +{ + struct amdgpu_dm_backlight_caps caps =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + unsigned int min, max; + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + dm_test_curve_caps_init(&caps); + get_brightness_range(&caps, &min, &max); + + /* + * scale_input_to_fw(65535, 65535) =3D 255 matches the last point exactly, + * so lum =3D 100 and the curved firmware level is + * DIV_ROUND_CLOSEST(100 * 255, 101) =3D 252. That is + * DIV_ROUND_CLOSEST(252 * 65535, 255) =3D 64764 in the userspace domain + * and 3084 + DIV_ROUND_CLOSEST(62451 * 64764, 65535) =3D 64800 in the + * firmware domain. + */ + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)6480= 0); + 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 =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + unsigned int min, max, i; + u32 previous =3D 0; + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + dm_test_curve_caps_init(&caps); + get_brightness_range(&caps, &min, &max); + + for (i =3D 0; i <=3D max; i +=3D 1023) { + u32 level =3D convert_brightness_from_user(&caps, i); + + KUNIT_ASSERT_GE(test, level, previous); + previous =3D 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 =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + unsigned int min, max, i; + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + dm_test_curve_caps_init(&caps); + get_brightness_range(&caps, &min, &max); + + for (i =3D 0; i <=3D max; i +=3D 1023) { + u32 level =3D 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 =3D {}; + unsigned int min, max; + + caps.aux_support =3D false; + caps.min_input_signal =3D AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal =3D AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps.data_points =3D 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)18= 696); + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, 32767), (u32)34= 309); + KUNIT_EXPECT_EQ(test, convert_brightness_from_user(&caps, max), (u32)6553= 5); +} + /** * 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[] = =3D { 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), From nobody Sat Sep 26 10:02:06 2026 Received: from mail-10627.protonmail.ch (mail-10627.protonmail.ch [79.135.106.27]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED613485CDA for ; Wed, 2 Sep 2026 12:32:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.27 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352364; cv=none; b=GNZ3frtX8kqwm1/l6+G2c5WXnXGGjbyRWYOwvlVrnUKJ6erdl6mdRd39K3hAAzWqZjX/YpF2pR+RMjJ9TC421/54UnLt00GPcSswXr7XkInRQfz5+RJ/4jTbk27bW4v8dRkbQV9lLWMtlWXVCAogqDGlGp3SowZL65RTHDDsDF4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352364; c=relaxed/simple; bh=fYGOyhA2StUelasH7XKrzHRkHuX2F9/4fnBKkQrQYFU=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=GW2MefAapsULlRAYJEmQwnGYvEOWKQ/PUrLgZSe1rYPtmI/YkoXPpNJJZ7KJ50XuqII8XjeEoi/VLVPf06TDydtc6Wl445aMpiy3fWsfMsmaDt0zTAhZx14LWWjaMVCox4y7gGc7orWTI3qee+cW8vE0sAxi/kMRac0zHLvw6BY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works; spf=pass smtp.mailfrom=empyreal.works; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b=J0KMevOO; arc=none smtp.client-ip=79.135.106.27 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=empyreal.works Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b="J0KMevOO" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=empyreal.works; s=protonmail2; t=1788352359; x=1788611559; bh=TtQB3bvuIw6qqZqo5OFatGDqsqwyLHaGUzXVPABnou4=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=J0KMevOOnZ+DJQcSD90B5AQQB6BV1Rn/51KRY334BRUuz4qqczIlmp/6iavNjWnKZ 7X4sb/1UpLv2hXH/L334P+lDaEUQkouzIsQU/nhewsJM4gNIr1hkfRGpVoMvr24RQw RaMbNL6MFBqObrR7gq4IrBQ8uUHXUb2ANmBjMV2WOg9WgjnGQE0dne3lfSH8CVWb2W rEFw32yFpQRxTPzu/cyszdL96cwbPN3Gc+XKP8GO3tUhPIlg/pUrjFqi3gRPHI5R7Y dmZ7ia00EHMFAKUL1AlEPPtGM5BzoXIEZTIM7LRK6mAwwSVlexAEh52JhE3fMNTxiq b1IS3RTvKMcPA== Date: Wed, 02 Sep 2026 12:32:29 +0000 To: amd-gfx@lists.freedesktop.org From: Andrei Rusu de Castro 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, stable@vger.kernel.org Subject: [PATCH 3/4] drm/amd/display: pass userspace brightness to power module Message-ID: <20260902-brightness-3-50f94618@empyreal.works> In-Reply-To: <20260902-brightness-cover-abf809f2@empyreal.works> References: <20260902-brightness-cover-abf809f2@empyreal.works> Feedback-ID: 182420409:user:proton X-Pm-Message-ID: 5d77d30cad3537273c8d09ea7186f4c942ade71b Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- .../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/gp= u/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_di= splay_manager *dm) !(amdgpu_dc_feature_mask & DC_DISABLE_FRACTIONAL_PWM_MASK); init_data[i].use_custom_backlight_caps =3D false; init_data[i].custom_backlight_caps_config_no =3D 0; - init_data[i].use_linear_backlight_curve =3D false; + init_data[i].use_linear_backlight_curve =3D + !!(amdgpu_dc_debug_mask & + DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); init_data[i].def_varibright_enable =3D 0; init_data[i].varibright_level =3D 0; /* @@ -436,6 +438,8 @@ static int amdgpu_dm_init_power_module(struct amdgpu_di= splay_manager *dm) dm->backlight_caps[i].min_input_signal * 0x101; init_data[i].max_backlight_pwm =3D dm->backlight_caps[i].max_input_signal * 0x101; + init_data[i].brightness_mask =3D + dm->backlight_caps[i].brightness_mask; init_data[i].min_abm_backlight =3D dm->backlight_caps[i].min_input_signal * 0x101; =20 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); =20 +static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backl= ight_caps *caps, + u32 brightness) +{ + unsigned int min, max; + + if (!get_brightness_range(caps, &min, &max) || max <=3D min) + return 0; + + if (brightness >=3D 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_c= aps *caps, + u32 user_brightness) +{ + u32 brightness; + + if (!caps) + return user_brightness; + + if (!caps->aux_support) + return convert_brightness_to_millipercent(caps, user_brightness); + + brightness =3D convert_brightness_from_user(caps, user_brightness); + if (caps->brightness_mask) + brightness |=3D 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_displa= y_manager *dm, bool rc =3D false, reallow_idle =3D false; struct drm_connector *connector; struct dc_stream_state *stream; - unsigned int min, max; =20 list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, hea= d) { struct amdgpu_dm_connector *aconnector =3D to_amdgpu_dm_connector(connec= tor); @@ -285,12 +319,9 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_displ= ay_manager *dm, /* update scratch register */ if (bl_idx =3D=3D 0) amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightnes= s[bl_idx]); - brightness =3D convert_brightness_from_user(caps, dm->brightness[bl_idx]); link =3D (struct dc_link *)dm->backlight_link[bl_idx]; - - /* Apply brightness quirk */ - if (caps->brightness_mask) - brightness |=3D caps->brightness_mask; + brightness =3D convert_brightness_for_power_module(caps, + dm->brightness[bl_idx]); =20 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_displa= y_manager *dm, rc =3D 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 =3D DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; rc =3D 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_b= acklight_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_c= aps *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/driv= ers/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 =3D aux; __entry->ac =3D ac; ), - TP_printk("%ps: brightness requested=3D%u converted=3D%u aux=3D%s power= =3D%s", + TP_printk("%ps: brightness requested=3D%u converted=3D%u unit=3D%s aux=3D= %s power=3D%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 { =20 unsigned int min_backlight_pwm; unsigned int max_backlight_pwm; + unsigned int brightness_mask; =20 unsigned int min_abm_backlight; unsigned int num_backlight_levels; diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gp= u/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 =3D init_params[inst].custom_backlight_caps_config_no; + core_power->bl_prop[inst].brightness_mask =3D + init_params[inst].brightness_mask; =20 // 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/driver= s/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_legac= y( return 0; =20 // 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 =3D 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, =20 if (backlight_control_type =3D=3D BACKLIGHT_CONTROL_AMD_AUX && !is_hdr) backlight_level_params->control_type =3D BACKLIGHT_CONTROL_PWM; + + if (backlight_level_params->control_type =3D=3D BACKLIGHT_CONTROL_PWM) + backlight_level_params->backlight_pwm_u16_16 |=3D + bl_prop->brightness_mask; } =20 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/dr= ivers/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; =20 /* Describes the panel's min and max luminance in millinits measured * on full white screen, in min and max backlight settings. From nobody Sat Sep 26 10:02:06 2026 Received: from mail-4318.protonmail.ch (mail-4318.protonmail.ch [185.70.43.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A19404156C2 for ; Wed, 2 Sep 2026 12:33:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.43.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352395; cv=none; b=n9x0o8viyyZxCSTyHc/eO0RPM5jQIiJkoqw04URioarubNt+P9kIcL6yQACxaq1CRA9g03S26y5lUfeBdR6j5ULIXs2zLZ7xIAUzxte2gw5mx/luL+icYnwIiPYXbCoTgu6SB/uxxq/9TpPbAnOg3l4e4gegaQ0mcmz4ePpO7O4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788352395; c=relaxed/simple; bh=Q+WERAoXcy+fnKWqVT2j7uYLBtUH8F++1rZQvy+rkP0=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=YF7lhoZGopq6l6jC7d+MiRVtAwN9SKHwY4I5ZfqxWam+sCH55z/VCXrGGzzjyc22B9fhsF0RV/zdLH4xvjvGvq398tcKqCRR2YDru0ee5xy2qQ+1tW+ggcAU8ORcGVZrIoWxVOL1Dw4B/hwCMEwVD7IP/eSj2NKHcTQPPbgS6yA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works; spf=pass smtp.mailfrom=empyreal.works; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b=Sqp15A5H; arc=none smtp.client-ip=185.70.43.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=empyreal.works Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=empyreal.works Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=empyreal.works header.i=@empyreal.works header.b="Sqp15A5H" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=empyreal.works; s=protonmail2; t=1788352385; x=1788611585; bh=czoiifOrjQOOSsp1SWakje73Vk1OJ3PnpNEpueAQcgE=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=Sqp15A5HwsyvkJafJtKjiCWipKY5Lm0Dgx7tOPJADdvcYUNCJ4V3xeNy16bUXgoX1 EyAMg7j/uDuL1w0l8f09IFyjGGFA1Nm0NONajtmMqLXAyzIXhjD4XVPhQJ5c2a7cH2 u4EELTxTwc6v6S1o3d/DahMzOzKEoNxWknutf8MgX1MoTe0X3O4UnieZpEyooTsVA0 x9Yi+w0U/iHmEP6tcJ+EhDvqEFNmhe0sE5dajLJO2e4WNIKAbpSihf5pv4AGprDNpH 6BB/8/FlyYzgTUpXEtf5xmLFHDrb9Jgyy+InJu0hFUM+YvkE1UdvWESvKyxYOrQz8B Vbg8ZpewTbXCA== Date: Wed, 02 Sep 2026 12:33:01 +0000 To: amd-gfx@lists.freedesktop.org From: Andrei Rusu de Castro 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 4/4] drm/amd/display: test power module brightness input domain Message-ID: <20260902-brightness-4-abf809f2@empyreal.works> In-Reply-To: <20260902-brightness-cover-abf809f2@empyreal.works> References: <20260902-brightness-cover-abf809f2@empyreal.works> Feedback-ID: 182420409:user:proton X-Pm-Message-ID: b22fbbc5f2b81e94182aea359eb77092d11efe8a Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- .../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_backligh= t_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" =20 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); } =20 +/* 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 *tes= t) +{ + struct amdgpu_dm_backlight_caps caps =3D {}; + + KUNIT_EXPECT_EQ(test, convert_brightness_for_power_module(NULL, 100), 100= U); + 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 mana= ger + * 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 =3D {}; + unsigned int min, max; + + caps.min_input_signal =3D AMDGPU_DM_DEFAULT_MIN_BACKLIGHT; + caps.max_input_signal =3D AMDGPU_DM_DEFAULT_MAX_BACKLIGHT; + caps.data_points =3D 3; + caps.luminance_data[0].input_signal =3D 50; + caps.luminance_data[0].luminance =3D 20; + caps.luminance_data[1].input_signal =3D 128; + caps.luminance_data[1].luminance =3D 50; + caps.luminance_data[2].input_signal =3D 230; + caps.luminance_data[2].luminance =3D 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), 10= 0000U); + 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 =3D {}; + unsigned int min, max; + + caps.min_input_signal =3D 0; + caps.max_input_signal =3D 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), 10= 0000U); +} + +/** + * 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 kun= it *test) +{ + struct set_backlight_level_params params =3D {}; + struct core_power core_power =3D {}; + + core_power.bl_prop[0].brightness_mask =3D 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 byp= ass + * @test: The KUnit test context + */ +static void dm_test_power_module_linear_curve_uses_panel_instance(struct k= unit *test) +{ + struct core_power core_power =3D {}; + unsigned int backlight_lut[101] =3D {}; + + core_power.bl_prop[0].backlight_lut =3D backlight_lut; + core_power.bl_prop[0].num_backlight_levels =3D ARRAY_SIZE(backlight_lut); + core_power.bl_prop[1].min_backlight_pwm =3D 3084; + core_power.bl_prop[1].max_backlight_pwm =3D 65535; + core_power.bl_prop[1].backlight_range =3D 62451; + core_power.bl_prop[1].use_linear_backlight_curve =3D true; + backlight_lut[50] =3D 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 *te= st) +{ + struct amdgpu_dm_backlight_caps caps =3D {}; + uint saved_mask =3D amdgpu_dm_get_dc_debug_mask(); + + amdgpu_dm_set_dc_debug_mask(saved_mask & ~DC_DISABLE_CUSTOM_BRIGHTNESS_CU= RVE); + caps.aux_support =3D true; + caps.aux_min_input_signal =3D 1; + caps.aux_max_input_signal =3D 512; + caps.brightness_mask =3D 3; + caps.data_points =3D 2; + caps.luminance_data[0].input_signal =3D 50; + caps.luminance_data[0].luminance =3D 20; + caps.luminance_data[1].input_signal =3D 200; + caps.luminance_data[1].luminance =3D 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() */ =20 /** @@ -1590,7 +1736,7 @@ static void dm_test_curve_from_user_monotonic(struct = kunit *test) for (i =3D 0; i <=3D max; i +=3D 1023) { u32 level =3D convert_brightness_from_user(&caps, i); =20 - KUNIT_ASSERT_GE(test, level, previous); + KUNIT_EXPECT_GE(test, level, previous); previous =3D level; } =20 @@ -1619,8 +1765,8 @@ static void dm_test_curve_from_user_within_range(stru= ct kunit *test) for (i =3D 0; i <=3D max; i +=3D 1023) { u32 level =3D convert_brightness_from_user(&caps, i); =20 - 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); } =20 KUNIT_EXPECT_LE(test, convert_brightness_from_user(&caps, max), (u32)max); @@ -2198,6 +2344,13 @@ static struct kunit_case dm_backlight_test_cases[] = =3D { 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),