From: Leo Li <[email protected]> [Why]
Newer generations of Display Core Next (DCN) hardware allow for the entire DCN block to be power-gated in a feature called Idle Power States (IPS). Once DCN is in IPS, HW register access will timeout. Therefore, allowing/disallowing IPS requires synchronization with the rest of the driver through the big dc_lock mutex. It happens that writing vblank interrupt control and reading counter/scanout position registers requires IPS disallow. Since that requires the dc_lock, it turns into a blocking operation. The immediately obvious (not)solution is to disable IPS. But since it provides sizable power savings, and it's not going away for future APUs, it's not a good option. The other (non)solution is to get rid of the dc_lock or make it not block. But since all hardware (HW) and Display Micro-controller (DMCU) access is invalid when DCN is in IPS (because everything is gated), it needs to be synchronized with all other HW/DMCU access. It happens that the dc_lock is responsible for that. I don't think replacing it with a spinlock is a valid solution either. Consider link training: IPS cannot be allowed while that is happening, yet the timescale of link training can be in the 100ms. A spinlock would spin too long here. So let's implement the newly introduced deferred vblank enable/disable callbacks in DRM vblank core. [How] Implement the deferred CRTC callbacks to request the DRM vblank core to defer vblank enable/disable. Only implement for ASICs that have IPS support. Otherwise, keep the non-deferred behavior. To ensure interrupts are enabled before programming flips (otherwise the page flip interrupt for event delivery may not fire), call drm_crtc_vblank_wait_deferred_enable() in the commit_planes code when getting a reference on vblanks. Signed-off-by: Leo Li <[email protected]> --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 8 + .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 177 +++++++++++++++++- 2 files changed, 184 insertions(+), 1 deletion(-) 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 a8e4e3ab5e402..0405466666e2f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -10268,6 +10268,14 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, &acrtc_attach->dm_irq_params.vrr_params.adjust); spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); } + + /* + * If vblank is being enabled in worker thread, wait for it to + * enable interrupts before programming pflips, otherwise the + * interrupt may not fire. + */ + drm_crtc_vblank_wait_deferred_enable(pcrtc); + mutex_lock(&dm->dc_lock); update_planes_and_stream_adapter(dm->dc, acrtc_state->update_type, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 3df38f3cb7423..fd84977f55ecd 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -454,6 +454,140 @@ static void amdgpu_dm_crtc_disable_vblank(struct drm_crtc *crtc) acrtc_state, false); } +/* + * Deferred vblank enable/disable works differently: the drm vblank core manages + * the workqueue instead of amdgpu_dm, sandwiching the vblank_enable/disable() + * with the crtc pre/post callbacks. Therefore, they need to be sequenced + * differently from their non-deferred variants. + */ + +static int amdgpu_dm_crtc_deferred_enable_vblank(struct drm_crtc *crtc) +{ + struct amdgpu_device *adev = drm_to_adev(crtc->dev); + struct amdgpu_display_manager *dm = &adev->dm; + int ret; + + ret = amdgpu_dm_crtc_set_vblank(crtc, true); + if (!ret) + dm->active_vblank_irq_count++; + + return ret; +} + +static void amdgpu_dm_crtc_deferred_disable_vblank(struct drm_crtc *crtc) +{ + struct amdgpu_device *adev = drm_to_adev(crtc->dev); + struct amdgpu_display_manager *dm = &adev->dm; + + amdgpu_dm_crtc_set_vblank(crtc, false); + + if (dm->active_vblank_irq_count) + dm->active_vblank_irq_count--; +} + + +static void amdgpu_dm_crtc_pre_enable_vblank(struct drm_crtc *crtc) +{ + struct amdgpu_device *adev = drm_to_adev(crtc->dev); + struct amdgpu_display_manager *dm = &adev->dm; + + mutex_lock(&dm->dc_lock); + dc_allow_idle_optimizations(dm->dc, false); +} + +static void amdgpu_dm_crtc_post_enable_vblank(struct drm_crtc *crtc) +{ + struct amdgpu_device *adev = drm_to_adev(crtc->dev); + struct amdgpu_display_manager *dm = &adev->dm; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); + struct dm_crtc_state *acrtc_state = to_dm_crtc_state(crtc->state); + struct vblank_control_work vblank_work = { 0 }; + + /* If crtc disabled, skip panel optimizations exit */ + if (!crtc->enabled) { + mutex_unlock(&dm->dc_lock); + return; + } + + vblank_work.dm = dm; + vblank_work.acrtc = acrtc; + vblank_work.enable = true; + if (acrtc_state->stream) { + dc_stream_retain(acrtc_state->stream); + vblank_work.stream = acrtc_state->stream; + } + + /* + * Control PSR based on vblank requirements from OS + * + * If panel supports PSR SU, there's no need to disable PSR when OS is + * submitting fast atomic commits (we infer this by whether the OS + * requests vblank events). Fast atomic commits will simply trigger a + * full-frame-update (FFU); a specific case of selective-update (SU) + * where the SU region is the full hactive*vactive region. See + * fill_dc_dirty_rects(). + */ + if (vblank_work.stream && vblank_work.stream->link && vblank_work.acrtc) { + amdgpu_dm_crtc_set_panel_sr_feature( + &vblank_work, true, + vblank_work.acrtc->dm_irq_params.allow_sr_entry); + } + + if (vblank_work.stream) + dc_stream_release(vblank_work.stream); + + mutex_unlock(&dm->dc_lock); +} + +static void amdgpu_dm_crtc_pre_disable_vblank(struct drm_crtc *crtc) +{ + struct amdgpu_device *adev = drm_to_adev(crtc->dev); + struct amdgpu_display_manager *dm = &adev->dm; + + mutex_lock(&dm->dc_lock); +} + +static void amdgpu_dm_crtc_post_disable_vblank(struct drm_crtc *crtc) +{ + struct amdgpu_device *adev = drm_to_adev(crtc->dev); + struct amdgpu_display_manager *dm = &adev->dm; + struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc); + struct dm_crtc_state *acrtc_state = to_dm_crtc_state(crtc->state); + struct vblank_control_work vblank_work = { 0 }; + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); + + /* If a vblank_get got ahead of us (can happen when !disable_immediate), + * skip panel optimizations */ + if (atomic_read(&vblank->refcount) > 0) { + mutex_unlock(&dm->dc_lock); + return; + } + + vblank_work.dm = dm; + vblank_work.acrtc = acrtc; + vblank_work.enable = false; + if (acrtc_state->stream) { + dc_stream_retain(acrtc_state->stream); + vblank_work.stream = acrtc_state->stream; + } + + if (vblank_work.stream && vblank_work.stream->link && vblank_work.acrtc) { + amdgpu_dm_crtc_set_panel_sr_feature( + &vblank_work, false, + vblank_work.acrtc->dm_irq_params.allow_sr_entry); + } + + if (vblank_work.stream) + dc_stream_release(vblank_work.stream); + + if (dm->active_vblank_irq_count == 0) { + dc_post_update_surfaces_to_stream(dm->dc); + dc_allow_idle_optimizations(dm->dc, true); + } + + mutex_unlock(&dm->dc_lock); +} + static void amdgpu_dm_crtc_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state) { @@ -623,6 +757,33 @@ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { #endif }; +static const struct drm_crtc_funcs amdgpu_dm_crtc_deferred_vblank_funcs = { + .reset = amdgpu_dm_crtc_reset_state, + .destroy = amdgpu_dm_crtc_destroy, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = amdgpu_dm_crtc_duplicate_state, + .atomic_destroy_state = amdgpu_dm_crtc_destroy_state, + .set_crc_source = amdgpu_dm_crtc_set_crc_source, + .verify_crc_source = amdgpu_dm_crtc_verify_crc_source, + .get_crc_sources = amdgpu_dm_crtc_get_crc_sources, + .get_vblank_counter = amdgpu_get_vblank_counter_kms, + .enable_vblank = amdgpu_dm_crtc_deferred_enable_vblank, + .disable_vblank = amdgpu_dm_crtc_deferred_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, +#if defined(CONFIG_DEBUG_FS) + .late_register = amdgpu_dm_crtc_late_register, +#endif +#ifdef AMD_PRIVATE_COLOR + .atomic_set_property = amdgpu_dm_atomic_crtc_set_property, + .atomic_get_property = amdgpu_dm_atomic_crtc_get_property, +#endif + .pre_enable_vblank = amdgpu_dm_crtc_pre_enable_vblank, + .post_enable_vblank = amdgpu_dm_crtc_post_enable_vblank, + .pre_disable_vblank = amdgpu_dm_crtc_pre_disable_vblank, + .post_disable_vblank = amdgpu_dm_crtc_post_disable_vblank, +}; + static void amdgpu_dm_crtc_helper_disable(struct drm_crtc *crtc) { } @@ -755,6 +916,7 @@ int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, struct drm_plane *plane, uint32_t crtc_index) { + const struct drm_crtc_funcs *crtc_funcs; struct amdgpu_crtc *acrtc = NULL; struct drm_plane *cursor_plane; bool has_degamma; @@ -771,12 +933,25 @@ int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, if (!acrtc) goto fail; + /* + * Only enable deferred vblank enable/disable for ASICs with IPS + * support + */ + if (dm->dc->caps.ips_support) { + crtc_funcs = &amdgpu_dm_crtc_deferred_vblank_funcs; + drm_dbg_driver(dm->ddev, + "Initializing CRTC %d with deferred vBlank enable/disable\n", + crtc_index); + } else { + crtc_funcs = &amdgpu_dm_crtc_funcs; + } + res = drm_crtc_init_with_planes( dm->ddev, &acrtc->base, plane, cursor_plane, - &amdgpu_dm_crtc_funcs, NULL); + crtc_funcs, NULL); if (res) goto fail; -- 2.52.0
