drm/msm/dpu: Reorder encoder kickoff for CWB

Add a helper that will handle the correct order of the encoder kickoffs
for concurrent writeback.

For concurrent writeback, the realtime encoder must always kickoff last
as it will call the trigger flush and start.

This avoids the following scenario where the writeback encoder
increments the pending kickoff count after the WB_DONE interrupt is
fired:

If the realtime encoder is kicked off first, the encoder kickoff will
flush/start the encoder and increment the pending kickoff count. The
WB_DONE interrupt then fires (before the writeback encoder is kicked
off). When the writeback encoder enters its kickoff, it will skip the
flush/start (due to CWB being enabled) and hit a frame done timeout
as the frame was kicked off (and the WB_DONE interrupt fired) without
the pending kickoff count being incremented.

In addition, the writeback timer should only start after the realtime
encoder is kicked off to ensure that we don't get timeouts when the
system has a heavy load (ex. when debug logs are enabled)

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
Signed-off-by: Jessica Zhang <quic_jesszhan@quicinc.com>
Patchwork: https://patchwork.freedesktop.org/patch/637491/
Link: https://lore.kernel.org/r/20250214-concurrent-wb-v6-13-a44c293cf422@quicinc.com
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
This commit is contained in:
Jessica Zhang 2025-02-14 16:14:36 -08:00 committed by Dmitry Baryshkov
parent 8144d17a81
commit ad06972d53

View file

@ -953,6 +953,45 @@ static int _dpu_crtc_wait_for_frame_done(struct drm_crtc *crtc)
return rc;
}
static int dpu_crtc_kickoff_clone_mode(struct drm_crtc *crtc)
{
struct drm_encoder *encoder;
struct drm_encoder *rt_encoder = NULL, *wb_encoder;
struct dpu_kms *dpu_kms = _dpu_crtc_get_kms(crtc);
/* Find encoder for real time display */
drm_for_each_encoder_mask(encoder, crtc->dev,
crtc->state->encoder_mask) {
if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
wb_encoder = encoder;
else
rt_encoder = encoder;
}
if (!rt_encoder || !wb_encoder) {
DRM_DEBUG_ATOMIC("real time or wb encoder not found\n");
return -EINVAL;
}
dpu_encoder_prepare_for_kickoff(wb_encoder);
dpu_encoder_prepare_for_kickoff(rt_encoder);
dpu_vbif_clear_errors(dpu_kms);
/*
* Kickoff real time encoder last as it's the encoder that
* will do the flush
*/
dpu_encoder_kickoff(wb_encoder);
dpu_encoder_kickoff(rt_encoder);
/* Don't start frame done timers until the kickoffs have finished */
dpu_encoder_start_frame_done_timer(wb_encoder);
dpu_encoder_start_frame_done_timer(rt_encoder);
return 0;
}
/**
* dpu_crtc_commit_kickoff - trigger kickoff of the commit for this crtc
* @crtc: Pointer to drm crtc object
@ -981,13 +1020,27 @@ void dpu_crtc_commit_kickoff(struct drm_crtc *crtc)
goto end;
}
}
/*
* Encoder will flush/start now, unless it has a tx pending. If so, it
* may delay and flush at an irq event (e.g. ppdone)
*/
drm_for_each_encoder_mask(encoder, crtc->dev,
crtc->state->encoder_mask)
dpu_encoder_prepare_for_kickoff(encoder);
if (drm_crtc_in_clone_mode(crtc->state)) {
if (dpu_crtc_kickoff_clone_mode(crtc))
goto end;
} else {
/*
* Encoder will flush/start now, unless it has a tx pending.
* If so, it may delay and flush at an irq event (e.g. ppdone)
*/
drm_for_each_encoder_mask(encoder, crtc->dev,
crtc->state->encoder_mask)
dpu_encoder_prepare_for_kickoff(encoder);
dpu_vbif_clear_errors(dpu_kms);
drm_for_each_encoder_mask(encoder, crtc->dev,
crtc->state->encoder_mask) {
dpu_encoder_kickoff(encoder);
dpu_encoder_start_frame_done_timer(encoder);
}
}
if (atomic_inc_return(&dpu_crtc->frame_pending) == 1) {
/* acquire bandwidth and other resources */
@ -997,13 +1050,6 @@ void dpu_crtc_commit_kickoff(struct drm_crtc *crtc)
dpu_crtc->play_count++;
dpu_vbif_clear_errors(dpu_kms);
drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) {
dpu_encoder_kickoff(encoder);
dpu_encoder_start_frame_done_timer(encoder);
}
reinit_completion(&dpu_crtc->frame_done_comp);
end: