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 <[email protected]>
---
 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 | 1147 +++++++++++++++++
 .../display/dc/link/protocols/link_hdmi_frl.h |   53 +
 .../amd/display/dc/link/protocols/link_hpd.c  |    1 +
 14 files changed, 2094 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 7924fe4ab3a5..46feab491612 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 e7d3f9bd8aa5..ca56854dc9f1 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)
 {
@@ -2078,6 +2236,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;
@@ -2181,6 +2347,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;
 
@@ -2193,6 +2361,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);
@@ -2295,6 +2465,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;
 
@@ -2309,11 +2481,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(
@@ -2323,9 +2497,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);
@@ -2416,8 +2595,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);
@@ -2437,6 +2617,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 765b731a12a4..eecaa57c484b 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);
 
@@ -620,6 +646,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:
@@ -823,12 +850,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 eb791285ed06..64930a120401 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)
@@ -329,6 +519,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,
@@ -343,6 +613,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;
@@ -360,6 +639,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 ef9306686b14..344fbe3f5a0c 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"
@@ -551,6 +552,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));
@@ -601,3 +604,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..6db1f07fdb79
--- /dev/null
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_hdmi_frl.c
@@ -0,0 +1,1147 @@
+/*
+ * 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)
+{
+       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);
+}
+
+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

Reply via email to