Re: [PATCH v9 6/7] drm/i915/tgl: switch between dc3co and dc5 based on display idleness

Imre Deak
2019-09-27T16:19:17Z

On Thu, Sep 26, 2019 at 08:26:20PM +0530, Anshuman Gupta wrote:
> DC3CO is useful power state, when DMC detects PSR2 idle frame
> while an active video playback, playing 30fps video on 60hz panel
> is the classic example of this use case.
>
> B.Specs:49196 has a restriction to enable DC3CO only for Video Playback.
> It will be worthy to enable DC3CO after completion of each pageflip
> and switch back to DC5 when display is idle because driver doesn't
> differentiate between video playback and a normal pageflip.
> We will use Frontbuffer flush call tgl_dc3co_flush() to enable DC3CO
> state only for ORIGIN_FLIP flush call, because DC3CO state has primarily
> targeted for VPB use case. We are not interested here for frontbuffer
> invalidates calls because that triggers PSR2 exit, which will
> explicitly disable DC3CO.
>
> DC5 and DC6 saves more power, but can't be entered during video
> playback because there are not enough idle frames in a row to meet
> most PSR2 panel deep sleep entry requirement typically 4 frames.
> As PSR2 existing implementation is using minimum 6 idle frames for
> deep sleep, it is safer to enable DC5/6 after 6 idle frames
> (By scheduling a delayed work of 6 idle frames, once DC3CO has been
> enabled after a pageflip).
>
> After manually waiting for 6 idle frames DC5/6 will be enabled and
> PSR2 deep sleep idle frames will be restored to 6 idle frames, at this
> point DMC will triggers DC5/6 once PSR2 enters to deep sleep after
> 6 idle frames.
> In future when we will enable S/W PSR2 tracking, we can change the
> PSR2 required deep sleep idle frames to 1 so DMC can trigger the
> DC5/6 immediately after S/W manual waiting of 6 idle frames get
> complete.
>
> v2: calculated s/w state to switch over dc3co when there is an
> update. [Imre]
> Used cancel_delayed_work_sync() in order to avoid any race
> with already scheduled delayed work. [Imre]
> v3: Cancel_delayed_work_sync() may blocked the commit work.
> hence dropping it, dc5_idle_thread() checks the valid wakeref before
> putting the reference count, which avoids any chances of dropping
> a zero wakeref. [Imre (IRC)]
> v4: Used frontbuffer flush mechanism. [Imre]
> v5: Used psr.pipe to extract frontbuffer busy bits. [Imre]
> Used cancel_delayed_work_sync() in encoder disable path. [Imre]
> Used mod_delayed_work() instead of cancelling and scheduling a
> delayed work. [Imre]
> Used psr.lock in tgl_dc5_idle_thread() to enable psr2 deep
> sleep. [Imre]
> Removed DC5_REQ_IDLE_FRAMES macro. [Imre]
> v6: Inited the busy_frontbuffer_bits, used dc3co_exitline check instead
> of TGL and dc3co allowed_dc_mask checks, used delayed_work_pending
> with the psr lock and removed the psr2_deep_slp_disabled flag. [Imre]
>
> Cc: Jani Nikula <jani.nikula@intel.com>
> Cc: Imre Deak <imre.deak@intel.com>
> Cc: Animesh Manna <animesh.manna@intel.com>
> Signed-off-by: Anshuman Gupta <anshuman.gupta@intel.com>
> ---
> .../drm/i915/display/intel_display_power.c | 84 +++++++++++++++++++
> .../drm/i915/display/intel_display_power.h | 4 +
> .../gpu/drm/i915/display/intel_frontbuffer.c | 1 +
> drivers/gpu/drm/i915/display/intel_psr.c | 34 ++++++++
> drivers/gpu/drm/i915/display/intel_psr.h | 2 +
> drivers/gpu/drm/i915/i915_drv.h | 1 +
> 6 files changed, 126 insertions(+)
>
> diff --git a/drivers/gpu/drm/i915/display/intel_display_power.c b/drivers/gpu/drm/i915/display/intel_display_power.c
> index 84e4cfd95b43..00bb82e6a18f 100644
> --- a/drivers/gpu/drm/i915/display/intel_display_power.c
> +++ b/drivers/gpu/drm/i915/display/intel_display_power.c
> @@ -20,6 +20,7 @@
> #include "intel_sideband.h"
> #include "intel_tc.h"
> #include "intel_pm.h"
> +#include "intel_psr.h"
>
> bool intel_display_power_well_is_enabled(struct drm_i915_private *dev_priv,
> enum i915_power_well_id power_well_id);
> @@ -797,6 +798,9 @@ void tgl_clear_psr2_transcoder_exitline(const struct intel_crtc_state *cstate)
> val = I915_READ(EXITLINE(cstate->cpu_transcoder));
> val &= ~(EXITLINE_MASK | EXITLINE_ENABLE);
> I915_WRITE(EXITLINE(cstate->cpu_transcoder), val);
> +
> + /* As psr2 encoder has disabled, cancel the dc5 idle delayed work */
> + cancel_delayed_work_sync(&dev_priv->csr.idle_work);

Let's move this to intel_psr_disable_locked(), for symmetry with the
cancel_work in intel_psr_exit().

> }
>
> void tgl_set_psr2_transcoder_exitline(const struct intel_crtc_state *cstate)
> @@ -816,6 +820,19 @@ void tgl_set_psr2_transcoder_exitline(const struct intel_crtc_state *cstate)
> I915_WRITE(EXITLINE(cstate->cpu_transcoder), val);
> }
>
> +static u32 intel_get_frame_time_us(const struct intel_crtc_state *cstate)
> +{
> + u32 frametime_us;
> +
> + if (!cstate || !cstate->base.active)
> + return 0;
> +
> + frametime_us = DIV_ROUND_UP(1000*1000,
> + drm_mode_vrefresh(&cstate->base.adjusted_mode));

Just return the value directly, and leave spaces around operators (see
Documentation/process/coding-style.rst 3.1). Let's move the helper where
it'll be used (intel_ddi.c).

> +
> + return frametime_us;
> +}
> +
> /*
> * DC3CO requires to enable exitline and program DC3CO requires
> * exit scanlines to TRANS_EXITLINE register, which should only
> @@ -872,6 +889,72 @@ void tgl_dc3co_exitline_get_config(struct intel_crtc_state *crtc_state)
> crtc_state->dc3co_exitline = val & EXITLINE_MASK;
> }
>
> +/*
> + * When we will enable manual PSR2 S/W tracking in future
> + * we will implement this entire DC3CO flush logic in
> + * intel_psr_flush().
> + */
> +void tgl_dc3co_flush(struct drm_i915_private *dev_priv,
> + unsigned int frontbuffer_bits, enum fb_op_origin origin)
> +{
> + struct intel_crtc_state *cstate;
> + struct intel_crtc *crtc;
> + u32 delay;
> +
> + if (!CAN_PSR(dev_priv))
> + return;
> +
> + if (origin != ORIGIN_FLIP)
> + return;
> +
> + mutex_lock(&dev_priv->psr.lock);
> +
> + if (!dev_priv->psr.dc3co_exitline)
> + goto unlock;
> +
> + crtc = intel_get_crtc_for_pipe(dev_priv, dev_priv->psr.pipe);
> + cstate = to_intel_crtc_state(crtc->base.state);

Using crtc->state is verboten in general (except for the atomic code
that uses it as a starting point of a new commit). So let's compute the
frame time too in the compute hook along with exitline and store it in
dev->psr.

> +
> + if (!dev_priv->psr.psr2_enabled || !dev_priv->psr.active)
> + goto unlock;
> +
> + /*
> + * At every flip frontbuffer flush modified delay of delayed work,
> + * when delayed schedules that means display has been idle.
> + */

We only want to act on the flip event if it happened on psr.pipe. So

if (!(frontbuffer_bits &
INTEL_FRONTBUFFER_ALL_MASK(dev_priv->psr.pipe)))
goto unlock;

> + tgl_psr2_deep_sleep_disable(dev_priv);
> + tgl_set_target_dc_state(dev_priv, DC_STATE_EN_DC3CO);
> + /* DC5/DC6 required idle frames = 6 */
> + delay = 6 * intel_get_frame_time_us(cstate);
> + mod_delayed_work(system_wq, &dev_priv->csr.idle_work,
> + usecs_to_jiffies(delay));
> +
> +unlock:
> + mutex_unlock(&dev_priv->psr.lock);
> +}
> +
> +void tgl_enable_psr2_deep_sleep_dc6(struct drm_i915_private *dev_priv)
> +{
> + tgl_set_target_dc_state(dev_priv, DC_STATE_EN_UPTO_DC6);
> + tgl_psr2_deep_sleep_enable(dev_priv);
> +}
> +
> +static void tgl_dc5_idle_thread(struct work_struct *work)
> +{
> + struct drm_i915_private *dev_priv =
> + container_of(work, typeof(*dev_priv), csr.idle_work.work);
> +
> + mutex_lock(&dev_priv->psr.lock);
> + /* If delayed work is pending, it is not idle */
> + if (delayed_work_pending(&dev_priv->csr.idle_work))
> + goto unlock;
> +
> + DRM_DEBUG_KMS("DC5/6 idle thread\n");
> + tgl_enable_psr2_deep_sleep_dc6(dev_priv);
> +unlock:
> + mutex_unlock(&dev_priv->psr.lock);
> +}
> +
> static void
> allowed_dc_mask_to_target_dc_state(struct drm_i915_private *dev_priv)
> {
> @@ -4241,6 +4324,7 @@ int intel_power_domains_init(struct drm_i915_private *dev_priv)
>
> INIT_DELAYED_WORK(&power_domains->async_put_work,
> intel_display_power_put_async_work);
> + INIT_DELAYED_WORK(&dev_priv->csr.idle_work, tgl_dc5_idle_thread);

Let's move csr.idle_work to psr.idle_work and all the above
funcs/changes you added now in intel_display_power.c to intel_psr.c, as
all of it is psr specific.

>
> /*
> * The enabling order will be from lower to higher indexed wells,
> diff --git a/drivers/gpu/drm/i915/display/intel_display_power.h b/drivers/gpu/drm/i915/display/intel_display_power.h
> index 4e76f93bbee5..0ebdeab26d8e 100644
> --- a/drivers/gpu/drm/i915/display/intel_display_power.h
> +++ b/drivers/gpu/drm/i915/display/intel_display_power.h
> @@ -10,6 +10,7 @@
> #include "intel_runtime_pm.h"
> #include "intel_sprite.h"
> #include "i915_reg.h"
> +#include "intel_frontbuffer.h"
>
> struct drm_i915_private;
> struct intel_encoder;
> @@ -266,6 +267,9 @@ void tgl_dc3co_exitline_compute_config(struct intel_encoder *encoder,
> void tgl_dc3co_exitline_get_config(struct intel_crtc_state *crtc_state);
> void tgl_clear_psr2_transcoder_exitline(const struct intel_crtc_state *state);
> void tgl_set_psr2_transcoder_exitline(const struct intel_crtc_state *state);
> +void tgl_dc3co_flush(struct drm_i915_private *dev_priv,
> + unsigned int frontbuffer_bits, enum fb_op_origin origin);
> +void tgl_enable_psr2_deep_sleep_dc6(struct drm_i915_private *dev_priv);
>
> const char *
> intel_display_power_domain_str(enum intel_display_power_domain domain);
> diff --git a/drivers/gpu/drm/i915/display/intel_frontbuffer.c b/drivers/gpu/drm/i915/display/intel_frontbuffer.c
> index fc40dc1fdbcc..c3b10f6e4382 100644
> --- a/drivers/gpu/drm/i915/display/intel_frontbuffer.c
> +++ b/drivers/gpu/drm/i915/display/intel_frontbuffer.c
> @@ -90,6 +90,7 @@ static void frontbuffer_flush(struct drm_i915_private *i915,
> might_sleep();
> intel_edp_drrs_flush(i915, frontbuffer_bits);
> intel_psr_flush(i915, frontbuffer_bits, origin);
> + tgl_dc3co_flush(i915, frontbuffer_bits, origin);

Let's call the above from intel_psr_flush() instead of having to export
it.

> intel_fbc_flush(i915, frontbuffer_bits, origin);
> }
>
> diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c
> index bf0b741d3243..5faaf35ba4ff 100644
> --- a/drivers/gpu/drm/i915/display/intel_psr.c
> +++ b/drivers/gpu/drm/i915/display/intel_psr.c
> @@ -534,10 +534,44 @@ transcoder_has_psr2(struct drm_i915_private *dev_priv, enum transcoder trans)
> return trans == TRANSCODER_EDP;
> }
>
> +static void psr2_program_idle_frames(struct drm_i915_private *dev_priv,
> + u32 idle_frames)
> +{
> + u32 val;
> +
> + idle_frames <<= EDP_PSR2_IDLE_FRAME_SHIFT;
> + val = I915_READ(EDP_PSR2_CTL(dev_priv->psr.transcoder));
> + val &= ~EDP_PSR2_IDLE_FRAME_MASK;
> + val |= idle_frames;
> + I915_WRITE(EDP_PSR2_CTL(dev_priv->psr.transcoder), val);
> +}
> +
> +void tgl_psr2_deep_sleep_disable(struct drm_i915_private *dev_priv)
> +{
> + psr2_program_idle_frames(dev_priv, 0);
> +}
> +
> +void tgl_psr2_deep_sleep_enable(struct drm_i915_private *dev_priv)
> +{
> + int idle_frames;
> +
> + /*
> + * Let's use 6 as the minimum to cover all known cases including the
> + * off-by-one issue that HW has in some cases.
> + */
> + idle_frames = max(6, dev_priv->vbt.psr.idle_frames);
> + idle_frames = max(idle_frames, dev_priv->psr.sink_sync_latency + 1);
> + psr2_program_idle_frames(dev_priv, idle_frames);
> +}
> +
> static void tgl_disallow_dc3co_on_psr2_exit(struct drm_i915_private *dev_priv)
> {
> if (!dev_priv->psr.dc3co_exitline)
> return;
> +
> + cancel_delayed_work(&dev_priv->csr.idle_work);
> + /* Before PSR2 exit disallow dc3co*/
> + tgl_enable_psr2_deep_sleep_dc6(dev_priv);
> }
>
> static bool intel_psr2_config_valid(struct intel_dp *intel_dp,
> diff --git a/drivers/gpu/drm/i915/display/intel_psr.h b/drivers/gpu/drm/i915/display/intel_psr.h
> index 46e4de8b8cd5..75a9862f36fd 100644
> --- a/drivers/gpu/drm/i915/display/intel_psr.h
> +++ b/drivers/gpu/drm/i915/display/intel_psr.h
> @@ -35,5 +35,7 @@ void intel_psr_short_pulse(struct intel_dp *intel_dp);
> int intel_psr_wait_for_idle(const struct intel_crtc_state *new_crtc_state,
> u32 *out_value);
> bool intel_psr_enabled(struct intel_dp *intel_dp);
> +void tgl_psr2_deep_sleep_disable(struct drm_i915_private *dev_priv);
> +void tgl_psr2_deep_sleep_enable(struct drm_i915_private *dev_priv);
>
> #endif /* __INTEL_PSR_H__ */
> diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
> index c3aba078262f..ed3dd4757f72 100644
> --- a/drivers/gpu/drm/i915/i915_drv.h
> +++ b/drivers/gpu/drm/i915/i915_drv.h
> @@ -340,6 +340,7 @@ struct intel_csr {
> u32 dc_state;
> u32 target_dc_state;
> u32 allowed_dc_mask;
> + struct delayed_work idle_work;
> intel_wakeref_t wakeref;
> };
>
> --
> 2.21.0
>