[PATCH 3/4] drm/amd/display: pass userspace brightness to power module
From: Andrei Rusu de Castro
Date: Wed Sep 02 2026 - 08:41:08 EST
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@xxxxxxxxxxxxxxx # 7.2.x
Signed-off-by: Andrei Rusu de Castro <arc@empyreal.works>
---
.../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 ++-
.../display/amdgpu_dm/amdgpu_dm_backlight.c | 46 +++++++++++++++----
.../display/amdgpu_dm/amdgpu_dm_backlight.h | 2 +
.../amd/display/amdgpu_dm/amdgpu_dm_trace.h | 3 +-
.../drm/amd/display/modules/inc/mod_power.h | 1 +
.../gpu/drm/amd/display/modules/power/power.c | 2 +
.../drm/amd/display/modules/power/power_abm.c | 6 ++-
.../amd/display/modules/power/power_helpers.h | 1 +
8 files changed, 55 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index ec483276d753..4e730527be4a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -425,7 +425,9 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm)
!(amdgpu_dc_feature_mask & DC_DISABLE_FRACTIONAL_PWM_MASK);
init_data[i].use_custom_backlight_caps = false;
init_data[i].custom_backlight_caps_config_no = 0;
- init_data[i].use_linear_backlight_curve = false;
+ init_data[i].use_linear_backlight_curve =
+ !!(amdgpu_dc_debug_mask &
+ DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE);
init_data[i].def_varibright_enable = 0;
init_data[i].varibright_level = 0;
/*
@@ -436,6 +438,8 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm)
dm->backlight_caps[i].min_input_signal * 0x101;
init_data[i].max_backlight_pwm =
dm->backlight_caps[i].max_input_signal * 0x101;
+ init_data[i].brightness_mask =
+ dm->backlight_caps[i].brightness_mask;
init_data[i].min_abm_backlight =
dm->backlight_caps[i].min_input_signal * 0x101;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
index 424b33573a73..04e810af4623 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c
@@ -218,6 +218,41 @@ u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
}
EXPORT_IF_KUNIT(convert_brightness_to_user);
+static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps,
+ u32 brightness)
+{
+ unsigned int min, max;
+
+ if (!get_brightness_range(caps, &min, &max) || max <= min)
+ return 0;
+
+ if (brightness >= max)
+ return 100 * 1000;
+
+ return DIV_ROUND_CLOSEST_ULL((u64)brightness * 100 * 1000, max);
+}
+
+STATIC_IFN_KUNIT
+u32 convert_brightness_for_power_module(const struct amdgpu_dm_backlight_caps *caps,
+ u32 user_brightness)
+{
+ u32 brightness;
+
+ if (!caps)
+ return user_brightness;
+
+ if (!caps->aux_support)
+ return convert_brightness_to_millipercent(caps, user_brightness);
+
+ brightness = convert_brightness_from_user(caps, user_brightness);
+ if (caps->brightness_mask)
+ brightness |= caps->brightness_mask;
+
+ return brightness;
+}
+
+EXPORT_IF_KUNIT(convert_brightness_for_power_module);
+
STATIC_IFN_KUNIT
struct dc_stream_state *dm_find_stream_with_link(
struct amdgpu_display_manager *dm,
@@ -262,7 +297,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
bool rc = false, reallow_idle = false;
struct drm_connector *connector;
struct dc_stream_state *stream;
- unsigned int min, max;
list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) {
struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector);
@@ -285,12 +319,9 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
/* update scratch register */
if (bl_idx == 0)
amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]);
- brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]);
link = (struct dc_link *)dm->backlight_link[bl_idx];
-
- /* Apply brightness quirk */
- if (caps->brightness_mask)
- brightness |= caps->brightness_mask;
+ brightness = convert_brightness_for_power_module(caps,
+ dm->brightness[bl_idx]);
if (trace_amdgpu_dm_brightness_enabled()) {
trace_amdgpu_dm_brightness(__builtin_return_address(0),
@@ -314,9 +345,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness,
AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true);
} else {
- /* power module uses millipercent */
- get_brightness_range(caps, &min, &max);
- brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000;
rc = mod_power_set_backlight_percent(dm->power_module, stream,
brightness, 0, false);
}
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
index 90bed0ea5d00..396e7654e299 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h
@@ -72,6 +72,8 @@ u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps,
uint32_t brightness);
u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
uint32_t brightness);
+u32 convert_brightness_for_power_module(const struct amdgpu_dm_backlight_caps *caps,
+ u32 user_brightness);
int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm,
struct backlight_device *bd);
void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps,
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h
index f33a2c1e0da5..5e7782f9e89f 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h
@@ -744,10 +744,11 @@ TRACE_EVENT(amdgpu_dm_brightness,
__entry->aux = aux;
__entry->ac = ac;
),
- TP_printk("%ps: brightness requested=%u converted=%u aux=%s power=%s",
+ TP_printk("%ps: brightness requested=%u converted=%u unit=%s aux=%s power=%s",
(void *)__entry->function,
(u32)__entry->user_brightness,
(u32)__entry->converted_brightness,
+ (__entry->aux) ? "millinits" : "millipercent",
(__entry->aux) ? "true" : "false",
(__entry->ac) ? "AC" : "DC"
)
diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h
index 02bee3b1956d..56b41861e164 100644
--- a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h
+++ b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h
@@ -19,6 +19,7 @@ struct mod_power_init_params {
unsigned int min_backlight_pwm;
unsigned int max_backlight_pwm;
+ unsigned int brightness_mask;
unsigned int min_abm_backlight;
unsigned int num_backlight_levels;
diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c
index ee15c14a899e..1d5e94893e36 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power.c
@@ -157,6 +157,8 @@ struct mod_power *mod_power_create(struct dc *dc,
init_params[inst].use_custom_backlight_caps;
core_power->bl_prop[inst].custom_backlight_caps_config_no =
init_params[inst].custom_backlight_caps_config_no;
+ core_power->bl_prop[inst].brightness_mask =
+ init_params[inst].brightness_mask;
// Do not allow less than 101 backlight levels
if (init_params[inst].num_backlight_levels < 101)
diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
index 5e86889eaa84..4d7d80ac60c9 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c
+++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c
@@ -529,7 +529,7 @@ static unsigned int backlight_millipercent_to_pwm_legacy(
return 0;
// Bypass the brightness mapping LUT
- if (core_power->bl_prop->use_linear_backlight_curve) {
+ if (core_power->bl_prop[inst].use_linear_backlight_curve) {
pwm = core_power->bl_prop[inst].min_backlight_pwm +
(unsigned int) div_u64((unsigned long long) millipercent *
core_power->bl_prop[inst].backlight_range,
@@ -1020,6 +1020,10 @@ void fill_backlight_level_params(struct core_power *core_power,
if (backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX && !is_hdr)
backlight_level_params->control_type = BACKLIGHT_CONTROL_PWM;
+
+ if (backlight_level_params->control_type == BACKLIGHT_CONTROL_PWM)
+ backlight_level_params->backlight_pwm_u16_16 |=
+ bl_prop->brightness_mask;
}
bool mod_power_set_backlight_nits(struct mod_power *mod_power,
diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
index 68679fa10946..1eeacbfb8d91 100644
--- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
+++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h
@@ -94,6 +94,7 @@ struct pwr_backlight_properties {
unsigned int min_backlight_pwm;
unsigned int max_backlight_pwm;
unsigned int backlight_range;
+ unsigned int brightness_mask;
/* Describes the panel's min and max luminance in millinits measured
* on full white screen, in min and max backlight settings.