From 25c4e6feb856075e7bcc37f03b28936a9aa954fb Mon Sep 17 00:00:00 2001 From: Aric Cyr Date: Thu, 1 Oct 2026 10:31:47 -0400 Subject: [PATCH] drm/amd/display: Invalidate the DRR shadow after a hardware re-init [Why] dc_stream_adjust_vmin_vmax() skips hardware programming when stream->adjust already matches the request and timing_adjust_pending is clear. That treats stream->adjust as a faithful shadow of the OTG, but dc_set_power_state(D0) calls init_hw(), which returns OTG_V_TOTAL_MIN/MAX and the static screen triggers to their reset defaults without touching any stream. After an S3/S4 resume or a GPU reset the shadow claims hardware is current when it is not. A resume that reuses the same stream object also short-circuits dc_commit_streams() on !streams_changed(), so enable_stream_timing() never runs either and nothing reprograms DRR. Replay then fails to arm: the static screen trigger is at its reset value and the coasting vtotal no longer matches the OTG. The max_v_total overflow path has a second problem. When OTG v_count halt is not allowed it stores the new values and marks the adjustment pending even though nothing was programmed, and no later call with the same values can clear that flag. [How] Add invalidate_drr_adjust() to mark every stream in current_state as having a pending timing adjustment, and call it once hardware has been re-initialized in dc_set_power_state(), so the next dc_stream_adjust_vmin_vmax() reprograms the OTG. Hoist the max_v_total check above the point where stream->adjust is updated and return early when v_count halt is not allowed, leaving the shadow describing the OTG instead of latching a flag nothing can clear. On the paths that do update stream->adjust, set the flag up front and clear it only after set_long_vtotal() or set_drr() has actually run. Reviewed-by: Josip Pavic Signed-off-by: Aric Cyr Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler --- drivers/gpu/drm/amd/display/dc/core/dc.c | 43 +++++++++++++++++++----- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index ef876a24afc25..05e8fcc710e32 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -446,6 +446,21 @@ static bool set_long_vtotal(struct dc *dc, struct dc_stream_state *stream, struc return false; } +/* A hardware re-init resets the OTG DRR registers while stream->adjust keeps the + * pre-reset values, so those values represent has to be marked stale. + */ +static void invalidate_drr_adjust(struct dc *dc) +{ + int i; + + if (!dc->current_state) + return; + + for (i = 0; i < dc->current_state->stream_count; i++) + if (dc->current_state->streams[i]) + dc->current_state->streams[i]->adjust.timing_adjust_pending = true; +} + /** * dc_stream_get_last_used_drr_vtotal - Looks up the pipe context of * dc_stream_state and gets the last VTOTAL used by DRR (Dynamic Refresh Rate) @@ -1343,6 +1358,9 @@ bool dc_stream_adjust_vmin_vmax(struct dc *dc, { int i; bool drr_unchanged; + bool exceeds_max_v_total = dc->caps.max_v_total != 0 && + (adjust->v_total_max > dc->caps.max_v_total || + adjust->v_total_min > dc->caps.max_v_total); /* * Don't adjust DRR while there's bandwidth optimizations pending to @@ -1370,6 +1388,12 @@ bool dc_stream_adjust_vmin_vmax(struct dc *dc, if (drr_unchanged) return true; + /* leave stream->adjust describing the OTG rather than latching + * a pending adjustment that no later call can ever clear. + */ + if (exceeds_max_v_total && !adjust->allow_otg_v_count_halt) + return false; + dc_exit_ips_for_hw_access(dc); stream->adjust.v_total_max = adjust->v_total_max; @@ -1377,16 +1401,15 @@ bool dc_stream_adjust_vmin_vmax(struct dc *dc, stream->adjust.v_total_mid_frame_num = adjust->v_total_mid_frame_num; stream->adjust.v_total_min = adjust->v_total_min; stream->adjust.allow_otg_v_count_halt = adjust->allow_otg_v_count_halt; + /* Only cleared once the new values have actually reached an OTG. */ + stream->adjust.timing_adjust_pending = true; - if (dc->caps.max_v_total != 0 && - (adjust->v_total_max > dc->caps.max_v_total || adjust->v_total_min > dc->caps.max_v_total)) { - if (adjust->allow_otg_v_count_halt) { - stream->adjust.timing_adjust_pending = false; - return set_long_vtotal(dc, stream, adjust); - } - /* Nothing reached hw, so stream->adjust no longer describes the OTG. */ - stream->adjust.timing_adjust_pending = true; - return false; + if (exceeds_max_v_total) { + if (!set_long_vtotal(dc, stream, adjust)) + return false; + + stream->adjust.timing_adjust_pending = false; + return true; } for (i = 0; i < MAX_PIPES; i++) { @@ -6310,6 +6333,8 @@ void dc_set_power_state(struct dc *dc, enum dc_acpi_cm_power_state power_state) dc->hwss.init_hw(dc); + invalidate_drr_adjust(dc); + if (dc->hwss.init_sys_ctx != NULL && dc->vm_pa_config.valid) { dc->hwss.init_sys_ctx(dc->hwseq, dc, &dc->vm_pa_config); -- GitLab