From: Tom Chung <[email protected]> [Why] If an external monitor is connected at power-on and then unplugged before the driver loads (e.g. at the GRUB menu), the system can no longer enter the s0i3 deepest suspend state, even though all connectors report disconnected.
The pre-OS firmware (GOP/vBIOS) lights up a front-end for the display that is present at power-on. The driver never fully tears this inherited pipe down, and the leftover pipe keeps the DCN block from reaching idle, which blocks s0i3. [How] Add dc_disable_dangling_timing_generators() to DC core and call it from amdgpu_dm right after dc_hardware_init(). It scans every enabled timing generator and classifies it as in-use or dangling. If a dangling pipe exists and nothing needs to be preserved, power down the hw blocks so DCN can reach idle. Assisted-by: Cursor:claude-opus-4.8 Reviewed-by: Mario Limonciello <[email protected]> Signed-off-by: Tom Chung <[email protected]> Signed-off-by: George Zhang <[email protected]> --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 8 ++ drivers/gpu/drm/amd/display/dc/core/dc.c | 109 ++++++++++++++++++ drivers/gpu/drm/amd/display/dc/dc.h | 2 + 3 files changed, 119 insertions(+) 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 d67dcaa3fa8f..d184cc4de693 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -779,6 +779,14 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) dc_hardware_init(adev->dm.dc); + /* GOP/vBIOS may leave an OPTC enabled for a display present at power-on + * but no longer driven (e.g. an external DP unplugged at boot). Such a + * dangling pipe keeps DCN out of idle and blocks s0i3. Power it down + * here when nothing needs to be preserved. + */ + if (adev->flags & AMD_IS_APU) + dc_disable_dangling_timing_generators(adev->dm.dc); + adev->dm.hpd_rx_offload_wq = amdgpu_dm_hpd_rx_irq_create_workqueue(adev); if (!adev->dm.hpd_rx_offload_wq) { drm_err(adev_to_drm(adev), "failed to create hpd rx offload workqueue.\n"); diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 0b54d84c203d..4a1d63e157af 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -6234,6 +6234,115 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src) dal_irq_service_ack(dc->res_pool->irqs, src); } +/* Preserve this tg if a physical link is still lighting a present display */ +static bool should_preserve_tg(struct dc *dc, struct timing_generator *tg) +{ + unsigned int i, j; + + /* Check if a physical link is lighting this tg */ + for (i = 0; i < dc->link_count; i++) { + struct dc_link *link = dc->links[i]; + int fe; + + if (!link || link->ep_type != DISPLAY_ENDPOINT_PHY || + !link->link_enc || + !link->link_enc->funcs->is_dig_enabled || + !link->link_enc->funcs->is_dig_enabled(link->link_enc) || + !link->link_enc->funcs->get_dig_frontend) + continue; + + /* Get the DIG front-end this link's encoder drives; skip if none */ + fe = link->link_enc->funcs->get_dig_frontend(link->link_enc); + if (fe == ENGINE_ID_UNKNOWN) + continue; + + /* Find the stream encoder bound to this link's front-end */ + for (j = 0; j < dc->res_pool->stream_enc_count; j++) { + struct stream_encoder *se = dc->res_pool->stream_enc[j]; + + /* Skip unless this stream encoder feeds our front-end and drives this tg */ + if (se->id != fe || !se->funcs->dig_source_otg || + (int)se->funcs->dig_source_otg(se) != tg->inst) + continue; + + /* This link drives the OTG: keep a seamless-boot eDP, or + * any external link whose sink is still connected. + */ + if (link->connector_signal == SIGNAL_TYPE_EDP) + return true; + if (link->link_enc->funcs->get_hpd_state && + dc->link_srv->get_hpd_state(link)) + return true; + } + } + + return false; +} + +/* + * GOP/vBIOS may leave an OPTC enabled for a display present at power-on but no + * longer driven (e.g. external DP unplugged at boot). Such a dangling pipe keeps + * DCN out of idle and blocks s0i3. If nothing needs to survive (no committed + * stream or seamless-boot eDP) and no sink is still connected, power down all hw + * blocks. + */ +void dc_disable_dangling_timing_generators(struct dc *dc) +{ + struct dce_hwseq *hws = dc->hwseq; + bool any_dangling = false; + bool any_preserved = false; + bool any_connected = false; + unsigned int i; + + /* No real hw to touch on a virtual/emulated environment */ + if (dc->ctx->dce_environment == DCE_ENV_VIRTUAL_HW) + return; + + /* Wake hw out of IPS before reading/touching tg state */ + dc_exit_ips_for_hw_access(dc); + + /* Classify every enabled tg as either to-preserve or dangling */ + for (i = 0; i < dc->res_pool->timing_generator_count; i++) { + struct timing_generator *tg = dc->res_pool->timing_generators[i]; + + if (!tg || !tg->funcs->is_tg_enabled || + !tg->funcs->is_tg_enabled(tg)) + continue; + + if (should_preserve_tg(dc, tg)) + any_preserved = true; + else + any_dangling = true; + } + + /* A physically connected sink (HPD asserted) will be re-lit by a + * subsequent atomic commit. For that case we don't call the global + * power_down(). + */ + for (i = 0; i < dc->link_count; i++) { + struct dc_link *link = dc->links[i]; + + if (link && link->ep_type == DISPLAY_ENDPOINT_PHY && + link->link_enc && link->link_enc->funcs && + link->link_enc->funcs->get_hpd_state && + dc->link_srv->get_hpd_state(link)) { + any_connected = true; + break; + } + } + + if (!any_dangling) + return; + + if (!any_preserved && !any_connected && hws && hws->funcs.power_down) { + /* Truly headless / all sinks unplugged: nothing to preserve */ + DC_LOG_DC("%s: powering down dangling hw blocks to allow idle\n", + __func__); + hws->funcs.power_down(dc); + return; + } +} + void dc_power_down_on_boot(struct dc *dc) { if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW && diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index c78fdfc0ef4b..572559d403ba 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -3010,6 +3010,8 @@ void dc_resume(struct dc *dc); void dc_power_down_on_boot(struct dc *dc); +void dc_disable_dangling_timing_generators(struct dc *dc); + /* * HDCP Interfaces */ -- 2.55.0
