Contributors: 24
Author Tokens Token Proportion Commits Commit Proportion
Peichen Huang 1276 54.44% 5 10.64%
Jack Chang 401 17.11% 6 12.77%
Wenjing Liu 117 4.99% 7 14.89%
Bhawanpreet Lakha 94 4.01% 6 12.77%
Harry Wentland 88 3.75% 3 6.38%
Sung Joon Kim 70 2.99% 1 2.13%
David Francis 70 2.99% 1 2.13%
Yongqiang Sun 55 2.35% 1 2.13%
Leon Huang 33 1.41% 1 2.13%
Chris Park 30 1.28% 1 2.13%
Jerry (Fangzhi) Zuo 18 0.77% 1 2.13%
Jimmy Kizito 14 0.60% 2 4.26%
Camille Cho 14 0.60% 1 2.13%
Derek Lai 13 0.55% 1 2.13%
Aurabindo Pillai 12 0.51% 1 2.13%
Tom Chung 11 0.47% 1 2.13%
Duncan Ma 8 0.34% 1 2.13%
Srinivasan S 5 0.21% 1 2.13%
Wyatt Wood 5 0.21% 1 2.13%
Eric Yang 4 0.17% 1 2.13%
Yue Hin Lau 3 0.13% 1 2.13%
Aric Cyr 1 0.04% 1 2.13%
Hamza Mahfooz 1 0.04% 1 2.13%
Nicholas Kazlauskas 1 0.04% 1 2.13%
Total 2344 47


/*
 * Copyright 2025 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: AMD
 *
 */

#include "link_dp_panel_replay.h"
#include "link_edp_panel_control.h"
#include "link_dpcd.h"
#include "dm_helpers.h"
#include "dc/dc_dmub_srv.h"
#include "dce/dmub_replay.h"

#define DC_LOGGER \
	link->ctx->logger

#define DP_SINK_PR_ENABLE_AND_CONFIGURATION		0x37B
#define DP_SINK_ENABLE_FRAME_SKIPPING_MODE_SHIFT	(5)

static unsigned int dp_pr_calc_num_static_frames(unsigned int vsync_rate_hz)
{
	// at least 2 frames for static screen
	unsigned int num_frames = 2;

	// get number of frames for at least 50ms
	if (vsync_rate_hz > 40)
		num_frames = (vsync_rate_hz + 10) / 20;

	return num_frames;
}

static void dp_pr_set_static_screen_param(struct dc_link *link)
{
	struct dc_static_screen_params params = {0};
	struct dc *dc = link->ctx->dc;
	// only support DP sst for now
	if (!dc_is_dp_sst_signal(link->connector_signal))
		return;

	for (int i = 0; i < MAX_PIPES; i++) {
		if (dc->current_state->res_ctx.pipe_ctx[i].stream &&
			dc->current_state->res_ctx.pipe_ctx[i].stream->link == link) {
			struct dc_stream_state *stream = dc->current_state->res_ctx.pipe_ctx[i].stream;
			unsigned int vsync_rate_hz = div64_u64(div64_u64(
											(stream->timing.pix_clk_100hz * (u64)100),
											stream->timing.v_total),
											stream->timing.h_total);

			params.triggers.cursor_update = true;
			params.triggers.overlay_update = true;
			params.triggers.surface_update = true;
			params.num_frames = dp_pr_calc_num_static_frames(vsync_rate_hz);

			dc_stream_set_static_screen_params(dc, &stream, 1, &params);
			break;
		}
	}
}

static bool dp_setup_panel_replay(struct dc_link *link, const struct dc_stream_state *stream)
{
	/* To-do: Setup Replay */
	struct dc *dc;
	struct dmub_replay *replay;
	int i;
	unsigned int panel_inst;
	struct replay_context replay_context = { 0 };
	unsigned int lineTimeInNs = 0;

	union panel_replay_enable_and_configuration_1 pr_config_1 = { 0 };
	union panel_replay_enable_and_configuration_2 pr_config_2 = { 0 };

	union dpcd_alpm_configuration alpm_config;
	uint8_t data = 0;

	replay_context.controllerId = CONTROLLER_ID_UNDEFINED;

	if (!link)
		return false;

	//Clear Panel Replay enable & config
	dm_helpers_dp_write_dpcd(link->ctx, link,
		DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_1,
		(uint8_t *)&(pr_config_1.raw), sizeof(uint8_t));

	dm_helpers_dp_write_dpcd(link->ctx, link,
		DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_2,
		(uint8_t *)&(pr_config_2.raw), sizeof(uint8_t));

	if (!(link->replay_settings.config.replay_supported))
		return false;

	dc = link->ctx->dc;

	//not sure should keep or not
	replay = dc->res_pool->replay;

	if (!replay)
		return false;

	if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
		return false;

	replay_context.aux_inst = link->ddc->ddc_pin->hw_info.ddc_channel;
	replay_context.digbe_inst = link->link_enc->transmitter;
	replay_context.digfe_inst = link->link_enc->preferred_engine;

	for (i = 0; i < MAX_PIPES; i++) {
		if (dc->current_state->res_ctx.pipe_ctx[i].stream
				== stream) {
			/* dmcu -1 for all controller id values,
			 * therefore +1 here
			 */
			replay_context.controllerId =
				dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg->inst + 1;
			break;
		}
	}

	lineTimeInNs =
		((stream->timing.h_total * 1000000) /
			(stream->timing.pix_clk_100hz / 10)) + 1;

	replay_context.line_time_in_ns = lineTimeInNs;

	link->replay_settings.replay_feature_enabled = dp_pr_copy_settings(link, &replay_context);

	if (link->replay_settings.replay_feature_enabled) {
		if (dc_is_embedded_signal(link->connector_signal)) {
			pr_config_1.bits.PANEL_REPLAY_ENABLE = 1;
			pr_config_1.bits.PANEL_REPLAY_CRC_ENABLE = 1;
			pr_config_1.bits.IRQ_HPD_ASSDP_MISSING = 1;
			pr_config_1.bits.IRQ_HPD_VSCSDP_UNCORRECTABLE_ERROR = 1;
			pr_config_1.bits.IRQ_HPD_RFB_ERROR = 1;
			pr_config_1.bits.IRQ_HPD_ACTIVE_FRAME_CRC_ERROR = 1;
			pr_config_1.bits.PANEL_REPLAY_SELECTIVE_UPDATE_ENABLE = 1;
			pr_config_1.bits.PANEL_REPLAY_EARLY_TRANSPORT_ENABLE = 1;
		} else {
			pr_config_1.bits.PANEL_REPLAY_ENABLE = 1;
		}

		pr_config_2.bits.SINK_REFRESH_RATE_UNLOCK_GRANTED = 0;

		if (link->dpcd_caps.vesa_replay_caps.bits.SU_Y_GRANULARITY_EXT_CAP_SUPPORTED)
			pr_config_2.bits.SU_Y_GRANULARITY_EXT_VALUE_ENABLED = 1;

		pr_config_2.bits.SU_REGION_SCAN_LINE_CAPTURE_INDICATION = 0;

		dm_helpers_dp_write_dpcd(link->ctx, link,
			DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_1,
			(uint8_t *)&(pr_config_1.raw), sizeof(uint8_t));

		dm_helpers_dp_write_dpcd(link->ctx, link,
			DP_PANEL_REPLAY_ENABLE_AND_CONFIGURATION_2,
			(uint8_t *)&(pr_config_2.raw), sizeof(uint8_t));

		//ALPM Setup
		memset(&alpm_config, 0, sizeof(alpm_config));
		alpm_config.bits.ENABLE = link->replay_settings.config.alpm_mode != DC_ALPM_UNSUPPORTED ? 1 : 0;

		if (link->replay_settings.config.alpm_mode == DC_ALPM_AUXLESS) {
			alpm_config.bits.ALPM_MODE_SEL = 1;
			alpm_config.bits.ACDS_PERIOD_DURATION = 1;
		}

		dm_helpers_dp_write_dpcd(
			link->ctx,
			link,
			DP_RECEIVER_ALPM_CONFIG,
			&alpm_config.raw,
			sizeof(alpm_config.raw));

		//Enable frame skipping
		if (link->replay_settings.config.frame_skip_supported)
			data = data | (1 << DP_SINK_ENABLE_FRAME_SKIPPING_MODE_SHIFT);

		dm_helpers_dp_write_dpcd(link->ctx, link,
			DP_SINK_PR_ENABLE_AND_CONFIGURATION,
			(uint8_t *)&(data), sizeof(uint8_t));
	}

	return true;
}


bool dp_pr_get_panel_inst(const struct dc *dc,
		const struct dc_link *link,
		unsigned int *inst_out)
{
	if (!dc || !link || !inst_out)
		return false;

	if (dc->config.frame_update_cmd_version2 == false)
		return dc_get_edp_link_panel_inst(dc, link, inst_out);

	if (!dc_is_dp_sst_signal(link->connector_signal)) /* only supoprt DP sst (eDP included) for now */
		return false;

	for (unsigned int i = 0; i < MAX_PIPES; i++) {
		if (dc->current_state->res_ctx.pipe_ctx[i].stream &&
			dc->current_state->res_ctx.pipe_ctx[i].stream->link == link) {
			/* *inst_out is equal to otg number */
			if (dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg)
				*inst_out = dc->current_state->res_ctx.pipe_ctx[i].stream_res.tg->inst;
			else
				*inst_out = 0;

			return true;
		}
	}

	return false;
}

bool dp_setup_replay(struct dc_link *link, const struct dc_stream_state *stream)
{
	if (!link)
		return false;
	if (link->replay_settings.config.replay_version == DC_VESA_PANEL_REPLAY)
		return dp_setup_panel_replay(link, stream);
	else if (link->replay_settings.config.replay_version == DC_FREESYNC_REPLAY)
		return edp_setup_freesync_replay(link, stream);
	else
		return false;
}

bool dp_pr_enable(struct dc_link *link, bool enable)
{
	struct dc *dc = link->ctx->dc;
	unsigned int panel_inst = 0;
	union dmub_rb_cmd cmd;

	if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
		return false;

	if (enable && !dc_is_embedded_signal(link->connector_signal))
		dp_pr_set_static_screen_param(link);

	if (link->replay_settings.replay_allow_active != enable) {
		//for sending PR enable commands to DMUB
		memset(&cmd, 0, sizeof(cmd));

		cmd.pr_enable.header.type = DMUB_CMD__PR;
		cmd.pr_enable.header.sub_type = DMUB_CMD__PR_ENABLE;
		cmd.pr_enable.header.payload_bytes = sizeof(struct dmub_cmd_pr_enable_data);
		cmd.pr_enable.data.panel_inst = panel_inst;
		cmd.pr_enable.data.enable = enable ? 1 : 0;

		dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);

		link->replay_settings.replay_allow_active = enable;
	}
	return true;
}

bool dp_pr_copy_settings(struct dc_link *link, struct replay_context *replay_context)
{
	struct dc *dc = link->ctx->dc;
	unsigned int panel_inst = 0;
	union dmub_rb_cmd cmd;
	struct pipe_ctx *pipe_ctx = NULL;

	if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
		return false;

	for (unsigned int i = 0; i < MAX_PIPES; i++) {
		if (dc->current_state->res_ctx.pipe_ctx[i].stream &&
			dc->current_state->res_ctx.pipe_ctx[i].stream->link &&
			dc->current_state->res_ctx.pipe_ctx[i].stream->link == link &&
			dc_is_dp_sst_signal(dc->current_state->res_ctx.pipe_ctx[i].stream->link->connector_signal)) {
			pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i];
			/* todo: need update for MST */
			break;
		}
	}

	if (!pipe_ctx)
		return false;

	memset(&cmd, 0, sizeof(cmd));
	cmd.pr_copy_settings.header.type = DMUB_CMD__PR;
	cmd.pr_copy_settings.header.sub_type = DMUB_CMD__PR_COPY_SETTINGS;
	cmd.pr_copy_settings.header.payload_bytes = sizeof(struct dmub_cmd_pr_copy_settings_data);
	cmd.pr_copy_settings.data.panel_inst = panel_inst;
	// HW inst
	cmd.pr_copy_settings.data.aux_inst = replay_context->aux_inst;
	cmd.pr_copy_settings.data.digbe_inst = replay_context->digbe_inst;
	cmd.pr_copy_settings.data.digfe_inst = replay_context->digfe_inst;
	if (pipe_ctx->plane_res.dpp)
		cmd.pr_copy_settings.data.dpp_inst = pipe_ctx->plane_res.dpp->inst;
	else
		cmd.pr_copy_settings.data.dpp_inst = 0;
	if (pipe_ctx->stream_res.tg)
		cmd.pr_copy_settings.data.otg_inst = pipe_ctx->stream_res.tg->inst;
	else
		cmd.pr_copy_settings.data.otg_inst = 0;

	cmd.pr_copy_settings.data.dpphy_inst = link->link_enc->transmitter;

	cmd.pr_copy_settings.data.line_time_in_ns = replay_context->line_time_in_ns;
	cmd.pr_copy_settings.data.flags.bitfields.fec_enable_status = (link->fec_state == dc_link_fec_enabled);
	cmd.pr_copy_settings.data.flags.bitfields.dsc_enable_status = (pipe_ctx->stream->timing.flags.DSC == 1);
	cmd.pr_copy_settings.data.debug.u32All = link->replay_settings.config.debug_flags;

	cmd.pr_copy_settings.data.su_granularity_needed = link->dpcd_caps.vesa_replay_caps.bits.PR_SU_GRANULARITY_NEEDED;
	cmd.pr_copy_settings.data.su_x_granularity = link->dpcd_caps.vesa_replay_su_info.pr_su_x_granularity;
	cmd.pr_copy_settings.data.su_y_granularity = link->dpcd_caps.vesa_replay_su_info.pr_su_y_granularity;
	cmd.pr_copy_settings.data.su_y_granularity_extended_caps =
		link->dpcd_caps.vesa_replay_su_info.pr_su_y_granularity_extended_caps;

	if (pipe_ctx->stream->timing.dsc_cfg.num_slices_v > 0)
		cmd.pr_copy_settings.data.dsc_slice_height = (pipe_ctx->stream->timing.v_addressable +
			pipe_ctx->stream->timing.v_border_top + pipe_ctx->stream->timing.v_border_bottom) /
			pipe_ctx->stream->timing.dsc_cfg.num_slices_v;

	if (dc_is_embedded_signal(link->connector_signal))
		cmd.pr_copy_settings.data.main_link_activity_option = OPTION_1C;
	else
		// For external DP, use option 1-A
		cmd.pr_copy_settings.data.main_link_activity_option = OPTION_1A;

	dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
	return true;
}

bool dp_pr_update_state(struct dc_link *link, struct dmub_cmd_pr_update_state_data *update_state_data)
{
	struct dc *dc = link->ctx->dc;
	unsigned int panel_inst = 0;
	union dmub_rb_cmd cmd;

	if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
		return false;

	memset(&cmd, 0, sizeof(cmd));
	memcpy(&cmd.pr_update_state.data, update_state_data, sizeof(struct dmub_cmd_pr_update_state_data));

	cmd.pr_update_state.header.type = DMUB_CMD__PR;
	cmd.pr_update_state.header.sub_type = DMUB_CMD__PR_UPDATE_STATE;
	cmd.pr_update_state.header.payload_bytes = sizeof(struct dmub_cmd_pr_update_state_data);
	cmd.pr_update_state.data.panel_inst = panel_inst;

	dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
	return true;
}

bool dp_pr_set_general_cmd(struct dc_link *link, struct dmub_cmd_pr_general_cmd_data *general_cmd_data)
{
	struct dc *dc = link->ctx->dc;
	unsigned int panel_inst = 0;
	union dmub_rb_cmd cmd;

	if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
		return false;

	memset(&cmd, 0, sizeof(cmd));
	memcpy(&cmd.pr_general_cmd.data, general_cmd_data, sizeof(struct dmub_cmd_pr_general_cmd_data));

	cmd.pr_general_cmd.header.type = DMUB_CMD__PR;
	cmd.pr_general_cmd.header.sub_type = DMUB_CMD__PR_GENERAL_CMD;
	cmd.pr_general_cmd.header.payload_bytes = sizeof(struct dmub_cmd_pr_general_cmd_data);
	cmd.pr_general_cmd.data.panel_inst = panel_inst;

	dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
	return true;
}

bool dp_pr_get_state(const struct dc_link *link, uint64_t *state)
{
	const struct dc *dc = link->ctx->dc;
	unsigned int panel_inst = 0;
	uint32_t retry_count = 0;
	uint32_t replay_state = 0;

	if (!dp_pr_get_panel_inst(dc, link, &panel_inst))
		return false;

	do {
		// Send gpint command and wait for ack
		if (!dc_wake_and_execute_gpint(dc->ctx, DMUB_GPINT__GET_REPLAY_STATE, panel_inst,
					       &replay_state, DM_DMUB_WAIT_TYPE_WAIT_WITH_REPLY)) {
			// Return invalid state when GPINT times out
			replay_state = PR_STATE_INVALID;
		}
		/* Copy 32-bit result into 64-bit output */
		*state = replay_state;
	} while (++retry_count <= 1000 && *state == PR_STATE_INVALID);

	// Assert if max retry hit
	if (retry_count >= 1000 && *state == PR_STATE_INVALID) {
		ASSERT(0);
		/* To-do: Add retry fail log */
	}

	return true;
}