diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c
index cb0d19b6ee56..4b1b8a18863e 100644
--- a/drivers/gpu/drm/i915/display/intel_ddi.c
+++ b/drivers/gpu/drm/i915/display/intel_ddi.c
@@ -2514,6 +2514,8 @@ static void intel_ddi_pre_enable_hdmi(struct intel_atomic_state *state,
 
 	intel_ddi_enable_pipe_clock(encoder, crtc_state);
 
+	intel_hdmi_start_frl(encoder, crtc_state);
+
 	dig_port->set_infoframes(encoder,
 				 crtc_state->has_infoframe,
 				 crtc_state, conn_state);
diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.c b/drivers/gpu/drm/i915/display/intel_hdmi.c
index 9e8ee6d5bc5d..6553763306ff 100644
--- a/drivers/gpu/drm/i915/display/intel_hdmi.c
+++ b/drivers/gpu/drm/i915/display/intel_hdmi.c
@@ -3285,3 +3285,386 @@ intel_hdmi_dsc_get_bpp(int src_fractional_bpp, int slice_width, int num_slices,
 
 	return 0;
 }
+
+static
+bool is_flt_ready(struct intel_encoder *encoder)
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus);
+
+	return drm_scdc_read_status_flags(adapter) & SCDC_FLT_READY;
+}
+
+static
+bool intel_hdmi_frl_prepare_lts2(struct intel_encoder *encoder,
+				 const struct intel_crtc_state *crtc_state,
+				 int ffe_level)
+{
+#define TIMEOUT_FLT_READY_MS  250
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus);
+	bool flt_ready = false;
+	int frl_rate;
+	int frl_lanes;
+
+	frl_rate = crtc_state->frl.required_rate;
+	frl_lanes = crtc_state->frl.required_lanes;
+
+	if (!frl_rate || !frl_lanes)
+		return false;
+
+	/*
+	 * POLL for FRL ready : READ SCDC 0x40 Bit 6 FLT ready
+	 * #TODO Check if 250 msec is required
+	 */
+	wait_for(flt_ready = is_flt_ready(encoder) == true,
+		 TIMEOUT_FLT_READY_MS);
+
+	if (!flt_ready) {
+		drm_dbg_kms(&dev_priv->drm,
+			    "HDMI sink not ready for FRL in %d\n",
+			    TIMEOUT_FLT_READY_MS);
+
+		return false;
+	}
+
+	/*
+	 * #TODO As per spec, during prepare phase LTS2, the TXFFE to be
+	 * programmed to be 0 for each lane in the PHY registers.
+	 */
+
+	if (drm_scdc_config_frl(adapter, frl_rate, frl_lanes, ffe_level) < 0) {
+		drm_dbg_kms(&dev_priv->drm,
+			    "Failed to write SCDC config regs for FRL\n");
+
+		return false;
+	}
+
+	return flt_ready;
+}
+
+enum frl_lt_status {
+	FRL_TRAINING_PASSED,
+	FRL_CHANGE_RATE,
+	FRL_TRAIN_CONTINUE,
+	FRL_TRAIN_RETRAIN,
+	FRL_TRAIN_STOP,
+};
+
+static
+u8 get_frl_update_flag(struct intel_encoder *encoder)
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus);
+
+	return drm_scdc_read_update_flags(adapter);
+}
+
+static
+int get_link_training_patterns(struct intel_encoder *encoder,
+			       enum drm_scdc_frl_ltp ltp[4])
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus);
+
+	return drm_scdc_get_ltp(adapter, ltp);
+}
+
+static enum frl_lt_status
+intel_hdmi_train_lanes(struct intel_encoder *encoder,
+		       const struct intel_crtc_state *crtc_state,
+		       int ffe_level)
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	enum transcoder trans = crtc_state->cpu_transcoder;
+	enum drm_scdc_frl_ltp ltp[4];
+	int num_lanes = crtc_state->frl.required_lanes;
+	int lane;
+
+	/*
+	 * LTS3 Link Training in Progress.
+	 * Section 6.4.2.3 Table 6-34.
+	 *
+	 * Transmit link training pattern as requested by the sink
+	 * for a specific rate.
+	 * Source keep on Polling on FLT update flag and keep
+	 * repeating patterns till timeout or request for new rate,
+	 * or training is successful.
+	 */
+	if (!(get_frl_update_flag(encoder) & SCDC_FLT_UPDATE))
+		return FRL_TRAIN_CONTINUE;
+
+	if (get_link_training_patterns(encoder, ltp) < 0)
+		return FRL_TRAIN_STOP;
+
+	if (ltp[0] == ltp[1] && ltp[1] == ltp[2]) {
+		if (num_lanes == 3 || (num_lanes == 4 && ltp[2] == ltp[3])) {
+			if (ltp[0] == SCDC_FRL_NO_LTP)
+				return FRL_TRAINING_PASSED;
+			if (ltp[0] == SCDC_FRL_CHNG_RATE)
+				return FRL_CHANGE_RATE;
+		}
+	}
+
+	for (lane = 0; lane < num_lanes; lane++) {
+		if (ltp[lane] >= SCDC_FRL_LTP1 && ltp[lane] <= SCDC_FRL_LTP8)
+			/* write the LTP for the lane*/
+			intel_de_write(dev_priv, TRANS_HDMI_FRL_TRAIN(trans),
+				       TRANS_HDMI_FRL_LTP(ltp[lane], lane));
+		else if (ltp[lane] == SCDC_FRL_CHNG_FFE) {
+			/*
+			 * #TODO Update TxFFE for the lane
+			 *
+			 * Read the existing TxFFE for the lane, from PHY regs.
+			 * If TxFFE is already at FFE_level (i.e. max level)
+			 * then Set TXFFE0 for the lane.
+			 * Otherwise increment TxFFE for the lane.
+			 */
+		}
+	}
+
+	return FRL_TRAIN_CONTINUE;
+}
+
+static int
+clear_scdc_update_flags(struct intel_encoder *encoder, u8 flags)
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus);
+
+	return drm_scdc_clear_update_flags(adapter, flags);
+}
+
+static enum frl_lt_status
+frl_train_complete_ltsp(struct intel_encoder *encoder,
+			const struct intel_crtc_state *crtc_state)
+{
+#define FLT_UPDATE_TIMEOUT_MS 200
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	enum transcoder trans = crtc_state->cpu_transcoder;
+	u32 buf;
+	u8 update_flag = 0;
+
+	/*
+	 * Start FRL transmission with only Gap Characters, with Scrambing,
+	 * Reed Solomon FEC, and Super block structure.
+	 */
+	buf = intel_de_read(dev_priv, TRANS_HDMI_FRL_CFG(trans));
+	intel_de_write(dev_priv, TRANS_HDMI_FRL_CFG(trans),
+		       buf | TRANS_HDMI_FRL_TRAINING_COMPLETE);
+
+	/* Clear SCDC FLT_UPDATE by writing 1 */
+	if (clear_scdc_update_flags(encoder, SCDC_FLT_UPDATE) < 0)
+		return FRL_TRAIN_STOP;
+
+	wait_for((update_flag = get_frl_update_flag(encoder)) &
+		 (SCDC_FRL_START | SCDC_FLT_UPDATE), FLT_UPDATE_TIMEOUT_MS);
+
+	if (update_flag & SCDC_FRL_START)
+		return FRL_TRAINING_PASSED;
+
+	if (update_flag & SCDC_FLT_UPDATE) {
+		drm_dbg_kms(&dev_priv->drm,
+			    "FRL update received for retraining the lanes\n");
+		clear_scdc_update_flags(encoder, SCDC_FLT_UPDATE);
+
+		return FRL_TRAIN_RETRAIN;
+	}
+
+	drm_err(&dev_priv->drm, "FRL TRAINING: FRL update timedout\n");
+
+	return FRL_TRAIN_STOP;
+}
+
+static enum frl_lt_status
+intel_hdmi_frl_train_lts3(struct intel_encoder *encoder,
+			  const struct intel_crtc_state *crtc_state,
+			  int ffe_level)
+{
+/*
+ * Time interval specified for link training HDMI2.1 Spec:
+ * Sec 6.4.2.1 Table 6-31
+ */
+#define FLT_TIMEOUT_MS 200
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	enum frl_lt_status status;
+	enum transcoder trans = crtc_state->cpu_transcoder;
+	u32 buf;
+
+	buf = intel_de_read(dev_priv, TRANS_HDMI_FRL_CFG(trans));
+	intel_de_write(dev_priv, TRANS_HDMI_FRL_CFG(trans),
+		       buf | TRANS_HDMI_FRL_ENABLE);
+
+#define done ((status = intel_hdmi_train_lanes(encoder, crtc_state, ffe_level)) != FRL_TRAIN_CONTINUE)
+	wait_for(done, FLT_TIMEOUT_MS);
+
+	/* TIMEDOUT */
+	if (status == FRL_TRAIN_CONTINUE) {
+		drm_err(&dev_priv->drm, "FRL TRAINING: FLT TIMEDOUT\n");
+
+		return FRL_TRAIN_STOP;
+	}
+
+	if (status != FRL_TRAINING_PASSED)
+		return status;
+
+	return frl_train_complete_ltsp(encoder, crtc_state);
+}
+
+static void intel_hdmi_frl_ltsl(struct intel_encoder *encoder,
+				const struct intel_crtc_state *crtc_state)
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(dev_priv, intel_hdmi->ddc_bus);
+	int lanes = crtc_state->frl.required_lanes;
+
+	/* Clear flags */
+	drm_scdc_config_frl(adapter, 0, lanes, 0);
+	drm_scdc_clear_update_flags(adapter, SCDC_FLT_UPDATE);
+}
+
+static bool get_next_frl_rate(int *curr_rate_gbps, int max_sink_rate)
+{
+	int valid_rate[] =  {48, 40, 32, 24, 18, 9};
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(valid_rate); i++) {
+		if (max_sink_rate < valid_rate[i])
+			continue;
+
+		if (*curr_rate_gbps < valid_rate[i]) {
+			*curr_rate_gbps = valid_rate[i];
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static int get_ffe_level(int rate_gbps)
+{
+	/*
+	 * #TODO check for FFE_LEVEL to be programmed
+	 *
+	 * Should start with max ffe_levels supported by source. MAX can be 3.
+	 * Currently setting ffe_level = 0.
+	 */
+	return 0;
+}
+
+/*
+ * intel_hdmi_start_frl - Start FRL training for HDMI2.1 sink
+ *
+ */
+void intel_hdmi_start_frl(struct intel_encoder *encoder,
+			  const struct intel_crtc_state *crtc_state)
+{
+	struct drm_i915_private *dev_priv = to_i915(encoder->base.dev);
+	struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
+	struct intel_hdmi *intel_hdmi = &dig_port->hdmi;
+	struct intel_connector *intel_connector = intel_hdmi->attached_connector;
+	struct drm_connector *connector = &intel_connector->base;
+	int *rate;
+	int max_rate = crtc_state->dsc.compression_enable ? intel_hdmi->max_dsc_frl_rate :
+							intel_hdmi->max_frl_rate;
+	int req_rate = crtc_state->frl.required_lanes * crtc_state->frl.required_rate;
+	int ffe_level = get_ffe_level(req_rate);
+	enum transcoder trans = crtc_state->cpu_transcoder;
+	enum frl_lt_status status;
+	u32 buf = 0;
+
+	if (DISPLAY_VER(dev_priv) < 14)
+		return;
+
+	if (!crtc_state->frl.enable)
+		goto ltsl_tmds_mode;
+
+	if (intel_hdmi->frl.trained &&
+	    intel_hdmi->frl.rate_gbps >= req_rate &&
+	    intel_hdmi->frl.ffe_level == ffe_level) {
+		drm_dbg_kms(&dev_priv->drm,
+			    "[CONNECTOR:%d:%s] FRL Already trained with rate=%d, ffe_level=%d\n",
+			    connector->base.id, connector->name,
+			    req_rate, ffe_level);
+
+		return;
+	}
+
+	intel_hdmi_reset_frl_config(intel_hdmi);
+
+	if (!intel_hdmi_frl_prepare_lts2(encoder, crtc_state, ffe_level))
+		status = FRL_TRAIN_STOP;
+	else
+		status = intel_hdmi_frl_train_lts3(encoder, crtc_state, ffe_level);
+
+	switch (status) {
+	case FRL_TRAINING_PASSED:
+		intel_hdmi->frl.trained = true;
+		intel_hdmi->frl.rate_gbps = req_rate;
+		intel_hdmi->frl.ffe_level = ffe_level;
+		drm_dbg_kms(&dev_priv->drm,
+			    "[CONNECTOR:%d:%s] FRL Training Passed with rate=%d, ffe_level=%d\n",
+			    connector->base.id, connector->name,
+			    req_rate, ffe_level);
+
+		return;
+	case FRL_TRAIN_STOP:
+		/*
+		 * Cannot go with FRL transmission.
+		 * Reset FRL rates so during next modeset TMDS mode will be
+		 * selected.
+		 */
+		if (crtc_state->dsc.compression_enable)
+			intel_hdmi->max_dsc_frl_rate = 0;
+		else
+			intel_hdmi->max_frl_rate = 0;
+		break;
+	case FRL_CHANGE_RATE:
+		/*
+		 * Sink request for change of FRL rate.
+		 * Set FRL rates for the connector with lower rate.
+		 */
+		if (crtc_state->dsc.compression_enable)
+			rate = &intel_hdmi->max_dsc_frl_rate;
+		else
+			rate = &intel_hdmi->max_frl_rate;
+		if (!get_next_frl_rate(rate, max_rate))
+			*rate = 0;
+		break;
+	case FRL_TRAIN_RETRAIN:
+		/*
+		 * For Retraining with same rate, we send a uevent to userspace.
+		 * TODO Need to check how many times we can retry.
+		 */
+		fallthrough;
+	default:
+		break;
+	}
+
+ltsl_tmds_mode:
+	intel_hdmi_frl_ltsl(encoder, crtc_state);
+	buf = intel_de_read(dev_priv, TRANS_HDMI_FRL_CFG(trans));
+	intel_de_write(dev_priv, TRANS_HDMI_FRL_CFG(trans),
+		       buf & ~(TRANS_HDMI_FRL_ENABLE | TRANS_HDMI_FRL_TRAINING_COMPLETE));
+
+	if (crtc_state->frl.enable && !intel_hdmi->frl.trained) {
+		drm_err(&dev_priv->drm,
+			"[CONNECTOR:%d:%s] FRL Training Failed with rate=%d, ffe_level=%d\n",
+			connector->base.id, connector->name,
+			req_rate, ffe_level);
+		/* Send event to user space, to try with next rate or fall back to TMDS */
+		schedule_work(&intel_connector->modeset_retry_work);
+	}
+}
diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.h b/drivers/gpu/drm/i915/display/intel_hdmi.h
index 774dda2376ed..a0a5c3159079 100644
--- a/drivers/gpu/drm/i915/display/intel_hdmi.h
+++ b/drivers/gpu/drm/i915/display/intel_hdmi.h
@@ -54,5 +54,7 @@ int intel_hdmi_dsc_get_num_slices(const struct intel_crtc_state *crtc_state,
 				  int src_max_slices, int src_max_slice_width,
 				  int hdmi_max_slices, int hdmi_throughput);
 int intel_hdmi_dsc_get_slice_height(int vactive);
+void intel_hdmi_start_frl(struct intel_encoder *encoder,
+			  const struct intel_crtc_state *crtc_state);
 
 #endif /* __INTEL_HDMI_H__ */
