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
