aboutsummaryrefslogtreecommitdiffstats
path: root/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.14.71/2697-drm-amd-display-Multi-display-synchronization-logic.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.14.71/2697-drm-amd-display-Multi-display-synchronization-logic.patch')
-rw-r--r--meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.14.71/2697-drm-amd-display-Multi-display-synchronization-logic.patch835
1 files changed, 835 insertions, 0 deletions
diff --git a/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.14.71/2697-drm-amd-display-Multi-display-synchronization-logic.patch b/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.14.71/2697-drm-amd-display-Multi-display-synchronization-logic.patch
new file mode 100644
index 00000000..adb778fc
--- /dev/null
+++ b/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.14.71/2697-drm-amd-display-Multi-display-synchronization-logic.patch
@@ -0,0 +1,835 @@
+From a18ea09cef36665ae3c8199c1ccc0300925121d6 Mon Sep 17 00:00:00 2001
+From: Mikita Lipski <mikita.lipski@amd.com>
+Date: Tue, 17 Oct 2017 15:29:22 -0400
+Subject: [PATCH 2697/4131] drm/amd/display: Multi display synchronization
+ logic
+
+This feature synchronizes multiple displays with various timings
+to a display with the highest refresh rate
+it is enabled if edid caps flag multi_display_sync is set to one
+
+There are limitations on refresh rates allowed
+that can be synchronized. That would
+prevent from underflow and other potential
+corruptions.
+
+Multi display synchronization is using the
+same functions as timing_sync in order to minimize
+redunduncy and decision to disable synchronization is
+based on trigger parametre set in DM
+
+Feature is developed for DCN1 and DCE11
+
+Signed-off-by: Mikita Lipski <mikita.lipski@amd.com>
+Reviewed-by: Mikita Lipski <Mikita.Lipski@amd.com>
+Acked-by: Harry Wentland <harry.wentland@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+---
+ drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 54 ++++-
+ drivers/gpu/drm/amd/display/dc/core/dc.c | 26 +-
+ drivers/gpu/drm/amd/display/dc/dc.h | 3 +
+ drivers/gpu/drm/amd/display/dc/dc_hw_types.h | 16 ++
+ .../amd/display/dc/dce110/dce110_hw_sequencer.c | 46 +++-
+ .../display/dc/dce110/dce110_timing_generator.c | 265 +++++++++++++++++----
+ .../display/dc/dce110/dce110_timing_generator.h | 6 +
+ .../drm/amd/display/dc/dcn10/dcn10_hw_sequencer.c | 29 ++-
+ .../amd/display/dc/dcn10/dcn10_timing_generator.c | 66 ++++-
+ .../drm/amd/display/dc/inc/hw/timing_generator.h | 6 +-
+ drivers/gpu/drm/amd/display/dc/inc/hw_sequencer.h | 5 +
+ 11 files changed, 456 insertions(+), 66 deletions(-)
+
+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 95760a2..a1dc201 100644
+--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+@@ -2430,6 +2430,56 @@ static int create_fake_sink(struct amdgpu_dm_connector *aconnector)
+ return 0;
+ }
+
++static void set_multisync_trigger_params(
++ struct dc_stream_state *stream)
++{
++ if (stream->triggered_crtc_reset.enabled) {
++ stream->triggered_crtc_reset.event = CRTC_EVENT_VSYNC_RISING;
++ stream->triggered_crtc_reset.delay = TRIGGER_DELAY_NEXT_LINE;
++ }
++}
++
++static void set_master_stream(struct dc_stream_state *stream_set[],
++ int stream_count)
++{
++ int j, highest_rfr = 0, master_stream = 0;
++
++ for (j = 0; j < stream_count; j++) {
++ if (stream_set[j] && stream_set[j]->triggered_crtc_reset.enabled) {
++ int refresh_rate = 0;
++
++ refresh_rate = (stream_set[j]->timing.pix_clk_khz*1000)/
++ (stream_set[j]->timing.h_total*stream_set[j]->timing.v_total);
++ if (refresh_rate > highest_rfr) {
++ highest_rfr = refresh_rate;
++ master_stream = j;
++ }
++ }
++ }
++ for (j = 0; j < stream_count; j++) {
++ if (stream_set[j] && j != master_stream)
++ stream_set[j]->triggered_crtc_reset.event_source = stream_set[master_stream];
++ }
++}
++
++static void dm_enable_per_frame_crtc_master_sync(struct dc_state *context)
++{
++ int i = 0;
++
++ if (context->stream_count < 2)
++ return;
++ for (i = 0; i < context->stream_count ; i++) {
++ if (!context->streams[i])
++ continue;
++ /* TODO: add a function to read AMD VSDB bits and will set
++ * crtc_sync_master.multi_sync_enabled flag
++ * For now its set to false
++ */
++ set_multisync_trigger_params(context->streams[i]);
++ }
++ set_master_stream(context->streams, context->stream_count);
++}
++
+ static struct dc_stream_state *
+ create_stream_for_sink(struct amdgpu_dm_connector *aconnector,
+ const struct drm_display_mode *drm_mode,
+@@ -4518,8 +4568,10 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
+ }
+ }
+
+- if (dm_state->context)
++ if (dm_state->context) {
++ dm_enable_per_frame_crtc_master_sync(dm_state->context);
+ WARN_ON(!dc_commit_state(dm->dc, dm_state->context));
++ }
+
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
+index 4118a34..a2501d6 100644
+--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
++++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
+@@ -674,6 +674,28 @@ void dc_destroy(struct dc **dc)
+ *dc = NULL;
+ }
+
++static void enable_timing_multisync(
++ struct dc *dc,
++ struct dc_state *ctx)
++{
++ int i = 0, multisync_count = 0;
++ int pipe_count = dc->res_pool->pipe_count;
++ struct pipe_ctx *multisync_pipes[MAX_PIPES] = { NULL };
++
++ for (i = 0; i < pipe_count; i++) {
++ if (!ctx->res_ctx.pipe_ctx[i].stream ||
++ !ctx->res_ctx.pipe_ctx[i].stream->triggered_crtc_reset.enabled)
++ continue;
++ multisync_pipes[multisync_count] = &ctx->res_ctx.pipe_ctx[i];
++ multisync_count++;
++ }
++
++ if (multisync_count > 1) {
++ dc->hwss.enable_per_frame_crtc_position_reset(
++ dc, multisync_count, multisync_pipes);
++ }
++}
++
+ static void program_timing_sync(
+ struct dc *dc,
+ struct dc_state *ctx)
+@@ -852,7 +874,9 @@ static enum dc_status dc_commit_state_no_check(struct dc *dc, struct dc_state *c
+ }
+ result = dc->hwss.apply_ctx_to_hw(dc, context);
+
+- program_timing_sync(dc, context);
++ if (context->stream_count > 1)
++ enable_timing_multisync(dc, context);
++ program_timing_sync(dc, context);
+
+ dc_enable_stereo(dc, context, dc_streams, context->stream_count);
+
+diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
+index 29a3832..224b560 100644
+--- a/drivers/gpu/drm/amd/display/dc/dc.h
++++ b/drivers/gpu/drm/amd/display/dc/dc.h
+@@ -619,6 +619,9 @@ struct dc_stream_state {
+
+ /* from stream struct */
+ struct kref refcount;
++
++ struct crtc_trigger_info triggered_crtc_reset;
++
+ };
+
+ struct dc_stream_update {
+diff --git a/drivers/gpu/drm/amd/display/dc/dc_hw_types.h b/drivers/gpu/drm/amd/display/dc/dc_hw_types.h
+index ea58d10..587c0bb 100644
+--- a/drivers/gpu/drm/amd/display/dc/dc_hw_types.h
++++ b/drivers/gpu/drm/amd/display/dc/dc_hw_types.h
+@@ -673,6 +673,22 @@ enum dc_timing_3d_format {
+ TIMING_3D_FORMAT_MAX,
+ };
+
++enum trigger_delay {
++ TRIGGER_DELAY_NEXT_PIXEL = 0,
++ TRIGGER_DELAY_NEXT_LINE,
++};
++
++enum crtc_event {
++ CRTC_EVENT_VSYNC_RISING = 0,
++ CRTC_EVENT_VSYNC_FALLING
++};
++
++struct crtc_trigger_info {
++ bool enabled;
++ struct dc_stream_state *event_source;
++ enum crtc_event event;
++ enum trigger_delay delay;
++};
+
+ struct dc_crtc_timing {
+
+diff --git a/drivers/gpu/drm/amd/display/dc/dce110/dce110_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/dce110/dce110_hw_sequencer.c
+index 6e0ea54..b4504f1 100644
+--- a/drivers/gpu/drm/amd/display/dc/dce110/dce110_hw_sequencer.c
++++ b/drivers/gpu/drm/amd/display/dc/dce110/dce110_hw_sequencer.c
+@@ -2455,20 +2455,16 @@ static void dce110_enable_timing_synchronization(
+
+ for (i = 1 /* skip the master */; i < group_size; i++)
+ grouped_pipes[i]->stream_res.tg->funcs->enable_reset_trigger(
+- grouped_pipes[i]->stream_res.tg, gsl_params.gsl_group);
+-
+-
++ grouped_pipes[i]->stream_res.tg,
++ gsl_params.gsl_group);
+
+ for (i = 1 /* skip the master */; i < group_size; i++) {
+ DC_SYNC_INFO("GSL: waiting for reset to occur.\n");
+ wait_for_reset_trigger_to_occur(dc_ctx, grouped_pipes[i]->stream_res.tg);
+- /* Regardless of success of the wait above, remove the reset or
+- * the driver will start timing out on Display requests. */
+- DC_SYNC_INFO("GSL: disabling trigger-reset.\n");
+- grouped_pipes[i]->stream_res.tg->funcs->disable_reset_trigger(grouped_pipes[i]->stream_res.tg);
++ grouped_pipes[i]->stream_res.tg->funcs->disable_reset_trigger(
++ grouped_pipes[i]->stream_res.tg);
+ }
+
+-
+ /* GSL Vblank synchronization is a one time sync mechanism, assumption
+ * is that the sync'ed displays will not drift out of sync over time*/
+ DC_SYNC_INFO("GSL: Restoring register states.\n");
+@@ -2478,6 +2474,39 @@ static void dce110_enable_timing_synchronization(
+ DC_SYNC_INFO("GSL: Set-up complete.\n");
+ }
+
++static void dce110_enable_per_frame_crtc_position_reset(
++ struct dc *dc,
++ int group_size,
++ struct pipe_ctx *grouped_pipes[])
++{
++ struct dc_context *dc_ctx = dc->ctx;
++ struct dcp_gsl_params gsl_params = { 0 };
++ int i;
++
++ gsl_params.gsl_group = 0;
++ gsl_params.gsl_master = grouped_pipes[0]->stream->triggered_crtc_reset.event_source->status.primary_otg_inst;
++
++ for (i = 0; i < group_size; i++)
++ grouped_pipes[i]->stream_res.tg->funcs->setup_global_swap_lock(
++ grouped_pipes[i]->stream_res.tg, &gsl_params);
++
++ DC_SYNC_INFO("GSL: enabling trigger-reset\n");
++
++ for (i = 1; i < group_size; i++)
++ grouped_pipes[i]->stream_res.tg->funcs->enable_crtc_reset(
++ grouped_pipes[i]->stream_res.tg,
++ gsl_params.gsl_master,
++ &grouped_pipes[i]->stream->triggered_crtc_reset);
++
++ DC_SYNC_INFO("GSL: waiting for reset to occur.\n");
++ for (i = 1; i < group_size; i++)
++ wait_for_reset_trigger_to_occur(dc_ctx, grouped_pipes[i]->stream_res.tg);
++
++ for (i = 0; i < group_size; i++)
++ grouped_pipes[i]->stream_res.tg->funcs->tear_down_global_swap_lock(grouped_pipes[i]->stream_res.tg);
++
++}
++
+ static void init_hw(struct dc *dc)
+ {
+ int i;
+@@ -2974,6 +3003,7 @@ static const struct hw_sequencer_funcs dce110_funcs = {
+ .power_down = dce110_power_down,
+ .enable_accelerated_mode = dce110_enable_accelerated_mode,
+ .enable_timing_synchronization = dce110_enable_timing_synchronization,
++ .enable_per_frame_crtc_position_reset = dce110_enable_per_frame_crtc_position_reset,
+ .update_info_frame = dce110_update_info_frame,
+ .enable_stream = dce110_enable_stream,
+ .disable_stream = dce110_disable_stream,
+diff --git a/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.c b/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.c
+index 67ac737..08e49dd 100644
+--- a/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.c
++++ b/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.c
+@@ -1224,26 +1224,46 @@ void dce110_timing_generator_setup_global_swap_lock(
+
+ /* This pipe will belong to GSL Group zero. */
+ set_reg_field_value(value,
+- 1,
+- DCP_GSL_CONTROL,
+- DCP_GSL0_EN);
++ 1,
++ DCP_GSL_CONTROL,
++ DCP_GSL0_EN);
+
+ set_reg_field_value(value,
+- gsl_params->gsl_master == tg->inst,
+- DCP_GSL_CONTROL,
+- DCP_GSL_MASTER_EN);
++ gsl_params->gsl_master == tg->inst,
++ DCP_GSL_CONTROL,
++ DCP_GSL_MASTER_EN);
+
+ set_reg_field_value(value,
+- HFLIP_READY_DELAY,
+- DCP_GSL_CONTROL,
+- DCP_GSL_HSYNC_FLIP_FORCE_DELAY);
++ HFLIP_READY_DELAY,
++ DCP_GSL_CONTROL,
++ DCP_GSL_HSYNC_FLIP_FORCE_DELAY);
+
+ /* Keep signal low (pending high) during 6 lines.
+ * Also defines minimum interval before re-checking signal. */
+ set_reg_field_value(value,
+- HFLIP_CHECK_DELAY,
+- DCP_GSL_CONTROL,
+- DCP_GSL_HSYNC_FLIP_CHECK_DELAY);
++ HFLIP_CHECK_DELAY,
++ DCP_GSL_CONTROL,
++ DCP_GSL_HSYNC_FLIP_CHECK_DELAY);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmDCP_GSL_CONTROL), value);
++ value = 0;
++
++ set_reg_field_value(value,
++ gsl_params->gsl_master,
++ DCIO_GSL0_CNTL,
++ DCIO_GSL0_VSYNC_SEL);
++
++ set_reg_field_value(value,
++ 0,
++ DCIO_GSL0_CNTL,
++ DCIO_GSL0_TIMING_SYNC_SEL);
++
++ set_reg_field_value(value,
++ 0,
++ DCIO_GSL0_CNTL,
++ DCIO_GSL0_GLOBAL_UNLOCK_SEL);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmDCIO_GSL0_CNTL), value);
+
+
+ {
+@@ -1253,38 +1273,38 @@ void dce110_timing_generator_setup_global_swap_lock(
+ CRTC_REG(mmCRTC_V_TOTAL));
+
+ set_reg_field_value(value,
+- 0,/* DCP_GSL_PURPOSE_SURFACE_FLIP */
+- DCP_GSL_CONTROL,
+- DCP_GSL_SYNC_SOURCE);
++ 0,/* DCP_GSL_PURPOSE_SURFACE_FLIP */
++ DCP_GSL_CONTROL,
++ DCP_GSL_SYNC_SOURCE);
+
+ /* Checkpoint relative to end of frame */
+ check_point = get_reg_field_value(value_crtc_vtotal,
+- CRTC_V_TOTAL,
+- CRTC_V_TOTAL);
++ CRTC_V_TOTAL,
++ CRTC_V_TOTAL);
+
+ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_GSL_WINDOW), 0);
+ }
+
+ set_reg_field_value(value,
+- 1,
+- DCP_GSL_CONTROL,
+- DCP_GSL_DELAY_SURFACE_UPDATE_PENDING);
++ 1,
++ DCP_GSL_CONTROL,
++ DCP_GSL_DELAY_SURFACE_UPDATE_PENDING);
+
+ dm_write_reg(tg->ctx, address, value);
+
+ /********************************************************************/
+ address = CRTC_REG(mmCRTC_GSL_CONTROL);
+
+- value = 0;
++ value = dm_read_reg(tg->ctx, address);
+ set_reg_field_value(value,
+- check_point - FLIP_READY_BACK_LOOKUP,
+- CRTC_GSL_CONTROL,
+- CRTC_GSL_CHECK_LINE_NUM);
++ check_point - FLIP_READY_BACK_LOOKUP,
++ CRTC_GSL_CONTROL,
++ CRTC_GSL_CHECK_LINE_NUM);
+
+ set_reg_field_value(value,
+- VFLIP_READY_DELAY,
+- CRTC_GSL_CONTROL,
+- CRTC_GSL_FORCE_DELAY);
++ VFLIP_READY_DELAY,
++ CRTC_GSL_CONTROL,
++ CRTC_GSL_FORCE_DELAY);
+
+ dm_write_reg(tg->ctx, address, value);
+ }
+@@ -1555,6 +1575,138 @@ void dce110_timing_generator_enable_reset_trigger(
+ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL), value);
+ }
+
++void dce110_timing_generator_enable_crtc_reset(
++ struct timing_generator *tg,
++ int source_tg_inst,
++ struct crtc_trigger_info *crtc_tp)
++{
++ uint32_t value = 0;
++ uint32_t rising_edge = 0;
++ uint32_t falling_edge = 0;
++ struct dce110_timing_generator *tg110 = DCE110TG_FROM_TG(tg);
++
++ /* Setup trigger edge */
++ switch (crtc_tp->event) {
++ case CRTC_EVENT_VSYNC_RISING:
++ rising_edge = 1;
++ break;
++
++ case CRTC_EVENT_VSYNC_FALLING:
++ falling_edge = 1;
++ break;
++ }
++
++ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_TRIGB_CNTL));
++
++ set_reg_field_value(value,
++ source_tg_inst,
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_SOURCE_SELECT);
++
++ set_reg_field_value(value,
++ TRIGGER_POLARITY_SELECT_LOGIC_ZERO,
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_POLARITY_SELECT);
++
++ set_reg_field_value(value,
++ rising_edge,
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_RISING_EDGE_DETECT_CNTL);
++
++ set_reg_field_value(value,
++ falling_edge,
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_FALLING_EDGE_DETECT_CNTL);
++
++ set_reg_field_value(value,
++ 1, /* clear trigger status */
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_CLEAR);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_TRIGB_CNTL), value);
++
++ /**************************************************************/
++
++ switch (crtc_tp->delay) {
++ case TRIGGER_DELAY_NEXT_LINE:
++ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL));
++
++ set_reg_field_value(value,
++ 0, /* force H count to H_TOTAL and V count to V_TOTAL */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_MODE);
++
++ set_reg_field_value(value,
++ 0, /* TriggerB - we never use TriggerA */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_TRIG_SEL);
++
++ set_reg_field_value(value,
++ 1, /* clear trigger status */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_CLEAR);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL), value);
++
++ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_VERT_SYNC_CONTROL));
++
++ set_reg_field_value(value,
++ 1,
++ CRTC_VERT_SYNC_CONTROL,
++ CRTC_FORCE_VSYNC_NEXT_LINE_CLEAR);
++
++ set_reg_field_value(value,
++ 2,
++ CRTC_VERT_SYNC_CONTROL,
++ CRTC_AUTO_FORCE_VSYNC_MODE);
++
++ break;
++
++ case TRIGGER_DELAY_NEXT_PIXEL:
++ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_VERT_SYNC_CONTROL));
++
++ set_reg_field_value(value,
++ 1,
++ CRTC_VERT_SYNC_CONTROL,
++ CRTC_FORCE_VSYNC_NEXT_LINE_CLEAR);
++
++ set_reg_field_value(value,
++ 0,
++ CRTC_VERT_SYNC_CONTROL,
++ CRTC_AUTO_FORCE_VSYNC_MODE);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_VERT_SYNC_CONTROL), value);
++
++ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL));
++
++ set_reg_field_value(value,
++ 2, /* force H count to H_TOTAL and V count to V_TOTAL */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_MODE);
++
++ set_reg_field_value(value,
++ 1, /* TriggerB - we never use TriggerA */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_TRIG_SEL);
++
++ set_reg_field_value(value,
++ 1, /* clear trigger status */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_CLEAR);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL), value);
++ break;
++ }
++
++ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_MASTER_UPDATE_MODE));
++
++ set_reg_field_value(value,
++ 2,
++ CRTC_MASTER_UPDATE_MODE,
++ MASTER_UPDATE_MODE);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_MASTER_UPDATE_MODE), value);
++}
+ void dce110_timing_generator_disable_reset_trigger(
+ struct timing_generator *tg)
+ {
+@@ -1564,34 +1716,48 @@ void dce110_timing_generator_disable_reset_trigger(
+ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL));
+
+ set_reg_field_value(value,
+- 0, /* force counter now mode is disabled */
+- CRTC_FORCE_COUNT_NOW_CNTL,
+- CRTC_FORCE_COUNT_NOW_MODE);
++ 0, /* force counter now mode is disabled */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_MODE);
+
+ set_reg_field_value(value,
+- 1, /* clear trigger status */
+- CRTC_FORCE_COUNT_NOW_CNTL,
+- CRTC_FORCE_COUNT_NOW_CLEAR);
++ 1, /* clear trigger status */
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_CLEAR);
+
+ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL), value);
+
++ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_VERT_SYNC_CONTROL));
++
++ set_reg_field_value(value,
++ 1,
++ CRTC_VERT_SYNC_CONTROL,
++ CRTC_FORCE_VSYNC_NEXT_LINE_CLEAR);
++
++ set_reg_field_value(value,
++ 0,
++ CRTC_VERT_SYNC_CONTROL,
++ CRTC_AUTO_FORCE_VSYNC_MODE);
++
++ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_VERT_SYNC_CONTROL), value);
++
+ /********************************************************************/
+ value = dm_read_reg(tg->ctx, CRTC_REG(mmCRTC_TRIGB_CNTL));
+
+ set_reg_field_value(value,
+- TRIGGER_SOURCE_SELECT_LOGIC_ZERO,
+- CRTC_TRIGB_CNTL,
+- CRTC_TRIGB_SOURCE_SELECT);
++ TRIGGER_SOURCE_SELECT_LOGIC_ZERO,
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_SOURCE_SELECT);
+
+ set_reg_field_value(value,
+- TRIGGER_POLARITY_SELECT_LOGIC_ZERO,
+- CRTC_TRIGB_CNTL,
+- CRTC_TRIGB_POLARITY_SELECT);
++ TRIGGER_POLARITY_SELECT_LOGIC_ZERO,
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_POLARITY_SELECT);
+
+ set_reg_field_value(value,
+- 1, /* clear trigger status */
+- CRTC_TRIGB_CNTL,
+- CRTC_TRIGB_CLEAR);
++ 1, /* clear trigger status */
++ CRTC_TRIGB_CNTL,
++ CRTC_TRIGB_CLEAR);
+
+ dm_write_reg(tg->ctx, CRTC_REG(mmCRTC_TRIGB_CNTL), value);
+ }
+@@ -1611,10 +1777,16 @@ bool dce110_timing_generator_did_triggered_reset_occur(
+ struct dce110_timing_generator *tg110 = DCE110TG_FROM_TG(tg);
+ uint32_t value = dm_read_reg(tg->ctx,
+ CRTC_REG(mmCRTC_FORCE_COUNT_NOW_CNTL));
+-
+- return get_reg_field_value(value,
+- CRTC_FORCE_COUNT_NOW_CNTL,
+- CRTC_FORCE_COUNT_NOW_OCCURRED) != 0;
++ uint32_t value1 = dm_read_reg(tg->ctx,
++ CRTC_REG(mmCRTC_VERT_SYNC_CONTROL));
++ bool force = get_reg_field_value(value,
++ CRTC_FORCE_COUNT_NOW_CNTL,
++ CRTC_FORCE_COUNT_NOW_OCCURRED) != 0;
++ bool vert_sync = get_reg_field_value(value1,
++ CRTC_VERT_SYNC_CONTROL,
++ CRTC_FORCE_VSYNC_NEXT_LINE_OCCURRED) != 0;
++
++ return (force || vert_sync);
+ }
+
+ /**
+@@ -1928,6 +2100,7 @@ static const struct timing_generator_funcs dce110_tg_funcs = {
+ .setup_global_swap_lock =
+ dce110_timing_generator_setup_global_swap_lock,
+ .enable_reset_trigger = dce110_timing_generator_enable_reset_trigger,
++ .enable_crtc_reset = dce110_timing_generator_enable_crtc_reset,
+ .disable_reset_trigger = dce110_timing_generator_disable_reset_trigger,
+ .tear_down_global_swap_lock =
+ dce110_timing_generator_tear_down_global_swap_lock,
+diff --git a/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.h b/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.h
+index 82737de..232747c 100644
+--- a/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.h
++++ b/drivers/gpu/drm/amd/display/dc/dce110/dce110_timing_generator.h
+@@ -174,6 +174,12 @@ void dce110_timing_generator_setup_global_swap_lock(
+ void dce110_timing_generator_tear_down_global_swap_lock(
+ struct timing_generator *tg);
+
++/* Reset crtc position on master VSync */
++void dce110_timing_generator_enable_crtc_reset(
++ struct timing_generator *tg,
++ int source,
++ struct crtc_trigger_info *crtc_tp);
++
+ /* Reset slave controllers on master VSync */
+ void dce110_timing_generator_enable_reset_trigger(
+ struct timing_generator *tg,
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_hw_sequencer.c b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_hw_sequencer.c
+index d575921..3a2457f 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_hw_sequencer.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_hw_sequencer.c
+@@ -1304,14 +1304,15 @@ static void dcn10_enable_timing_synchronization(
+
+ for (i = 1; i < group_size; i++)
+ grouped_pipes[i]->stream_res.tg->funcs->enable_reset_trigger(
+- grouped_pipes[i]->stream_res.tg, grouped_pipes[0]->stream_res.tg->inst);
+-
++ grouped_pipes[i]->stream_res.tg,
++ grouped_pipes[0]->stream_res.tg->inst);
+
+ DC_SYNC_INFO("Waiting for trigger\n");
+
+ /* Need to get only check 1 pipe for having reset as all the others are
+ * synchronized. Look at last pipe programmed to reset.
+ */
++
+ wait_for_reset_trigger_to_occur(dc_ctx, grouped_pipes[1]->stream_res.tg);
+ for (i = 1; i < group_size; i++)
+ grouped_pipes[i]->stream_res.tg->funcs->disable_reset_trigger(
+@@ -1320,6 +1321,29 @@ static void dcn10_enable_timing_synchronization(
+ DC_SYNC_INFO("Sync complete\n");
+ }
+
++static void dcn10_enable_per_frame_crtc_position_reset(
++ struct dc *dc,
++ int group_size,
++ struct pipe_ctx *grouped_pipes[])
++{
++ struct dc_context *dc_ctx = dc->ctx;
++ int i;
++
++ DC_SYNC_INFO("Setting up\n");
++ for (i = 0; i < group_size; i++)
++ grouped_pipes[i]->stream_res.tg->funcs->enable_crtc_reset(
++ grouped_pipes[i]->stream_res.tg,
++ grouped_pipes[i]->stream->triggered_crtc_reset.event_source->status.primary_otg_inst,
++ &grouped_pipes[i]->stream->triggered_crtc_reset);
++
++ DC_SYNC_INFO("Waiting for trigger\n");
++
++ for (i = 1; i < group_size; i++)
++ wait_for_reset_trigger_to_occur(dc_ctx, grouped_pipes[i]->stream_res.tg);
++
++ DC_SYNC_INFO("Multi-display sync is complete\n");
++}
++
+ static void print_rq_dlg_ttu(
+ struct dc *core_dc,
+ struct pipe_ctx *pipe_ctx)
+@@ -2485,6 +2509,7 @@ static const struct hw_sequencer_funcs dcn10_funcs = {
+ .power_down = dce110_power_down,
+ .enable_accelerated_mode = dce110_enable_accelerated_mode,
+ .enable_timing_synchronization = dcn10_enable_timing_synchronization,
++ .enable_per_frame_crtc_position_reset = dcn10_enable_per_frame_crtc_position_reset,
+ .update_info_frame = dce110_update_info_frame,
+ .enable_stream = dce110_enable_stream,
+ .disable_stream = dce110_disable_stream,
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_timing_generator.c b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_timing_generator.c
+index fced178..c178cc0 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_timing_generator.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_timing_generator.c
+@@ -610,12 +610,28 @@ static bool tgn10_did_triggered_reset_occur(
+ struct timing_generator *tg)
+ {
+ struct dcn10_timing_generator *tgn10 = DCN10TG_FROM_TG(tg);
+- uint32_t occurred;
++ uint32_t occurred_force, occurred_vsync;
+
+ REG_GET(OTG_FORCE_COUNT_NOW_CNTL,
+- OTG_FORCE_COUNT_NOW_OCCURRED, &occurred);
++ OTG_FORCE_COUNT_NOW_OCCURRED, &occurred_force);
+
+- return occurred != 0;
++ REG_GET(OTG_VERT_SYNC_CONTROL,
++ OTG_FORCE_VSYNC_NEXT_LINE_OCCURRED, &occurred_vsync);
++
++ return occurred_vsync != 0 || occurred_force != 0;
++}
++
++static void tgn10_disable_reset_trigger(struct timing_generator *tg)
++{
++ struct dcn10_timing_generator *tgn10 = DCN10TG_FROM_TG(tg);
++
++ REG_WRITE(OTG_TRIGA_CNTL, 0);
++
++ REG_SET(OTG_FORCE_COUNT_NOW_CNTL, 0,
++ OTG_FORCE_COUNT_NOW_CLEAR, 1);
++
++ REG_SET(OTG_VERT_SYNC_CONTROL, 0,
++ OTG_FORCE_VSYNC_NEXT_LINE_CLEAR, 1);
+ }
+
+ static void tgn10_enable_reset_trigger(struct timing_generator *tg, int source_tg_inst)
+@@ -652,14 +668,49 @@ static void tgn10_enable_reset_trigger(struct timing_generator *tg, int source_t
+ OTG_FORCE_COUNT_NOW_MODE, 2);
+ }
+
+-static void tgn10_disable_reset_trigger(struct timing_generator *tg)
++void tgn10_enable_crtc_reset(
++ struct timing_generator *tg,
++ int source_tg_inst,
++ struct crtc_trigger_info *crtc_tp)
+ {
+ struct dcn10_timing_generator *tgn10 = DCN10TG_FROM_TG(tg);
++ uint32_t falling_edge = 0;
++ uint32_t rising_edge = 0;
+
+- REG_WRITE(OTG_TRIGA_CNTL, 0);
++ switch (crtc_tp->event) {
+
+- REG_SET(OTG_FORCE_COUNT_NOW_CNTL, 0,
+- OTG_FORCE_COUNT_NOW_CLEAR, 1);
++ case CRTC_EVENT_VSYNC_RISING:
++ rising_edge = 1;
++ break;
++
++ case CRTC_EVENT_VSYNC_FALLING:
++ falling_edge = 1;
++ break;
++ }
++
++ REG_SET_4(OTG_TRIGA_CNTL, 0,
++ /* vsync signal from selected OTG pipe based
++ * on OTG_TRIG_SOURCE_PIPE_SELECT setting
++ */
++ OTG_TRIGA_SOURCE_SELECT, 20,
++ OTG_TRIGA_SOURCE_PIPE_SELECT, source_tg_inst,
++ /* always detect falling edge */
++ OTG_TRIGA_RISING_EDGE_DETECT_CNTL, rising_edge,
++ OTG_TRIGA_FALLING_EDGE_DETECT_CNTL, falling_edge);
++
++ switch (crtc_tp->delay) {
++ case TRIGGER_DELAY_NEXT_LINE:
++ REG_SET(OTG_VERT_SYNC_CONTROL, 0,
++ OTG_AUTO_FORCE_VSYNC_MODE, 1);
++ break;
++ case TRIGGER_DELAY_NEXT_PIXEL:
++ REG_SET(OTG_FORCE_COUNT_NOW_CNTL, 0,
++ /* force H count to H_TOTAL and V count to V_TOTAL in
++ * progressive mode and V_TOTAL-1 in interlaced mode
++ */
++ OTG_FORCE_COUNT_NOW_MODE, 2);
++ break;
++ }
+ }
+
+ static void tgn10_wait_for_state(struct timing_generator *tg,
+@@ -1174,6 +1225,7 @@ static const struct timing_generator_funcs dcn10_tg_funcs = {
+ .set_blank_color = tgn10_program_blank_color,
+ .did_triggered_reset_occur = tgn10_did_triggered_reset_occur,
+ .enable_reset_trigger = tgn10_enable_reset_trigger,
++ .enable_crtc_reset = tgn10_enable_crtc_reset,
+ .disable_reset_trigger = tgn10_disable_reset_trigger,
+ .lock = tgn10_lock,
+ .unlock = tgn10_unlock,
+diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/timing_generator.h b/drivers/gpu/drm/amd/display/dc/inc/hw/timing_generator.h
+index c6ab38c..75f7a01 100644
+--- a/drivers/gpu/drm/amd/display/dc/inc/hw/timing_generator.h
++++ b/drivers/gpu/drm/amd/display/dc/inc/hw/timing_generator.h
+@@ -158,7 +158,11 @@ struct timing_generator_funcs {
+ const struct dcp_gsl_params *gsl_params);
+ void (*unlock)(struct timing_generator *tg);
+ void (*lock)(struct timing_generator *tg);
+- void (*enable_reset_trigger)(struct timing_generator *tg, int source_tg_inst);
++ void (*enable_reset_trigger)(struct timing_generator *tg,
++ int source_tg_inst);
++ void (*enable_crtc_reset)(struct timing_generator *tg,
++ int source_tg_inst,
++ struct crtc_trigger_info *crtc_tp);
+ void (*disable_reset_trigger)(struct timing_generator *tg);
+ void (*tear_down_global_swap_lock)(struct timing_generator *tg);
+ void (*enable_advanced_request)(struct timing_generator *tg,
+diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw_sequencer.h b/drivers/gpu/drm/amd/display/dc/inc/hw_sequencer.h
+index 8734689..cebbba3 100644
+--- a/drivers/gpu/drm/amd/display/dc/inc/hw_sequencer.h
++++ b/drivers/gpu/drm/amd/display/dc/inc/hw_sequencer.h
+@@ -114,6 +114,11 @@ struct hw_sequencer_funcs {
+ int group_size,
+ struct pipe_ctx *grouped_pipes[]);
+
++ void (*enable_per_frame_crtc_position_reset)(
++ struct dc *dc,
++ int group_size,
++ struct pipe_ctx *grouped_pipes[]);
++
+ void (*enable_display_pipe_clock_gating)(
+ struct dc_context *ctx,
+ bool clock_gating);
+--
+2.7.4
+