Add support for FRL Link training state and transition to different states during FRL Link training. Signed-off-by: Ankit Nautiyal <ankit.k.nautiyal@xxxxxxxxx> --- 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