aboutsummaryrefslogtreecommitdiffstats
path: root/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.19.8/3564-drm-amd-display-re-structure-odm-to-allow-4-to-1-sup.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.19.8/3564-drm-amd-display-re-structure-odm-to-allow-4-to-1-sup.patch')
-rw-r--r--meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.19.8/3564-drm-amd-display-re-structure-odm-to-allow-4-to-1-sup.patch1142
1 files changed, 1142 insertions, 0 deletions
diff --git a/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.19.8/3564-drm-amd-display-re-structure-odm-to-allow-4-to-1-sup.patch b/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.19.8/3564-drm-amd-display-re-structure-odm-to-allow-4-to-1-sup.patch
new file mode 100644
index 00000000..3a24bee8
--- /dev/null
+++ b/meta-amd-bsp/recipes-kernel/linux/linux-yocto-4.19.8/3564-drm-amd-display-re-structure-odm-to-allow-4-to-1-sup.patch
@@ -0,0 +1,1142 @@
+From 2141ed4abbffcf5e3b7adbd7381bdfd572f138c0 Mon Sep 17 00:00:00 2001
+From: Dmytro Laktyushkin <Dmytro.Laktyushkin@amd.com>
+Date: Tue, 6 Aug 2019 17:17:28 -0400
+Subject: [PATCH 3564/4256] drm/amd/display: re structure odm to allow 4 to 1
+ support
+
+Currently odm is handled using top_bottom pipe by special casing
+the differing opps to differentiate from mpc combine.
+
+Since top/bottom pipe list was made to track mpc muxing this creates
+difficulties in adding a 4 pipe odm case support.
+
+Rather than continue using mpc combine list, this change reworks odm
+to use it's own linked list to keep track of odm combine pipes. This
+also opens up options for using mpo with odm, if a practical use case
+is ever found.
+
+Signed-off-by: Dmytro Laktyushkin <Dmytro.Laktyushkin@amd.com>
+Reviewed-by: Charlene Liu <Charlene.Liu@amd.com>
+Acked-by: Leo Li <sunpeng.li@amd.com>
+---
+ drivers/gpu/drm/amd/display/dc/core/dc.c | 10 +-
+ .../gpu/drm/amd/display/dc/core/dc_link_dp.c | 33 ++-
+ .../drm/amd/display/dc/core/dc_link_hwss.c | 36 +--
+ .../gpu/drm/amd/display/dc/core/dc_resource.c | 150 ++++-------
+ .../display/dc/dce110/dce110_hw_sequencer.c | 7 +-
+ .../amd/display/dc/dcn10/dcn10_hw_sequencer.c | 2 +-
+ .../drm/amd/display/dc/dcn20/dcn20_hwseq.c | 90 ++++---
+ .../drm/amd/display/dc/dcn20/dcn20_resource.c | 244 ++++++++++++------
+ .../display/dc/dcn20/dcn20_stream_encoder.c | 2 +-
+ .../gpu/drm/amd/display/dc/inc/core_types.h | 2 +
+ .../amd/display/dc/inc/hw/stream_encoder.h | 2 +-
+ drivers/gpu/drm/amd/display/dc/inc/resource.h | 3 -
+ 12 files changed, 316 insertions(+), 265 deletions(-)
+
+diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c
+index af2586b5c238..bc713677faa9 100644
+--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
++++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
+@@ -1869,9 +1869,7 @@ static void commit_planes_do_stream_update(struct dc *dc,
+ for (j = 0; j < dc->res_pool->pipe_count; j++) {
+ struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[j];
+
+- if (!pipe_ctx->top_pipe &&
+- pipe_ctx->stream &&
+- pipe_ctx->stream == stream) {
++ if (!pipe_ctx->top_pipe && !pipe_ctx->prev_odm_pipe && pipe_ctx->stream == stream) {
+
+ if (stream_update->periodic_interrupt0 &&
+ dc->hwss.setup_periodic_interrupt)
+@@ -1897,7 +1895,7 @@ static void commit_planes_do_stream_update(struct dc *dc,
+
+ if (stream_update->dither_option) {
+ #if defined(CONFIG_DRM_AMD_DC_DCN2_0)
+- struct pipe_ctx *odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe = pipe_ctx->next_odm_pipe;
+ #endif
+ resource_build_bit_depth_reduction_params(pipe_ctx->stream,
+ &pipe_ctx->stream->bit_depth_params);
+@@ -1905,10 +1903,12 @@ static void commit_planes_do_stream_update(struct dc *dc,
+ &stream->bit_depth_params,
+ &stream->clamping);
+ #if defined(CONFIG_DRM_AMD_DC_DCN2_0)
+- if (odm_pipe)
++ while (odm_pipe) {
+ odm_pipe->stream_res.opp->funcs->opp_program_fmt(odm_pipe->stream_res.opp,
+ &stream->bit_depth_params,
+ &stream->clamping);
++ odm_pipe = odm_pipe->next_odm_pipe;
++ }
+ #endif
+ }
+
+diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c
+index 2e87942b3e9c..2aa44b28b673 100644
+--- a/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c
++++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_dp.c
+@@ -3095,14 +3095,19 @@ static void set_crtc_test_pattern(struct dc_link *link,
+ controller_test_pattern, color_depth);
+ #if defined(CONFIG_DRM_AMD_DC_DCN2_0)
+ else if (opp->funcs->opp_set_disp_pattern_generator) {
+- struct pipe_ctx *bot_odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe;
++ int opp_cnt = 1;
+
+- if (bot_odm_pipe) {
+- struct output_pixel_processor *bot_opp = bot_odm_pipe->stream_res.opp;
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
++ opp_cnt++;
+
+- bot_opp->funcs->opp_program_bit_depth_reduction(bot_opp, &params);
+- width /= 2;
+- bot_opp->funcs->opp_set_disp_pattern_generator(bot_opp,
++ width /= opp_cnt;
++
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) {
++ struct output_pixel_processor *odm_opp = odm_pipe->stream_res.opp;
++
++ odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, &params);
++ odm_opp->funcs->opp_set_disp_pattern_generator(odm_opp,
+ controller_test_pattern,
+ color_depth,
+ NULL,
+@@ -3131,14 +3136,18 @@ static void set_crtc_test_pattern(struct dc_link *link,
+ color_depth);
+ #if defined(CONFIG_DRM_AMD_DC_DCN2_0)
+ else if (opp->funcs->opp_set_disp_pattern_generator) {
+- struct pipe_ctx *bot_odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe;
++ int opp_cnt = 1;
++
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
++ opp_cnt++;
+
+- if (bot_odm_pipe) {
+- struct output_pixel_processor *bot_opp = bot_odm_pipe->stream_res.opp;
++ width /= opp_cnt;
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) {
++ struct output_pixel_processor *odm_opp = odm_pipe->stream_res.opp;
+
+- bot_opp->funcs->opp_program_bit_depth_reduction(bot_opp, &params);
+- width /= 2;
+- bot_opp->funcs->opp_set_disp_pattern_generator(bot_opp,
++ odm_opp->funcs->opp_program_bit_depth_reduction(odm_opp, &params);
++ odm_opp->funcs->opp_set_disp_pattern_generator(odm_opp,
+ CONTROLLER_DP_TEST_PATTERN_VIDEOMODE,
+ color_depth,
+ NULL,
+diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c
+index 35c5467e60e8..fe9a4e4b9d1f 100644
+--- a/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c
++++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_hwss.c
+@@ -275,7 +275,7 @@ void dp_retrain_link_dp_test(struct dc_link *link,
+
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (pipes[i].stream != NULL &&
+- !pipes[i].top_pipe &&
++ !pipes[i].top_pipe && !pipes[i].prev_odm_pipe &&
+ pipes[i].stream->link != NULL &&
+ pipes[i].stream_res.stream_enc != NULL) {
+ udelay(100);
+@@ -381,7 +381,11 @@ void dp_set_dsc_on_stream(struct pipe_ctx *pipe_ctx, bool enable)
+ struct display_stream_compressor *dsc = pipe_ctx->stream_res.dsc;
+ struct dc *core_dc = pipe_ctx->stream->ctx->dc;
+ struct dc_stream_state *stream = pipe_ctx->stream;
+- struct pipe_ctx *odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe;
++ int opp_cnt = 1;
++
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
++ opp_cnt++;
+
+ if (enable) {
+ struct dsc_config dsc_cfg;
+@@ -389,26 +393,24 @@ void dp_set_dsc_on_stream(struct pipe_ctx *pipe_ctx, bool enable)
+ enum optc_dsc_mode optc_dsc_mode;
+
+ /* Enable DSC hw block */
+- dsc_cfg.pic_width = stream->timing.h_addressable + stream->timing.h_border_left + stream->timing.h_border_right;
++ dsc_cfg.pic_width = (stream->timing.h_addressable + stream->timing.h_border_left + stream->timing.h_border_right) / opp_cnt;
+ dsc_cfg.pic_height = stream->timing.v_addressable + stream->timing.v_border_top + stream->timing.v_border_bottom;
+ dsc_cfg.pixel_encoding = stream->timing.pixel_encoding;
+ dsc_cfg.color_depth = stream->timing.display_color_depth;
+ dsc_cfg.dc_dsc_cfg = stream->timing.dsc_cfg;
++ ASSERT(dsc_cfg.dc_dsc_cfg.num_slices_h % opp_cnt == 0);
++ dsc_cfg.dc_dsc_cfg.num_slices_h /= opp_cnt;
++
++ dsc->funcs->dsc_set_config(dsc, &dsc_cfg, &dsc_optc_cfg);
++ dsc->funcs->dsc_enable(dsc, pipe_ctx->stream_res.opp->inst);
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) {
++ struct display_stream_compressor *odm_dsc = odm_pipe->stream_res.dsc;
+
+- if (odm_pipe) {
+- struct display_stream_compressor *bot_dsc = odm_pipe->stream_res.dsc;
+-
+- dsc_cfg.pic_width /= 2;
+- ASSERT(dsc_cfg.dc_dsc_cfg.num_slices_h % 2 == 0);
+- dsc_cfg.dc_dsc_cfg.num_slices_h /= 2;
+- dsc->funcs->dsc_set_config(dsc, &dsc_cfg, &dsc_optc_cfg);
+- bot_dsc->funcs->dsc_set_config(bot_dsc, &dsc_cfg, &dsc_optc_cfg);
+- dsc->funcs->dsc_enable(dsc, pipe_ctx->stream_res.opp->inst);
+- bot_dsc->funcs->dsc_enable(bot_dsc, odm_pipe->stream_res.opp->inst);
+- } else {
+- dsc->funcs->dsc_set_config(dsc, &dsc_cfg, &dsc_optc_cfg);
+- dsc->funcs->dsc_enable(dsc, pipe_ctx->stream_res.opp->inst);
++ odm_dsc->funcs->dsc_set_config(odm_dsc, &dsc_cfg, &dsc_optc_cfg);
++ odm_dsc->funcs->dsc_enable(odm_dsc, odm_pipe->stream_res.opp->inst);
+ }
++ dsc_cfg.dc_dsc_cfg.num_slices_h *= opp_cnt;
++ dsc_cfg.pic_width *= opp_cnt;
+
+ optc_dsc_mode = dsc_optc_cfg.is_pixel_format_444 ? OPTC_DSC_ENABLED_444 : OPTC_DSC_ENABLED_NATIVE_SUBSAMPLED;
+
+@@ -449,7 +451,7 @@ void dp_set_dsc_on_stream(struct pipe_ctx *pipe_ctx, bool enable)
+
+ /* disable DSC block */
+ pipe_ctx->stream_res.dsc->funcs->dsc_disable(pipe_ctx->stream_res.dsc);
+- if (odm_pipe)
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
+ odm_pipe->stream_res.dsc->funcs->dsc_disable(odm_pipe->stream_res.dsc);
+ }
+ }
+diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
+index fa94dfc04dce..1464f4c60089 100644
+--- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
++++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
+@@ -1114,8 +1114,9 @@ struct pipe_ctx *resource_get_head_pipe_for_stream(
+ {
+ int i;
+ for (i = 0; i < MAX_PIPES; i++) {
+- if (res_ctx->pipe_ctx[i].stream == stream &&
+- !res_ctx->pipe_ctx[i].top_pipe) {
++ if (res_ctx->pipe_ctx[i].stream == stream
++ && !res_ctx->pipe_ctx[i].top_pipe
++ && !res_ctx->pipe_ctx[i].prev_odm_pipe) {
+ return &res_ctx->pipe_ctx[i];
+ break;
+ }
+@@ -1123,15 +1124,11 @@ struct pipe_ctx *resource_get_head_pipe_for_stream(
+ return NULL;
+ }
+
+-static struct pipe_ctx *resource_get_tail_pipe_for_stream(
++static struct pipe_ctx *resource_get_tail_pipe(
+ struct resource_context *res_ctx,
+- struct dc_stream_state *stream)
++ struct pipe_ctx *head_pipe)
+ {
+- struct pipe_ctx *head_pipe, *tail_pipe;
+- head_pipe = resource_get_head_pipe_for_stream(res_ctx, stream);
+-
+- if (!head_pipe)
+- return NULL;
++ struct pipe_ctx *tail_pipe;
+
+ tail_pipe = head_pipe->bottom_pipe;
+
+@@ -1147,31 +1144,20 @@ static struct pipe_ctx *resource_get_tail_pipe_for_stream(
+ * A free_pipe for a stream is defined here as a pipe
+ * that has no surface attached yet
+ */
+-static struct pipe_ctx *acquire_free_pipe_for_stream(
++static struct pipe_ctx *acquire_free_pipe_for_head(
+ struct dc_state *context,
+ const struct resource_pool *pool,
+- struct dc_stream_state *stream)
++ struct pipe_ctx *head_pipe)
+ {
+ int i;
+ struct resource_context *res_ctx = &context->res_ctx;
+
+- struct pipe_ctx *head_pipe = NULL;
+-
+- /* Find head pipe, which has the back end set up*/
+-
+- head_pipe = resource_get_head_pipe_for_stream(res_ctx, stream);
+-
+- if (!head_pipe) {
+- ASSERT(0);
+- return NULL;
+- }
+-
+ if (!head_pipe->plane_state)
+ return head_pipe;
+
+ /* Re-use pipe already acquired for this stream if available*/
+ for (i = pool->pipe_count - 1; i >= 0; i--) {
+- if (res_ctx->pipe_ctx[i].stream == stream &&
++ if (res_ctx->pipe_ctx[i].stream == head_pipe->stream &&
+ !res_ctx->pipe_ctx[i].plane_state) {
+ return &res_ctx->pipe_ctx[i];
+ }
+@@ -1185,8 +1171,7 @@ static struct pipe_ctx *acquire_free_pipe_for_stream(
+ if (!pool->funcs->acquire_idle_pipe_for_layer)
+ return NULL;
+
+- return pool->funcs->acquire_idle_pipe_for_layer(context, pool, stream);
+-
++ return pool->funcs->acquire_idle_pipe_for_layer(context, pool, head_pipe->stream);
+ }
+
+ #if defined(CONFIG_DRM_AMD_DC_DCN1_0)
+@@ -1200,7 +1185,7 @@ static int acquire_first_split_pipe(
+ for (i = 0; i < pool->pipe_count; i++) {
+ struct pipe_ctx *split_pipe = &res_ctx->pipe_ctx[i];
+
+- if (split_pipe->top_pipe && !dc_res_is_odm_head_pipe(split_pipe) &&
++ if (split_pipe->top_pipe &&
+ split_pipe->top_pipe->plane_state == split_pipe->plane_state) {
+ split_pipe->top_pipe->bottom_pipe = split_pipe->bottom_pipe;
+ if (split_pipe->bottom_pipe)
+@@ -1261,39 +1246,41 @@ bool dc_add_plane_to_context(
+ return false;
+ }
+
+- tail_pipe = resource_get_tail_pipe_for_stream(&context->res_ctx, stream);
+- ASSERT(tail_pipe);
++ /* retain new surface, but only once per stream */
++ dc_plane_state_retain(plane_state);
+
+- free_pipe = acquire_free_pipe_for_stream(context, pool, stream);
++ while (head_pipe) {
++ tail_pipe = resource_get_tail_pipe(&context->res_ctx, head_pipe);
++ ASSERT(tail_pipe);
+
+-#if defined(CONFIG_DRM_AMD_DC_DCN1_0)
+- if (!free_pipe) {
+- int pipe_idx = acquire_first_split_pipe(&context->res_ctx, pool, stream);
+- if (pipe_idx >= 0)
+- free_pipe = &context->res_ctx.pipe_ctx[pipe_idx];
+- }
+-#endif
+- if (!free_pipe)
+- return false;
++ free_pipe = acquire_free_pipe_for_head(context, pool, head_pipe);
+
+- /* retain new surfaces */
+- dc_plane_state_retain(plane_state);
+- free_pipe->plane_state = plane_state;
+-
+- if (head_pipe != free_pipe) {
+- free_pipe->stream_res.tg = tail_pipe->stream_res.tg;
+- free_pipe->stream_res.abm = tail_pipe->stream_res.abm;
+- free_pipe->stream_res.opp = tail_pipe->stream_res.opp;
+- free_pipe->stream_res.stream_enc = tail_pipe->stream_res.stream_enc;
+- free_pipe->stream_res.audio = tail_pipe->stream_res.audio;
+- free_pipe->clock_source = tail_pipe->clock_source;
+- free_pipe->top_pipe = tail_pipe;
+- tail_pipe->bottom_pipe = free_pipe;
+- } else if (free_pipe->bottom_pipe && free_pipe->bottom_pipe->plane_state == NULL) {
+- ASSERT(free_pipe->bottom_pipe->stream_res.opp != free_pipe->stream_res.opp);
+- free_pipe->bottom_pipe->plane_state = plane_state;
+- }
++ #if defined(CONFIG_DRM_AMD_DC_DCN1_0)
++ if (!free_pipe) {
++ int pipe_idx = acquire_first_split_pipe(&context->res_ctx, pool, stream);
++ if (pipe_idx >= 0)
++ free_pipe = &context->res_ctx.pipe_ctx[pipe_idx];
++ }
++ #endif
++ if (!free_pipe) {
++ dc_plane_state_release(plane_state);
++ return false;
++ }
+
++ free_pipe->plane_state = plane_state;
++
++ if (head_pipe != free_pipe) {
++ free_pipe->stream_res.tg = tail_pipe->stream_res.tg;
++ free_pipe->stream_res.abm = tail_pipe->stream_res.abm;
++ free_pipe->stream_res.opp = tail_pipe->stream_res.opp;
++ free_pipe->stream_res.stream_enc = tail_pipe->stream_res.stream_enc;
++ free_pipe->stream_res.audio = tail_pipe->stream_res.audio;
++ free_pipe->clock_source = tail_pipe->clock_source;
++ free_pipe->top_pipe = tail_pipe;
++ tail_pipe->bottom_pipe = free_pipe;
++ }
++ head_pipe = head_pipe->next_odm_pipe;
++ }
+ /* assign new surfaces*/
+ stream_status->plane_states[stream_status->plane_count] = plane_state;
+
+@@ -1302,35 +1289,6 @@ bool dc_add_plane_to_context(
+ return true;
+ }
+
+-struct pipe_ctx *dc_res_get_odm_bottom_pipe(struct pipe_ctx *pipe_ctx)
+-{
+- struct pipe_ctx *bottom_pipe = pipe_ctx->bottom_pipe;
+-
+- /* ODM should only be updated once per otg */
+- if (pipe_ctx->top_pipe)
+- return NULL;
+-
+- while (bottom_pipe) {
+- if (bottom_pipe->stream_res.opp != pipe_ctx->stream_res.opp)
+- break;
+- bottom_pipe = bottom_pipe->bottom_pipe;
+- }
+-
+- return bottom_pipe;
+-}
+-
+-bool dc_res_is_odm_head_pipe(struct pipe_ctx *pipe_ctx)
+-{
+- struct pipe_ctx *top_pipe = pipe_ctx->top_pipe;
+-
+- if (!top_pipe)
+- return false;
+- if (top_pipe && top_pipe->stream_res.opp == pipe_ctx->stream_res.opp)
+- return false;
+-
+- return true;
+-}
+-
+ bool dc_remove_plane_from_context(
+ const struct dc *dc,
+ struct dc_stream_state *stream,
+@@ -1357,12 +1315,6 @@ bool dc_remove_plane_from_context(
+ struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
+
+ if (pipe_ctx->plane_state == plane_state) {
+- if (dc_res_is_odm_head_pipe(pipe_ctx)) {
+- pipe_ctx->plane_state = NULL;
+- pipe_ctx->bottom_pipe = NULL;
+- continue;
+- }
+-
+ if (pipe_ctx->top_pipe)
+ pipe_ctx->top_pipe->bottom_pipe = pipe_ctx->bottom_pipe;
+
+@@ -1377,13 +1329,10 @@ bool dc_remove_plane_from_context(
+ * For head pipe detach surfaces from pipe for tail
+ * pipe just zero it out
+ */
+- if (!pipe_ctx->top_pipe) {
++ if (!pipe_ctx->top_pipe)
+ pipe_ctx->plane_state = NULL;
+- if (!dc_res_get_odm_bottom_pipe(pipe_ctx))
+- pipe_ctx->bottom_pipe = NULL;
+- } else {
++ else
+ memset(pipe_ctx, 0, sizeof(*pipe_ctx));
+- }
+ }
+ }
+
+@@ -1752,9 +1701,6 @@ enum dc_status dc_remove_stream_from_ctx(
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (new_ctx->res_ctx.pipe_ctx[i].stream == stream &&
+ !new_ctx->res_ctx.pipe_ctx[i].top_pipe) {
+- struct pipe_ctx *odm_pipe =
+- dc_res_get_odm_bottom_pipe(&new_ctx->res_ctx.pipe_ctx[i]);
+-
+ del_pipe = &new_ctx->res_ctx.pipe_ctx[i];
+
+ ASSERT(del_pipe->stream_res.stream_enc);
+@@ -1779,8 +1725,6 @@ enum dc_status dc_remove_stream_from_ctx(
+ dc->res_pool->funcs->remove_stream_from_ctx(dc, new_ctx, stream);
+
+ memset(del_pipe, 0, sizeof(*del_pipe));
+- if (odm_pipe)
+- memset(odm_pipe, 0, sizeof(*odm_pipe));
+
+ break;
+ }
+@@ -2494,6 +2438,12 @@ void dc_resource_state_copy_construct(
+
+ if (cur_pipe->bottom_pipe)
+ cur_pipe->bottom_pipe = &dst_ctx->res_ctx.pipe_ctx[cur_pipe->bottom_pipe->pipe_idx];
++
++ if (cur_pipe->next_odm_pipe)
++ cur_pipe->next_odm_pipe = &dst_ctx->res_ctx.pipe_ctx[cur_pipe->next_odm_pipe->pipe_idx];
++
++ if (cur_pipe->prev_odm_pipe)
++ cur_pipe->prev_odm_pipe = &dst_ctx->res_ctx.pipe_ctx[cur_pipe->prev_odm_pipe->pipe_idx];
+ }
+
+ for (i = 0; i < dst_ctx->stream_count; i++) {
+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 5a046e5bc756..2693404cab1a 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
+@@ -1338,7 +1338,7 @@ static enum dc_status apply_single_controller_ctx_to_hw(
+ struct drr_params params = {0};
+ unsigned int event_triggers = 0;
+ #if defined(CONFIG_DRM_AMD_DC_DCN2_0)
+- struct pipe_ctx *odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe = pipe_ctx->next_odm_pipe;
+ #endif
+
+ if (dc->hwss.disable_stream_gating) {
+@@ -1406,7 +1406,7 @@ static enum dc_status apply_single_controller_ctx_to_hw(
+ &stream->bit_depth_params,
+ &stream->clamping);
+ #if defined(CONFIG_DRM_AMD_DC_DCN2_0)
+- if (odm_pipe) {
++ while (odm_pipe) {
+ odm_pipe->stream_res.opp->funcs->opp_set_dyn_expansion(
+ odm_pipe->stream_res.opp,
+ COLOR_SPACE_YCBCR601,
+@@ -1417,6 +1417,7 @@ static enum dc_status apply_single_controller_ctx_to_hw(
+ odm_pipe->stream_res.opp,
+ &stream->bit_depth_params,
+ &stream->clamping);
++ odm_pipe = odm_pipe->next_odm_pipe;
+ }
+ #endif
+
+@@ -2076,7 +2077,7 @@ enum dc_status dce110_apply_ctx_to_hw(
+ if (pipe_ctx_old->stream && !pipe_need_reprogram(pipe_ctx_old, pipe_ctx))
+ continue;
+
+- if (pipe_ctx->top_pipe)
++ if (pipe_ctx->top_pipe || pipe_ctx->prev_odm_pipe)
+ continue;
+
+ status = apply_single_controller_ctx_to_hw(
+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 1835157b9fad..344190be93ae 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
+@@ -2535,7 +2535,7 @@ struct pipe_ctx *find_top_pipe_for_stream(
+ if (pipe_ctx->stream != stream)
+ continue;
+
+- if (!pipe_ctx->top_pipe)
++ if (!pipe_ctx->top_pipe && !pipe_ctx->prev_odm_pipe)
+ return pipe_ctx;
+ }
+ return NULL;
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_hwseq.c b/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_hwseq.c
+index 57d9645659d8..c8c17fac2e24 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_hwseq.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_hwseq.c
+@@ -529,11 +529,9 @@ enum dc_status dcn20_enable_stream_timing(
+ struct dc_stream_state *stream = pipe_ctx->stream;
+ struct drr_params params = {0};
+ unsigned int event_triggers = 0;
+-
+-
+-#if defined(CONFIG_DRM_AMD_DC_DCN2_0)
+- struct pipe_ctx *odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
+-#endif
++ struct pipe_ctx *odm_pipe;
++ int opp_cnt = 1;
++ int opp_inst[MAX_PIPES] = { pipe_ctx->stream_res.opp->inst };
+
+ /* by upper caller loop, pipe0 is parent pipe and be called first.
+ * back end is set up by for pipe0. Other children pipe share back end
+@@ -544,14 +542,17 @@ enum dc_status dcn20_enable_stream_timing(
+
+ /* TODO check if timing_changed, disable stream if timing changed */
+
+- if (odm_pipe) {
+- int opp_inst[2] = { pipe_ctx->stream_res.opp->inst, odm_pipe->stream_res.opp->inst };
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) {
++ opp_inst[opp_cnt] = odm_pipe->stream_res.opp->inst;
++ opp_cnt++;
++ }
+
++ if (opp_cnt > 1)
+ pipe_ctx->stream_res.tg->funcs->set_odm_combine(
+ pipe_ctx->stream_res.tg,
+- opp_inst, 2,
++ opp_inst, opp_cnt,
+ &pipe_ctx->stream->timing);
+- }
++
+ /* HW program guide assume display already disable
+ * by unplug sequence. OTG assume stop.
+ */
+@@ -575,7 +576,7 @@ enum dc_status dcn20_enable_stream_timing(
+ pipe_ctx->stream->signal,
+ true);
+
+- if (odm_pipe)
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
+ odm_pipe->stream_res.opp->funcs->opp_pipe_clock_control(
+ odm_pipe->stream_res.opp,
+ true);
+@@ -661,7 +662,7 @@ bool dcn20_set_output_transfer_func(struct pipe_ctx *pipe_ctx,
+ */
+ if (mpc->funcs->power_on_mpc_mem_pwr)
+ mpc->funcs->power_on_mpc_mem_pwr(mpc, mpcc_id, true);
+- if ((pipe_ctx->top_pipe == NULL || dc_res_is_odm_head_pipe(pipe_ctx))
++ if (pipe_ctx->top_pipe == NULL
+ && mpc->funcs->set_output_gamma && stream->out_transfer_func) {
+ if (stream->out_transfer_func->type == TF_TYPE_HWPWL)
+ params = &stream->out_transfer_func->pwl;
+@@ -823,17 +824,21 @@ bool dcn20_set_input_transfer_func(struct pipe_ctx *pipe_ctx,
+
+ static void dcn20_update_odm(struct dc *dc, struct dc_state *context, struct pipe_ctx *pipe_ctx)
+ {
+- struct pipe_ctx *combine_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe;
++ int opp_cnt = 1;
++ int opp_inst[MAX_PIPES] = { pipe_ctx->stream_res.opp->inst };
+
+- if (combine_pipe) {
+- int opp_inst[2] = { pipe_ctx->stream_res.opp->inst,
+- combine_pipe->stream_res.opp->inst };
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) {
++ opp_inst[opp_cnt] = odm_pipe->stream_res.opp->inst;
++ opp_cnt++;
++ }
+
++ if (opp_cnt > 1)
+ pipe_ctx->stream_res.tg->funcs->set_odm_combine(
+ pipe_ctx->stream_res.tg,
+- opp_inst, 2,
++ opp_inst, opp_cnt,
+ &pipe_ctx->stream->timing);
+- } else
++ else
+ pipe_ctx->stream_res.tg->funcs->set_odm_bypass(
+ pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing);
+ }
+@@ -848,7 +853,8 @@ void dcn20_blank_pixel_data(
+ struct dc_stream_state *stream = pipe_ctx->stream;
+ enum dc_color_space color_space = stream->output_color_space;
+ enum controller_dp_test_pattern test_pattern = CONTROLLER_DP_TEST_PATTERN_SOLID_COLOR;
+- struct pipe_ctx *bot_odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe;
++ int odm_cnt = 1;
+
+ int width = stream->timing.h_addressable + stream->timing.h_border_left + stream->timing.h_border_right;
+ int height = stream->timing.v_addressable + stream->timing.v_border_bottom + stream->timing.v_border_top;
+@@ -856,8 +862,10 @@ void dcn20_blank_pixel_data(
+ /* get opp dpg blank color */
+ color_space_to_black_color(dc, color_space, &black_color);
+
+- if (bot_odm_pipe)
+- width = width / 2;
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
++ odm_cnt++;
++
++ width = width / odm_cnt;
+
+ if (blank) {
+ if (stream_res->abm)
+@@ -877,9 +885,9 @@ void dcn20_blank_pixel_data(
+ width,
+ height);
+
+- if (bot_odm_pipe) {
+- bot_odm_pipe->stream_res.opp->funcs->opp_set_disp_pattern_generator(
+- bot_odm_pipe->stream_res.opp,
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) {
++ odm_pipe->stream_res.opp->funcs->opp_set_disp_pattern_generator(
++ odm_pipe->stream_res.opp,
+ dc->debug.visual_confirm != VISUAL_CONFIRM_DISABLE ?
+ CONTROLLER_DP_TEST_PATTERN_COLORRAMP : test_pattern,
+ stream->timing.display_color_depth,
+@@ -1021,7 +1029,7 @@ static void dcn20_program_all_pipe_in_tree(
+ struct pipe_ctx *pipe_ctx,
+ struct dc_state *context)
+ {
+- if (pipe_ctx->top_pipe == NULL) {
++ if (pipe_ctx->top_pipe == NULL && !pipe_ctx->prev_odm_pipe) {
+ bool blank = !is_pipe_tree_visible(pipe_ctx);
+
+ pipe_ctx->stream_res.tg->funcs->program_global_sync(
+@@ -1312,8 +1320,8 @@ bool dcn20_update_bandwidth(
+
+ pipe_ctx->stream_res.tg->funcs->set_vtg_params(
+ pipe_ctx->stream_res.tg, &pipe_ctx->stream->timing);
+-
+- dc->hwss.blank_pixel_data(dc, pipe_ctx, blank);
++ if (pipe_ctx->prev_odm_pipe == NULL)
++ dc->hwss.blank_pixel_data(dc, pipe_ctx, blank);
+ }
+
+ pipe_ctx->plane_res.hubp->funcs->hubp_setup(
+@@ -1403,12 +1411,15 @@ static void dcn20_disable_stream_gating(struct dc *dc, struct pipe_ctx *pipe_ctx
+ {
+ #ifdef CONFIG_DRM_AMD_DC_DSC_SUPPORT
+ struct dce_hwseq *hws = dc->hwseq;
+- struct pipe_ctx *bot_odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
+
+ if (pipe_ctx->stream_res.dsc) {
++ struct pipe_ctx *odm_pipe = pipe_ctx->next_odm_pipe;
++
+ dcn20_dsc_pg_control(hws, pipe_ctx->stream_res.dsc->inst, true);
+- if (bot_odm_pipe)
+- dcn20_dsc_pg_control(hws, bot_odm_pipe->stream_res.dsc->inst, true);
++ while (odm_pipe) {
++ dcn20_dsc_pg_control(hws, odm_pipe->stream_res.dsc->inst, true);
++ odm_pipe = odm_pipe->next_odm_pipe;
++ }
+ }
+ #endif
+ }
+@@ -1417,12 +1428,15 @@ static void dcn20_enable_stream_gating(struct dc *dc, struct pipe_ctx *pipe_ctx)
+ {
+ #ifdef CONFIG_DRM_AMD_DC_DSC_SUPPORT
+ struct dce_hwseq *hws = dc->hwseq;
+- struct pipe_ctx *bot_odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
+
+ if (pipe_ctx->stream_res.dsc) {
++ struct pipe_ctx *odm_pipe = pipe_ctx->next_odm_pipe;
++
+ dcn20_dsc_pg_control(hws, pipe_ctx->stream_res.dsc->inst, false);
+- if (bot_odm_pipe)
+- dcn20_dsc_pg_control(hws, bot_odm_pipe->stream_res.dsc->inst, false);
++ while (odm_pipe) {
++ dcn20_dsc_pg_control(hws, odm_pipe->stream_res.dsc->inst, false);
++ odm_pipe = odm_pipe->next_odm_pipe;
++ }
+ }
+ #endif
+ }
+@@ -1552,18 +1566,22 @@ void dcn20_unblank_stream(struct pipe_ctx *pipe_ctx,
+ struct encoder_unblank_param params = { { 0 } };
+ struct dc_stream_state *stream = pipe_ctx->stream;
+ struct dc_link *link = stream->link;
+- params.odm = dc_res_get_odm_bottom_pipe(pipe_ctx);
++ struct pipe_ctx *odm_pipe;
+
++ params.opp_cnt = 1;
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe) {
++ params.opp_cnt++;
++ }
+ /* only 3 items below are used by unblank */
+ params.timing = pipe_ctx->stream->timing;
+
+ params.link_settings.link_rate = link_settings->link_rate;
+
+ if (dc_is_dp_signal(pipe_ctx->stream->signal)) {
+- if (optc1_is_two_pixels_per_containter(&stream->timing) || params.odm)
++ if (optc1_is_two_pixels_per_containter(&stream->timing) || params.opp_cnt)
+ params.timing.pix_clk_100hz /= 2;
+ pipe_ctx->stream_res.stream_enc->funcs->dp_set_odm_combine(
+- pipe_ctx->stream_res.stream_enc, params.odm);
++ pipe_ctx->stream_res.stream_enc, params.opp_cnt);
+ pipe_ctx->stream_res.stream_enc->funcs->dp_unblank(pipe_ctx->stream_res.stream_enc, &params);
+ }
+
+@@ -1654,7 +1672,7 @@ static void dcn20_reset_hw_ctx_wrap(
+ if (!pipe_ctx_old->stream)
+ continue;
+
+- if (pipe_ctx_old->top_pipe)
++ if (pipe_ctx_old->top_pipe || pipe_ctx_old->prev_odm_pipe)
+ continue;
+
+ if (!pipe_ctx->stream ||
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_resource.c b/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_resource.c
+index c59f31dcdc0d..aa1342ccf8b4 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_resource.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_resource.c
+@@ -1317,7 +1317,11 @@ static void get_pixel_clock_parameters(
+ struct pixel_clk_params *pixel_clk_params)
+ {
+ const struct dc_stream_state *stream = pipe_ctx->stream;
+- bool odm_combine = dc_res_get_odm_bottom_pipe(pipe_ctx) != NULL;
++ struct pipe_ctx *odm_pipe;
++ int opp_cnt = 1;
++
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
++ opp_cnt++;
+
+ pixel_clk_params->requested_pix_clk_100hz = stream->timing.pix_clk_100hz;
+ pixel_clk_params->encoder_object_id = stream->link->link_enc->id;
+@@ -1335,7 +1339,9 @@ static void get_pixel_clock_parameters(
+ if (stream->timing.pixel_encoding == PIXEL_ENCODING_YCBCR422)
+ pixel_clk_params->color_depth = COLOR_DEPTH_888;
+
+- if (optc1_is_two_pixels_per_containter(&stream->timing) || odm_combine)
++ if (opp_cnt == 4)
++ pixel_clk_params->requested_pix_clk_100hz /= 4;
++ else if (optc1_is_two_pixels_per_containter(&stream->timing) || opp_cnt == 2)
+ pixel_clk_params->requested_pix_clk_100hz /= 2;
+
+ if (stream->timing.timing_3d_format == TIMING_3D_FORMAT_HW_FRAME_PACKING)
+@@ -1479,22 +1485,16 @@ static enum dc_status remove_dsc_from_stream_resource(struct dc *dc,
+ for (i = 0; i < MAX_PIPES; i++) {
+ if (new_ctx->res_ctx.pipe_ctx[i].stream == dc_stream && !new_ctx->res_ctx.pipe_ctx[i].top_pipe) {
+ pipe_ctx = &new_ctx->res_ctx.pipe_ctx[i];
+- break;
++
++ if (pipe_ctx->stream_res.dsc)
++ release_dsc(&new_ctx->res_ctx, dc->res_pool, &pipe_ctx->stream_res.dsc);
+ }
+ }
+
+ if (!pipe_ctx)
+ return DC_ERROR_UNEXPECTED;
+-
+- if (pipe_ctx->stream_res.dsc) {
+- struct pipe_ctx *odm_pipe = dc_res_get_odm_bottom_pipe(pipe_ctx);
+-
+- release_dsc(&new_ctx->res_ctx, dc->res_pool, &pipe_ctx->stream_res.dsc);
+- if (odm_pipe)
+- release_dsc(&new_ctx->res_ctx, dc->res_pool, &odm_pipe->stream_res.dsc);
+- }
+-
+- return DC_OK;
++ else
++ return DC_OK;
+ }
+ #endif
+
+@@ -1593,17 +1593,94 @@ static void swizzle_to_dml_params(
+ }
+ }
+
+-static bool dcn20_split_stream_for_combine(
++static bool dcn20_split_stream_for_odm(
++ struct resource_context *res_ctx,
++ const struct resource_pool *pool,
++ struct pipe_ctx *prev_odm_pipe,
++ struct pipe_ctx *next_odm_pipe)
++{
++ int pipe_idx = next_odm_pipe->pipe_idx;
++ struct scaler_data *sd = &prev_odm_pipe->plane_res.scl_data;
++ struct pipe_ctx *sec_next_pipe = next_odm_pipe->next_odm_pipe;
++ int new_width;
++
++ *next_odm_pipe = *prev_odm_pipe;
++ next_odm_pipe->next_odm_pipe = sec_next_pipe;
++
++ next_odm_pipe->pipe_idx = pipe_idx;
++ next_odm_pipe->plane_res.mi = pool->mis[next_odm_pipe->pipe_idx];
++ next_odm_pipe->plane_res.hubp = pool->hubps[next_odm_pipe->pipe_idx];
++ next_odm_pipe->plane_res.ipp = pool->ipps[next_odm_pipe->pipe_idx];
++ next_odm_pipe->plane_res.xfm = pool->transforms[next_odm_pipe->pipe_idx];
++ next_odm_pipe->plane_res.dpp = pool->dpps[next_odm_pipe->pipe_idx];
++ next_odm_pipe->plane_res.mpcc_inst = pool->dpps[next_odm_pipe->pipe_idx]->inst;
++#ifdef CONFIG_DRM_AMD_DC_DSC_SUPPORT
++ next_odm_pipe->stream_res.dsc = NULL;
++#endif
++ if (prev_odm_pipe->next_odm_pipe && prev_odm_pipe->next_odm_pipe != next_odm_pipe) {
++ ASSERT(!next_odm_pipe->next_odm_pipe);
++ next_odm_pipe->next_odm_pipe = prev_odm_pipe->next_odm_pipe;
++ next_odm_pipe->next_odm_pipe->prev_odm_pipe = next_odm_pipe;
++ }
++ prev_odm_pipe->next_odm_pipe = next_odm_pipe;
++ next_odm_pipe->prev_odm_pipe = prev_odm_pipe;
++ ASSERT(next_odm_pipe->top_pipe == NULL);
++
++ if (prev_odm_pipe->plane_state) {
++ /* HACTIVE halved for odm combine */
++ sd->h_active /= 2;
++ /* Copy scl_data to secondary pipe */
++ next_odm_pipe->plane_res.scl_data = *sd;
++
++ /* Calculate new vp and recout for left pipe */
++ /* Need at least 16 pixels width per side */
++ if (sd->recout.x + 16 >= sd->h_active)
++ return false;
++ new_width = sd->h_active - sd->recout.x;
++ sd->viewport.width -= dc_fixpt_floor(dc_fixpt_mul_int(
++ sd->ratios.horz, sd->recout.width - new_width));
++ sd->viewport_c.width -= dc_fixpt_floor(dc_fixpt_mul_int(
++ sd->ratios.horz_c, sd->recout.width - new_width));
++ sd->recout.width = new_width;
++
++ /* Calculate new vp and recout for right pipe */
++ sd = &next_odm_pipe->plane_res.scl_data;
++ new_width = sd->recout.width + sd->recout.x - sd->h_active;
++ /* Need at least 16 pixels width per side */
++ if (new_width <= 16)
++ return false;
++ sd->viewport.width -= dc_fixpt_floor(dc_fixpt_mul_int(
++ sd->ratios.horz, sd->recout.width - new_width));
++ sd->viewport_c.width -= dc_fixpt_floor(dc_fixpt_mul_int(
++ sd->ratios.horz_c, sd->recout.width - new_width));
++ sd->recout.width = new_width;
++ sd->viewport.x += dc_fixpt_floor(dc_fixpt_mul_int(
++ sd->ratios.horz, sd->h_active - sd->recout.x));
++ sd->viewport_c.x += dc_fixpt_floor(dc_fixpt_mul_int(
++ sd->ratios.horz_c, sd->h_active - sd->recout.x));
++ sd->recout.x = 0;
++ }
++ next_odm_pipe->stream_res.opp = pool->opps[next_odm_pipe->pipe_idx];
++#ifdef CONFIG_DRM_AMD_DC_DSC_SUPPORT
++ if (next_odm_pipe->stream->timing.flags.DSC == 1) {
++ acquire_dsc(res_ctx, pool, &next_odm_pipe->stream_res.dsc);
++ ASSERT(next_odm_pipe->stream_res.dsc);
++ if (next_odm_pipe->stream_res.dsc == NULL)
++ return false;
++ }
++#endif
++
++ return true;
++}
++
++static void dcn20_split_stream_for_mpc(
+ struct resource_context *res_ctx,
+ const struct resource_pool *pool,
+ struct pipe_ctx *primary_pipe,
+- struct pipe_ctx *secondary_pipe,
+- bool is_odm_combine)
++ struct pipe_ctx *secondary_pipe)
+ {
+ int pipe_idx = secondary_pipe->pipe_idx;
+- struct scaler_data *sd = &primary_pipe->plane_res.scl_data;
+ struct pipe_ctx *sec_bot_pipe = secondary_pipe->bottom_pipe;
+- int new_width;
+
+ *secondary_pipe = *primary_pipe;
+ secondary_pipe->bottom_pipe = sec_bot_pipe;
+@@ -1626,57 +1703,9 @@ static bool dcn20_split_stream_for_combine(
+ primary_pipe->bottom_pipe = secondary_pipe;
+ secondary_pipe->top_pipe = primary_pipe;
+
+- if (is_odm_combine) {
+- if (primary_pipe->plane_state) {
+- /* HACTIVE halved for odm combine */
+- sd->h_active /= 2;
+- /* Copy scl_data to secondary pipe */
+- secondary_pipe->plane_res.scl_data = *sd;
+-
+- /* Calculate new vp and recout for left pipe */
+- /* Need at least 16 pixels width per side */
+- if (sd->recout.x + 16 >= sd->h_active)
+- return false;
+- new_width = sd->h_active - sd->recout.x;
+- sd->viewport.width -= dc_fixpt_floor(dc_fixpt_mul_int(
+- sd->ratios.horz, sd->recout.width - new_width));
+- sd->viewport_c.width -= dc_fixpt_floor(dc_fixpt_mul_int(
+- sd->ratios.horz_c, sd->recout.width - new_width));
+- sd->recout.width = new_width;
+-
+- /* Calculate new vp and recout for right pipe */
+- sd = &secondary_pipe->plane_res.scl_data;
+- new_width = sd->recout.width + sd->recout.x - sd->h_active;
+- /* Need at least 16 pixels width per side */
+- if (new_width <= 16)
+- return false;
+- sd->viewport.width -= dc_fixpt_floor(dc_fixpt_mul_int(
+- sd->ratios.horz, sd->recout.width - new_width));
+- sd->viewport_c.width -= dc_fixpt_floor(dc_fixpt_mul_int(
+- sd->ratios.horz_c, sd->recout.width - new_width));
+- sd->recout.width = new_width;
+- sd->viewport.x += dc_fixpt_floor(dc_fixpt_mul_int(
+- sd->ratios.horz, sd->h_active - sd->recout.x));
+- sd->viewport_c.x += dc_fixpt_floor(dc_fixpt_mul_int(
+- sd->ratios.horz_c, sd->h_active - sd->recout.x));
+- sd->recout.x = 0;
+- }
+- secondary_pipe->stream_res.opp = pool->opps[secondary_pipe->pipe_idx];
+-#ifdef CONFIG_DRM_AMD_DC_DSC_SUPPORT
+- if (secondary_pipe->stream->timing.flags.DSC == 1) {
+- acquire_dsc(res_ctx, pool, &secondary_pipe->stream_res.dsc);
+- ASSERT(secondary_pipe->stream_res.dsc);
+- if (secondary_pipe->stream_res.dsc == NULL)
+- return false;
+- }
+-#endif
+- } else {
+- ASSERT(primary_pipe->plane_state);
+- resource_build_scaling_params(primary_pipe);
+- resource_build_scaling_params(secondary_pipe);
+- }
+-
+- return true;
++ ASSERT(primary_pipe->plane_state);
++ resource_build_scaling_params(primary_pipe);
++ resource_build_scaling_params(secondary_pipe);
+ }
+
+ void dcn20_populate_dml_writeback_from_context(
+@@ -2106,20 +2135,24 @@ static bool dcn20_validate_dsc(struct dc *dc, struct dc_state *new_ctx)
+ struct pipe_ctx *pipe_ctx = &new_ctx->res_ctx.pipe_ctx[i];
+ struct dc_stream_state *stream = pipe_ctx->stream;
+ struct dsc_config dsc_cfg;
++ struct pipe_ctx *odm_pipe;
++ int opp_cnt = 1;
++
++ for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
++ opp_cnt++;
+
+ /* Only need to validate top pipe */
+- if (pipe_ctx->top_pipe || !stream || !stream->timing.flags.DSC)
++ if (pipe_ctx->top_pipe || pipe_ctx->prev_odm_pipe || !stream || !stream->timing.flags.DSC)
+ continue;
+
+- dsc_cfg.pic_width = stream->timing.h_addressable + stream->timing.h_border_left
+- + stream->timing.h_border_right;
++ dsc_cfg.pic_width = (stream->timing.h_addressable + stream->timing.h_border_left
++ + stream->timing.h_border_right) / opp_cnt;
+ dsc_cfg.pic_height = stream->timing.v_addressable + stream->timing.v_border_top
+ + stream->timing.v_border_bottom;
+- if (dc_res_get_odm_bottom_pipe(pipe_ctx))
+- dsc_cfg.pic_width /= 2;
+ dsc_cfg.pixel_encoding = stream->timing.pixel_encoding;
+ dsc_cfg.color_depth = stream->timing.display_color_depth;
+ dsc_cfg.dc_dsc_cfg = stream->timing.dsc_cfg;
++ dsc_cfg.dc_dsc_cfg.num_slices_h /= opp_cnt;
+
+ if (!pipe_ctx->stream_res.dsc->funcs->dsc_validate_stream(pipe_ctx->stream_res.dsc, &dsc_cfg))
+ return false;
+@@ -2143,6 +2176,8 @@ static struct pipe_ctx *dcn20_find_secondary_pipe(struct dc *dc,
+ * if this primary pipe has a bottom pipe in prev. state
+ * and if the bottom pipe is still available (which it should be),
+ * pick that pipe as secondary
++ * Same logic applies for ODM pipes. Since mpo is not allowed with odm
++ * check in else case.
+ */
+ if (dc->current_state->res_ctx.pipe_ctx[primary_pipe->pipe_idx].bottom_pipe) {
+ preferred_pipe_idx = dc->current_state->res_ctx.pipe_ctx[primary_pipe->pipe_idx].bottom_pipe->pipe_idx;
+@@ -2150,6 +2185,12 @@ static struct pipe_ctx *dcn20_find_secondary_pipe(struct dc *dc,
+ secondary_pipe = &res_ctx->pipe_ctx[preferred_pipe_idx];
+ secondary_pipe->pipe_idx = preferred_pipe_idx;
+ }
++ } else if (dc->current_state->res_ctx.pipe_ctx[primary_pipe->pipe_idx].next_odm_pipe) {
++ preferred_pipe_idx = dc->current_state->res_ctx.pipe_ctx[primary_pipe->pipe_idx].next_odm_pipe->pipe_idx;
++ if (res_ctx->pipe_ctx[preferred_pipe_idx].stream == NULL) {
++ secondary_pipe = &res_ctx->pipe_ctx[preferred_pipe_idx];
++ secondary_pipe->pipe_idx = preferred_pipe_idx;
++ }
+ }
+
+ /*
+@@ -2220,6 +2261,38 @@ bool dcn20_fast_validate_bw(
+ if (!pipes)
+ return false;
+
++ /* merge previously split odm pipes since mode support needs to make the decision */
++ for (i = 0; i < dc->res_pool->pipe_count; i++) {
++ struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
++ struct pipe_ctx *odm_pipe = pipe->next_odm_pipe;
++
++ if (pipe->prev_odm_pipe)
++ continue;
++
++ pipe->next_odm_pipe = NULL;
++ while (odm_pipe) {
++ struct pipe_ctx *next_odm_pipe = odm_pipe->next_odm_pipe;
++
++ odm_pipe->plane_state = NULL;
++ odm_pipe->stream = NULL;
++ odm_pipe->top_pipe = NULL;
++ odm_pipe->bottom_pipe = NULL;
++ odm_pipe->prev_odm_pipe = NULL;
++ odm_pipe->next_odm_pipe = NULL;
++#ifdef CONFIG_DRM_AMD_DC_DSC_SUPPORT
++ if (odm_pipe->stream_res.dsc)
++ release_dsc(&context->res_ctx, dc->res_pool, &odm_pipe->stream_res.dsc);
++#endif
++ /* Clear plane_res and stream_res */
++ memset(&odm_pipe->plane_res, 0, sizeof(odm_pipe->plane_res));
++ memset(&odm_pipe->stream_res, 0, sizeof(odm_pipe->stream_res));
++ odm_pipe = next_odm_pipe;
++ }
++ if (pipe->plane_state)
++ resource_build_scaling_params(pipe);
++ }
++
++ /* merge previously mpc split pipes since mode support needs to make the decision */
+ for (i = 0; i < dc->res_pool->pipe_count; i++) {
+ struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
+ struct pipe_ctx *hsplit_pipe = pipe->bottom_pipe;
+@@ -2227,7 +2300,6 @@ bool dcn20_fast_validate_bw(
+ if (!hsplit_pipe || hsplit_pipe->plane_state != pipe->plane_state)
+ continue;
+
+- /* merge previously split pipe since mode support needs to make the decision */
+ pipe->bottom_pipe = hsplit_pipe->bottom_pipe;
+ if (hsplit_pipe->bottom_pipe)
+ hsplit_pipe->bottom_pipe->top_pipe = pipe;
+@@ -2235,10 +2307,7 @@ bool dcn20_fast_validate_bw(
+ hsplit_pipe->stream = NULL;
+ hsplit_pipe->top_pipe = NULL;
+ hsplit_pipe->bottom_pipe = NULL;
+-#ifdef CONFIG_DRM_AMD_DC_DSC_SUPPORT
+- if (hsplit_pipe->stream_res.dsc && hsplit_pipe->stream_res.dsc != pipe->stream_res.dsc)
+- release_dsc(&context->res_ctx, dc->res_pool, &hsplit_pipe->stream_res.dsc);
+-#endif
++
+ /* Clear plane_res and stream_res */
+ memset(&hsplit_pipe->plane_res, 0, sizeof(hsplit_pipe->plane_res));
+ memset(&hsplit_pipe->stream_res, 0, sizeof(hsplit_pipe->stream_res));
+@@ -2351,10 +2420,9 @@ bool dcn20_fast_validate_bw(
+ if (!pipe->top_pipe && !pipe->plane_state && context->bw_ctx.dml.vba.ODMCombineEnabled[pipe_idx]) {
+ hsplit_pipe = dcn20_find_secondary_pipe(dc, &context->res_ctx, dc->res_pool, pipe);
+ ASSERT(hsplit_pipe);
+- if (!dcn20_split_stream_for_combine(
++ if (!dcn20_split_stream_for_odm(
+ &context->res_ctx, dc->res_pool,
+- pipe, hsplit_pipe,
+- true))
++ pipe, hsplit_pipe))
+ goto validate_fail;
+ pipe_split_from[hsplit_pipe->pipe_idx] = pipe_idx;
+ dcn20_build_mapped_resource(dc, context, pipe->stream);
+@@ -2395,11 +2463,15 @@ bool dcn20_fast_validate_bw(
+ if (!hsplit_pipe)
+ continue;
+
+- if (!dcn20_split_stream_for_combine(
++ if (context->bw_ctx.dml.vba.ODMCombineEnabled[pipe_idx]) {
++ if (!dcn20_split_stream_for_odm(
++ &context->res_ctx, dc->res_pool,
++ pipe, hsplit_pipe))
++ goto validate_fail;
++ } else
++ dcn20_split_stream_for_mpc(
+ &context->res_ctx, dc->res_pool,
+- pipe, hsplit_pipe,
+- context->bw_ctx.dml.vba.ODMCombineEnabled[pipe_idx]))
+- goto validate_fail;
++ pipe, hsplit_pipe);
+ pipe_split_from[hsplit_pipe->pipe_idx] = pipe_idx;
+ }
+ } else if (hsplit_pipe && hsplit_pipe->plane_state == pipe->plane_state) {
+diff --git a/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_stream_encoder.c b/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_stream_encoder.c
+index a4e67286cdad..9cfc69ccdb39 100644
+--- a/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_stream_encoder.c
++++ b/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_stream_encoder.c
+@@ -458,7 +458,7 @@ void enc2_stream_encoder_dp_unblank(
+ uint64_t m_vid_l = n_vid;
+
+ /* YCbCr 4:2:0 : Computed VID_M will be 2X the input rate */
+- if (is_two_pixels_per_containter(&param->timing) || param->odm) {
++ if (is_two_pixels_per_containter(&param->timing) || param->opp_cnt) {
+ /*this logic should be the same in get_pixel_clock_parameters() */
+ n_multiply = 1;
+ }
+diff --git a/drivers/gpu/drm/amd/display/dc/inc/core_types.h b/drivers/gpu/drm/amd/display/dc/inc/core_types.h
+index 74186cf1c285..bfe0d06d1c20 100644
+--- a/drivers/gpu/drm/amd/display/dc/inc/core_types.h
++++ b/drivers/gpu/drm/amd/display/dc/inc/core_types.h
+@@ -297,6 +297,8 @@ struct pipe_ctx {
+
+ struct pipe_ctx *top_pipe;
+ struct pipe_ctx *bottom_pipe;
++ struct pipe_ctx *next_odm_pipe;
++ struct pipe_ctx *prev_odm_pipe;
+
+ #ifdef CONFIG_DRM_AMD_DC_DCN1_0
+ struct _vcs_dpi_display_dlg_regs_st dlg_regs;
+diff --git a/drivers/gpu/drm/amd/display/dc/inc/hw/stream_encoder.h b/drivers/gpu/drm/amd/display/dc/inc/hw/stream_encoder.h
+index 8bb3e3d56ac9..fe9b7a10a1c3 100644
+--- a/drivers/gpu/drm/amd/display/dc/inc/hw/stream_encoder.h
++++ b/drivers/gpu/drm/amd/display/dc/inc/hw/stream_encoder.h
+@@ -91,7 +91,7 @@ struct encoder_unblank_param {
+ struct dc_link_settings link_settings;
+ struct dc_crtc_timing timing;
+ #ifdef CONFIG_DRM_AMD_DC_DCN2_0
+- bool odm;
++ int opp_cnt;
+ #endif
+ };
+
+diff --git a/drivers/gpu/drm/amd/display/dc/inc/resource.h b/drivers/gpu/drm/amd/display/dc/inc/resource.h
+index 47f81072d7e9..1cc1c8ce633b 100644
+--- a/drivers/gpu/drm/amd/display/dc/inc/resource.h
++++ b/drivers/gpu/drm/amd/display/dc/inc/resource.h
+@@ -179,7 +179,4 @@ void update_audio_usage(
+
+ unsigned int resource_pixel_format_to_bpp(enum surface_pixel_format format);
+
+-struct pipe_ctx *dc_res_get_odm_bottom_pipe(struct pipe_ctx *pipe_ctx);
+-bool dc_res_is_odm_head_pipe(struct pipe_ctx *pipe_ctx);
+-
+ #endif /* DRIVERS_GPU_DRM_AMD_DC_DEV_DC_INC_RESOURCE_H_ */
+--
+2.17.1
+