The power module consumes millipercent and builds its own PWM lookup table from the ATIF brightness transfer characteristics.
The Linux display manager instead converts the userspace request to a firmware level, then derives a percentage from that hardware-domain value. For a non-zero PWM minimum this maps the minimum above zero and the maximum above 100 percent. It also applies the ATIF curve once in the display manager and again in the power module. Pass the original userspace percentage to the power module on PWM panels. Keep the existing nits conversion for AUX panels, but keep the resulting millinit value unmasked while the power module derives cached percentages and PWM levels. The brightness mask is a final DP source-level quirk. Carry it into the power module and apply it immediately before an AMD AUX write or after PWM derivation when the effective hardware handoff is PWM. This preserves ordinary PWM, forced-PWM OLED, AUX fallback-to-PWM, live AMD AUX, and mode-change replay without perturbing an intermediate unit. DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE previously bypassed only the display manager's copy of the curve. Wire it to the power module's existing linear bypass and use the selected panel's policy rather than panel zero's. Label the brightness trace value as millipercent or millinits so the corrected input domain is explicit. A live DCN 3.5.1 trace exposed the domain mismatch. KUnit coverage is added separately. Reported-by: Sashiko <[email protected]> Closes: https://sashiko.dev/#/patchset/[email protected]?part=3 Fixes: 3c108046e1d6 ("drm/amd/display: Add power module on Linux") Cc: [email protected] # 7.2.x Assisted-by: LLM Signed-off-by: Andrei Rusu de Castro <[email protected]> --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 +- .../display/amdgpu_dm/amdgpu_dm_backlight.c | 44 ++++++++++++--- .../display/amdgpu_dm/amdgpu_dm_backlight.h | 2 + .../amd/display/amdgpu_dm/amdgpu_dm_trace.h | 3 +- .../drm/amd/display/modules/inc/mod_power.h | 1 + .../gpu/drm/amd/display/modules/power/power.c | 2 + .../drm/amd/display/modules/power/power_abm.c | 55 +++++++++++++++---- .../amd/display/modules/power/power_helpers.h | 14 +++++ 9 files changed, 107 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index ec483276d753..4e730527be4a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -425,7 +425,9 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) !(amdgpu_dc_feature_mask & DC_DISABLE_FRACTIONAL_PWM_MASK); init_data[i].use_custom_backlight_caps = false; init_data[i].custom_backlight_caps_config_no = 0; - init_data[i].use_linear_backlight_curve = false; + init_data[i].use_linear_backlight_curve = + !!(amdgpu_dc_debug_mask & + DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE); init_data[i].def_varibright_enable = 0; init_data[i].varibright_level = 0; /* @@ -436,6 +438,8 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) dm->backlight_caps[i].min_input_signal * 0x101; init_data[i].max_backlight_pwm = dm->backlight_caps[i].max_input_signal * 0x101; + init_data[i].brightness_mask = + dm->backlight_caps[i].brightness_mask; init_data[i].min_abm_backlight = dm->backlight_caps[i].min_input_signal * 0x101; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 3524931451c8..5969c5fbe480 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -229,7 +229,7 @@ struct amdgpu_dm_backlight_caps { */ bool aux_support; /** - * @brightness_mask: After deriving brightness, OR it with this mask. + * @brightness_mask: OR this with the final source backlight value. * Workaround for panels with issues with certain brightness values. */ u32 brightness_mask; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index 424b33573a73..adeb4a7ab7c9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -218,6 +218,39 @@ u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, } EXPORT_IF_KUNIT(convert_brightness_to_user); +static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps, + u32 brightness) +{ + unsigned int min, max; + + if (!get_brightness_range(caps, &min, &max) || max <= min) + return 0; + + if (brightness >= max) + return 100 * 1000; + + return DIV_ROUND_CLOSEST_ULL((u64)brightness * 100 * 1000, max); +} + +STATIC_IFN_KUNIT +u32 convert_brightness_for_power_module(const struct amdgpu_dm_backlight_caps *caps, + u32 user_brightness) +{ + u32 brightness; + + if (!caps) + return user_brightness; + + if (!caps->aux_support) + return convert_brightness_to_millipercent(caps, user_brightness); + + brightness = convert_brightness_from_user(caps, user_brightness); + + return brightness; +} + +EXPORT_IF_KUNIT(convert_brightness_for_power_module); + STATIC_IFN_KUNIT struct dc_stream_state *dm_find_stream_with_link( struct amdgpu_display_manager *dm, @@ -262,7 +295,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, bool rc = false, reallow_idle = false; struct drm_connector *connector; struct dc_stream_state *stream; - unsigned int min, max; list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); @@ -285,12 +317,9 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, /* update scratch register */ if (bl_idx == 0) amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); - brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); link = (struct dc_link *)dm->backlight_link[bl_idx]; - - /* Apply brightness quirk */ - if (caps->brightness_mask) - brightness |= caps->brightness_mask; + brightness = convert_brightness_for_power_module(caps, + dm->brightness[bl_idx]); if (trace_amdgpu_dm_brightness_enabled()) { trace_amdgpu_dm_brightness(__builtin_return_address(0), @@ -314,9 +343,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, rc = mod_power_set_backlight_nits(dm->power_module, stream, brightness, AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true); } else { - /* power module uses millipercent */ - get_brightness_range(caps, &min, &max); - brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; rc = mod_power_set_backlight_percent(dm->power_module, stream, brightness, 0, false); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h index 90bed0ea5d00..396e7654e299 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.h @@ -72,6 +72,8 @@ u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *caps, uint32_t brightness); u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps, uint32_t brightness); +u32 convert_brightness_for_power_module(const struct amdgpu_dm_backlight_caps *caps, + u32 user_brightness); int amdgpu_dm_backlight_get_device_index(struct amdgpu_display_manager *dm, struct backlight_device *bd); void amdgpu_dm_backlight_fill_props(const struct amdgpu_dm_backlight_caps *caps, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h index f33a2c1e0da5..5e7782f9e89f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_trace.h @@ -744,10 +744,11 @@ TRACE_EVENT(amdgpu_dm_brightness, __entry->aux = aux; __entry->ac = ac; ), - TP_printk("%ps: brightness requested=%u converted=%u aux=%s power=%s", + TP_printk("%ps: brightness requested=%u converted=%u unit=%s aux=%s power=%s", (void *)__entry->function, (u32)__entry->user_brightness, (u32)__entry->converted_brightness, + (__entry->aux) ? "millinits" : "millipercent", (__entry->aux) ? "true" : "false", (__entry->ac) ? "AC" : "DC" ) diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h index 02bee3b1956d..56b41861e164 100644 --- a/drivers/gpu/drm/amd/display/modules/inc/mod_power.h +++ b/drivers/gpu/drm/amd/display/modules/inc/mod_power.h @@ -19,6 +19,7 @@ struct mod_power_init_params { unsigned int min_backlight_pwm; unsigned int max_backlight_pwm; + unsigned int brightness_mask; unsigned int min_abm_backlight; unsigned int num_backlight_levels; diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index ee15c14a899e..1d5e94893e36 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -157,6 +157,8 @@ struct mod_power *mod_power_create(struct dc *dc, init_params[inst].use_custom_backlight_caps; core_power->bl_prop[inst].custom_backlight_caps_config_no = init_params[inst].custom_backlight_caps_config_no; + core_power->bl_prop[inst].brightness_mask = + init_params[inst].brightness_mask; // Do not allow less than 101 backlight levels if (init_params[inst].num_backlight_levels < 101) diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c index 5e86889eaa84..0e00042bced4 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c @@ -3,6 +3,7 @@ // Copyright 2026 Advanced Micro Devices, Inc. #include "dm_services.h" +#include "dm_helpers.h" #include "dc.h" #include "mod_power.h" #include "core_types.h" @@ -266,6 +267,16 @@ void mod_power_set_backlight_control_type(struct core_power *core_power, core_power->bl_prop[inst].backlight_control_type = backlight_control_type; } +STATIC_IFN_KUNIT +unsigned int backlight_apply_source_mask(struct core_power *core_power, + unsigned int backlight, + unsigned int inst) +{ + return backlight | core_power->bl_prop[inst].brightness_mask; +} + +EXPORT_IF_KUNIT(backlight_apply_source_mask); + /* Returns true when the panel uses the VESA AUX backlight control path, which * requires zero-anchored linear brightness interpolation. */ @@ -529,7 +540,7 @@ static unsigned int backlight_millipercent_to_pwm_legacy( return 0; // Bypass the brightness mapping LUT - if (core_power->bl_prop->use_linear_backlight_curve) { + if (core_power->bl_prop[inst].use_linear_backlight_curve) { pwm = core_power->bl_prop[inst].min_backlight_pwm + (unsigned int) div_u64((unsigned long long) millipercent * core_power->bl_prop[inst].backlight_range, @@ -615,8 +626,10 @@ static unsigned int backlight_millinit_to_pwm_legacy( * 0 nits = 0 PWM and max_brightness_millinits = max_backlight_pwm. * Otherwise, falls back to the legacy min→max nits range mapping. */ -static unsigned int backlight_millinit_to_pwm( - struct core_power *core_power, unsigned int millinit, unsigned int inst) +STATIC_IFN_KUNIT +unsigned int backlight_millinit_to_pwm(struct core_power *core_power, + unsigned int millinit, + unsigned int inst) { if (!is_vesa_abc(core_power, inst)) return backlight_millinit_to_pwm_legacy(core_power, millinit, inst); @@ -639,6 +652,8 @@ static unsigned int backlight_millinit_to_pwm( core_power->bl_prop[inst].max_brightness_millinits); } +EXPORT_IF_KUNIT(backlight_millinit_to_pwm); + static bool validate_ext_backlight_caps( struct dm_acpi_atif_backlight_caps *ext_backlight_caps) { @@ -894,6 +909,7 @@ void mod_power_update_backlight_on_mode_change( bool is_hdr) { struct set_backlight_level_params backlight_level_params = { 0 }; + unsigned int backlight_millinit; /* Cache the panel's backlight control type once at mode-change/init * time. It is a stable per-panel property (decided in the OS shim @@ -905,9 +921,15 @@ void mod_power_update_backlight_on_mode_change( if ((link->dpcd_sink_ext_caps.bits.hdr_aux_backlight_control == 1 || link->dpcd_sink_ext_caps.bits.sdr_aux_backlight_control == 1) && - link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX) + link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX) { + backlight_millinit = + core_power->bl_state[panel_inst].backlight_millinit; + backlight_millinit = + backlight_apply_source_mask(core_power, backlight_millinit, + panel_inst); dc_link_set_backlight_level_nits(link, core_power->bl_state[panel_inst].isHDR, - core_power->bl_state[panel_inst].backlight_millinit, 0); + backlight_millinit, 0); + } backlight_level_params.frame_ramp = 0; @@ -918,11 +940,12 @@ void mod_power_update_backlight_on_mode_change( dc_link_set_backlight_level(link, &backlight_level_params); } -static bool set_backlight_millinits_aux(struct core_power *core_power, - struct dc_stream_state *stream, - unsigned int backlight_millinits, - unsigned int transition_time_millisec, - unsigned int inst) +STATIC_IFN_KUNIT +bool set_backlight_millinits_aux(struct core_power *core_power, + struct dc_stream_state *stream, + unsigned int backlight_millinits, + unsigned int transition_time_millisec, + unsigned int inst) { struct dc_link *link = NULL; @@ -939,10 +962,17 @@ static bool set_backlight_millinits_aux(struct core_power *core_power, link->dc->caps.dmub_caps.aux_backlight_support) return true; + if (link->backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX) + backlight_millinits = + backlight_apply_source_mask(core_power, backlight_millinits, + inst); + return dc_link_set_backlight_level_nits(link, core_power->bl_state[inst].isHDR, backlight_millinits, transition_time_millisec); } +EXPORT_IF_KUNIT(set_backlight_millinits_aux); + static bool set_backlight(struct core_power *core_power, struct dc_stream_state *stream, struct set_backlight_level_params *backlight_level_params, @@ -1020,6 +1050,11 @@ void fill_backlight_level_params(struct core_power *core_power, if (backlight_control_type == BACKLIGHT_CONTROL_AMD_AUX && !is_hdr) backlight_level_params->control_type = BACKLIGHT_CONTROL_PWM; + + if (backlight_level_params->control_type == BACKLIGHT_CONTROL_PWM) + backlight_level_params->backlight_pwm_u16_16 = + backlight_apply_source_mask(core_power, backlight_pwm, + panel_inst); } bool mod_power_set_backlight_nits(struct mod_power *mod_power, diff --git a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h index 68679fa10946..d4d8ebee1cde 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_helpers.h +++ b/drivers/gpu/drm/amd/display/modules/power/power_helpers.h @@ -94,6 +94,7 @@ struct pwr_backlight_properties { unsigned int min_backlight_pwm; unsigned int max_backlight_pwm; unsigned int backlight_range; + unsigned int brightness_mask; /* Describes the panel's min and max luminance in millinits measured * on full white screen, in min and max backlight settings. @@ -203,6 +204,19 @@ unsigned int backlight_millipercent_to_pwm( struct core_power *core_power, unsigned int millipercent, unsigned int inst); unsigned int backlight_millipercent_to_millinit( struct core_power *core_power, unsigned int millipercent, unsigned int inst); +#if IS_ENABLED(CONFIG_DRM_AMD_DC_KUNIT_TEST) +unsigned int backlight_millinit_to_pwm(struct core_power *core_power, + unsigned int millinit, + unsigned int inst); +unsigned int backlight_apply_source_mask(struct core_power *core_power, + unsigned int backlight, + unsigned int inst); +bool set_backlight_millinits_aux(struct core_power *core_power, + struct dc_stream_state *stream, + unsigned int backlight_millinits, + unsigned int transition_time_millisec, + unsigned int inst); +#endif void fill_backlight_level_params(struct core_power *core_power, struct set_backlight_level_params *backlight_level_params, int panel_inst, uint8_t aux_inst, unsigned int backlight_pwm, -- 2.54.0
