Blob Blame History Raw
From b1f6d01c4a3b7eeb2eb035e79d425cd6a696fa45 Mon Sep 17 00:00:00 2001
From: Dmytro Laktyushkin <Dmytro.Laktyushkin@amd.com>
Date: Tue, 6 Aug 2019 17:17:28 -0400
Subject: drm/amd/display: re structure odm to allow 4 to 1 support
Git-commit: b1f6d01c4a3b7eeb2eb035e79d425cd6a696fa45
Patch-mainline: v5.4-rc1
References: bsc#1152489

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>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Acked-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 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 55505b9f8ab4..f09811c0bf02 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc.c
@@ -1860,9 +1860,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)
@@ -1888,7 +1886,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);
@@ -1896,10 +1894,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 97e992e30d1a..30bae60918ea 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
@@ -1117,8 +1117,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;
 		}
@@ -1126,15 +1127,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;
 
@@ -1150,31 +1147,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];
 		}
@@ -1188,8 +1174,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)
@@ -1203,7 +1188,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)
@@ -1264,39 +1249,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;
 
@@ -1305,35 +1292,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,
@@ -1360,12 +1318,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;
 
@@ -1380,13 +1332,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));
-			}
 		}
 	}
 
@@ -1755,9 +1704,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);
@@ -1782,8 +1728,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;
 		}
@@ -2497,6 +2441,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 94b55143827e..e475a6488234 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
@@ -1341,7 +1341,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) {
@@ -1409,7 +1409,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,
@@ -1420,6 +1420,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
 
@@ -2079,7 +2080,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 2d50cfc66e92..283915b2c2b1 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
@@ -2537,7 +2537,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 e146d1d8d45e..e49325940fd3 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 68d197b82c75..fea466d9bd08 100644
--- a/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/dcn20/dcn20_resource.c
@@ -1319,7 +1319,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;
@@ -1337,7 +1341,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)
@@ -1481,22 +1487,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
 
@@ -1595,17 +1595,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;
@@ -1628,57 +1705,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(
@@ -2108,20 +2137,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;
@@ -2145,6 +2178,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;
@@ -2152,6 +2187,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;
+			}
 		}
 
 		/*
@@ -2222,6 +2263,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;
@@ -2229,7 +2302,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;
@@ -2237,10 +2309,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));
@@ -2353,10 +2422,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);
@@ -2397,11 +2465,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 c8ca49656d8d..e24107ec5398 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
@@ -460,7 +460,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.28.0