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 fb19a0781e59..65e7d49a5f93 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 ec5d6ddeefbe..7934341b4715 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -6233,6 +6233,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 a10ee711668e..0987258df40c 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -3012,6 +3012,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 */