[Intel-gfx] [RFC 15/15] drm/i915/display/mtl: Add functions for FRL trainining state machine
Ankit Nautiyal
ankit.k.nautiyal at intel.com
Mon Nov 7 07:20:45 UTC 2022
Add support for FRL Link training state and transition
to different states during FRL Link training.
Signed-off-by: Ankit Nautiyal <ankit.k.nautiyal at intel.com>
---
drivers/gpu/drm/i915/display/intel_ddi.c | 2 +
drivers/gpu/drm/i915/display/intel_hdmi.c | 383 ++++++++++++++++++++++
drivers/gpu/drm/i915/display/intel_hdmi.h | 2 +
3 files changed, 387 insertions(+)
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__ */
--
2.25.1
More information about the Intel-gfx
mailing list