Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Matthew Stewart 1277 100.00% 1 100.00%
Total 1277 1


/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright 2026 Advanced Micro Devices, Inc.
 */

#include "dm_services.h"
#include "core_types.h"
#include "resource.h"
#include "dce/dce_hwseq.h"
#include "dcn10/dcn10_hwseq.h"
#include "reg_helper.h"
#include "hubp.h"
#include "dchubbub.h"
#include "timing_generator.h"
#include "opp.h"
#include "mpc.h"
#include "dcn42b_hwseq.h"

#define CTX \
	hws->ctx

#define REG(reg)\
	hws->regs->reg

#define DC_LOGGER \
	hws->ctx->logger

#undef FN
#define FN(reg_name, field_name) \
	hws->shifts->field_name, hws->masks->field_name

/*
 * dcn42b_init_pipes - Initialize pipes for dcn42b
 *
 * This function is modeled after dcn10_init_pipes but handles the case
 * where num_timing_generator != num_pipes (e.g., 3 TGs but 4 pipes).
 *
 * For dcn42b:
 * - num_timing_generator = 3
 * - num_pipes (num_dpp) = 4
 *
 * The key difference is that we iterate over timing generators separately
 * from pipes to avoid accessing timing_generators[i] when i >= num_timing_generator.
 */
void dcn42b_init_pipes(struct dc *dc, struct dc_state *context)
{
	uint8_t i;
	struct dce_hwseq *hws = dc->hwseq;
	struct hubbub *hubbub = dc->res_pool->hubbub;
	bool can_apply_seamless_boot = false;
	bool tg_enabled[MAX_PIPES] = {false};

	for (i = 0; i < context->stream_count; i++) {
		if (context->streams[i]->apply_seamless_boot_optimization) {
			can_apply_seamless_boot = true;
			break;
		}
	}

	for (i = 0; i < dc->res_pool->timing_generator_count; i++) {
		struct timing_generator *tg = dc->res_pool->timing_generators[i];
		struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];

		/* There is assumption that pipe_ctx is not mapping irregularly
		 * to non-preferred front end. If pipe_ctx->stream is not NULL,
		 * we will use the pipe, so don't disable
		 */
		if (pipe_ctx->stream != NULL && can_apply_seamless_boot)
			continue;

		/* Blank controller using driver code instead of
		 * command table.
		 */
		if (tg->funcs->is_tg_enabled(tg)) {
			if (hws->funcs.init_blank != NULL) {
				hws->funcs.init_blank(dc, tg);
				tg->funcs->lock(tg);
			} else {
				tg->funcs->lock(tg);
				tg->funcs->set_blank(tg, true);
				hwss_wait_for_blank_complete(tg);
			}
		}
	}

	/* Reset det size */
	for (i = 0; i < dc->res_pool->pipe_count; i++) {
		struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];
		struct hubp *hubp = dc->res_pool->hubps[i];

		/* Do not need to reset for seamless boot */
		if (pipe_ctx->stream != NULL && can_apply_seamless_boot)
			continue;

		if (hubbub && hubp) {
			if (hubbub->funcs->program_det_size)
				hubbub->funcs->program_det_size(hubbub, hubp->inst, 0);
			if (hubbub->funcs->program_det_segments)
				hubbub->funcs->program_det_segments(hubbub, hubp->inst, 0);
		}
	}

	/* num_opp will be equal to number of mpcc */
	for (i = 0; i < dc->res_pool->res_cap->num_opp; i++) {
		struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];

		/* Cannot reset the MPC mux if seamless boot */
		if (pipe_ctx->stream != NULL && can_apply_seamless_boot)
			continue;

		dc->res_pool->mpc->funcs->mpc_init_single_inst(
				dc->res_pool->mpc, i);
	}

	/* initialize DWB pointer to MCIF_WB */
	for (i = 0; i < dc->res_pool->res_cap->num_dwb; i++)
		dc->res_pool->dwbc[i]->mcif = dc->res_pool->mcif_wb[i];

	for (i = 0; i < dc->res_pool->timing_generator_count; i++) {
		struct timing_generator *tg = dc->res_pool->timing_generators[i];
		struct hubp *hubp = dc->res_pool->hubps[i];
		struct dpp *dpp = dc->res_pool->dpps[i];
		struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[i];

		/* There is assumption that pipe_ctx is not mapping irregularly
		 * to non-preferred front end. If pipe_ctx->stream is not NULL,
		 * we will use the pipe, so don't disable
		 */
		if (can_apply_seamless_boot &&
			pipe_ctx->stream != NULL &&
			pipe_ctx->stream_res.tg->funcs->is_tg_enabled(
				pipe_ctx->stream_res.tg)) {
			// Enable double buffering for OTG_BLANK no matter if
			// seamless boot is enabled or not to suppress global sync
			// signals when OTG blanked. This is to prevent pipe from
			// requesting data while in PSR.
			tg->funcs->tg_init(tg);
			hubp->power_gated = true;
			tg_enabled[i] = true;
			continue;
		}

		/* Disable on the current state so the new one isn't cleared. */
		pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];

		hubp->funcs->hubp_reset(hubp);
		dpp->funcs->dpp_reset(dpp);

		pipe_ctx->stream_res.tg = tg;
		pipe_ctx->pipe_idx = i;

		pipe_ctx->plane_res.hubp = hubp;
		pipe_ctx->plane_res.dpp = dpp;
		pipe_ctx->plane_res.mpcc_inst = (uint8_t)dpp->inst;
		hubp->mpcc_id = dpp->inst;
		hubp->opp_id = OPP_ID_INVALID;
		hubp->power_gated = false;

		dc->res_pool->opps[i]->mpc_tree_params.opp_id = dc->res_pool->opps[i]->inst;
		dc->res_pool->opps[i]->mpc_tree_params.opp_list = NULL;
		dc->res_pool->opps[i]->mpcc_disconnect_pending[pipe_ctx->plane_res.mpcc_inst] = true;
		pipe_ctx->stream_res.opp = dc->res_pool->opps[i];

		hws->funcs.plane_atomic_disconnect(dc, context, pipe_ctx);

		if (tg->funcs->is_tg_enabled(tg))
			tg->funcs->unlock(tg);

		dc->hwss.disable_plane(dc, context, pipe_ctx);

		pipe_ctx->stream_res.tg = NULL;
		pipe_ctx->plane_res.hubp = NULL;

		if (tg->funcs->is_tg_enabled(tg)) {
			if (tg->funcs->init_odm)
				tg->funcs->init_odm(tg);
		}

		tg->funcs->tg_init(tg);
	}

	/* Clean up MPC tree */
	for (i = 0; i < dc->res_pool->pipe_count; i++) {
		if (tg_enabled[i]) {
			if (dc->res_pool->opps[i]->mpc_tree_params.opp_list) {
				if (dc->res_pool->opps[i]->mpc_tree_params.opp_list->mpcc_bot) {
					int bot_id = dc->res_pool->opps[i]->mpc_tree_params.opp_list->mpcc_bot->mpcc_id;

					if ((bot_id < MAX_MPCC) && (bot_id < MAX_PIPES) && (!tg_enabled[bot_id]))
						dc->res_pool->opps[i]->mpc_tree_params.opp_list = NULL;
				}
			}
		}
	}

	/* Power gate DSCs */
	if (hws->funcs.dsc_pg_control != NULL) {
		uint32_t num_opps = 0;
		uint32_t opp_id_src0 = OPP_ID_INVALID;
		uint32_t opp_id_src1 = OPP_ID_INVALID;

		// Step 1: To find out which OPTC is running & OPTC DSC is ON
		// We can't use res_pool->res_cap->num_timing_generator to check
		// Because it records display pipes default setting built in driver,
		// not display pipes of the current chip.
		// Some ASICs would be fused display pipes less than the default setting.
		// In dcnxx_resource_construct function, driver would obatin real information.
		for (i = 0; i < dc->res_pool->timing_generator_count; i++) {
			uint32_t optc_dsc_state = 0;
			struct timing_generator *tg = dc->res_pool->timing_generators[i];

			if (tg->funcs->is_tg_enabled(tg)) {
				if (tg->funcs->get_dsc_status)
					tg->funcs->get_dsc_status(tg, &optc_dsc_state);
				// Only one OPTC with DSC is ON, so if we got one result, we would exit this block.
				// non-zero value is DSC enabled
				if (optc_dsc_state != 0) {
					tg->funcs->get_optc_source(tg, &num_opps, &opp_id_src0, &opp_id_src1);
					break;
				}
			}
		}

		// Step 2: To power down DSC but skip DSC  of running OPTC
		for (i = 0; i < dc->res_pool->res_cap->num_dsc; i++) {
			struct dcn_dsc_state s  = {0};

			dc->res_pool->dscs[i]->funcs->dsc_read_state(dc->res_pool->dscs[i], &s);

			if ((s.dsc_opp_source == opp_id_src0 || s.dsc_opp_source == opp_id_src1) &&
				s.dsc_clock_en && s.dsc_fw_en)
				continue;

			hws->funcs.dsc_pg_control(hws, dc->res_pool->dscs[i]->inst, false);
		}
	}
}