AMD-GFX Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Harry Wentland <harry.wentland@amd.com>
To: <amd-gfx@lists.freedesktop.org>
Cc: <Jerry.Zuo@amd.com>, Harry Wentland <harry.wentland@amd.com>
Subject: [PATCH v5 08/13] drm/amd/display Add DC link support for FRL
Date: Tue, 12 May 2026 11:52:39 -0400	[thread overview]
Message-ID: <20260512155244.403854-9-harry.wentland@amd.com> (raw)
In-Reply-To: <20260512155244.403854-1-harry.wentland@amd.com>

Start bringing in the protocol layer for FRL in DC link.
This includes FRL training, timing validation, and other
protocol bits.

Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Fangzhi Zuo <Jerry.Zuo@amd.com>
---
 drivers/gpu/drm/amd/display/dc/link/Makefile  |    2 +
 .../display/dc/link/hwss/link_hwss_hpo_frl.c  |  113 ++
 .../display/dc/link/hwss/link_hwss_hpo_frl.h  |   34 +
 .../drm/amd/display/dc/link/link_detection.c  |  111 ++
 .../gpu/drm/amd/display/dc/link/link_dpms.c   |  185 ++-
 .../gpu/drm/amd/display/dc/link/link_dpms.h   |    1 +
 .../drm/amd/display/dc/link/link_factory.c    |   44 +
 .../drm/amd/display/dc/link/link_validation.c |  298 +++++
 .../drm/amd/display/dc/link/link_validation.h |   13 +
 .../amd/display/dc/link/protocols/link_ddc.c  |   90 ++
 .../amd/display/dc/link/protocols/link_ddc.h  |    4 +
 .../display/dc/link/protocols/link_hdmi_frl.c | 1149 +++++++++++++++++
 .../display/dc/link/protocols/link_hdmi_frl.h |   53 +
 .../amd/display/dc/link/protocols/link_hpd.c  |    1 +
 14 files changed, 2096 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c
 create mode 100644 drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.h
 create mode 100644 drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c
 create mode 100644 drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.h

diff --git a/drivers/gpu/drm/amd/display/dc/link/Makefile b/drivers/gpu/drm/amd/display/dc/link/Makefile
index 0f3670e30232..f558a20eceaa 100644
--- a/drivers/gpu/drm/amd/display/dc/link/Makefile
+++ b/drivers/gpu/drm/amd/display/dc/link/Makefile
@@ -45,6 +45,7 @@ AMD_DISPLAY_FILES += $(AMD_DAL_LINK_ACCESSORIES)
 LINK_HWSS = link_hwss_dio.o link_hwss_dpia.o link_hwss_hpo_dp.o \
 link_hwss_dio_fixed_vs_pe_retimer.o link_hwss_hpo_fixed_vs_pe_retimer_dp.o \
 link_hwss_virtual.o
+LINK_HWSS += link_hwss_hpo_frl.o
 
 AMD_DAL_LINK_HWSS = $(addprefix $(AMDDALPATH)/dc/link/hwss/, \
 $(LINK_HWSS))
@@ -58,6 +59,7 @@ link_dp_training.o link_dp_training_8b_10b.o link_dp_training_128b_132b.o \
 link_dp_training_dpia.o link_dp_training_auxless.o \
 link_dp_training_fixed_vs_pe_retimer.o link_dp_phy.o link_dp_capability.o \
 link_edp_panel_control.o link_dp_panel_replay.o link_dp_irq_handler.o link_dp_dpia_bw.o
+LINK_PROTOCOLS += link_hdmi_frl.o
 
 AMD_DAL_LINK_PROTOCOLS = $(addprefix $(AMDDALPATH)/dc/link/protocols/, \
 $(LINK_PROTOCOLS))
diff --git a/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c
new file mode 100644
index 000000000000..a2c0fa21ee94
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2022 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_hwss_hpo_frl.h"
+#include "core_types.h"
+#include "link/hwss/link_hwss_virtual.h"
+
+static void setup_hpo_frl_stream_attribute(struct pipe_ctx *pipe_ctx)
+{
+	struct hpo_frl_stream_encoder *stream_enc = pipe_ctx->stream_res.hpo_frl_stream_enc;
+	struct dc_stream_state *stream = pipe_ctx->stream;
+	struct pipe_ctx *odm_pipe;
+	struct dc *dc = stream->link->ctx->dc;
+	struct dc_stream_state *temp_stream = &dc->scratch.temp_stream;
+	int odm_combine_num_segments = 1;
+
+	memcpy(temp_stream, stream, sizeof(struct dc_stream_state));
+
+	/* Modify patched_crtc_timing as required for padding */
+	if (pipe_ctx->dsc_padding_params.dsc_hactive_padding) {
+		temp_stream->timing.h_addressable = stream->timing.h_addressable + pipe_ctx->dsc_padding_params.dsc_hactive_padding;
+		temp_stream->timing.h_total = stream->timing.h_total + pipe_ctx->dsc_padding_params.dsc_htotal_padding;
+	}
+
+	/* get number of ODM combine input segments */
+	for (odm_pipe = pipe_ctx->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
+		odm_combine_num_segments++;
+
+	stream_enc->funcs->hdmi_frl_set_stream_attribute(
+			stream_enc,
+			&temp_stream->timing,
+			&stream->link->frl_link_settings.borrow_params,
+			odm_combine_num_segments);
+}
+
+static void disable_hpo_frl_link_output(struct dc_link *link,
+		const struct link_resource *link_res,
+		enum signal_type signal)
+{
+	(void)link_res;
+	if (dc_is_hdmi_frl_signal(signal))
+		link->hpo_frl_link_enc->funcs->disable_link_encoder(link->hpo_frl_link_enc);
+	link->link_enc->funcs->disable_output(link->link_enc, signal);
+}
+
+static void setup_hpo_frl_audio_output(struct pipe_ctx *pipe_ctx,
+		struct audio_output *audio_output, uint32_t audio_inst)
+{
+	pipe_ctx->stream_res.hpo_frl_stream_enc->funcs->hdmi_audio_setup(
+			pipe_ctx->stream_res.hpo_frl_stream_enc,
+			audio_inst,
+			&pipe_ctx->stream->audio_info,
+			&audio_output->crtc_info);
+}
+
+static void enable_hpo_frl_audio_packet(struct pipe_ctx *pipe_ctx)
+{
+	pipe_ctx->stream_res.hpo_frl_stream_enc->funcs->audio_mute_control(
+			pipe_ctx->stream_res.hpo_frl_stream_enc, false);
+}
+
+static void disable_hpo_frl_audio_packet(struct pipe_ctx *pipe_ctx)
+{
+	pipe_ctx->stream_res.hpo_frl_stream_enc->funcs->audio_mute_control(
+		pipe_ctx->stream_res.hpo_frl_stream_enc, true);
+
+	if (pipe_ctx->stream_res.audio)
+		pipe_ctx->stream_res.hpo_frl_stream_enc->funcs->hdmi_audio_disable(
+			pipe_ctx->stream_res.hpo_frl_stream_enc);
+}
+
+static const struct link_hwss hpo_frl_link_hwss = {
+	.setup_stream_encoder = virtual_setup_stream_encoder,
+	.reset_stream_encoder = virtual_reset_stream_encoder,
+	.setup_stream_attribute = setup_hpo_frl_stream_attribute,
+	.disable_link_output = disable_hpo_frl_link_output,
+	.setup_audio_output = setup_hpo_frl_audio_output,
+	.enable_audio_packet = enable_hpo_frl_audio_packet,
+	.disable_audio_packet = disable_hpo_frl_audio_packet,
+};
+
+bool can_use_hpo_frl_link_hwss(const struct dc_link *link,
+		const struct link_resource *link_res)
+{
+	(void)link;
+	return link_res->hpo_frl_link_enc != NULL;
+}
+
+const struct link_hwss *get_hpo_frl_link_hwss(void)
+{
+	return &hpo_frl_link_hwss;
+}
diff --git a/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.h b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.h
new file mode 100644
index 000000000000..ea8d9760132f
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/link/hwss/link_hwss_hpo_frl.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 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
+ *
+ */
+#ifndef __LINK_HWSS_HPO_FRL_H__
+#define __LINK_HWSS_HPO_FRL_H__
+
+#include "link_hwss.h"
+
+bool can_use_hpo_frl_link_hwss(const struct dc_link *link,
+		const struct link_resource *link_res);
+const struct link_hwss *get_hpo_frl_link_hwss(void);
+
+#endif /* __LINK_HWSS_HPO_FRL_H__ */
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c
index 1ac6a22fecfe..a3212fd151d1 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c
+++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c
@@ -41,6 +41,7 @@
 #include "protocols/link_dp_dpia.h"
 #include "protocols/link_dp_phy.h"
 #include "protocols/link_dp_training.h"
+#include "protocols/link_hdmi_frl.h"
 #include "protocols/link_dp_dpia_bw.h"
 #include "accessories/link_dp_trace.h"
 
@@ -78,6 +79,7 @@ static enum ddc_transaction_type get_ddc_transaction_type(enum signal_type sink_
 	case SIGNAL_TYPE_DVI_SINGLE_LINK:
 	case SIGNAL_TYPE_DVI_DUAL_LINK:
 	case SIGNAL_TYPE_HDMI_TYPE_A:
+	case SIGNAL_TYPE_HDMI_FRL:
 	case SIGNAL_TYPE_LVDS:
 	case SIGNAL_TYPE_RGB:
 		transaction_type = DDC_TRANSACTION_TYPE_I2C;
@@ -769,6 +771,60 @@ static bool should_prepare_phy_clocks_for_link_verification(const struct dc *dc,
 	return !can_apply_seamless_boot && reason != DETECT_REASON_BOOT;
 }
 
+static bool is_hdmi_frl_in_use(struct dc_link *link)
+{
+	int i;
+	unsigned int hdmi_conn_count = 0;
+	unsigned int hdmi_stream_count = 0;
+	bool hdmi_frl_in_use = false;
+	bool incoming_link_identical = false;
+
+	/*Enumerate HDMI connector from all present links */
+	for (i = 0; i < link->dc->link_count; i++) {
+		if (link->dc->links[i] != NULL &&
+				dc_is_hdmi_signal(link->dc->links[i]->connector_signal))
+			hdmi_conn_count++;
+	}
+	/* If less than 2 HDMI Connector, assume HPO is always available*/
+	if (hdmi_conn_count < 2)
+		return false;
+
+	/*Enumerate existing HDMI stream count*/
+	for (i = 0; i < link->dc->current_state->stream_count; i++) {
+		if (dc_is_hdmi_signal(link->dc->current_state->streams[i]->signal))
+			hdmi_stream_count++;
+		if (link == link->dc->current_state->streams[i]->link &&
+				(dc_is_hdmi_frl_signal(link->dc->current_state->streams[i]->signal)))
+			incoming_link_identical = true;
+	}
+
+	if (hdmi_stream_count > 1 || (hdmi_stream_count == 1 && !incoming_link_identical)) {
+		for (i = 0; i < link->dc->current_state->stream_count; i++) {
+			if (dc_is_hdmi_frl_signal(
+					link->dc->current_state->streams[i]->signal)) {
+				hdmi_frl_in_use = true;
+				break;
+			}
+		}
+	}
+
+	/* Check if previous link already has been assigned with FRL*/
+	if (!hdmi_frl_in_use && !incoming_link_identical) {
+		for (i = 0; i < link->dc->link_count; i++) {
+			if (link->dc->links[i] != NULL &&
+					link->dc->links[i]->local_sink != NULL &&
+					link != link->dc->links[i] &&
+					dc_is_hdmi_frl_signal(
+							link->dc->links[i]->local_sink->sink_signal)) {
+				hdmi_frl_in_use = true;
+				break;
+			}
+		}
+	}
+
+	return hdmi_frl_in_use;
+}
+
 static void prepare_phy_clocks_for_destructive_link_verification(const struct dc *dc)
 {
 	dc_z10_restore(dc);
@@ -796,6 +852,16 @@ static void verify_link_capability_destructive(struct dc_link *link,
 		dp_verify_link_cap_with_retries(
 				link, &known_limit_link_setting,
 				LINK_TRAINING_MAX_VERIFY_RETRY);
+	} else if (dc_is_hdmi_signal(link->local_sink->sink_signal)) {
+		if (!is_hdmi_frl_in_use(link)) {
+			link_set_all_streams_dpms_off_for_link(link);
+			hdmi_frl_verify_link_cap(link, &link->frl_reported_link_cap);
+			link->local_sink->sink_signal = (link->frl_verified_link_cap.frl_link_rate != HDMI_FRL_LINK_RATE_DISABLE)
+												? SIGNAL_TYPE_HDMI_FRL : SIGNAL_TYPE_HDMI_TYPE_A;
+		} else {
+			link->local_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+			link->frl_verified_link_cap.frl_link_rate = HDMI_FRL_LINK_RATE_DISABLE;
+		}
 	} else {
 		ASSERT(0);
 	}
@@ -815,6 +881,13 @@ static void verify_link_capability_non_destructive(struct dc_link *link)
 			link->verified_link_cap = link->reported_link_cap;
 		else
 			link->verified_link_cap = dp_get_max_link_cap(link);
+	} else if (dc_is_hdmi_signal(link->local_sink->sink_signal)) {
+		link->verified_link_cap = link->reported_link_cap;
+
+		if (is_hdmi_frl_in_use(link)) {
+			link->local_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
+			link->frl_verified_link_cap.frl_link_rate = HDMI_FRL_LINK_RATE_DISABLE;
+		}
 	}
 }
 
@@ -850,6 +923,27 @@ static bool should_verify_link_capability_destructively(struct dc_link *link,
 				destrictive = false;
 			}
 		}
+	} else if (dc_is_hdmi_signal(link->local_sink->sink_signal) && link->link_enc &&
+				link->link_enc->features.flags.bits.IS_HDMI_FRL_CAPABLE &&
+				link->local_sink->edid_caps.max_frl_rate != 0) {
+		int i = 0;
+		struct pipe_ctx *pipes =
+				link->dc->current_state->res_ctx.pipe_ctx;
+
+		destrictive = true;
+		if (is_hdmi_frl_in_use(link)) {
+			destrictive = false;
+		} else if (link->dc->config.skip_frl_pretraining) {
+			for (i = 0; i < MAX_PIPES; i++) {
+				if (pipes[i].stream != NULL &&
+					pipes[i].stream->link == link) {
+					/*If link is already active, skip PHY programming*/
+					if (link->link_status.link_active) {
+						destrictive = false;
+					}
+				}
+			}
+		}
 	}
 
 	return destrictive;
@@ -1007,6 +1101,7 @@ static bool detect_link_and_local_sink(struct dc_link *link,
 
 		/* From Disconnected-to-Connected. */
 		switch (link->connector_signal) {
+		case SIGNAL_TYPE_HDMI_FRL:
 		case SIGNAL_TYPE_HDMI_TYPE_A: {
 			sink_caps.transaction_type = DDC_TRANSACTION_TYPE_I2C;
 			if (aud_support->hdmi_audio_native)
@@ -1234,6 +1329,13 @@ static bool detect_link_and_local_sink(struct dc_link *link,
 		if (dc_is_hdmi_signal(link->connector_signal))
 			read_scdc_caps(link->ddc, link->local_sink);
 
+		if (dc_is_hdmi_signal(link->connector_signal) && dc->debug.enable_hdmi_idcc) {
+			memset(&link->hdmi_cable_id, 0, sizeof(union hdmi_idcc_cable_id));
+			read_idcc_data(link->ddc, HDMI_IDCC_SCOPE_RW_CA,
+				link->hdmi_cable_id.raw, 0, 4);
+		}
+		if (sink->edid_caps.rr_capable)
+			hdmi_frl_write_read_request_enable(link->ddc);
 		/* When FreeSync is toggled through OSD,
 		 * we see same EDID no matter what. Check MCCS caps
 		 * to see if we should update FreeSync caps now.
@@ -1248,6 +1350,12 @@ static bool detect_link_and_local_sink(struct dc_link *link,
 				same_edid = false;
 		}
 
+		if (reason != DETECT_REASON_FALLBACK && dc_is_hdmi_signal(link->connector_signal) &&
+				link->link_enc->features.flags.bits.IS_HDMI_FRL_CAPABLE && sink->edid_caps.max_frl_rate != 0) {
+			hdmi_frl_retrieve_link_cap(link, link->local_sink);
+		}
+		if (reason == DETECT_REASON_FALLBACK && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL)
+			same_edid = false;
 		if (link->connector_signal == SIGNAL_TYPE_DISPLAY_PORT &&
 		    sink_caps.transaction_type ==
 		    DDC_TRANSACTION_TYPE_I2C_OVER_AUX) {
@@ -1262,6 +1370,8 @@ static bool detect_link_and_local_sink(struct dc_link *link,
 				link_disconnect_remap(prev_sink, link);
 				sink = prev_sink;
 				prev_sink = NULL;
+				if (reason == DETECT_REASON_FALLBACK && sink->sink_signal == SIGNAL_TYPE_HDMI_FRL)
+						sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A;
 			}
 
 			if (!sink->edid_caps.analog)
@@ -1529,6 +1639,7 @@ bool link_is_hdcp22(struct dc_link *link, enum signal_type signal)
 	case SIGNAL_TYPE_DVI_SINGLE_LINK:
 	case SIGNAL_TYPE_DVI_DUAL_LINK:
 	case SIGNAL_TYPE_HDMI_TYPE_A:
+	case SIGNAL_TYPE_HDMI_FRL:
 		ret = (link->hdcp_caps.rx_caps.fields.version == 0x4) ? 1:0;
 		break;
 	default:
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c
index d52155e86f0f..9096b34fc35f 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.c
+++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.c
@@ -48,6 +48,7 @@
 #include "protocols/link_edp_panel_control.h"
 #include "protocols/link_dp_panel_replay.h"
 #include "protocols/link_dp_dpia_bw.h"
+#include "link/protocols/link_hdmi_frl.h"
 
 #include "dm_helpers.h"
 #include "link_enc_cfg.h"
@@ -71,6 +72,10 @@
 	DC_LOG_RETIMER_REDRIVER(  \
 		__VA_ARGS__)
 
+#define FRL_INFO(...) \
+	DC_LOG_HDMI_FRL_LTP(  \
+		__VA_ARGS__)
+
 #define MAX_MTP_SLOT_COUNT 64
 #define LINK_TRAINING_ATTEMPTS 4
 #define PEAK_FACTOR_X1000 1006
@@ -550,14 +555,21 @@ static void update_psp_stream_config(struct pipe_ctx *pipe_ctx, bool dpms_off)
 	if (dp_is_128b_132b_signal(pipe_ctx))
 		config.stream_enc_idx =
 				(uint8_t)(pipe_ctx->stream_res.hpo_dp_stream_enc->id - ENGINE_ID_HPO_DP_0);
+	if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal))
+		config.stream_enc_idx =
+				(uint8_t)(pipe_ctx->stream_res.hpo_frl_stream_enc->id - ENGINE_ID_HPO_0);
 
 	/* dig back end */
 	config.dig_be = pipe_ctx->stream->link->link_enc_hw_inst;
+	if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal))
+		config.dig_be = (uint8_t)pipe_ctx->stream_res.hpo_frl_stream_enc->stream_enc_inst;
 
 	/* link encoder index */
 	config.link_enc_idx = (uint8_t)(link_enc->transmitter - TRANSMITTER_UNIPHY_A);
 	if (dp_is_128b_132b_signal(pipe_ctx))
 		config.link_enc_idx = (uint8_t)pipe_ctx->link_res.hpo_dp_link_enc->inst;
+	if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal))
+		config.link_enc_idx = (uint8_t)pipe_ctx->stream->link->hpo_frl_link_enc->inst;
 
 	/* dio output index is dpia index for DPIA endpoint & dcio index by default */
 	if (pipe_ctx->stream->link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA)
@@ -577,6 +589,7 @@ static void update_psp_stream_config(struct pipe_ctx *pipe_ctx, bool dpms_off)
 	config.assr_enabled = (panel_mode == DP_PANEL_MODE_EDP) ? 1 : 0;
 	config.mst_enabled = (pipe_ctx->stream->signal ==
 			SIGNAL_TYPE_DISPLAY_PORT_MST) ? 1 : 0;
+	config.frl_enabled = dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) ? 1 : 0;
 	config.dp2_enabled = dp_is_128b_132b_signal(pipe_ctx) ? 1 : 0;
 	config.usb4_enabled = (pipe_ctx->stream->link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA) ?
 			1 : 0;
@@ -588,6 +601,38 @@ static void update_psp_stream_config(struct pipe_ctx *pipe_ctx, bool dpms_off)
 	cp_psp->funcs.update_stream_config(cp_psp->handle, &config);
 }
 
+void link_wait_for_unlocked(struct dc_link *link)
+{
+	unsigned long long enter_timestamp;
+	unsigned long long finish_timestamp;
+	unsigned long long time_taken_in_ns;
+	bool waited = false;
+
+	DC_LOGGER_INIT(link->ctx->logger);
+
+	enter_timestamp = dm_get_timestamp(link->ctx);
+
+	while (link->is_link_locked) { // busy wait
+		if (!waited) {
+			DC_LOG_DC("%s: 0x%p ...", __func__, link);
+
+			waited = true;
+		}
+
+		udelay(1);
+	}
+
+	if (!waited)
+		return;
+
+	finish_timestamp = dm_get_timestamp(link->ctx);
+	time_taken_in_ns = dm_get_elapse_time_in_ns(link->ctx,
+							finish_timestamp, enter_timestamp);
+
+	DC_LOG_DC("%s: 0x%p took %llu ms.", __func__,
+			link, div_u64(time_taken_in_ns, 1000000));
+}
+
 static void set_avmute(struct pipe_ctx *pipe_ctx, bool enable)
 {
 	struct dc  *dc = pipe_ctx->stream->ctx->dc;
@@ -595,6 +640,9 @@ static void set_avmute(struct pipe_ctx *pipe_ctx, bool enable)
 	if (!dc_is_hdmi_signal(pipe_ctx->stream->signal))
 		return;
 
+	if (pipe_ctx->stream->timing.flags.DSC)
+		return;
+
 	dc->hwss.set_avmute(pipe_ctx, enable);
 }
 
@@ -1813,6 +1861,13 @@ static void enable_link_hdmi(struct pipe_ctx *pipe_ctx)
 			stream->phy_pix_clk,
 			(stream->timing.flags.LTE_340MCSC_SCRAMBLE != 0));
 
+	if (dc->debug.enable_hdmi_idcc) {
+		union hdmi_idcc_source_id source_id;
+
+		source_id.raw = 0xff;
+		write_idcc_data(stream->link->ddc, HDMI_IDCC_SCOPE_WRITE,
+				&source_id.raw, 0, 1);
+	}
 	memset(&stream->link->cur_link_settings, 0,
 			sizeof(struct dc_link_settings));
 
@@ -1838,6 +1893,109 @@ static void enable_link_hdmi(struct pipe_ctx *pipe_ctx)
 		read_scdc_data(link->ddc);
 }
 
+static enum dc_status enable_link_hdmi_frl(struct pipe_ctx *pipe_ctx)
+{
+	enum link_result link_stat = LINK_RESULT_UNKNOWN;
+	enum dc_status status = DC_OK;
+	struct dc_stream_state *stream = pipe_ctx->stream;
+	struct dc *core_dc = pipe_ctx->stream->ctx->dc;
+	enum clock_source_id frl_phy_clock_source_id;
+	bool frl_poll_start = true;
+
+	DC_LOGGER_INIT(stream->ctx->logger);
+
+	if ((!stream->link->link_enc) ||
+			(!stream->link->hpo_frl_link_enc) ||
+			(!core_dc->res_pool->dccg->funcs->enable_hdmicharclk) ||
+			(!(pipe_ctx->stream_res.hpo_frl_stream_enc)))
+		return DC_ERROR_UNEXPECTED;
+
+	/* get link settings for video mode timing */
+	hdmi_frl_decide_link_settings(stream, &stream->link->frl_link_settings, &pipe_ctx->dsc_padding_params);
+
+	switch (stream->link->frl_link_settings.frl_link_rate) {
+	case HDMI_FRL_LINK_RATE_3GBPS:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 166667;
+		break;
+	case HDMI_FRL_LINK_RATE_6GBPS:
+	case HDMI_FRL_LINK_RATE_6GBPS_4LANE:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 333333;
+		break;
+	case HDMI_FRL_LINK_RATE_8GBPS:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 444444;
+		break;
+	case HDMI_FRL_LINK_RATE_10GBPS:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 555555;
+		break;
+	case HDMI_FRL_LINK_RATE_12GBPS:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 666667;
+		break;
+	case HDMI_FRL_LINK_RATE_16GBPS:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 888889;
+		break;
+	case HDMI_FRL_LINK_RATE_20GBPS:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 1111111;
+		break;
+	case HDMI_FRL_LINK_RATE_24GBPS:
+		pipe_ctx->stream_res.pix_clk_params.requested_sym_clk = 1333333;
+		break;
+	default:
+		break;
+	}
+
+	stream->phy_pix_clk = pipe_ctx->stream_res.pix_clk_params.requested_sym_clk;
+
+	memset(&stream->link->cur_link_settings, 0,
+			sizeof(struct dc_link_settings));
+
+	/* Find proper clock source in HDMI FRL mode for phy used for DCCG */
+	frl_phy_clock_source_id = hdmi_frl_find_matching_phypll(stream->link);
+
+	FRL_INFO("FRL LINK TRAINING:  LTS:P Start\n");
+	/* Setup FRL PHY, enable HDMI character clock and HPO link encoder */
+	core_dc->hwss.setup_hdmi_frl_link(stream->link,
+			(pipe_ctx->stream_res.hpo_frl_stream_enc->id - ENGINE_ID_HPO_0),
+			frl_phy_clock_source_id);
+
+	link_stat = hdmi_frl_perform_link_training_with_retries(stream->link);
+
+	if (core_dc->res_pool->dccg->funcs->set_valid_pixel_rate)
+		core_dc->res_pool->dccg->funcs->set_valid_pixel_rate(
+						core_dc->res_pool->dccg,
+						core_dc->clk_mgr->funcs->get_dtb_ref_clk_frequency(core_dc->clk_mgr),
+						pipe_ctx->stream_res.tg->inst,
+						(stream->timing.pix_clk_100hz / 10));
+
+	/* Enable FRL packet transmission */
+	if (link_stat == LINK_RESULT_SUCCESS) {
+		stream->link->hpo_frl_link_enc->funcs->enable_output(
+				stream->link->hpo_frl_link_enc);
+		if (stream->link->frl_flags.apply_vsdb_rcc_wa)
+			stream->link->hpo_frl_link_enc->funcs->apply_vsdb_rcc_wa(stream->link->hpo_frl_link_enc);
+		if (frl_poll_start)
+			hdmi_frl_poll_start(stream->link->ddc);
+
+		FRL_INFO("FRL LINK TRAINING:  LTS:P Success\n");
+
+		/* Set HDMISTREAMCLK source to DTBCLK0 and bypass DTO */
+		if (core_dc->res_pool->dccg->funcs->set_hdmistreamclk) {
+			core_dc->res_pool->dccg->funcs->set_hdmistreamclk(
+					core_dc->res_pool->dccg,
+					DTBCLK0,
+					pipe_ctx->stream_res.tg->inst);
+		}
+
+		pipe_ctx->stream_res.hpo_frl_stream_enc->funcs->hdmi_frl_enable(
+			pipe_ctx->stream_res.hpo_frl_stream_enc,
+			pipe_ctx->stream_res.tg->inst);
+	} else {
+		status = DC_FAIL_HDMI_FRL_LINK_TRAINING;
+		stream->link->frl_link_settings.frl_link_rate = 0;
+	}
+
+	return status;
+}
+
 static enum dc_status enable_link_dp(struct dc_state *state,
 				     struct pipe_ctx *pipe_ctx)
 {
@@ -2076,6 +2234,14 @@ static enum dc_status enable_link(
 		enable_link_hdmi(pipe_ctx);
 		status = DC_OK;
 		break;
+	case SIGNAL_TYPE_HDMI_FRL:
+		if (link->local_sink &&
+			link->local_sink->edid_caps.panel_patch.delay_hdmi_link_training &&
+			stream->timing.pix_clk_100hz == 6627500) {
+			msleep(link->local_sink->edid_caps.panel_patch.delay_hdmi_link_training);
+		}
+		status = enable_link_hdmi_frl(pipe_ctx);
+		break;
 	case SIGNAL_TYPE_LVDS:
 		enable_link_lvds(pipe_ctx);
 		status = DC_OK;
@@ -2179,6 +2345,8 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx)
 
 	if (dp_is_128b_132b_signal(pipe_ctx))
 		vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg;
+	if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal))
+		vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg;
 	if (dc_is_virtual_signal(pipe_ctx->stream->signal))
 		return;
 
@@ -2191,6 +2359,8 @@ void link_set_dpms_off(struct pipe_ctx *pipe_ctx)
 		}
 	}
 
+	link_wait_for_unlocked(link);
+
 	if (!pipe_ctx->stream->sink->edid_caps.panel_patch.skip_avmute) {
 		if (dc_is_hdmi_signal(pipe_ctx->stream->signal))
 			set_avmute(pipe_ctx, true);
@@ -2293,6 +2463,8 @@ void link_set_dpms_on(
 
 	if (dp_is_128b_132b_signal(pipe_ctx))
 		vpg = pipe_ctx->stream_res.hpo_dp_stream_enc->vpg;
+	if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal))
+		vpg = pipe_ctx->stream_res.hpo_frl_stream_enc->vpg;
 	if (dc_is_virtual_signal(pipe_ctx->stream->signal))
 		return;
 
@@ -2307,11 +2479,13 @@ void link_set_dpms_on(
 		}
 	}
 
+	link_wait_for_unlocked(stream->link);
 	if (!dc->config.unify_link_enc_assignment)
 		link_enc = link_enc_cfg_get_link_enc(link);
 	ASSERT(link_enc);
 
 	if (!dc_is_virtual_signal(pipe_ctx->stream->signal)
+			&& !dc_is_hdmi_frl_signal(pipe_ctx->stream->signal)
 			&& !dp_is_128b_132b_signal(pipe_ctx)) {
 		if (link_enc)
 			link_enc->funcs->setup(
@@ -2321,9 +2495,14 @@ void link_set_dpms_on(
 
 	pipe_ctx->stream->link->link_state_valid = true;
 
+	if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal))
+		hdmi_frl_decide_link_settings(stream, &stream->link->frl_link_settings, &pipe_ctx->dsc_padding_params);
+
 	if (pipe_ctx->stream_res.tg->funcs->set_out_mux) {
 		if (dp_is_128b_132b_signal(pipe_ctx))
 			otg_out_dest = OUT_MUX_HPO_DP;
+		else if (dc_is_hdmi_frl_signal(pipe_ctx->stream->signal))
+			otg_out_dest = OUT_MUX_HPO_FRL;
 		else
 			otg_out_dest = OUT_MUX_DIO;
 		pipe_ctx->stream_res.tg->funcs->set_out_mux(pipe_ctx->stream_res.tg, otg_out_dest);
@@ -2414,8 +2593,9 @@ void link_set_dpms_on(
 		 * show the stream anyway. But MST displays can't proceed
 		 * without link training.
 		 */
-		if (status != DC_FAIL_DP_LINK_TRAINING ||
-				pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) {
+			if ((status != DC_FAIL_DP_LINK_TRAINING &&
+					status != DC_FAIL_HDMI_FRL_LINK_TRAINING) ||
+					pipe_ctx->stream->signal == SIGNAL_TYPE_DISPLAY_PORT_MST) {
 			if (false == stream->link->link_status.link_active)
 				disable_link(stream->link, &pipe_ctx->link_res,
 						pipe_ctx->stream->signal);
@@ -2435,6 +2615,7 @@ void link_set_dpms_on(
 	 * from transmitter control.
 	 */
 	if (!(dc_is_virtual_signal(pipe_ctx->stream->signal) ||
+			dc_is_hdmi_frl_signal(pipe_ctx->stream->signal) ||
 			dp_is_128b_132b_signal(pipe_ctx))) {
 
 			if (link_enc)
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_dpms.h b/drivers/gpu/drm/amd/display/dc/link/link_dpms.h
index bd6fc63064a3..e8662147dd8e 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_dpms.h
+++ b/drivers/gpu/drm/amd/display/dc/link/link_dpms.h
@@ -50,4 +50,5 @@ struct fixed31_32 link_calculate_sst_avg_time_slots_per_mtp(
 void link_set_dsc_on_stream(struct pipe_ctx *pipe_ctx, bool enable);
 bool link_set_dsc_enable(struct pipe_ctx *pipe_ctx, bool enable);
 bool link_update_dsc_config(struct pipe_ctx *pipe_ctx);
+void link_wait_for_unlocked(struct dc_link *link);
 #endif /* __DC_LINK_DPMS_H__ */
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_factory.c b/drivers/gpu/drm/amd/display/dc/link/link_factory.c
index 2cc646425207..36c0df83aa5b 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_factory.c
+++ b/drivers/gpu/drm/amd/display/dc/link/link_factory.c
@@ -42,6 +42,7 @@
 #include "protocols/link_dp_training.h"
 #include "protocols/link_edp_panel_control.h"
 #include "protocols/link_dp_panel_replay.h"
+#include "protocols/link_hdmi_frl.h"
 #include "protocols/link_hpd.h"
 #include "gpio_service_interface.h"
 #include "atomfirmware.h"
@@ -101,6 +102,8 @@ static void construct_link_service_validation(struct link_service *link_srv)
 	link_srv->validate_mode_timing = link_validate_mode_timing;
 	link_srv->dp_link_bandwidth_kbps = dp_link_bandwidth_kbps;
 	link_srv->validate_dp_tunnel_bandwidth = link_validate_dp_tunnel_bandwidth;
+	link_srv->frl_link_bandwidth_kbps = frl_link_bandwidth_kbps;
+	link_srv->frl_margin_check_uncompressed_video = frl_capacity_computations_uncompressed_video;
 	link_srv->dp_required_hblank_size_bytes = dp_required_hblank_size_bytes;
 }
 
@@ -121,6 +124,7 @@ static void construct_link_service_dpms(struct link_service *link_srv)
 	link_srv->set_dsc_on_stream = link_set_dsc_on_stream;
 	link_srv->set_dsc_enable = link_set_dsc_enable;
 	link_srv->update_dsc_config = link_update_dsc_config;
+	link_srv->wait_for_unlocked = link_wait_for_unlocked;
 }
 
 /* link ddc implements generic display communication protocols such as i2c, aux
@@ -243,6 +247,25 @@ static void construct_link_service_dp_panel_replay(struct link_service *link_srv
 	link_srv->dp_pr_get_state = dp_pr_get_state;
 }
 
+/* link hdmi frl implements FRL link capability and link training related
+ * functions. FRL link is established by order of retrieve_link, verify_link,
+ * and poll_status. Other helper functions exist to obtain information required
+ * to maintain the correct sequence according to HDMI specification. Each
+ * sequence and state inside link training functions are timing sensitive and order sensitive.
+ * It is mandatory that these functions are debugged with FRL_LTP output message
+ * configurable in DSAT.  Any changes in the LT sequence should follow the HDMI
+ * specification as much as possible and tested through HDMI electrical and
+ * link layer compliance.
+ */
+static void construct_link_service_hdmi_frl(struct link_service *link_srv)
+{
+	link_srv->hdmi_frl_poll_status_flag = hdmi_frl_poll_status_flag;
+	link_srv->hdmi_frl_get_verified_link_cap =
+			hdmi_frl_get_verified_link_cap;
+	link_srv->hdmi_frl_set_preferred_link_settings =
+			hdmi_frl_set_preferred_link_settings;
+}
+
 /* link dp cts implements dp compliance test automation protocols and manual
  * testing interfaces for debugging and certification purpose.
  */
@@ -296,6 +319,7 @@ static void construct_link_service(struct link_service *link_srv)
 	construct_link_service_dp_irq_handler(link_srv);
 	construct_link_service_edp_panel_control(link_srv);
 	construct_link_service_dp_panel_replay(link_srv);
+	construct_link_service_hdmi_frl(link_srv);
 	construct_link_service_dp_cts(link_srv);
 	construct_link_service_dp_trace(link_srv);
 }
@@ -401,6 +425,8 @@ static void link_destruct(struct dc_link *link)
 		link->link_enc->funcs->destroy(&link->link_enc);
 	}
 
+	if (link->hpo_frl_link_enc)
+		link->hpo_frl_link_enc->funcs->destroy(&link->hpo_frl_link_enc);
 	if (link->local_sink)
 		dc_sink_release(link->local_sink);
 
@@ -622,6 +648,7 @@ static bool construct_phy(struct dc_link *link,
 
 	DC_LOG_DC("BIOS object table - DP_IS_USB_C: %d", link->link_enc->features.flags.bits.DP_IS_USB_C);
 	DC_LOG_DC("BIOS object table - IS_DP2_CAPABLE: %d", link->link_enc->features.flags.bits.IS_DP2_CAPABLE);
+	DC_LOG_DC("BIOS object table - IS_HDMI_FRL_CAPABLE: %d", link->link_enc->features.flags.bits.IS_HDMI_FRL_CAPABLE);
 
 	switch (link->link_id.id) {
 	case CONNECTOR_ID_HDMI_TYPE_A:
@@ -825,12 +852,29 @@ static bool construct_phy(struct dc_link *link,
 	 */
 	program_hpd_filter(link);
 
+	/* If the connector is HDMI FRL capable, also create an HPO link encoder */
+	if ((link->link_enc->features.flags.bits.IS_HDMI_FRL_CAPABLE) &&
+		(!link->link_enc->features.flags.bits.DP_IS_USB_C) &&
+			(link->dc->res_pool->funcs->hpo_frl_link_enc_create)) {
+		enum engine_id hpo_eng_id;
+		hpo_eng_id = ENGINE_ID_HPO_0;
+
+		link->hpo_frl_link_enc = link->dc->res_pool->funcs->hpo_frl_link_enc_create(
+				hpo_eng_id,
+				dc_ctx);
+		if (link->hpo_frl_link_enc == NULL) {
+			DC_ERROR("Failed to create HPO link encoder!\n");
+			goto hpo_enc_create_fail;
+		}
+	}
+
 	link->psr_settings.psr_vtotal_control_support = false;
 	link->psr_settings.psr_version = DC_PSR_VERSION_UNSUPPORTED;
 	link->replay_settings.config.replay_version = DC_REPLAY_VERSION_UNSUPPORTED;
 
 	DC_LOG_DC("BIOS object table - %s finished successfully.\n", __func__);
 	return true;
+hpo_enc_create_fail:
 device_tag_fail:
 link_enc_create_fail:
 panel_cntl_create_fail:
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_validation.c b/drivers/gpu/drm/amd/display/dc/link/link_validation.c
index ec083e8b75b4..d7bd352880b3 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_validation.c
+++ b/drivers/gpu/drm/amd/display/dc/link/link_validation.c
@@ -31,6 +31,7 @@
 #include "link_validation.h"
 #include "protocols/link_dp_capability.h"
 #include "protocols/link_dp_dpia_bw.h"
+#include "protocols/link_hdmi_frl.h"
 #include "resource.h"
 
 #define DC_LOGGER_INIT(logger)
@@ -263,6 +264,195 @@ uint32_t dp_link_bandwidth_kbps(
 	return link_rate_per_lane_kbps * link_settings->lane_count / 10000 * total_data_bw_efficiency_x10000;
 }
 
+uint32_t frl_link_bandwidth_kbps(enum hdmi_frl_link_rate link_rate)
+{
+	switch (link_rate) {
+	case HDMI_FRL_LINK_RATE_3GBPS:
+		return 9000000;
+	case HDMI_FRL_LINK_RATE_6GBPS:
+		return 18000000;
+	case HDMI_FRL_LINK_RATE_6GBPS_4LANE:
+		return 24000000;
+	case HDMI_FRL_LINK_RATE_8GBPS:
+		return 32000000;
+	case HDMI_FRL_LINK_RATE_10GBPS:
+		return 40000000;
+	case HDMI_FRL_LINK_RATE_12GBPS:
+		return 48000000;
+	case HDMI_FRL_LINK_RATE_16GBPS:
+		return 64000000;
+	case HDMI_FRL_LINK_RATE_20GBPS:
+		return 80000000;
+	case HDMI_FRL_LINK_RATE_24GBPS:
+		return 96000000;
+	default:
+		return 0;
+	}
+}
+
+bool frl_capacity_computations_common(struct frl_cap_chk_params_fixed31_32 *params,
+	struct frl_cap_chk_intermediates_fixed31_32 *inter)
+{
+	struct fixed31_32 audio_bw_reserve = dc_fixpt_from_int((params->compressed ? 192000 : 0));
+	struct fixed31_32 pixel_rate_tolerance = dc_fixpt_div_int(dc_fixpt_from_int(5), 1000);
+	struct fixed31_32 max_audio_tol_rate;
+	struct fixed31_32 overhead_m;
+
+	inter->c_frl_sb = 4 * C_FRL_CB + params->lanes;
+	inter->overhead_sb = dc_fixpt_div_int(dc_fixpt_from_int(params->lanes), inter->c_frl_sb);
+	inter->overhead_rs = dc_fixpt_div_int(dc_fixpt_from_int(32), inter->c_frl_sb);
+	inter->overhead_map = dc_fixpt_div_int(dc_fixpt_from_int(25), (inter->c_frl_sb * 10));
+
+	inter->overhead_min = dc_fixpt_add(inter->overhead_sb, inter->overhead_rs);
+	inter->overhead_min = dc_fixpt_add(inter->overhead_min, inter->overhead_map);
+	overhead_m = dc_fixpt_div_int(dc_fixpt_from_int(3), 1000);
+	inter->overhead_max = dc_fixpt_add(inter->overhead_min, overhead_m);
+
+	pixel_rate_tolerance = dc_fixpt_add_int(pixel_rate_tolerance, 1);
+
+	inter->f_pixel_clock_max = dc_fixpt_mul(params->f_pixel_clock_nominal, pixel_rate_tolerance);
+	inter->t_line = dc_fixpt_div(dc_fixpt_from_int(params->h_active + params->h_blank), inter->f_pixel_clock_max);
+	inter->r_bit_min = dc_fixpt_div_int(dc_fixpt_from_int(TOLERANCE_FRL_BIT), 1000000);
+	inter->r_bit_min = dc_fixpt_sub(dc_fixpt_from_int(1), inter->r_bit_min);
+	inter->r_bit_min = dc_fixpt_mul(params->r_bit_nominal, inter->r_bit_min);
+
+	inter->r_frl_char_min = dc_fixpt_div_int(inter->r_bit_min, 18);
+	inter->c_frl_line = dc_fixpt_mul(inter->t_line, inter->r_frl_char_min);
+	inter->c_frl_line = dc_fixpt_mul_int(inter->c_frl_line, params->lanes);
+
+	switch (params->audio_packet_type) {
+	case 0x02:
+		if (params->layout == 0)
+			inter->ap = dc_fixpt_div_int(dc_fixpt_from_int(25), 100);
+		else if (params->layout == 1)
+			inter->ap = dc_fixpt_from_int(1);
+		break;
+	case 0x08:
+		inter->ap = dc_fixpt_div_int(dc_fixpt_from_int(25), 100);
+		break;
+	case 0x09:
+		inter->ap = dc_fixpt_from_int(1);
+		break;
+	case 0x07:
+	case 0x0e:
+	case 0x0f:
+	case 0x0b:
+	case 0x0c:
+		/* Unsupported audio format */
+		return false;
+	default:
+		inter->ap = dc_fixpt_from_int(0);
+	}
+
+	inter->r_ap = dc_fixpt_max(audio_bw_reserve, dc_fixpt_mul(params->f_audio, inter->ap));
+	inter->r_ap = dc_fixpt_add(inter->r_ap, dc_fixpt_from_int(2 * ACR_RATE_MAX));
+	max_audio_tol_rate = dc_fixpt_div_int(dc_fixpt_from_int(TOLERANCE_AUDIO_CLOCK), 1000000);
+	max_audio_tol_rate = dc_fixpt_add(dc_fixpt_from_int(1), max_audio_tol_rate);
+	inter->r_ap = dc_fixpt_mul(inter->r_ap, max_audio_tol_rate);
+
+	inter->avg_audio_packets_line = dc_fixpt_mul(inter->r_ap, inter->t_line);
+	inter->avg_audio_packets_line = dc_fixpt_div_int(inter->avg_audio_packets_line, 1000000);
+	inter->audio_packets_line = dc_fixpt_ceil(inter->avg_audio_packets_line);
+
+	inter->blank_audio_min = 32 + 32 * inter->audio_packets_line;
+
+	params->borrow_params.audio_packets_line = inter->audio_packets_line;
+
+	return true;
+}
+
+bool frl_capacity_computations_uncompressed_video(struct frl_cap_chk_params_fixed31_32 *params,
+	struct frl_cap_chk_intermediates_fixed31_32 *inter)
+{
+	bool res;
+	int k_420;
+	struct fixed31_32 k_cd;
+	struct fixed31_32 c_frl_free;
+	int c_frl_free_int;
+	int c_frl_rc_margin;
+	struct fixed31_32 c_frl_rc_savings;
+	int c_frl_rc_savings_int;
+	int bpp;
+	struct fixed31_32 bytes_line;
+	int tb_active;
+	int tb_blank;
+	struct fixed31_32 f_tb_average;
+	struct fixed31_32 t_active_ref;
+	struct fixed31_32 t_blank_ref;
+	struct fixed31_32 t_active_min;
+	struct fixed31_32 t_blank_min;
+	int c_frl_actual_payload;
+	struct fixed31_32 utilization;
+
+	res = frl_capacity_computations_common(params, inter);
+	if (res != true)
+		return res;
+
+	k_420 = params->pixel_encoding == HDMI_FRL_PIXEL_ENCODING_420 ? 2 : 1;
+	if (params->pixel_encoding == HDMI_FRL_PIXEL_ENCODING_422)
+		k_cd = dc_fixpt_from_int(1);
+	else
+		k_cd = dc_fixpt_div_int(dc_fixpt_from_int(params->bpc), 8);
+
+	c_frl_free = dc_fixpt_div_int(dc_fixpt_mul_int(k_cd, params->h_blank), k_420);
+	c_frl_free = dc_fixpt_sub_int(c_frl_free, 32 * (1 + inter->audio_packets_line) + 7);
+	c_frl_free = dc_fixpt_max(c_frl_free, dc_fixpt_from_int(0));
+	c_frl_free_int = dc_fixpt_ceil(c_frl_free);
+	c_frl_rc_margin  = 4;
+	c_frl_rc_savings = dc_fixpt_mul_int(dc_fixpt_div_int(dc_fixpt_from_int(7), 8), c_frl_free_int);
+	c_frl_rc_savings = dc_fixpt_sub_int(c_frl_rc_savings, c_frl_rc_margin);
+	c_frl_rc_savings_int = dc_fixpt_floor(dc_fixpt_max(c_frl_rc_savings, dc_fixpt_from_int(0)));
+
+	bpp              = dc_fixpt_ceil(dc_fixpt_mul_int(dc_fixpt_div_int(k_cd, k_420), 24));
+	bytes_line       = dc_fixpt_div_int(dc_fixpt_from_int(bpp * params->h_active), 8);
+	tb_active        = dc_fixpt_ceil(dc_fixpt_div_int(bytes_line, 3));
+	tb_blank         = dc_fixpt_ceil(dc_fixpt_div_int(dc_fixpt_mul_int(k_cd, params->h_blank), k_420));
+
+	if (!(inter->blank_audio_min <= tb_blank)) {
+		return false;
+	}
+
+	f_tb_average = dc_fixpt_div_int(inter->f_pixel_clock_max, (params->h_active + params->h_blank));
+	f_tb_average = dc_fixpt_mul_int(f_tb_average, (tb_active + tb_blank));
+
+	t_active_ref = dc_fixpt_div(dc_fixpt_from_int(params->h_active), dc_fixpt_from_int(params->h_active + params->h_blank));
+	t_active_ref = dc_fixpt_mul(inter->t_line, t_active_ref);
+
+	t_blank_ref = dc_fixpt_div(dc_fixpt_from_int(params->h_blank), dc_fixpt_from_int(params->h_active + params->h_blank));
+	t_blank_ref = dc_fixpt_mul(inter->t_line, t_blank_ref);
+
+	t_active_min = dc_fixpt_sub(dc_fixpt_from_int(1), inter->overhead_max);
+	t_active_min = dc_fixpt_mul(t_active_min, inter->r_frl_char_min);
+	t_active_min = dc_fixpt_mul_int(t_active_min, params->lanes);
+	t_active_min = dc_fixpt_div_int(t_active_min, 1000);
+	t_blank_min = t_active_min;
+
+	t_active_min = dc_fixpt_div(dc_fixpt_mul_int(dc_fixpt_div(dc_fixpt_from_int(3), dc_fixpt_from_int(2)), tb_active), t_active_min);
+
+	t_blank_min = dc_fixpt_div(dc_fixpt_from_int(tb_blank), t_blank_min);
+
+	if (dc_fixpt_le(t_active_min, t_active_ref) && dc_fixpt_le(t_blank_min, t_blank_ref)) {
+		params->borrow_params.borrow_mode = FRL_BORROW_MODE_NONE;
+	} else if (dc_fixpt_lt(t_active_ref, t_active_min) && dc_fixpt_le(t_blank_min, t_blank_ref)) {
+		params->borrow_params.borrow_mode = FRL_BORROW_MODE_FROM_BLANK;
+	} else {
+		return false;
+	}
+
+
+	c_frl_actual_payload = dc_fixpt_ceil(dc_fixpt_mul_int(dc_fixpt_div(dc_fixpt_from_int(3), dc_fixpt_from_int(2)), tb_active)) + tb_blank - c_frl_rc_savings_int;
+
+	utilization = dc_fixpt_div(dc_fixpt_from_int(c_frl_actual_payload), inter->c_frl_line);
+	utilization = dc_fixpt_mul_int(utilization, 1000);
+
+	inter->margin = dc_fixpt_sub(dc_fixpt_from_int(1), dc_fixpt_add(utilization, inter->overhead_max));
+
+	if (dc_fixpt_lt(inter->margin, dc_fixpt_from_int(0)) && dc_fixpt_lt(dc_fixpt_from_fraction(1, 100), dc_fixpt_abs(inter->margin)))
+		return false;
+
+	return true;
+}
+
 static uint32_t dp_get_timing_bandwidth_kbps(
 	const struct dc_crtc_timing *timing,
 	const struct dc_link *link)
@@ -331,6 +521,86 @@ static bool dp_validate_mode_timing(
 		return false;
 }
 
+bool frl_validate_mode_timing(
+	struct dc_link *link,
+	const struct dc_crtc_timing *timing,
+	struct dc_hdmi_frl_link_settings *frl_link_settings)
+{
+	uint32_t req_bw;
+	uint32_t max_bw;
+	enum engine_id hpo_eng_id;
+	unsigned int i;
+	unsigned int hpo_frl_stream_enc_index = 0;
+	bool frl_output_valid = false;
+
+	if (!link)
+		return false;
+	if (!link->local_sink)
+		return false;
+
+	req_bw = dc_bandwidth_in_kbps_from_timing(timing, dc_link_get_highest_encoding_format(link));
+	max_bw = frl_link_bandwidth_kbps(frl_link_settings->frl_link_rate);
+
+	/* Use Engine ID to determine which hpo stream
+	 * encoder should be used.
+	 */
+	if (link->connector_signal == SIGNAL_TYPE_VIRTUAL)
+		frl_output_valid = true;
+	else {
+		struct audio_check audio_frl_check = {0};
+		struct audio_info audio_info = {0};
+		hpo_eng_id = ENGINE_ID_HPO_0;
+
+		for (i = 0; i < link->dc->res_pool->hpo_frl_stream_enc_count; i++) {
+			if (link->dc->res_pool->hpo_frl_stream_enc[i]->id == hpo_eng_id) {
+				hpo_frl_stream_enc_index = i;
+				break;
+			}
+		}
+		/*add audio check*/
+		for (i = 0; i < (link->local_sink->edid_caps.audio_mode_count); i++) {
+			audio_info.modes[i].channel_count = link->local_sink->edid_caps.audio_modes[i].channel_count;
+			audio_info.modes[i].format_code = link->local_sink->edid_caps.audio_modes[i].format_code;
+			audio_info.modes[i].sample_rates.all = link->local_sink->edid_caps.audio_modes[i].sample_rate;
+			audio_info.modes[i].sample_size = link->local_sink->edid_caps.audio_modes[i].sample_size;
+		}
+		audio_info.mode_count = link->local_sink->edid_caps.audio_mode_count;
+		get_audio_check(&audio_info, &audio_frl_check);
+
+		frl_output_valid =
+			link->dc->res_pool->hpo_frl_stream_enc[hpo_frl_stream_enc_index]->funcs->validate_hdmi_frl_output(
+				link->dc->res_pool->hpo_frl_stream_enc[hpo_frl_stream_enc_index],
+				timing, &audio_frl_check,
+				frl_link_settings,
+				link->local_sink->edid_caps.frl_dsc_max_frl_rate);
+	}
+
+	if (req_bw <= max_bw && frl_output_valid) {
+		/* remember the biggest mode here, during
+		 * initial link training (to get
+		 * verified_link_cap), LS sends event about
+		 * cannot train at reported cap to upper
+		 * layer and upper layer will re-enumerate modes.
+		 * this is not necessary if the lower
+		 * verified_link_cap is enough to drive
+		 * all the modes
+		 */
+
+		/* TODO: DYNAMIC_VALIDATION needs to be implemented */
+		/* if (flags.DYNAMIC_VALIDATION == 1)
+		 * dpsst->max_req_bw_for_verified_linkcap = dal_max(
+		 * dpsst->max_req_bw_for_verified_linkcap, req_bw);
+		 */
+		return true;
+	} else if (frl_output_valid && timing->dsc_cfg.is_frl) {
+		/* HDMI DSC calculation is validated within frl_output_valid
+		 * and req_bw may exceed max_bw
+		 */
+		return true;
+	} else
+		return false;
+}
+
 enum dc_status link_validate_mode_timing(
 		const struct dc_stream_state *stream,
 		struct dc_link *link,
@@ -345,6 +615,15 @@ enum dc_status link_validate_mode_timing(
 	if (link->remote_sinks[0] && link->remote_sinks[0]->sink_signal == SIGNAL_TYPE_VIRTUAL)
 		return DC_OK;
 
+	/* If DSC is supported, but native 422 DSC is not supported,
+	 * HDMI 2.1a specification requires that all 422 format be disabled (7.7.1)
+	*/
+	if (dc_is_hdmi_signal(stream->signal)) {
+		if (timing->pixel_encoding == PIXEL_ENCODING_YCBCR422 && link->dc->config.no_native422_support) {
+			return DC_SURFACE_PIXEL_FORMAT_UNSUPPORTED;
+		}
+	}
+
 	/* Passive Dongle */
 	if (max_pix_clk != 0 && get_tmds_output_pixel_clock_100hz(timing) > max_pix_clk)
 		return DC_EXCEED_DONGLE_CAP;
@@ -362,6 +641,25 @@ enum dc_status link_validate_mode_timing(
 			return DC_NO_DP_LINK_BANDWIDTH;
 		break;
 
+	case SIGNAL_TYPE_HDMI_FRL:
+		{
+			uint32_t pxl_clk_mhz;
+			/* Limit pixel clock to DTBCLK Limit (Base Pix > 4 * DTBCLK) */
+			pxl_clk_mhz = (timing->pix_clk_100hz + 10000 - 1) / 10000 ;
+			if (timing->pixel_encoding == PIXEL_ENCODING_YCBCR420)
+				pxl_clk_mhz /= 2;
+			else if (timing->pixel_encoding == PIXEL_ENCODING_YCBCR422)
+				pxl_clk_mhz = pxl_clk_mhz * 2 / 3;
+			if (pxl_clk_mhz > DTBCLK_LIMIT && link->ctx->dce_version < DCN_VERSION_3_1)
+				return DC_NO_HDMI_FRL_LINK_BANDWIDTH;
+		}
+
+		if (!frl_validate_mode_timing(
+				link,
+				timing,
+				hdmi_frl_get_verified_link_cap(link)))
+			return DC_NO_HDMI_FRL_LINK_BANDWIDTH;
+		break;
 	default:
 		break;
 	}
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_validation.h b/drivers/gpu/drm/amd/display/dc/link/link_validation.h
index 595774e76453..ccea329c556c 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_validation.h
+++ b/drivers/gpu/drm/amd/display/dc/link/link_validation.h
@@ -26,6 +26,8 @@
 #define __LINK_VALIDATION_H__
 #include "link_service.h"
 
+#define TOLERANCE_AUDIO_CLOCK 1000
+
 enum dc_status link_validate_mode_timing(
 		const struct dc_stream_state *stream,
 		struct dc_link *link,
@@ -33,9 +35,20 @@ enum dc_status link_validate_mode_timing(
 enum dc_status link_validate_dp_tunnel_bandwidth(
 		const struct dc *dc,
 		const struct dc_state *new_ctx);
+bool frl_validate_mode_timing(
+		struct dc_link *link,
+		const struct dc_crtc_timing *timing,
+		struct dc_hdmi_frl_link_settings *frl_link_settings);
 uint32_t dp_link_bandwidth_kbps(
 	const struct dc_link *link,
 	const struct dc_link_settings *link_settings);
+uint32_t frl_link_bandwidth_kbps(enum hdmi_frl_link_rate link_rate);
+uint32_t link_timing_bandwidth_kbps(const struct dc_crtc_timing *timing);
+bool frl_capacity_computations_common(struct frl_cap_chk_params_fixed31_32 *params,
+	struct frl_cap_chk_intermediates_fixed31_32 *inter);
+bool frl_capacity_computations_uncompressed_video(
+	struct frl_cap_chk_params_fixed31_32 *params,
+	struct frl_cap_chk_intermediates_fixed31_32 *inter);
 
 
 uint32_t dp_required_hblank_size_bytes(
diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c
index 70f854cca029..a596f1b02ec5 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.c
@@ -31,6 +31,7 @@
  * link training.
  */
 #include "link_ddc.h"
+#include "link_hdmi_frl.h"
 #include "vector.h"
 #include "dce/dce_aux.h"
 #include "dal_asic_id.h"
@@ -546,6 +547,8 @@ void write_scdc_data(struct ddc_service *ddc_service,
 		(ddc_service->link->local_sink->edid_caps.panel_patch.skip_scdc_overwrite ||
 		!ddc_service->link->local_sink->edid_caps.scdc_present))
 		return;
+	hdmi_frl_LTS_clear_Link_Setting(ddc_service);
+	hdmi_frl_LTS_clear_Update_flag(ddc_service);
 
 	link_query_ddc_data(ddc_service, slave_address, &offset,
 			sizeof(offset), &sink_version, sizeof(sink_version));
@@ -596,3 +599,90 @@ void read_scdc_data(struct ddc_service *ddc_service)
 				sizeof(status_data.byte));
 	}
 }
+void write_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
+		uint8_t *write_buf, uint8_t offset, uint8_t write_len)
+{
+	uint8_t slave_address = HDMI_IDCC_ADDRESS;
+	uint8_t idcc_header[5] = {0};
+	uint8_t dummy_buf[1] = {0};
+	uint8_t checksum = 0;
+	int i;
+
+	idcc_header[0] = HDMI_IDCC_MARKER0;
+	idcc_header[1] = HDMI_IDCC_MARKER1;
+	idcc_header[2] = (uint8_t)(HDMI_IDCC_MARKER2 | idcc_scope);
+	idcc_header[3] = offset;
+	idcc_header[4] = write_len;
+
+	/* Write the IDCC header */
+	for (i = 0; i < sizeof(idcc_header); ++i) {
+		link_query_ddc_data(ddc_service, slave_address,
+				&idcc_header[i], 1,
+				&dummy_buf[0], 1);
+		checksum += idcc_header[i];
+	}
+
+	/* Write the payload */
+	for (i = 0; i < write_len; ++i) {
+		link_query_ddc_data(ddc_service, slave_address,
+				&write_buf[i], 1,
+				&dummy_buf[0], 1);
+		checksum += write_buf[i];
+	}
+
+	/* Write the checksum */
+	if (write_len > 0) {
+		checksum = 0xff - checksum + 1;
+		link_query_ddc_data(ddc_service, slave_address,
+			&checksum, 1,
+			&dummy_buf[0], 1);
+	}
+}
+
+int read_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
+		uint8_t *read_buf, uint8_t offset, uint8_t read_len)
+{
+	uint8_t slave_address = HDMI_IDCC_ADDRESS;
+	uint8_t idcc_header[5] = {0};
+	uint8_t dummy_buf[1] = {0};
+	uint8_t read_buf_local[6] = {0};
+	uint8_t checksum = 0;
+	int i;
+
+	idcc_header[0] = HDMI_IDCC_MARKER0;
+	idcc_header[1] = HDMI_IDCC_MARKER1;
+	idcc_header[2] = (uint8_t)(HDMI_IDCC_MARKER2 | idcc_scope);
+	idcc_header[3] = offset;
+	idcc_header[4] = read_len;
+
+	/* Write the IDCC header */
+	for (i = 0; i < sizeof(idcc_header); ++i) {
+		link_query_ddc_data(ddc_service, slave_address,
+				&idcc_header[i], 1,
+				&dummy_buf[0], 1);
+		checksum += idcc_header[i];
+	}
+
+	/* Read the payload */
+	if (read_len > 0) {
+		dummy_buf[0] = 0x01;
+		if (read_len > 5)
+			read_len = 5;
+		link_query_ddc_data(ddc_service, slave_address,
+				&dummy_buf[0], 1,
+				&read_buf_local[0], read_len + 1);
+
+		memcpy(read_buf, read_buf_local, read_len);
+
+		/* Check checksum */
+		checksum = read_buf_local[read_len];
+		for (i = 0; i < 5; ++i)
+			checksum += idcc_header[i];
+		for (i = 0; i < read_len; ++i)
+			checksum += read_buf_local[i];
+		if (checksum != 0)
+			return -1;
+	}
+
+	return read_len;
+}
diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h
index d3e6f01a6a90..f2a80e12494b 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_ddc.h
@@ -94,6 +94,10 @@ void write_scdc_data(
 void read_scdc_data(
 		struct ddc_service *ddc_service);
 
+void write_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
+		uint8_t *write_buf, uint8_t offset, uint8_t write_len);
+int read_idcc_data(struct ddc_service *ddc_service, enum hdmi_idcc_scope idcc_scope,
+		uint8_t *read_buf, uint8_t offset, uint8_t read_len);
 void set_dongle_type(struct ddc_service *ddc,
 		enum display_dongle_type dongle_type);
 
diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c
new file mode 100644
index 000000000000..9087f786d6c9
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c
@@ -0,0 +1,1149 @@
+/*
+ * Copyright 2022 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
+ *
+ */
+
+/* FILE POLICY AND INTENDED USAGE:
+ * This file implements FRL link capability and link training related functions.
+ * FRL link is established by order of retrieve_link, verify_link, and poll_status.
+ * Other helper functions exist to obtain information required to maintain
+ * the correct sequence according to HDMI specification. Each sequence and state
+ * inside link training functions are timing sensitive and order sensitive.
+ * It is mandatory that these functions are debugged with FRL_LTP output message
+ * configurable in DSAT.  Any changes in the LT sequence should follow the HDMI
+ * specification as much as possible and tested through HDMI electrical and
+ * link layer compliance.
+ */
+#include "link_hdmi_frl.h"
+#include "link_ddc.h"
+#include "link/link_dpms.h"
+#include "link/link_validation.h"
+#include "resource.h"
+#include "dccg.h"
+
+#include "dml/dml1_frl_cap_chk.h"
+
+#define DC_LOGGER \
+	dc_logger
+#define DC_LOGGER_INIT(logger) \
+	struct dal_logger *dc_logger = logger
+
+#define FRL_INFO(...) \
+	DC_LOG_HDMI_FRL_LTP(  \
+		__VA_ARGS__)
+
+static bool hdmi_frl_test_max_rate(struct ddc_service *ddc_service)
+{
+	uint8_t slave_address = HDMI_SCDC_ADDRESS;
+	uint8_t offset = HDMI_SCDC_SOURCE_TEST_REQ;
+	union hdmi_scdc_source_test_req test_req = {0};
+
+	DC_LOGGER_INIT(ddc_service->link->ctx->logger);
+
+	link_query_ddc_data(ddc_service, slave_address,
+					&offset, sizeof(offset), &test_req.byte,
+					sizeof(test_req.byte));
+	if (test_req.fields.FRL_MAX) {
+		FRL_INFO("FRL TEST REQ:  FRL_MAX = 1");
+		return true;
+	}
+
+	return false;
+}
+
+static void hdmi_return_preeshoot_and_deemphasis(struct dc_link *link,
+		union hdmi_scdc_source_test_req *test_req, bool *de_emphasis_only,
+		bool *pre_shoot_only, bool *no_ffe)
+{
+	/* check if cable_id is valid */
+	if (link->hdmi_cable_id.raw[0] && link->frl_link_settings.frl_link_rate >=
+		HDMI_FRL_LINK_RATE_16GBPS) {
+		*de_emphasis_only = (test_req->fields.TXFFE_DEEMPHASIS && link->hdmi_cable_id.bits.no_DeEmphasis_n) ||
+					!link->hdmi_cable_id.bits.no_PreShoot_n;
+		*pre_shoot_only = (test_req->fields.TXFFE_PRESHOOT && link->hdmi_cable_id.bits.no_PreShoot_n) ||
+					!link->hdmi_cable_id.bits.no_DeEmphasis_n;
+		*no_ffe = test_req->fields.TXFFE_NOFFE ||
+					(!link->hdmi_cable_id.bits.no_PreShoot_n &&
+					 !link->hdmi_cable_id.bits.no_DeEmphasis_n);
+	} else {
+		*de_emphasis_only = test_req->fields.TXFFE_DEEMPHASIS;
+		*pre_shoot_only = test_req->fields.TXFFE_PRESHOOT;
+		*no_ffe = test_req->fields.TXFFE_NOFFE;
+	}
+}
+
+enum clock_source_id hdmi_frl_find_matching_phypll(
+		struct dc_link *link)
+{
+	switch (link->link_enc->transmitter) {
+	case TRANSMITTER_UNIPHY_A:
+		return CLOCK_SOURCE_COMBO_PHY_PLL0;
+	case TRANSMITTER_UNIPHY_B:
+		return CLOCK_SOURCE_COMBO_PHY_PLL1;
+	case TRANSMITTER_UNIPHY_C:
+		return CLOCK_SOURCE_COMBO_PHY_PLL2;
+	case TRANSMITTER_UNIPHY_D:
+		return CLOCK_SOURCE_COMBO_PHY_PLL3;
+	case TRANSMITTER_UNIPHY_E:
+		return CLOCK_SOURCE_COMBO_PHY_PLL4;
+	case TRANSMITTER_UNIPHY_F:
+		return CLOCK_SOURCE_COMBO_PHY_PLL5;
+	default:
+		return CLOCK_SOURCE_ID_UNDEFINED;
+	};
+}
+
+void hdmi_frl_retrieve_link_cap(struct dc_link *link, struct dc_sink *sink)
+{
+	enum hdmi_frl_link_rate encoder_link_rate = HDMI_FRL_LINK_RATE_6GBPS_4LANE;
+
+	if (link->link_enc->features.flags.bits.IS_FRL_8G_CAPABLE)
+		encoder_link_rate = HDMI_FRL_LINK_RATE_8GBPS;
+
+	if (link->link_enc->features.flags.bits.IS_FRL_10G_CAPABLE)
+		encoder_link_rate = HDMI_FRL_LINK_RATE_10GBPS;
+
+	if (link->link_enc->features.flags.bits.IS_FRL_12G_CAPABLE)
+		encoder_link_rate = HDMI_FRL_LINK_RATE_12GBPS;
+
+	if (link->dc->debug.max_frl_rate != 0 && encoder_link_rate > link->dc->debug.max_frl_rate)
+		encoder_link_rate = link->dc->debug.max_frl_rate;
+
+	if (link->frl_flags.force_frl_rate != 0 &&
+			link->frl_flags.force_frl_rate != 0xF)
+		encoder_link_rate = link->frl_flags.force_frl_rate;
+
+	link->frl_reported_link_cap.frl_link_rate =
+			(encoder_link_rate < sink->edid_caps.max_frl_rate) ?
+					encoder_link_rate : sink->edid_caps.max_frl_rate;
+
+	if (sink->edid_caps.max_frl_rate < HDMI_FRL_LINK_RATE_6GBPS_4LANE)
+		link->frl_reported_link_cap.frl_num_lanes = 3;
+	else
+		link->frl_reported_link_cap.frl_num_lanes = 4;
+}
+
+struct dc_hdmi_frl_link_settings *hdmi_frl_get_verified_link_cap(
+		struct dc_link *link)
+{
+	// TODO: rework hdmi_frl_get_verified_link_cap to be a const interface
+	return &link->frl_verified_link_cap;
+}
+
+
+void hdmi_frl_LTS_clear_Update_flag(struct ddc_service *ddc_service)
+{
+	uint8_t slave_address = HDMI_SCDC_ADDRESS;
+	uint8_t offset = HDMI_SCDC_UPDATE_0;
+	uint8_t write_buffer[2] = { 0 };
+	union hdmi_scdc_update_read_data scdc_update = {0};
+	DC_LOGGER_INIT(ddc_service->link->ctx->logger);
+
+	/*Check FLT_update flag*/
+	link_query_ddc_data(ddc_service, slave_address,
+					&offset, sizeof(offset), &scdc_update.byte[0],
+					sizeof(scdc_update.byte[0]));
+
+	if (scdc_update.fields.FLT_UPDATE != 0) {
+		FRL_INFO("FRL LINK TRAINING:  LTS:L Clear FLT_UPDATE.\n");
+		write_buffer[0] = HDMI_SCDC_UPDATE_0;
+		/*FLT_update - bit 5*/
+		write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
+		link_query_ddc_data(ddc_service, slave_address,
+				write_buffer, sizeof(write_buffer), NULL, 0);
+	}
+}
+
+
+bool hdmi_frl_poll_status_flag(struct dc_link *link)
+{
+	uint8_t slave_address = HDMI_SCDC_ADDRESS;
+	uint8_t offset = 0;
+	uint32_t ln0_pattern = 0;
+	uint32_t ln1_pattern = 0;
+	uint32_t ln2_pattern = 0;
+	uint32_t ln3_pattern = 0;
+	uint8_t write_buffer[2] = {0};
+	union hdmi_scdc_update_read_data scdc_update = {0};
+	union hdmi_scdc_source_test_req test_req = {0};
+	union hdmi_scdc_LTP_req_data ltp_req = {0};
+	struct hpo_frl_link_encoder *hpo_frl_link_enc = link->hpo_frl_link_enc;
+	struct link_encoder *dio_link_enc = link->link_enc;
+	bool flt_no_timeout = false;
+	bool link_update = false;
+
+	/*Test Condition - FLT_no_timeout avoid link training*/
+	offset = HDMI_SCDC_SOURCE_TEST_REQ;
+	link_query_ddc_data(link->ddc, slave_address, &offset,
+			sizeof(offset), &test_req.byte, sizeof(test_req.byte));
+	if (test_req.fields.FLT_NO_TIMEOUT)
+		flt_no_timeout = true;
+
+	offset = HDMI_SCDC_UPDATE_0;
+
+	/*Check FLT_update flag*/
+	link_query_ddc_data(link->ddc, slave_address,
+					&offset, sizeof(offset), &scdc_update.byte[0],
+					sizeof(scdc_update.byte[0]));
+
+	if (scdc_update.fields.FRL_START == 1) {
+		write_buffer[0] = HDMI_SCDC_UPDATE_0;
+		/*FLT_START - bit 4*/
+		write_buffer[1] = (scdc_update.fields.FRL_START << 4);
+		link_query_ddc_data(link->ddc, slave_address,
+				write_buffer, sizeof(write_buffer), NULL, 0);
+	}
+	if (scdc_update.fields.FLT_UPDATE) {
+		offset = HDMI_SCDC_LTP_REQ;
+
+		link_query_ddc_data(link->ddc, slave_address,
+						&offset, sizeof(offset), ltp_req.byte,
+						sizeof(ltp_req.byte));
+
+		ln0_pattern = ltp_req.fields.LN0_LTP_REQ;
+		ln1_pattern = ltp_req.fields.LN1_LTP_REQ;
+		ln2_pattern = ltp_req.fields.LN2_LTP_REQ;
+		ln3_pattern = ltp_req.fields.LN3_LTP_REQ;
+
+		if (ln0_pattern == 0x03 || ln1_pattern == 0x03 ||
+				ln2_pattern == 0x03 || ln3_pattern == 0x03) {
+			if (flt_no_timeout) {
+				hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
+					hpo_frl_link_enc,
+					ln0_pattern - 1,
+					ln1_pattern - 1,
+					ln2_pattern - 1,
+					ln3_pattern - 1);
+			} else
+				hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
+					hpo_frl_link_enc,
+					(ln0_pattern == 0x03) ? 0xF : ln0_pattern - 1,
+					(ln1_pattern == 0x03) ? 0xF : ln1_pattern - 1,
+					(ln2_pattern == 0x03) ? 0xF : ln2_pattern - 1,
+					(ln3_pattern == 0x03) ? 0xF : ln3_pattern - 1);
+		} else
+			hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
+				hpo_frl_link_enc,
+				ln0_pattern - 1,
+				ln1_pattern - 1,
+				ln2_pattern - 1,
+				ln3_pattern - 1);
+		if (ln0_pattern == 0x0E || ln1_pattern == 0x0E ||
+								ln2_pattern == 0x0E  || ln3_pattern == 0x0E) {
+			if (scdc_update.fields.FLT_UPDATE) {
+				if (link->dc->debug.limit_ffe == 0)
+					return false;
+
+				bool de_emphasis_only = false;
+				bool pre_shoot_only = false;
+				bool no_ffe = false;
+
+				hdmi_return_preeshoot_and_deemphasis(link, &test_req,
+					&de_emphasis_only, &pre_shoot_only, &no_ffe);
+
+				dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0xEE,
+						de_emphasis_only,
+						pre_shoot_only,
+						no_ffe,
+						&link->frl_link_settings);
+			}
+		} else
+			if (!flt_no_timeout) {
+				hdmi_frl_LTS_clear_Link_Setting(link->ddc);
+				hdmi_frl_LTS_clear_Update_flag(link->ddc);
+				hpo_frl_link_enc->funcs->set_hdmi_training_pattern(hpo_frl_link_enc, 0, 0, 0, 0);
+				hdmi_frl_perform_link_training_with_retries(link);
+				link_update = true;
+			}
+
+		write_buffer[0] = HDMI_SCDC_UPDATE_0;
+		/*Clear SCDC Update Flags*/
+		write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
+		link_query_ddc_data(link->ddc, slave_address,
+				write_buffer, sizeof(write_buffer), NULL, 0);
+	}
+	if (scdc_update.fields.SOURCE_TEST_UPDATE) {
+		offset = HDMI_SCDC_SOURCE_TEST_REQ;
+		bool de_emphasis_only = false;
+		bool pre_shoot_only = false;
+		bool no_ffe = false;
+
+		link_query_ddc_data(link->ddc, slave_address,
+						&offset, sizeof(offset), &test_req.byte,
+						sizeof(test_req.byte));
+
+		hdmi_return_preeshoot_and_deemphasis(link, &test_req,
+			&de_emphasis_only, &pre_shoot_only, &no_ffe);
+
+		dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0xFF,
+				de_emphasis_only,
+				pre_shoot_only,
+				no_ffe,
+				&link->frl_link_settings);
+
+
+		write_buffer[0] = HDMI_SCDC_UPDATE_0;
+		/*Clear SCDC Update Flags*/
+		write_buffer[1] = (scdc_update.fields.SOURCE_TEST_UPDATE << 3);
+		link_query_ddc_data(link->ddc, slave_address,
+				write_buffer, sizeof(write_buffer), NULL, 0);
+
+	}
+
+	return link_update;
+}
+
+void hdmi_frl_poll_start(struct ddc_service *ddc_service)
+{
+	uint8_t slave_address = HDMI_SCDC_ADDRESS;
+	uint8_t offset = HDMI_SCDC_UPDATE_0;
+	uint8_t write_buffer[2] = {0};
+	union hdmi_scdc_source_test_req test_req = {0};
+	union hdmi_scdc_update_read_data scdc_update = {0};
+	uint16_t num_polls = 100;
+	uint16_t wait_time = 2000;
+	bool flt_no_timeout = false;
+
+	DC_LOGGER_INIT(ddc_service->link->ctx->logger);
+
+	/*Test Condition - FLT_no_timeout avoid link training*/
+	offset = HDMI_SCDC_SOURCE_TEST_REQ;
+	link_query_ddc_data(ddc_service, slave_address, &offset,
+			sizeof(offset), &test_req.byte, sizeof(test_req.byte));
+	if (test_req.fields.FLT_NO_TIMEOUT)
+		flt_no_timeout = true;
+
+	/*Test Condition - No need to poll FRL_START if FLT_no_timeout*/
+	if (flt_no_timeout)
+		return;
+
+	offset = HDMI_SCDC_UPDATE_0;
+	/*LTS:P: Check FRL_START, poll for 200ms */
+	for (; num_polls; num_polls--) {
+		udelay(wait_time);
+		/*Check FLT_update flag*/
+		link_query_ddc_data(ddc_service, slave_address,
+						&offset, sizeof(offset), &scdc_update.byte[0],
+						sizeof(scdc_update.byte[0]));
+		FRL_INFO("FRL LINK TRAINING:  Read FRL_START = %d, FLT_UPDATE = %d.  num_polls = %d\n",
+				scdc_update.fields.FRL_START, scdc_update.fields.FLT_UPDATE, num_polls);
+		if (scdc_update.fields.FRL_START == 1) {
+			write_buffer[0] = HDMI_SCDC_UPDATE_0;
+			/*FLT_update - bit 5*/
+			write_buffer[1] = (scdc_update.fields.FRL_START << 4);
+			link_query_ddc_data(ddc_service, slave_address,
+					write_buffer, sizeof(write_buffer), NULL, 0);
+			break;
+		}
+		if (scdc_update.fields.FLT_UPDATE == 1) {
+			write_buffer[0] = HDMI_SCDC_UPDATE_0;
+				/*FLT_update - bit 5*/
+			write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
+			link_query_ddc_data(ddc_service, slave_address,
+					write_buffer, sizeof(write_buffer), NULL, 0);
+			break;
+		}
+	}
+	return;
+}
+
+void hdmi_frl_LTS_clear_Link_Setting(struct ddc_service *ddc_service)
+{
+	uint8_t slave_address = HDMI_SCDC_ADDRESS;
+	uint8_t offset = HDMI_SCDC_CONFIG_1;
+	uint8_t write_buffer[2] = { 0 };
+	union hdmi_scdc_configuration scdc_config = {0};
+	DC_LOGGER_INIT(ddc_service->link->ctx->logger);
+
+	/*Check FRL_Rate for fallback*/
+	link_query_ddc_data(ddc_service, slave_address,
+					&offset, sizeof(offset), &scdc_config.byte[1],
+					sizeof(scdc_config.byte[1]));
+	if (scdc_config.fields.FRL_RATE != 0) {
+		/*LTS:L FRL_Rate = 0*/
+		write_buffer[0] = HDMI_SCDC_CONFIG_1;
+		/*FRL_RATE*/
+		write_buffer[1] = 0;
+		link_query_ddc_data(ddc_service, slave_address,
+				write_buffer, sizeof(write_buffer), NULL, 0);
+		FRL_INFO("FRL LINK TRAINING: LTS:L - Clear FRL_Rate.\n");
+	}
+
+}
+
+static enum link_result hdmi_frl_perform_link_training(struct ddc_service *ddc_service,
+		struct dc_hdmi_frl_link_settings *link_settings)
+{
+	enum link_result result = LINK_RESULT_UNKNOWN;
+	uint8_t slave_address = HDMI_SCDC_ADDRESS;
+	uint8_t offset = HDMI_SCDC_STATUS_FLAGS;
+	uint32_t ln0_pattern = 0, pre_ln0_pattern = 0;
+	uint32_t ln1_pattern = 0, pre_ln1_pattern = 0;
+	uint32_t ln2_pattern = 0, pre_ln2_pattern = 0;
+	uint32_t ln3_pattern = 0, pre_ln3_pattern = 0;
+	uint8_t write_buffer[2] = {0};
+	union hdmi_scdc_update_read_data scdc_update = {0};
+	union hdmi_scdc_status_flags_data status_data = {0};
+	union hdmi_scdc_source_test_req test_req = {0};
+	union hdmi_scdc_LTP_req_data ltp_req = {0};
+	uint16_t num_polls = 0;
+	uint16_t max_polls = 105;
+	unsigned long long wait_time_ns = 2000000;
+	struct hpo_frl_link_encoder *hpo_frl_link_enc = ddc_service->link->hpo_frl_link_enc;
+	struct link_encoder *dio_link_enc = ddc_service->link->link_enc;
+	uint8_t sink_version = 0;
+	uint8_t FFE_Levels = (uint8_t)ddc_service->link->dc->debug.limit_ffe;
+	uint8_t current_FFE = 0;
+	bool override_FFE = false;
+	bool flt_no_timeout = false;
+	unsigned long long flt_poll_cur_time = 0, flt_poll_last_time = 0, flt_poll_elapsed_time_ns = 0;
+
+	DC_LOGGER_INIT(ddc_service->link->ctx->logger);
+	FRL_INFO("FRL LINK TRAINING:  Starting FRL Link Training.\n");
+
+	hdmi_frl_LTS_clear_Link_Setting(ddc_service);
+
+	offset = HDMI_SCDC_SINK_VERSION;
+	link_query_ddc_data(ddc_service, slave_address, &offset,
+			sizeof(offset), &sink_version, sizeof(sink_version));
+
+	FRL_INFO("FRL LINK TRAINING:  Read Sink Version = %d.\n", sink_version);
+
+	if (sink_version == 0) {
+		FRL_INFO("FRL LINK TRAINING: SKIP - FRL not supported by sink.\n");
+		return LINK_RESULT_FALLBACK;
+	}
+
+	if (sink_version == 1) {
+		/*Source Version = 1*/
+		write_buffer[0] = HDMI_SCDC_SOURCE_VERSION;
+		write_buffer[1] = 1;
+		link_query_ddc_data(ddc_service, slave_address,
+				write_buffer, sizeof(write_buffer), NULL, 0);
+		FRL_INFO("FRL LINK TRAINING:  Set Source Version = 1.\n");
+	}
+
+	FRL_INFO("FRL LINK TRAINING:  Poll for FLT_READY.\n");
+	offset = HDMI_SCDC_STATUS_FLAGS;
+
+	/*LTS:2: Check FLT Ready, poll for 200ms */
+	while (num_polls < max_polls) {
+		flt_poll_cur_time = dm_get_timestamp(ddc_service->ctx);
+		flt_poll_elapsed_time_ns = dm_get_elapse_time_in_ns(ddc_service->ctx, flt_poll_cur_time, flt_poll_last_time);
+		if (flt_poll_elapsed_time_ns < wait_time_ns)
+			continue;
+		flt_poll_last_time = dm_get_timestamp(ddc_service->ctx);
+		num_polls++;
+
+		link_query_ddc_data(ddc_service, slave_address,
+				&offset, sizeof(offset), &status_data.byte,
+				sizeof(status_data.byte));
+		FRL_INFO("FRL LINK TRAINING:  Read FLT_READY = %d.  num_polls = %d\n",
+				status_data.fields.FLT_READY, num_polls);
+		if (status_data.fields.FLT_READY) {
+			/* Spec recommends to clear update flag, but QD980 has problem */
+			hdmi_frl_LTS_clear_Update_flag(ddc_service);
+			dio_link_enc->funcs->prog_eq_setting(dio_link_enc, 0,
+					test_req.fields.TXFFE_DEEMPHASIS,
+					test_req.fields.TXFFE_PRESHOOT,
+					test_req.fields.TXFFE_NOFFE,
+					link_settings);
+			break;
+		}
+	}
+
+	/*Test Condition - FLT_no_timeout avoid link training*/
+	offset = HDMI_SCDC_SOURCE_TEST_REQ;
+	link_query_ddc_data(ddc_service, slave_address, &offset,
+			sizeof(offset), &test_req.byte, sizeof(test_req.byte));
+	FRL_INFO("FRL TEST REQ:  FLT_no_timeout = %d \n", test_req.fields.FLT_NO_TIMEOUT);
+	if (test_req.fields.FLT_NO_TIMEOUT)
+		flt_no_timeout = true;
+
+	if (status_data.fields.FLT_READY) {
+		/*Specify FRL rate*/
+		write_buffer[0] = HDMI_SCDC_CONFIG_1;
+		/*FRL_RATE*/
+		write_buffer[1] = (uint8_t)(link_settings->frl_link_rate | (FFE_Levels << 4));
+		link_query_ddc_data(ddc_service, slave_address,
+				write_buffer, sizeof(write_buffer), NULL, 0);
+		FRL_INFO("FRL LINK TRAINING:  Write link rate = %d.  Max FFE_Levels = %d\n",
+				link_settings->frl_link_rate, FFE_Levels);
+
+		FRL_INFO("FRL LINK TRAINING:  Poll for FLT_UPDATE.\n");
+		/*LTS:3: Start Link Training*/
+		/*Start FLT Timer = 200 ms*/
+		num_polls = 0;
+		if (flt_no_timeout)
+			max_polls = 500;
+
+		while (num_polls < max_polls) {
+			flt_poll_cur_time = dm_get_timestamp(ddc_service->ctx);
+			flt_poll_elapsed_time_ns = dm_get_elapse_time_in_ns(ddc_service->ctx, flt_poll_cur_time, flt_poll_last_time);
+			if (flt_poll_elapsed_time_ns < wait_time_ns)
+				continue;
+			flt_poll_last_time = flt_poll_cur_time;
+
+			num_polls++;
+
+			offset = HDMI_SCDC_UPDATE_0;
+			/*Check FLT_update flag*/
+			link_query_ddc_data(ddc_service, slave_address,
+							&offset, sizeof(offset), &scdc_update.byte[0],
+							sizeof(scdc_update.byte[0]));
+
+			FRL_INFO("FRL LINK TRAINING:  Read FLT_UPDATE = %d.  num_polls = %d\n",
+					scdc_update.fields.FLT_UPDATE, num_polls);
+			/*Set TxFFE = TxFFE0*/
+			/*Program FFE_Levels - scdc_config has this field at 0 */
+			if (override_FFE) {
+				if (flt_no_timeout)
+					current_FFE = 0;
+				if (current_FFE == 0)
+					dio_link_enc->funcs->prog_eq_setting(dio_link_enc, current_FFE,
+							test_req.fields.TXFFE_DEEMPHASIS,
+							test_req.fields.TXFFE_PRESHOOT,
+							test_req.fields.TXFFE_NOFFE,
+							link_settings);
+				else {
+					if (scdc_update.fields.FLT_UPDATE)
+						dio_link_enc->funcs->prog_eq_setting(dio_link_enc, current_FFE,
+								test_req.fields.TXFFE_DEEMPHASIS,
+								test_req.fields.TXFFE_PRESHOOT,
+								test_req.fields.TXFFE_NOFFE,
+								link_settings);
+				}
+				FRL_INFO("FRL LINK TRAINING:  TxFFE = %d.\n", current_FFE);
+				override_FFE = false;
+			}
+			if (scdc_update.fields.FLT_UPDATE) {
+				offset = HDMI_SCDC_LTP_REQ;
+				link_query_ddc_data(ddc_service, slave_address,
+								&offset, sizeof(offset), ltp_req.byte,
+								sizeof(ltp_req.byte));
+
+				pre_ln0_pattern = ln0_pattern;
+				pre_ln1_pattern = ln1_pattern;
+				pre_ln2_pattern = ln2_pattern;
+				pre_ln3_pattern = ln3_pattern;
+
+				ln0_pattern = ltp_req.fields.LN0_LTP_REQ;
+				ln1_pattern = ltp_req.fields.LN1_LTP_REQ;
+				ln2_pattern = ltp_req.fields.LN2_LTP_REQ;
+				ln3_pattern = ltp_req.fields.LN3_LTP_REQ;
+
+				FRL_INFO("FRL LINK TRAINING:  Read LN0_LTP_REQ = %d.  LN1_LTP_REQ = %d\n",
+						ltp_req.fields.LN0_LTP_REQ,
+						ltp_req.fields.LN1_LTP_REQ);
+				FRL_INFO("FRL LINK TRAINING:  Read LN2_LTP_REQ = %d.  LN3_LTP_REQ = %d\n",
+						ltp_req.fields.LN2_LTP_REQ,
+						ltp_req.fields.LN3_LTP_REQ);
+
+				/*Clear FLT_update flag*/
+				FRL_INFO("FRL LINK TRAINING:  Clear FLT_UPDATE flag.\n");
+				write_buffer[0] = HDMI_SCDC_UPDATE_0;
+				/*FLT_update - bit 5*/
+				write_buffer[1] = (scdc_update.fields.FLT_UPDATE << 5);
+				link_query_ddc_data(ddc_service, slave_address,
+						write_buffer, sizeof(write_buffer), NULL, 0);
+
+				if (ln0_pattern == 0x03 || ln1_pattern == 0x03 ||
+						ln2_pattern == 0x03 || ln3_pattern == 0x03)
+					if (!flt_no_timeout)
+						continue;
+
+				if (link_settings->frl_num_lanes == 3) {
+					ln3_pattern = 1;
+					if (!ln0_pattern && !ln1_pattern && !ln2_pattern) {
+						/*Link Training is done*/
+						FRL_INFO("FRL LINK TRAINING:  PASSED\n");
+						return LINK_RESULT_SUCCESS;
+					}
+					if (ln0_pattern == 0x0F || ln1_pattern == 0x0F || ln2_pattern == 0x0F) {
+						/*sink requesting to lower link rate*/
+						FRL_INFO("FRL LINK TRAINING:  Sink requesting lower link rate.\n");
+						return LINK_RESULT_LOWER_LINKRATE;
+					}
+					if (ln0_pattern == 0x0E || ln1_pattern == 0x0E || ln2_pattern == 0x0E) {
+						/*sink requesting to next FFE*/
+						FRL_INFO("FRL LINK TRAINING:  Sink requesting next FFE.\n");
+						if (ddc_service->link->dc->debug.limit_ffe == 0) {
+							return LINK_RESULT_LOWER_LINKRATE;
+						}
+						current_FFE++;
+						override_FFE = true;
+						if (current_FFE > 3)
+							current_FFE = 0;
+						if (flt_no_timeout)
+							current_FFE = 0;
+					}
+				} else {
+					if (!ln0_pattern && !ln1_pattern && !ln2_pattern && !ln3_pattern) {
+						/*Link Training is done*/
+						FRL_INFO("FRL LINK TRAINING:  PASSED\n");
+						return LINK_RESULT_SUCCESS;
+					}
+					if (ln0_pattern == 0x0F || ln1_pattern == 0x0F ||
+						ln2_pattern == 0x0F || ln3_pattern == 0x0F) {
+						/*sink requesting to lower link rate*/
+						FRL_INFO("FRL LINK TRAINING:  Sink requesting lower link rate.\n");
+						return LINK_RESULT_LOWER_LINKRATE;
+					}
+					if (ln0_pattern == 0x0E || ln1_pattern == 0x0E ||
+						ln2_pattern == 0x0E  || ln3_pattern == 0x0E) {
+						/*sink requesting to next FFE*/
+						FRL_INFO("FRL LINK TRAINING:  Sink requesting next FFE.\n");
+						if (ddc_service->link->dc->debug.limit_ffe == 0) {
+							return LINK_RESULT_LOWER_LINKRATE;
+						}
+						current_FFE++;
+						override_FFE = true;
+						if (current_FFE > 3)
+							current_FFE = 0;
+						if (flt_no_timeout)
+							current_FFE = 0;
+					}
+				}
+
+				if (override_FFE) {
+					ln0_pattern = pre_ln0_pattern;
+					ln1_pattern = pre_ln1_pattern;
+					ln2_pattern = pre_ln2_pattern;
+					ln3_pattern = pre_ln3_pattern;
+				}
+
+				FRL_INFO("FRL LINK TRAINING:  Setting Training Pattern [ln0,ln1,ln2,ln3] = [%d,%d,%d,%d].\n",
+						ln0_pattern, ln1_pattern, ln2_pattern, ln3_pattern);
+
+				hpo_frl_link_enc->funcs->set_hdmi_training_pattern(
+					hpo_frl_link_enc,
+					ln0_pattern - 1,
+					ln1_pattern - 1,
+					ln2_pattern - 1,
+					ln3_pattern - 1);
+				/* Workaround for DEDCN3AG-111
+				 * HDMI-FRL Incorrect Serialization Order for LTP4
+				 */
+			}
+
+		}
+		if (flt_no_timeout) {
+			return LINK_RESULT_SUCCESS;
+		} else {
+			FRL_INFO("FRL LINK TRAINING:  FAILED - Timeout waiting for FLT_UPDATE to be set by sink.\n");
+			write_buffer[0] = HDMI_SCDC_CONFIG_1;
+			/*FRL_RATE*/
+			write_buffer[1] = HDMI_FRL_LINK_RATE_DISABLE | (0 << 4);
+			link_query_ddc_data(ddc_service, slave_address,
+					write_buffer, sizeof(write_buffer), NULL, 0);
+			hdmi_frl_LTS_clear_Link_Setting(ddc_service);
+			result = LINK_RESULT_TIMEOUT;
+		}
+	} else {
+		FRL_INFO("FRL LINK TRAINING:  FAILED - FLT_READY not set by sink.\n");
+		result = LINK_RESULT_TIMEOUT;
+	}
+
+	return result;
+}
+
+enum link_result hdmi_frl_perform_link_training_with_retries(
+	struct dc_link *link)
+{
+	enum link_result status = LINK_RESULT_UNKNOWN;
+	unsigned int retry_count = 0;
+	unsigned int max_retries = 3;
+
+	DC_LOGGER_INIT(link->ctx->logger);
+
+	if (link->preferred_hdmi_frl_settings.valid)
+		max_retries = link->preferred_hdmi_frl_settings.max_retries;
+
+	/* FRL Link Training */
+	while (status != LINK_RESULT_FALLBACK) {
+		if (status == LINK_RESULT_SUCCESS) {
+			break;
+		} else if (status == LINK_RESULT_LOWER_LINKRATE) {
+			FRL_INFO("FRL LINK TRAINING:  Sink requested lower link rate during link enable. \n");
+			break;
+		} else {
+			if (retry_count > 0)
+				msleep(200);
+			status = hdmi_frl_perform_link_training(link->ddc,
+					&link->frl_link_settings);
+		};
+		retry_count++;
+		FRL_INFO("FRL LINK TRAINING: Retry count = %u out of %u\n", retry_count, max_retries);
+		if (retry_count > max_retries) {
+			status = LINK_RESULT_FALLBACK;
+			break;
+		}
+	}
+
+	return status;
+}
+
+enum link_result hdmi_frl_perform_link_training_with_fallback(
+	struct dc_link *link, struct link_resource *link_res,
+	enum clock_source_id frl_phy_clock_source_id)
+{
+	enum link_result status = LINK_RESULT_UNKNOWN;
+
+	DC_LOGGER_INIT(link->ctx->logger);
+
+	/* FRL Link Training */
+	while (status != LINK_RESULT_FALLBACK) {
+		if (link->frl_link_settings.frl_link_rate ==
+				HDMI_FRL_LINK_RATE_DISABLE) {
+			FRL_INFO("FRL LINK TRAINING:  Cannot Link Train.  Fall back to TMDS \n");
+			status = LINK_RESULT_FALLBACK;
+			break;
+		}
+
+		msleep(200);
+
+		link->ctx->dc->hwss.setup_hdmi_frl_link(link, 0,
+				frl_phy_clock_source_id);
+
+		status = hdmi_frl_perform_link_training(link->ddc,
+				&link->frl_link_settings);
+
+		link->dc->hwss.disable_link_output(link, link_res, SIGNAL_TYPE_HDMI_FRL);
+
+		if (status == LINK_RESULT_SUCCESS)
+			break;
+
+		link->frl_link_settings.frl_link_rate--;
+		if (link->frl_link_settings.frl_link_rate <
+				HDMI_FRL_LINK_RATE_6GBPS_4LANE)
+			link->frl_link_settings.frl_num_lanes = 3;
+	}
+
+	return status;
+}
+
+void hdmi_frl_verify_link_cap(struct dc_link *link,
+		struct dc_hdmi_frl_link_settings *known_limit_link_setting)
+{
+	struct dc_hdmi_frl_link_settings cur_link_setting = {0};
+	struct dc_hdmi_frl_link_settings *cur = &cur_link_setting;
+	bool success = false;
+	enum link_result status = LINK_RESULT_UNKNOWN;
+	enum clock_source_id frl_phy_clock_source_id;
+	unsigned int t_id = link->link_enc->transmitter;
+	struct link_resource link_res = {.hpo_frl_link_enc = link->hpo_frl_link_enc};
+	struct dc_stream_state *link_stream = NULL;
+	struct dc_stream_state *stream = NULL;
+	int i;
+
+	DC_LOGGER_INIT(link->ctx->logger);
+
+	link->frl_flags.force_frl_rate =
+			link->ctx->dc->debug.force_frl_rate;
+	link->frl_flags.force_frl_always =
+			link->preferred_hdmi_frl_settings.force_frl_always ||
+			link->ctx->dc->debug.force_frl_always;
+	link->frl_flags.force_frl_max =
+			link->preferred_hdmi_frl_settings.force_frl_max ||
+			link->ctx->dc->debug.force_frl_max ? true :
+			hdmi_frl_test_max_rate(link->ddc);
+	link->frl_flags.apply_vsdb_rcc_wa =
+			link->ctx->dc->debug.apply_vsdb_rcc_wa;
+
+	if (link->frl_flags.force_frl_rate == 0xF)
+		return;
+
+	if (link->local_sink &&
+		link->local_sink->edid_caps.panel_patch.force_frl)
+			link->frl_flags.force_frl_always = true;
+
+	if (!link->frl_flags.force_frl_max &&
+			link->local_sink->edid_caps.panel_patch.hdmi_comp_auto) {
+		link->frl_flags.force_frl_max = true;
+	}
+
+	if (link->local_sink &&
+		link->local_sink->edid_caps.panel_patch.vsdb_rcc_wa)
+			link->frl_flags.apply_vsdb_rcc_wa = true;
+
+	frl_phy_clock_source_id = hdmi_frl_find_matching_phypll(link);
+
+	cur_link_setting = *known_limit_link_setting;
+
+	if (link->frl_flags.force_frl_rate != 0) {
+		cur->frl_link_rate = (cur_link_setting.frl_link_rate <
+				link->frl_flags.force_frl_rate) ?
+						cur_link_setting.frl_link_rate :
+						link->frl_flags.force_frl_rate;
+		link->frl_verified_link_cap = *cur;
+		return;
+	}
+
+	if (link->local_sink) {
+		if (link->local_sink->edid_caps.panel_patch.hdmi_spe_handling) {
+			link->dc->hwss.disable_link_output(link, &link_res, link->connector_signal);
+			link->dc->res_pool->clock_sources[t_id]->funcs->cs_power_down(
+				link->dc->res_pool->clock_sources[t_id]);
+			link->frl_verified_link_cap = *cur;
+			return;
+		}
+		/* Monitor patch do decrease 10G to 8G*/
+		if (link->local_sink->edid_caps.panel_patch.block_10g) {
+			if (cur->frl_link_rate == HDMI_FRL_LINK_RATE_10GBPS)
+				cur->frl_link_rate--;
+		}
+	}
+
+	link->frl_link_settings = cur_link_setting;
+	/* disable PHY first for PNP */
+	if (link->dc->ctx->dce_version <= DCN_VERSION_3_0)
+		link->dc->hwss.disable_link_output(link, &link_res, SIGNAL_TYPE_HDMI_FRL);
+	else
+		link->dc->hwss.disable_link_output(link, &link_res, link->connector_signal);
+
+	link->dc->res_pool->clock_sources[t_id]->funcs->cs_power_down(
+			link->dc->res_pool->clock_sources[t_id]);
+	/*Either enable PHY ourselves or use VBIOS*/
+
+	FRL_INFO("FRL LINK TRAINING: Validation\n");
+
+	status = hdmi_frl_perform_link_training_with_fallback(link, &link_res, frl_phy_clock_source_id);
+
+	if (status == LINK_RESULT_SUCCESS) {
+		cur->frl_link_rate = link->frl_link_settings.frl_link_rate;
+		cur->frl_num_lanes = link->frl_link_settings.frl_num_lanes;
+		success = true;
+		link->frl_verified_link_cap = *cur;
+	}
+	if (!success) {
+		link->frl_verified_link_cap.frl_link_rate = HDMI_FRL_LINK_RATE_DISABLE;
+		link->frl_verified_link_cap.frl_num_lanes = 3;
+	}
+
+	for (i = 0; i < MAX_STREAMS; i++) {
+		stream = link->dc->current_state->streams[i];
+		if (stream && stream->link == link) {
+			link_stream = stream;
+			break;
+		}
+	}
+
+	if (link_stream) {
+		link->dc->hwss.disable_link_output(link, &link_res, link_stream->signal);
+	}
+}
+
+void hdmi_frl_set_preferred_link_settings(struct dc *dc,
+		struct dc_hdmi_frl_link_settings *link_setting,
+		struct dc_hdmi_frl_link_training_overrides *lt_overrides,
+		struct dc_link *link)
+{
+	int i;
+	struct pipe_ctx *pipe;
+	struct dc_stream_state *link_stream = 0;
+	struct pipe_ctx *link_pipe = 0;
+	struct pipe_ctx *odm_pipe;
+	int opp_cnt = 1;
+	enum link_result link_stat = LINK_RESULT_UNKNOWN;
+	enum clock_source_id frl_phy_clock_source_id;
+	struct dc_stream_state *temp_stream = &dc->scratch.temp_stream;
+
+	DC_LOGGER_INIT(link->ctx->logger);
+
+	for (i = 0; i < MAX_PIPES; i++) {
+		pipe = &dc->current_state->res_ctx.pipe_ctx[i];
+		if (pipe->stream && pipe->stream->link) {
+			if (pipe->stream->link == link) {
+				link_stream = pipe->stream;
+				link_pipe = pipe;
+				break;
+			}
+		}
+	}
+
+	/* Stream not found */
+	if (i == MAX_PIPES)
+		return;
+
+	FRL_INFO("FRL LINK TRAINING:  Preferred link Update = %d.\n", link_setting->frl_link_rate);
+
+	frl_validate_mode_timing(link, &link_stream->timing, link_setting);
+
+	if (lt_overrides)
+		link->preferred_hdmi_frl_settings = *lt_overrides;
+	else
+		memset(&link->preferred_hdmi_frl_settings, 0, sizeof(link->preferred_hdmi_frl_settings));
+
+	link_stream->link->frl_link_settings = *link_setting;
+	link_stream->link->frl_verified_link_cap = *link_setting;
+
+	while (link_stat != LINK_RESULT_SUCCESS) {
+		link_set_dpms_off(pipe);
+		/* For DCN3.0, can also have 4:1 combine mode.
+		 * TODO: Add function get_odm_combine_mode that has different
+		 *       implementation for DCN2/DCN3AG and DCN3.0
+		 */
+		for (odm_pipe = pipe->next_odm_pipe; odm_pipe; odm_pipe = odm_pipe->next_odm_pipe)
+			opp_cnt++;
+
+		memcpy(temp_stream, link_stream, sizeof(struct dc_stream_state));
+		/* Modify patched_crtc_timing as required for padding */
+		if (link_pipe->dsc_padding_params.dsc_hactive_padding) {
+			temp_stream->timing.h_addressable = link_stream->timing.h_addressable + link_pipe->dsc_padding_params.dsc_hactive_padding;
+			temp_stream->timing.h_total = link_stream->timing.h_total + link_pipe->dsc_padding_params.dsc_htotal_padding;
+		}
+
+		pipe->stream_res.hpo_frl_stream_enc->funcs->hdmi_frl_set_stream_attribute(
+			pipe->stream_res.hpo_frl_stream_enc,
+			&temp_stream->timing,
+			&link_stream->link->frl_link_settings.borrow_params,
+			opp_cnt);
+
+		if (pipe->stream_res.tg->funcs->set_out_mux)
+			pipe->stream_res.tg->funcs->set_out_mux(pipe->stream_res.tg, OUT_MUX_HPO_FRL);
+
+		if ((!link_stream->link->link_enc) ||
+				(!link_stream->link->hpo_frl_link_enc) ||
+				(!link_stream->ctx->dc->res_pool->dccg->funcs->enable_hdmicharclk) ||
+				(!(pipe->stream_res.hpo_frl_stream_enc)))
+			return;
+
+		switch (link_stream->link->frl_link_settings.frl_link_rate) {
+		case HDMI_FRL_LINK_RATE_3GBPS:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 166667;
+			break;
+		case HDMI_FRL_LINK_RATE_6GBPS:
+		case HDMI_FRL_LINK_RATE_6GBPS_4LANE:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 333333;
+			break;
+		case HDMI_FRL_LINK_RATE_8GBPS:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 444444;
+			break;
+		case HDMI_FRL_LINK_RATE_10GBPS:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 555555;
+			break;
+		case HDMI_FRL_LINK_RATE_12GBPS:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 666667;
+			break;
+		case HDMI_FRL_LINK_RATE_16GBPS:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 888889;
+			break;
+		case HDMI_FRL_LINK_RATE_20GBPS:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 1111111;
+			break;
+		case HDMI_FRL_LINK_RATE_24GBPS:
+			pipe->stream_res.pix_clk_params.requested_sym_clk = 1333333;
+			break;
+		default:
+			break;
+		}
+
+		link_stream->phy_pix_clk = pipe->stream_res.pix_clk_params.requested_sym_clk;
+
+		memset(&link_stream->link->cur_link_settings, 0,
+				sizeof(struct dc_link_settings));
+
+		/* Find proper clock source in HDMI FRL mode for phy used for DCCG */
+		frl_phy_clock_source_id = hdmi_frl_find_matching_phypll(link);
+
+		dc->hwss.setup_hdmi_frl_link(link,
+				(pipe->stream_res.hpo_frl_stream_enc->id - ENGINE_ID_HPO_0),
+				frl_phy_clock_source_id);
+
+		FRL_INFO("FRL LINK TRAINING:  Start forced link training at %d. \n",
+				link_stream->link->frl_link_settings.frl_link_rate);
+		link_stat = hdmi_frl_perform_link_training_with_retries(link_stream->link);
+
+		/* Enable FRL packet transmission */
+		if (link_stat == LINK_RESULT_SUCCESS) {
+			link_stream->link->hpo_frl_link_enc->funcs->enable_output(
+					link_stream->link->hpo_frl_link_enc);
+			if (link_stream->link->frl_flags.apply_vsdb_rcc_wa)
+				link_stream->link->hpo_frl_link_enc->funcs->apply_vsdb_rcc_wa(link_stream->link->hpo_frl_link_enc);
+			hdmi_frl_poll_start(link_stream->link->ddc);
+
+			/* Set HDMISTREAMCLK source to DTBCLK0 and bypass DTO */
+			if (dc->res_pool->dccg->funcs->set_hdmistreamclk) {
+				dc->res_pool->dccg->funcs->set_hdmistreamclk(
+						dc->res_pool->dccg,
+						DTBCLK0,
+						pipe->stream_res.tg->inst);
+			}
+
+			pipe->stream_res.hpo_frl_stream_enc->funcs->hdmi_frl_enable(
+				pipe->stream_res.hpo_frl_stream_enc,
+				pipe->stream_res.tg->inst);
+			resource_build_info_frame(pipe);
+			link_stream->ctx->dc->hwss.update_info_frame(pipe);
+
+			link_stream->ctx->dc->hwss.enable_audio_stream(pipe);
+			link_stream->ctx->dc->hwss.enable_stream(pipe);
+			link_stream->ctx->dc->hwss.unblank_stream(pipe,
+				&pipe->stream->link->cur_link_settings);
+			FRL_INFO("FRL LINK TRAINING:  Forced link training successful. \n");
+		}
+		if (link_stat == LINK_RESULT_LOWER_LINKRATE) {
+			link_stream->link->frl_link_settings.frl_link_rate--;
+			if (link_stream->link->frl_link_settings.frl_link_rate >
+				 HDMI_FRL_LINK_RATE_6GBPS)
+				link_stream->link->frl_link_settings.frl_num_lanes = 4;
+			else
+				link_stream->link->frl_link_settings.frl_num_lanes = 3;
+			FRL_INFO("FRL LINK TRAINING:  Lower link rate = %d.\n",
+				link_stream->link->frl_link_settings.frl_link_rate);
+		}
+		if (link_stat == LINK_RESULT_FALLBACK) {
+			FRL_INFO("FRL LINK TRAINING: Forced Link Training failed.  Fallback to TMDS. \n");
+			break;
+		}
+	}
+}
+
+static void update_borrow_mode_from_dsc_padding(struct dsc_padding_params *dsc_padding_params,
+	struct dc_crtc_timing *timing,
+	struct dc_hdmi_frl_link_settings *frl_link_settings)
+{
+#ifdef CONFIG_DRM_AMD_DC_FP
+	uint32_t h_active = timing->h_addressable + timing->h_border_left + timing->h_border_right;
+	uint32_t h_blank = timing->h_total - h_active;
+	struct frl_borrow_params *borrow_params = &frl_link_settings->borrow_params;
+
+	borrow_params->borrow_mode = frl_modify_borrow_mode_for_dsc_padding(timing->pix_clk_100hz,
+		h_active,
+		h_active + dsc_padding_params->dsc_hactive_padding,
+		h_blank,
+		h_blank + dsc_padding_params->dsc_htotal_padding,
+		borrow_params->hc_active_target,
+		borrow_params->hc_blank_target,
+		frl_link_settings->frl_num_lanes,
+		frl_link_settings->frl_link_rate);
+#endif
+}
+
+void hdmi_frl_decide_link_settings(struct dc_stream_state *stream,
+	struct dc_hdmi_frl_link_settings *frl_link_settings,
+	struct dsc_padding_params *dsc_padding_params)
+{
+	bool success = false;
+	struct dc_hdmi_frl_link_settings temp_settings = {0};
+
+	temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS;
+	temp_settings.frl_num_lanes = 3;
+
+	/* Verify FRL and fill in borrow_params to verified_link_cap*/
+	frl_validate_mode_timing(
+		stream->link,
+		&stream->timing,
+		&stream->link->frl_verified_link_cap);
+
+	if (stream->link->frl_flags.force_frl_rate != 0 &&
+		stream->link->frl_flags.force_frl_rate < stream->link->frl_verified_link_cap.frl_link_rate) {
+		switch (stream->link->frl_flags.force_frl_rate) {
+		case HDMI_FRL_LINK_RATE_3GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_3GBPS;
+			temp_settings.frl_num_lanes = 3;
+			break;
+		case HDMI_FRL_LINK_RATE_6GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_6GBPS;
+			temp_settings.frl_num_lanes = 3;
+			break;
+		case HDMI_FRL_LINK_RATE_6GBPS_4LANE:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_6GBPS;
+			temp_settings.frl_num_lanes = 4;
+			break;
+		case HDMI_FRL_LINK_RATE_8GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_8GBPS;
+			temp_settings.frl_num_lanes = 4;
+			break;
+		case HDMI_FRL_LINK_RATE_10GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_10GBPS;
+			temp_settings.frl_num_lanes = 4;
+			break;
+		case HDMI_FRL_LINK_RATE_12GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_12GBPS;
+			temp_settings.frl_num_lanes = 4;
+			break;
+		case HDMI_FRL_LINK_RATE_16GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_16GBPS;
+			temp_settings.frl_num_lanes = 4;
+			break;
+		case HDMI_FRL_LINK_RATE_20GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_20GBPS;
+			temp_settings.frl_num_lanes = 4;
+			break;
+		case HDMI_FRL_LINK_RATE_24GBPS:
+			temp_settings.frl_link_rate = HDMI_FRL_LINK_RATE_24GBPS;
+			temp_settings.frl_num_lanes = 4;
+			break;
+		default:
+			break;
+		}
+		*frl_link_settings = temp_settings;
+		return;
+	}
+	/*test equipment requires max rate, identified at FRL_Max = 1*/
+	if (stream->link->frl_flags.force_frl_max) {
+		*frl_link_settings = stream->link->frl_verified_link_cap;
+		return;
+	}
+
+	if (stream->link->local_sink)
+		if (stream->link->local_sink->edid_caps.panel_patch.hdmi_spe_handling) {
+			*frl_link_settings = stream->link->frl_verified_link_cap;
+			return;
+		}
+
+	do {
+		success = frl_validate_mode_timing(
+					stream->link,
+					&stream->timing,
+					&temp_settings);
+		if (temp_settings.frl_link_rate ==
+			 stream->link->frl_verified_link_cap.frl_link_rate)
+			break;
+		if (!success)
+			temp_settings.frl_link_rate++;
+		if (temp_settings.frl_link_rate > HDMI_FRL_LINK_RATE_6GBPS)
+			temp_settings.frl_num_lanes = 4;
+	} while (!success);
+
+	*frl_link_settings = temp_settings;
+}
+
+void hdmi_frl_write_read_request_enable(struct ddc_service *ddc_service)
+{
+	uint8_t slave_address = HDMI_SCDC_ADDRESS;
+	uint8_t offset = HDMI_SCDC_CONFIG_0;
+	uint8_t scdc_config = 0;
+	uint8_t write_buffer[2] = {0};
+
+	link_query_ddc_data(ddc_service, slave_address, &offset,
+			sizeof(offset), &scdc_config, sizeof(scdc_config));
+
+	write_buffer[0] = HDMI_SCDC_CONFIG_0;
+	write_buffer[1] = scdc_config;
+	write_buffer[1] |= 0x1;
+
+	link_query_ddc_data(ddc_service, slave_address, write_buffer,
+		sizeof(write_buffer), NULL, 0);
+}
diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.h b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.h
new file mode 100644
index 000000000000..abeec56c6528
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 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
+ *
+ */
+#ifndef __LINK_HDMI_FRL_H__
+#define __LINK_HDMI_FRL_H__
+#include "link_service.h"
+enum clock_source_id hdmi_frl_find_matching_phypll(
+		struct dc_link *link);
+void hdmi_frl_LTS_clear_Update_flag(struct ddc_service *ddc_service);
+void hdmi_frl_poll_start(struct ddc_service *ddc_service);
+void hdmi_frl_LTS_clear_Link_Setting(struct ddc_service *ddc_service);
+void hdmi_frl_retrieve_link_cap(struct dc_link *link, struct dc_sink *sink);
+enum link_result hdmi_frl_perform_link_training_with_retries(
+	struct dc_link *link);
+enum link_result hdmi_frl_perform_link_training_with_fallback(
+	struct dc_link *link, struct link_resource *link_res,
+	enum clock_source_id frl_phy_clock_source_id);
+void hdmi_frl_verify_link_cap(struct dc_link *link,
+		struct dc_hdmi_frl_link_settings *known_limit_link_setting);
+void hdmi_frl_decide_link_settings(struct dc_stream_state *stream,
+	struct dc_hdmi_frl_link_settings *frl_link_settings,
+	struct dsc_padding_params *dsc_paddding_params);
+bool hdmi_frl_poll_status_flag(struct dc_link *link);
+struct dc_hdmi_frl_link_settings *hdmi_frl_get_verified_link_cap(
+		struct dc_link *link);
+void hdmi_frl_set_preferred_link_settings(struct dc *dc,
+		struct dc_hdmi_frl_link_settings *link_setting,
+		struct dc_hdmi_frl_link_training_overrides *lt_overrides,
+		struct dc_link *link);
+void hdmi_frl_write_read_request_enable(
+		struct ddc_service *ddc_service);
+#endif /* __LINK_HDMI_FRL_H__ */
diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hpd.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hpd.c
index b157d05b67ad..cfb5ef0523a7 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_hpd.c
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hpd.c
@@ -80,6 +80,7 @@ bool program_hpd_filter(const struct dc_link *link)
 	case SIGNAL_TYPE_DVI_SINGLE_LINK:
 	case SIGNAL_TYPE_DVI_DUAL_LINK:
 	case SIGNAL_TYPE_HDMI_TYPE_A:
+	case SIGNAL_TYPE_HDMI_FRL:
 		/* Program hpd filter */
 		delay_on_connect_in_ms = 500;
 		delay_on_disconnect_in_ms = 100;
-- 
2.54.0


  parent reply	other threads:[~2026-05-12 15:53 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-12 15:52 [PATCH v5 00/13] HDMI FRL and DSC Support for amdgpu Harry Wentland
2026-05-12 15:52 ` [PATCH v5 01/13] drm/amd/display: Add HDMI FRL definitions to includes Harry Wentland
2026-05-12 15:52 ` [PATCH v5 02/13] drm/amd/display: Add DML changes to support HDMI FRL Harry Wentland
2026-05-12 15:52 ` [PATCH v5 03/13] drm/amd/display: add HDMI 2.1 FRL base support to DML 2.0 Harry Wentland
2026-05-12 15:52 ` [PATCH v5 04/13] drm/amd/display: Add DCCG DIO, HPO, OPP, and OPTC support for FRL Harry Wentland
2026-05-12 15:52 ` [PATCH v5 05/13] drm/amd/display: Add FRL support to clk_mgr, dsc, hdcp Harry Wentland
2026-05-12 15:52 ` [PATCH v5 06/13] drm/amd/display: Tie FRL programming together in HWSS Harry Wentland
2026-05-12 15:52 ` [PATCH v5 07/13] drm/amd/display: Add DC resource support for FRL Harry Wentland
2026-05-12 15:52 ` Harry Wentland [this message]
2026-05-12 15:52 ` [PATCH v5 09/13] drm/amd/display: Add support for FRL to DC core Harry Wentland
2026-05-12 15:52 ` [PATCH v5 10/13] drm/amd/display: Update HDCP and info_packet modules for FRL Harry Wentland
2026-05-12 15:52 ` [PATCH v5 11/13] drm/amd/display: Tie FRL support into amdgpu_dm Harry Wentland
2026-05-13  0:11   ` [PATCH] drm/amd/display: fix FRL link-status polling never running dyllan
2026-05-12 15:52 ` [PATCH v5 12/13] drm/amd/display: add HDMI 2.1 Compliance Support Harry Wentland
2026-05-12 15:52 ` [PATCH v5 13/13] drm/amd/display: add HDMI 2.1 DSC over FRL support Harry Wentland
2026-05-17 18:58 ` [PATCH v5 00/13] HDMI FRL and DSC Support for amdgpu Tomasz Pakuła

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260512155244.403854-9-harry.wentland@amd.com \
    --to=harry.wentland@amd.com \
    --cc=Jerry.Zuo@amd.com \
    --cc=amd-gfx@lists.freedesktop.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox