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

Reply via email to