All of lore.kernel.org
 help / color / mirror / Atom feed
From: <Roman.Li@amd.com>
To: <amd-gfx@lists.freedesktop.org>
Cc: Harry Wentland <harry.wentland@amd.com>,
	Leo Li <sunpeng.li@amd.com>,
	Aurabindo Pillai <aurabindo.pillai@amd.com>,
	Roman Li <roman.li@amd.com>, Wayne Lin <wayne.lin@amd.com>,
	Tom Chung <chiahsuan.chung@amd.com>,
	"Fangzhi Zuo" <jerry.zuo@amd.com>,
	Dan Wheeler <daniel.wheeler@amd.com>, Ray Wu <Ray.Wu@amd.com>,
	Ivan Lipski <ivan.lipski@amd.com>, Alex Hung <alex.hung@amd.com>,
	James Lin <PingLei.Lin@amd.com>,
	Chenyu Chen <Chen-Yu.Chen@amd.com>,
	Iswara Nagulendran <Iswara.Nagulendran@amd.com>,
	Anthony Koo <anthony.koo@amd.com>
Subject: [PATCH 30/41] drm/amd/display: Fix ABM over VABC
Date: Fri, 31 Jul 2026 17:12:51 -0400	[thread overview]
Message-ID: <20260731211302.3040343-31-Roman.Li@amd.com> (raw)
In-Reply-To: <20260731211302.3040343-1-Roman.Li@amd.com>

From: Iswara Nagulendran <Iswara.Nagulendran@amd.com>

[Why]
ABM does not take effect when brightness
is below twenty percent on VABC LCD panels.

[How]
Create new VESA aux path for brightness
translation functions. When VESA aux enabled
use zero-anchored linear interpolation to
translate instead of the legacy min max
backlight mapping.

Reviewed-by: Anthony Koo <anthony.koo@amd.com>
Signed-off-by: Iswara Nagulendran <Iswara.Nagulendran@amd.com>
Signed-off-by: Roman Li <roman.li@amd.com>
---
 .../drm/amd/display/modules/power/power_abm.c | 215 ++++++++++++++++--
 .../amd/display/modules/power/power_helpers.h |   9 +
 2 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
index b26ceaba940d..5e86889eaa84 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
@@ -245,29 +245,80 @@ struct iram_table_v_2_2 {
 #define MOD_POWER_TO_CORE(mod_power)\
 		container_of(mod_power, struct core_power, mod_public)
 
+/* Maximum brightness expressed in millipercent (100% * 1000). */
+#define BACKLIGHT_MILLIPERCENT_MAX (100 * 1000)
+
 static uint16_t backlight_8_to_16(unsigned int backlight_8bit)
 {
 	return (uint16_t)(backlight_8bit * 0x101);
 }
 
+/* Caches the link's backlight control type on the panel's backlight
+ * properties so the brightness translation helpers can pick the correct
+ * mapping.
+ */
+void mod_power_set_backlight_control_type(struct core_power *core_power,
+		unsigned int inst, enum backlight_control_type backlight_control_type)
+{
+	if (core_power == NULL)
+		return;
+
+	core_power->bl_prop[inst].backlight_control_type = backlight_control_type;
+}
+
+/* Returns true when the panel uses the VESA AUX backlight control path, which
+ * requires zero-anchored linear brightness interpolation.
+ */
+static bool is_vesa_abc(struct core_power *core_power, unsigned int inst)
+{
+	if (core_power == NULL)
+		return false;
+
+	return core_power->bl_prop[inst].backlight_control_type ==
+			BACKLIGHT_CONTROL_VESA_AUX;
+}
+
+/* Legacy millipercent→millinit conversion: scales linearly between
+ * [0%, 100%] → [min_brightness_millinits, max_brightness_millinits].
+ */
+static unsigned int backlight_millipercent_to_millinit_legacy(
+		struct core_power *core_power, unsigned int millipercent, unsigned int inst)
+{
+	if (core_power == NULL)
+		return 0;
+
+	return (unsigned int)div_u64((unsigned long long)millipercent *
+			core_power->bl_prop[inst].nits_range,
+			100000) +
+			core_power->bl_prop[inst].min_brightness_millinits;
+}
+
+/* Converts millipercent to millinit.
+ * For VESA AUX brightness control, uses simple linear interpolation with
+ * 0% = 0 nits and 100% = max_brightness_millinits.
+ * Otherwise, falls back to the legacy min→max nits range mapping.
+ */
 unsigned int backlight_millipercent_to_millinit(
 		struct core_power *core_power, unsigned int millipercent, unsigned int inst)
 {
-	unsigned int millinit = 0;
-	unsigned long long numerator = 0;
+	if (!is_vesa_abc(core_power, inst))
+		return backlight_millipercent_to_millinit_legacy(core_power, millipercent, inst);
 
 	if (core_power == NULL)
 		return 0;
 
-	numerator = ((unsigned long long)millipercent) *
-				core_power->bl_prop[inst].nits_range;
-	millinit = ((unsigned int)div_u64(numerator, 100000)) +
-			core_power->bl_prop[inst].min_brightness_millinits;
+	if (millipercent >= BACKLIGHT_MILLIPERCENT_MAX)
+		return core_power->bl_prop[inst].max_brightness_millinits;
 
-	return millinit;
+	return (unsigned int)div_u64((unsigned long long)millipercent *
+			core_power->bl_prop[inst].max_brightness_millinits,
+			BACKLIGHT_MILLIPERCENT_MAX);
 }
 
-static unsigned int backlight_millinit_to_millipercent(
+/* Legacy millinit→millipercent conversion: scales linearly between
+ * [min_brightness_millinits, max_brightness_millinits] → [0%, 100%].
+ */
+static unsigned int backlight_millinit_to_millipercent_legacy(
 		struct core_power *core_power, unsigned int millinit, unsigned int inst)
 {
 	unsigned int millipercent = 0;
@@ -280,7 +331,7 @@ static unsigned int backlight_millinit_to_millipercent(
 		return 0;
 
 	if (millinit >= core_power->bl_prop[inst].max_brightness_millinits)
-		return (100 * 1000);
+		return BACKLIGHT_MILLIPERCENT_MAX;
 
 	numerator = (((unsigned long long)millinit) -
 			core_power->bl_prop[inst].min_brightness_millinits) * 100000;
@@ -290,7 +341,31 @@ static unsigned int backlight_millinit_to_millipercent(
 	return millipercent;
 }
 
-static unsigned int backlight_pwm_to_millipercent(
+/* Converts millinit to millipercent.
+ * For VESA AUX brightness control, uses simple linear interpolation with
+ * 0 nits = 0% and max_brightness_millinits = 100%.
+ * Otherwise, falls back to the legacy min→max nits range mapping.
+ */
+static unsigned int backlight_millinit_to_millipercent(
+		struct core_power *core_power, unsigned int millinit, unsigned int inst)
+{
+	if (!is_vesa_abc(core_power, inst))
+		return backlight_millinit_to_millipercent_legacy(core_power, millinit, inst);
+
+	if (core_power == NULL)
+		return 0;
+
+	if (core_power->bl_prop[inst].max_brightness_millinits == 0)
+		return 0;
+
+	if (millinit >= core_power->bl_prop[inst].max_brightness_millinits)
+		return BACKLIGHT_MILLIPERCENT_MAX;
+
+	return (unsigned int)div_u64((unsigned long long)millinit * 100000,
+			core_power->bl_prop[inst].max_brightness_millinits);
+}
+
+static unsigned int backlight_pwm_to_millipercent_legacy(
 		struct core_power *core_power, unsigned int pwm, unsigned int inst)
 {
 	unsigned int millipercent = 0;
@@ -362,12 +437,37 @@ static unsigned int backlight_pwm_to_millipercent(
 	}
 
 	/* No interpolation, just take closest index */
-	millipercent = 1000 * 100 * mid / max_index;
+	millipercent = BACKLIGHT_MILLIPERCENT_MAX * mid / max_index;
 
 	return millipercent;
 }
 
-static unsigned int backlight_pwm_to_millinit(
+/* Converts PWM to millipercent.
+ * For VESA AUX brightness control, uses simple linear interpolation with
+ * 0 PWM = 0% and max_backlight_pwm = 100%.
+ * Otherwise, falls back to the legacy LUT based mapping.
+ */
+static unsigned int backlight_pwm_to_millipercent(
+		struct core_power *core_power, unsigned int pwm, unsigned int inst)
+{
+	if (!is_vesa_abc(core_power, inst))
+		return backlight_pwm_to_millipercent_legacy(core_power, pwm, inst);
+
+	if (core_power == NULL)
+		return 0;
+
+	if (core_power->bl_prop[inst].max_backlight_pwm == 0)
+		return 0;
+
+	if (pwm >= core_power->bl_prop[inst].max_backlight_pwm)
+		return BACKLIGHT_MILLIPERCENT_MAX;
+
+	return (unsigned int)div_u64((unsigned long long)pwm *
+			BACKLIGHT_MILLIPERCENT_MAX,
+			core_power->bl_prop[inst].max_backlight_pwm);
+}
+
+static unsigned int backlight_pwm_to_millinit_legacy(
 		struct core_power *core_power, unsigned int pwm, unsigned int inst)
 {
 	unsigned int millinit = 0;
@@ -394,7 +494,32 @@ static unsigned int backlight_pwm_to_millinit(
 	return millinit;
 }
 
-unsigned int backlight_millipercent_to_pwm(
+/* Converts PWM to millinit.
+ * For VESA AUX brightness control, uses simple linear interpolation with
+ * 0 PWM = 0 nits and max_backlight_pwm = max_brightness_millinits.
+ * Otherwise, falls back to the legacy min→max nits range mapping.
+ */
+static unsigned int backlight_pwm_to_millinit(
+		struct core_power *core_power, unsigned int pwm, unsigned int inst)
+{
+	if (!is_vesa_abc(core_power, inst))
+		return backlight_pwm_to_millinit_legacy(core_power, pwm, inst);
+
+	if (core_power == NULL)
+		return 0;
+
+	if (core_power->bl_prop[inst].max_backlight_pwm == 0)
+		return 0;
+
+	if (pwm >= core_power->bl_prop[inst].max_backlight_pwm)
+		return core_power->bl_prop[inst].max_brightness_millinits;
+
+	return (unsigned int)div_u64((unsigned long long)pwm *
+			core_power->bl_prop[inst].max_brightness_millinits,
+			core_power->bl_prop[inst].max_backlight_pwm);
+}
+
+static unsigned int backlight_millipercent_to_pwm_legacy(
 		struct core_power *core_power, unsigned int millipercent, unsigned int inst)
 {
 	unsigned int pwm = (unsigned int)-1;
@@ -431,7 +556,32 @@ unsigned int backlight_millipercent_to_pwm(
 	return pwm;
 }
 
-static unsigned int backlight_millinit_to_pwm(
+/* Converts millipercent to PWM.
+ * For VESA AUX brightness control, uses simple linear interpolation with
+ * 0% = 0 PWM and 100% = max_backlight_pwm.
+ * Otherwise, falls back to the legacy LUT based mapping.
+ */
+unsigned int backlight_millipercent_to_pwm(
+		struct core_power *core_power, unsigned int millipercent, unsigned int inst)
+{
+	if (!is_vesa_abc(core_power, inst))
+		return backlight_millipercent_to_pwm_legacy(core_power, millipercent, inst);
+
+	if (core_power == NULL)
+		return 0;
+
+	if (millipercent >= BACKLIGHT_MILLIPERCENT_MAX)
+		return core_power->bl_prop[inst].max_backlight_pwm;
+
+	return (unsigned int)div_u64((unsigned long long)millipercent *
+			core_power->bl_prop[inst].max_backlight_pwm,
+			BACKLIGHT_MILLIPERCENT_MAX);
+}
+
+/* Legacy millinit→PWM conversion: scales linearly between
+ * [min_brightness_millinits, max_brightness_millinits] → [min_backlight_pwm, max_backlight_pwm].
+ */
+static unsigned int backlight_millinit_to_pwm_legacy(
 		struct core_power *core_power, unsigned int millinit, unsigned int inst)
 {
 	unsigned int pwm = 0;
@@ -460,6 +610,35 @@ static unsigned int backlight_millinit_to_pwm(
 	return pwm;
 }
 
+/* Converts millinit to PWM.
+ * For VESA AUX brightness control, uses simple linear interpolation with
+ * 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)
+{
+	if (!is_vesa_abc(core_power, inst))
+		return backlight_millinit_to_pwm_legacy(core_power, millinit, inst);
+
+	if (core_power == NULL)
+		return 0;
+
+	if (core_power->bl_prop[inst].max_brightness_millinits == 0)
+		return 0;
+
+	if (millinit >= core_power->bl_prop[inst].max_brightness_millinits)
+		return core_power->bl_prop[inst].max_backlight_pwm;
+
+	/* millinit is bounded by max_brightness_millinits (up to ~10^7 for ~10000 nits).
+	 * max_backlight_pwm is a 32-bit value.
+	 * Worst-case product (~10^7 × UINT_MAX ≈ 4×10^16) fits within unsigned long long.
+	 */
+	return (unsigned int)div_u64((unsigned long long)millinit *
+			core_power->bl_prop[inst].max_backlight_pwm,
+			core_power->bl_prop[inst].max_brightness_millinits);
+}
+
 static bool validate_ext_backlight_caps(
 		struct dm_acpi_atif_backlight_caps *ext_backlight_caps)
 {
@@ -716,6 +895,14 @@ void mod_power_update_backlight_on_mode_change(
 {
     struct set_backlight_level_params backlight_level_params = { 0 };
 
+		/* 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
+		 * from panel type + DPCD caps), so the brightness translation
+		 * helpers can read it without it being passed on every call.
+		 */
+		mod_power_set_backlight_control_type(core_power, panel_inst,
+				link->backlight_control_type);
+
 		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)
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 548c8ff6ddb4..68679fa10946 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
+++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
@@ -102,6 +102,13 @@ struct pwr_backlight_properties {
 	unsigned int max_brightness_millinits;
 	unsigned int nits_range;
 
+	/* Backlight control type of the associated link. Cached here so the
+	 * brightness translation helpers can select the correct mapping
+	 * (legacy vs. VESA AUX zero-anchored) without threading the type
+	 * through every call.
+	 */
+	enum backlight_control_type backlight_control_type;
+
 	bool backlight_caps_valid;
 	bool use_custom_backlight_caps;
 	unsigned int custom_backlight_caps_config_no;
@@ -190,6 +197,8 @@ void reset_replay_dsync_error_count(struct dc_link *link);
 void change_replay_to_psr(struct dc_link *link);
 void change_psr_to_replay(struct dc_link *link);
 void initialize_backlight_caps(struct core_power *core_power, unsigned int inst);
+void mod_power_set_backlight_control_type(struct core_power *core_power,
+		unsigned int inst, enum backlight_control_type backlight_control_type);
 unsigned int backlight_millipercent_to_pwm(
 		struct core_power *core_power, unsigned int millipercent, unsigned int inst);
 unsigned int backlight_millipercent_to_millinit(
-- 
2.34.1


  parent reply	other threads:[~2026-07-31 21:16 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31 21:12 [PATCH 00/41] DC Patches July 31, 2026 Roman.Li
2026-07-31 21:12 ` [PATCH 03/41] drm/amd/display: Enable DCN6 init Roman.Li
2026-07-31 21:12 ` [PATCH 04/41] drm/amd/display: Dependent changes for DCN6 Roman.Li
2026-07-31 21:12 ` [PATCH 05/41] drm/amd/display: Enable DCN6 sources compilation Roman.Li
2026-07-31 21:12 ` [PATCH 06/41] drm/amd/display: Remove duplicate in tests/Makefile Roman.Li
2026-07-31 21:12 ` [PATCH 07/41] drm/amd/display: Resize MST HDCP per-connector arrays to 32 Roman.Li
2026-07-31 21:12 ` [PATCH 08/41] drm/amd/display: Bounds-check connector->index in dm_dp_mst_get_modes Roman.Li
2026-07-31 21:12 ` [PATCH 09/41] drm/amd/display: Ensure dtbclk is enabled Roman.Li
2026-07-31 21:12 ` [PATCH 10/41] drm/amd/display: Update VRR info packet to support 12-bit refresh rates Roman.Li
2026-07-31 21:12 ` [PATCH 11/41] drm/amd/display: Gate HDMI FRL status polling on active FRL link rate Roman.Li
2026-07-31 21:12 ` [PATCH 12/41] drm/amd/display: Fix wb_info leak and NULL deref in writeback Roman.Li
2026-07-31 21:12 ` [PATCH 13/41] drm/amd/display: Fix seamless mode switch not triggering for HDR to SDR transition Roman.Li
2026-07-31 21:12 ` [PATCH 14/41] drm/amd/display: Add KUnit tests for more crtc functions Roman.Li
2026-07-31 21:12 ` [PATCH 15/41] drm/amd/display: Add vblank handling tests for crtc Roman.Li
2026-07-31 21:12 ` [PATCH 16/41] drm/amd/display: Add idle worker " Roman.Li
2026-07-31 21:12 ` [PATCH 17/41] drm/amd/display: Add active plane count " Roman.Li
2026-07-31 21:12 ` [PATCH 18/41] drm/amd/display: Add KUnit test for crtc vblank event completion Roman.Li
2026-07-31 21:12 ` [PATCH 19/41] drm/amd/display: Add KUnit tests for crtc set_vupdate_irq Roman.Li
2026-07-31 21:12 ` [PATCH 20/41] drm/amd/display: Add KUnit tests for crtc set_static_screen_optimze Roman.Li
2026-07-31 21:12 ` [PATCH 21/41] drm/amd/display: Refactor stream validation Roman.Li
2026-07-31 21:12 ` [PATCH 22/41] drm/amd/display: Unify force_yuv debugfs into force_yuv_pixel_format Roman.Li
2026-07-31 21:12 ` [PATCH 23/41] drm/amd/display: Align connector KUnit tests with stream validation refactor Roman.Li
2026-07-31 21:12 ` [PATCH 24/41] drm/amd/display: Increase fclk change latency on dcn351 Roman.Li
2026-07-31 21:12 ` [PATCH 25/41] drm/amd/display: Add KUnit tests for crtc set_vblank Roman.Li
2026-07-31 21:12 ` [PATCH 26/41] drm/amd/display: Cover crtc set_vblank workqueue branch Roman.Li
2026-07-31 21:12 ` [PATCH 27/41] drm/amd/display: Cover crtc vblank IPS self-refresh restore Roman.Li
2026-07-31 21:12 ` [PATCH 28/41] drm/amd/display: Cover crtc vblank restore replay-supported path Roman.Li
2026-07-31 21:12 ` [PATCH 29/41] drm/amd/display: Cover crtc destroy_state stream release Roman.Li
2026-07-31 21:12 ` Roman.Li [this message]
2026-07-31 21:12 ` [PATCH 31/41] drm/amd/display: Add missing DCN42B register defines Roman.Li
2026-07-31 21:12 ` [PATCH 32/41] drm/amd/display: Add missing DMUB CACP and PR definitions Roman.Li
2026-07-31 21:12 ` [PATCH 33/41] drm/amd/display: Add missing OTG_CRC1_SELECT mask for DCN3.2 Roman.Li
2026-07-31 21:12 ` [PATCH 34/41] drm/amd/display: Fix CRC engine 1 enable/disable on DCN3.1.2+ Roman.Li
2026-07-31 21:12 ` [PATCH 35/41] drm/amd/display: Configure all CRC engines in pipe CRC source path Roman.Li
2026-07-31 21:12 ` [PATCH 36/41] drm/amd/display: Fix more KUnit connector use-after-free bugs Roman.Li
2026-07-31 21:12 ` [PATCH 37/41] drm/amd/display: Update BW bounding box unconditionally for DCN6 Roman.Li
2026-07-31 21:12 ` [PATCH 38/41] drm/amd/display: switch max FFE level cap based on FRL link rate Roman.Li
2026-07-31 21:13 ` [PATCH 39/41] drm/amd/display: Add FFE level defaults Roman.Li
2026-07-31 21:13 ` [PATCH 40/41] drm/amd/display: Migrate color manager HW and fix MCM blend LUT issues Roman.Li
2026-07-31 21:13 ` [PATCH 41/41] drm/amd/display: Promote DC to 3.2.392 Roman.Li
2026-08-04 13:24 ` [PATCH 00/41] DC Patches July 31, 2026 Wheeler, Daniel
2026-08-04 21:15   ` Timur Kristóf
2026-08-05 20:03     ` Wheeler, Daniel

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=20260731211302.3040343-31-Roman.Li@amd.com \
    --to=roman.li@amd.com \
    --cc=Chen-Yu.Chen@amd.com \
    --cc=Iswara.Nagulendran@amd.com \
    --cc=PingLei.Lin@amd.com \
    --cc=Ray.Wu@amd.com \
    --cc=alex.hung@amd.com \
    --cc=amd-gfx@lists.freedesktop.org \
    --cc=anthony.koo@amd.com \
    --cc=aurabindo.pillai@amd.com \
    --cc=chiahsuan.chung@amd.com \
    --cc=daniel.wheeler@amd.com \
    --cc=harry.wentland@amd.com \
    --cc=ivan.lipski@amd.com \
    --cc=jerry.zuo@amd.com \
    --cc=sunpeng.li@amd.com \
    --cc=wayne.lin@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.