[PATCH 14/44] drm/i915/hdmi: Add functions for FRL training state machine

Ankit Nautiyal <[email protected]>
Newsgroups org.freedesktop.lists.dri-devel,org.freedesktop.lists.intel-gfx,org.freedesktop.lists.intel-xe
Message-ID <[email protected]>
Add the FRL link training state machine and the helpers to drive it
with an HDMI FRL sink over SCDC: prepare for LTS:2, train the lanes in
LTS:3, complete training in LTS:P and fall back to legacy TMDS in LTS:L.

intel_hdmi_start_frl() drives these states for the requested rate and
lanes.

Signed-off-by: Ankit Nautiyal <[email protected]>
---
 drivers/gpu/drm/i915/display/intel_hdmi.c | 258 ++++++++++++++++++++++
 drivers/gpu/drm/i915/display/intel_hdmi.h |   2 +
 2 files changed, 260 insertions(+)

diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.c b/drivers/gpu/drm/i915/display/intel_hdmi.c
index 96542869740e..ff200d662a3f 100644
--- a/drivers/gpu/drm/i915/display/intel_hdmi.c
+++ b/drivers/gpu/drm/i915/display/intel_hdmi.c
@@ -3523,3 +3523,261 @@ void intel_hdmi_prepare_for_frl_mode(const struct intel_crtc_state *crtc_state)
 
 	intel_de_write(display, TRANS_HDMI_FRL_TRAIN(display, trans), write_buf);
 }
+
+static bool is_flt_ready(struct intel_encoder *encoder)
+{
+	struct intel_display *display = to_intel_display(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(display, intel_hdmi_ddc_pin(encoder));
+
+	return drm_scdc_read_status_flags(adapter) & SCDC_FLT_READY;
+}
+
+static u8 get_frl_update_flags(struct intel_encoder *encoder)
+{
+	struct intel_display *display = to_intel_display(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(display, intel_hdmi_ddc_pin(encoder));
+
+	return drm_scdc_read_update_flags(adapter);
+}
+
+static int clear_scdc_update_flags(struct intel_encoder *encoder, u8 flags)
+{
+	struct intel_display *display = to_intel_display(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(display, intel_hdmi_ddc_pin(encoder));
+
+	return drm_scdc_clear_update_flags(adapter, flags);
+}
+
+static bool
+intel_hdmi_frl_prepare_lts2(struct intel_encoder *encoder,
+			    int frl_rate, int frl_lanes,
+			    int ffe_level)
+{
+#define TIMEOUT_FLT_READY_MS 250
+	struct intel_display *display = to_intel_display(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(display, intel_hdmi_ddc_pin(encoder));
+	bool flt_ready = false;
+
+	if (!frl_rate || !frl_lanes)
+		return false;
+
+	if (read_poll_timeout(is_flt_ready, flt_ready,
+			      flt_ready, 1000,
+			      TIMEOUT_FLT_READY_MS * USEC_PER_MSEC,
+			      false, encoder)) {
+		drm_dbg_kms(display->drm,
+			    "HDMI sink not ready for FRL in %d\n",
+			    TIMEOUT_FLT_READY_MS);
+		return false;
+	}
+
+	if ((get_frl_update_flags(encoder) & SCDC_FLT_UPDATE))
+		clear_scdc_update_flags(encoder, SCDC_FLT_UPDATE);
+
+	/* #TODO: Source shall program TxFFE = 0 for all active lanes */
+
+	if (drm_scdc_config_frl(adapter, frl_rate, frl_lanes, ffe_level) < 0) {
+		drm_dbg_kms(display->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 int get_link_training_patterns(struct intel_encoder *encoder,
+				      enum drm_scdc_frl_ltp ltp[4])
+{
+	struct intel_display *display = to_intel_display(encoder);
+	struct i2c_adapter *adapter =
+		intel_gmbus_get_adapter(display, intel_hdmi_ddc_pin(encoder));
+
+	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 intel_display *display = to_intel_display(encoder);
+	enum transcoder trans = crtc_state->cpu_transcoder;
+	int num_lanes = crtc_state->frl.required_lanes;
+	enum drm_scdc_frl_ltp ltp[4];
+	u32 write_buf = 0;
+	int lane;
+
+	if (!(get_frl_update_flags(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_buf |= TRANS_HDMI_FRL_LTP(ltp[lane], lane);
+		/* #TODO handle FFE change */
+		else if (ltp[lane] == SCDC_FRL_CHNG_FFE)
+			continue;
+	}
+
+	intel_de_write(display, TRANS_HDMI_FRL_TRAIN(display, trans), write_buf);
+
+	clear_scdc_update_flags(encoder, SCDC_FLT_UPDATE);
+
+	return FRL_TRAIN_CONTINUE;
+}
+
+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 intel_display *display = to_intel_display(encoder);
+	enum transcoder trans = crtc_state->cpu_transcoder;
+	u8 update_flags = 0, clear_flags = 0;
+	u32 buf;
+
+	buf = intel_de_read(display, TRANS_HDMI_FRL_CFG(display, trans));
+	intel_de_write(display, TRANS_HDMI_FRL_CFG(display, trans),
+		       buf | TRANS_HDMI_FRL_TRAINING_COMPLETE);
+
+	if (clear_scdc_update_flags(encoder, SCDC_FLT_UPDATE) < 0)
+		return FRL_TRAIN_STOP;
+
+	if (!read_poll_timeout(get_frl_update_flags, update_flags,
+			       update_flags & (SCDC_FRL_START | SCDC_FLT_UPDATE),
+			       1000, FLT_UPDATE_TIMEOUT_MS * USEC_PER_MSEC,
+			       false, encoder)) {
+		if (update_flags & SCDC_FLT_UPDATE)
+			clear_flags |= SCDC_FLT_UPDATE;
+		if (update_flags & SCDC_FRL_START) {
+			clear_flags |= SCDC_FRL_START;
+			clear_scdc_update_flags(encoder, clear_flags);
+			return FRL_TRAINING_PASSED;
+		}
+		drm_dbg_kms(display->drm,
+			    "FRL update received for retraining the lanes\n");
+		clear_scdc_update_flags(encoder, clear_flags);
+		return FRL_TRAIN_RETRAIN;
+	}
+
+	drm_err(display->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)
+{
+#define FLT_TIMEOUT_MS 200
+	struct intel_display *display = to_intel_display(encoder);
+	enum frl_lt_status status;
+	enum transcoder trans = crtc_state->cpu_transcoder;
+	u32 buf;
+
+	buf = intel_de_read(display, TRANS_HDMI_FRL_CFG(display, trans));
+	intel_de_write(display, TRANS_HDMI_FRL_CFG(display, trans),
+		       buf | TRANS_HDMI_FRL_ENABLE);
+
+#define done (status != FRL_TRAIN_CONTINUE)
+	status = FRL_TRAIN_CONTINUE;
+	read_poll_timeout(intel_hdmi_train_lanes, status,
+			  done,
+			  1000, FLT_TIMEOUT_MS * USEC_PER_MSEC,
+			  false, encoder, crtc_state, ffe_level);
+
+	if (status == FRL_TRAIN_CONTINUE) {
+		drm_err(display->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 int get_ffe_level(int rate_gbps)
+{
+	return 0;
+}
+
+int intel_hdmi_start_frl(struct intel_encoder *encoder,
+			 const struct intel_crtc_state *crtc_state)
+{
+	struct intel_display *display = to_intel_display(encoder);
+	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 req_rate = crtc_state->frl.required_lanes * crtc_state->frl.required_rate;
+	int ffe_level = get_ffe_level(req_rate);
+	enum frl_lt_status status;
+
+	if (!crtc_state->frl.enable)
+		return 0;
+
+	if (intel_hdmi->frl.trained &&
+	    intel_hdmi->frl.rate_gbps >= req_rate) {
+		drm_dbg_kms(display->drm,
+			    "[CONNECTOR:%d:%s] FRL Already trained with rate=%d\n",
+			    connector->base.id, connector->name,
+			    intel_hdmi->frl.rate_gbps);
+		return 0;
+	}
+
+	intel_hdmi_reset_frl_config(intel_hdmi);
+
+	if (!intel_hdmi_frl_prepare_lts2(encoder,
+					 crtc_state->frl.required_rate,
+					 crtc_state->frl.required_lanes,
+					 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(display->drm,
+			    "[CONNECTOR:%d:%s] FRL Training Passed with rate=%d\n",
+			    connector->base.id, connector->name,
+			    intel_hdmi->frl.rate_gbps);
+		return 0;
+	default:
+		break;
+	}
+
+	if (crtc_state->frl.enable && !intel_hdmi->frl.trained)
+		drm_err(display->drm,
+			"[CONNECTOR:%d:%s] FRL Training Failed with rate=%d\n",
+			connector->base.id, connector->name,
+			req_rate);
+
+	return -EINVAL;
+}
diff --git a/drivers/gpu/drm/i915/display/intel_hdmi.h b/drivers/gpu/drm/i915/display/intel_hdmi.h
index 6283305b5274..1724e6075a9b 100644
--- a/drivers/gpu/drm/i915/display/intel_hdmi.h
+++ b/drivers/gpu/drm/i915/display/intel_hdmi.h
@@ -64,6 +64,8 @@ int intel_hdmi_dsc_get_num_slices(const struct drm_display_mode *mode,
 int intel_hdmi_dsc_get_slice_height(int vactive);
 bool intel_hdmi_is_frl(u32 clock);
 void intel_hdmi_prepare_for_frl_mode(const struct intel_crtc_state *crtc_state);
+int intel_hdmi_start_frl(struct intel_encoder *encoder,
+			 const struct intel_crtc_state *crtc_state);
 void hsw_write_infoframe(struct intel_encoder *encoder,
 			 const struct intel_crtc_state *crtc_state,
 			 unsigned int type,
-- 
2.50.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.