These patches handle the DP link training failure during atomic modeset. A new connector property is defined to indicate if link training retry is requested on failure. If link training fails, we set this connector property and send a hotplug uevent that gets exceuted after the current modeset is completed an dlocks are released. The userspace is expected to monitor this connector property to detect a change in connector and trigger a full modeset. Here changes are made in fb_dev to monitor this property as well as the atomic check modeset to use this new property to detect a change. In the modeset retry, the modes are validated based on link rate lower than the failed link rate and link is retrained at lower link rate.
Manasi Navare (7): drm: Add a link_train_retry field to drm_connector drm: Define a work struct for scheduling a uevent for modeset retry drm: Trigger a complete modeset if link_train_retry is set drm/i915; Add a function to return index of link rate drm/i915: Define the modeset retry work function drm/i915: Link Rate fallback on Link training failure drm/i915: Add support for DP link training compliance
Navare, Manasi D (1): drm/i915: Change the placement of some static functions in intel_dp.c
drivers/gpu/drm/drm_atomic_helper.c | 9 + drivers/gpu/drm/drm_fb_helper.c | 3 +- drivers/gpu/drm/i915/intel_ddi.c | 14 +- drivers/gpu/drm/i915/intel_dp.c | 286 ++++++++++++++++++-------- drivers/gpu/drm/i915/intel_dp_link_training.c | 12 +- drivers/gpu/drm/i915/intel_drv.h | 8 +- include/drm/drm_connector.h | 10 + 7 files changed, 249 insertions(+), 93 deletions(-)
This is a boolean that is set to true if link training fails during modeset in the atomic commit. This will be used to detect a change in the connector properties and to retrigger a full modeset so that the link can be retrained at lower link rate value.
Cc: dri-devel@lists.freedesktop.org Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com
Signed-off-by: Manasi Navare manasi.d.navare@intel.com --- include/drm/drm_connector.h | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index ac9d7d8..d499466 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -682,6 +682,11 @@ struct drm_connector { uint8_t num_h_tile, num_v_tile; uint8_t tile_h_loc, tile_v_loc; uint16_t tile_h_size, tile_v_size; + + /* Flag to indicate if link train retry is required for DisplayPort + * in case of link train failure during current modeset + */ + bool link_train_retry; };
#define obj_to_connector(x) container_of(x, struct drm_connector, base)
This work struct will be used to schedule a uevent on a separate thread. This will be scheduled after a link train failure during modeset to indicate a modeset retry request. It will get executed after the current modeset is complete and all locks are released. This was required to avoid deadlock.
Cc: dri-devel@lists.freedesktop.org Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com
Signed-off-by: Manasi Navare manasi.d.navare@intel.com --- include/drm/drm_connector.h | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index d499466..9218a24 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -687,6 +687,11 @@ struct drm_connector { * in case of link train failure during current modeset */ bool link_train_retry; + + /* Work struct to schedule a uevent on link train failure for + * DisplayPort. + */ + struct work_struct i915_modeset_retry_work; };
#define obj_to_connector(x) container_of(x, struct drm_connector, base)
On Wed, 2016-10-19 at 14:46 -0700, Manasi Navare wrote:
This work struct will be used to schedule a uevent on a separate thread. This will be scheduled after a link train failure during modeset to indicate a modeset retry request. It will get executed after the current modeset is complete and all locks are released. This was required to avoid deadlock.
Cc: dri-devel@lists.freedesktop.org Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com
Signed-off-by: Manasi Navare manasi.d.navare@intel.com
include/drm/drm_connector.h | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index d499466..9218a24 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -687,6 +687,11 @@ struct drm_connector { * in case of link train failure during current modeset */ bool link_train_retry;
- /* Work struct to schedule a uevent on link train failure for
* DisplayPort.
*/
- struct work_struct i915_modeset_retry_work;
Should this be in struct intel_connector instead ?
};
#define obj_to_connector(x) container_of(x, struct drm_connector, base)
The link_train_retry property of the connector needs to be checked to see if a full modeset is required. If this is set, then link train retry is requested possibly due to linktrain failure in the previous modeset. Hence we need to indicate connector status changed in order to trigger a full modeset.
Cc: dri-devel@lists.freedesktop.org Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com Signed-off-by: Manasi Navare manasi.d.navare@intel.com --- drivers/gpu/drm/drm_atomic_helper.c | 9 +++++++++ drivers/gpu/drm/drm_fb_helper.c | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 07b432f..aeb2215 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -517,6 +517,15 @@ static int handle_conflicting_encoders(struct drm_atomic_state *state, */ ret = update_connector_routing(state, connector, connector_state); + /* Set crtc->mode_changed and crtc->connectors_changed if + * link_train_retry flag is set in the connector. + */ + if (connector->link_train_retry) { + crtc_state = drm_atomic_get_existing_crtc_state(state, + connector->state->crtc); + crtc_state->connectors_changed = true; + crtc_state->mode_changed = true; + } if (ret) return ret; } diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 8fffac8..b408e62 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -2152,7 +2152,8 @@ static void drm_setup_crtcs(struct drm_fb_helper *fb_helper) fb_crtc->desired_mode = mode; fb_crtc->x = offset->x; fb_crtc->y = offset->y; - if (modeset->mode) + if (modeset->mode && + !(fb_helper->connector_info[i]->connector->link_train_retry)) drm_mode_destroy(dev, modeset->mode); modeset->mode = drm_mode_duplicate(dev, fb_crtc->desired_mode);
From: "Navare, Manasi D" manasi.d.navare@intel.com
These static helper functions are required to be used during fallback link rate implemnetation so they need to be placed at the top of the file.
v3: * Add cleanup to other patch (Mika Kahola) v2: * Dont move around functions declared in intel_drv.h (Rodrigo Vivi)
Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com Signed-off-by: Manasi Navare manasi.d.navare@intel.com Reviewed-by: Mika Kahola mika.kahola@intel.com --- drivers/gpu/drm/i915/intel_dp.c | 150 ++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 75 deletions(-)
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index 88f3b74..6b54b0e 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -213,6 +213,81 @@ static u8 intel_dp_max_lane_count(struct intel_dp *intel_dp) return max_dotclk; }
+static int +intel_dp_sink_rates(struct intel_dp *intel_dp, const int **sink_rates) +{ + if (intel_dp->num_sink_rates) { + *sink_rates = intel_dp->sink_rates; + return intel_dp->num_sink_rates; + } + + *sink_rates = default_rates; + + return (intel_dp_max_link_bw(intel_dp) >> 3) + 1; +} + +static int +intel_dp_source_rates(struct intel_dp *intel_dp, const int **source_rates) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + int size; + + if (IS_BROXTON(dev_priv)) { + *source_rates = bxt_rates; + size = ARRAY_SIZE(bxt_rates); + } else if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv)) { + *source_rates = skl_rates; + size = ARRAY_SIZE(skl_rates); + } else { + *source_rates = default_rates; + size = ARRAY_SIZE(default_rates); + } + + /* This depends on the fact that 5.4 is last value in the array */ + if (!intel_dp_source_supports_hbr2(intel_dp)) + size--; + + return size; +} + +static int intersect_rates(const int *source_rates, int source_len, + const int *sink_rates, int sink_len, + int *common_rates) +{ + int i = 0, j = 0, k = 0; + + while (i < source_len && j < sink_len) { + if (source_rates[i] == sink_rates[j]) { + if (WARN_ON(k >= DP_MAX_SUPPORTED_RATES)) + return k; + common_rates[k] = source_rates[i]; + ++k; + ++i; + ++j; + } else if (source_rates[i] < sink_rates[j]) { + ++i; + } else { + ++j; + } + } + return k; +} + +static int intel_dp_common_rates(struct intel_dp *intel_dp, + int *common_rates) +{ + const int *source_rates, *sink_rates; + int source_len, sink_len; + + sink_len = intel_dp_sink_rates(intel_dp, &sink_rates); + source_len = intel_dp_source_rates(intel_dp, &source_rates); + + return intersect_rates(source_rates, source_len, + sink_rates, sink_len, + common_rates); +} + static enum drm_mode_status intel_dp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) @@ -1282,19 +1357,6 @@ static void intel_aux_reg_init(struct intel_dp *intel_dp) intel_dp->aux.transfer = intel_dp_aux_transfer; }
-static int -intel_dp_sink_rates(struct intel_dp *intel_dp, const int **sink_rates) -{ - if (intel_dp->num_sink_rates) { - *sink_rates = intel_dp->sink_rates; - return intel_dp->num_sink_rates; - } - - *sink_rates = default_rates; - - return (intel_dp_max_link_bw(intel_dp) >> 3) + 1; -} - bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp) { struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); @@ -1307,31 +1369,6 @@ bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp) return false; }
-static int -intel_dp_source_rates(struct intel_dp *intel_dp, const int **source_rates) -{ - struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); - struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); - int size; - - if (IS_BROXTON(dev_priv)) { - *source_rates = bxt_rates; - size = ARRAY_SIZE(bxt_rates); - } else if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv)) { - *source_rates = skl_rates; - size = ARRAY_SIZE(skl_rates); - } else { - *source_rates = default_rates; - size = ARRAY_SIZE(default_rates); - } - - /* This depends on the fact that 5.4 is last value in the array */ - if (!intel_dp_source_supports_hbr2(intel_dp)) - size--; - - return size; -} - static void intel_dp_set_clock(struct intel_encoder *encoder, struct intel_crtc_state *pipe_config) @@ -1366,43 +1403,6 @@ bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp) } }
-static int intersect_rates(const int *source_rates, int source_len, - const int *sink_rates, int sink_len, - int *common_rates) -{ - int i = 0, j = 0, k = 0; - - while (i < source_len && j < sink_len) { - if (source_rates[i] == sink_rates[j]) { - if (WARN_ON(k >= DP_MAX_SUPPORTED_RATES)) - return k; - common_rates[k] = source_rates[i]; - ++k; - ++i; - ++j; - } else if (source_rates[i] < sink_rates[j]) { - ++i; - } else { - ++j; - } - } - return k; -} - -static int intel_dp_common_rates(struct intel_dp *intel_dp, - int *common_rates) -{ - const int *source_rates, *sink_rates; - int source_len, sink_len; - - sink_len = intel_dp_sink_rates(intel_dp, &sink_rates); - source_len = intel_dp_source_rates(intel_dp, &source_rates); - - return intersect_rates(source_rates, source_len, - sink_rates, sink_len, - common_rates); -} - static void snprintf_int_array(char *str, size_t len, const int *array, int nelem) {
This is required to return the index of link rate into common_rates array. This gets used to retry the link training at lower link rate.
Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com Signed-off-by: Manasi Navare manasi.d.navare@intel.com --- drivers/gpu/drm/i915/intel_dp.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index 6b54b0e..a60bef8 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -288,6 +288,21 @@ static int intel_dp_common_rates(struct intel_dp *intel_dp, common_rates); }
+static int intel_dp_link_rate_index(struct intel_dp *intel_dp, int *common_rates, + int link_rate) +{ + int common_len; + int index; + + common_len = intel_dp_common_rates(intel_dp, common_rates); + for (index = 0; index < common_len; index++) { + if (link_rate == common_rates[common_len - index - 1]) + return common_len - index - 1; + } + + return -1; +} + static enum drm_mode_status intel_dp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode)
This work function gets scheduled on link training failure during the atomic commit of modeset. It should get executed after the current modeset is completed and send a hotplug uevent to notify the usersapce about change in the connector link property requesting link_train_retry
Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com Signed-off-by: Manasi Navare manasi.d.navare@intel.com --- drivers/gpu/drm/i915/intel_dp.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index a60bef8..aae7f82 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -5675,6 +5675,22 @@ static bool intel_edp_init_connector(struct intel_dp *intel_dp, return false; }
+static void intel_dp_modeset_retry_work_fn(struct work_struct *work) +{ + struct drm_connector *connector; + + connector = container_of(work, typeof(*connector), + i915_modeset_retry_work); + + /* Grab the locks before changing connector property*/ + mutex_lock(&connector->dev->mode_config.mutex); + DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, + connector->name); + connector->link_train_retry = true; + mutex_unlock(&connector->dev->mode_config.mutex); + drm_kms_helper_hotplug_event(connector->dev); +} + bool intel_dp_init_connector(struct intel_digital_port *intel_dig_port, struct intel_connector *intel_connector) @@ -5687,6 +5703,10 @@ static bool intel_edp_init_connector(struct intel_dp *intel_dp, enum port port = intel_dig_port->port; int type;
+ /* Initialize the work for modeset in case of link train failure */ + INIT_WORK(&connector->i915_modeset_retry_work, + intel_dp_modeset_retry_work_fn); + if (WARN(intel_dig_port->max_lanes < 1, "Not enough lanes (%d) for DP on port %c\n", intel_dig_port->max_lanes, port_name(port)))
If link training at a link rate optimal for a particular mode fails during modeset's atomic commit phase, then we let the modeset complete and then retry. We save the link rate value at which link training failed and use a lower link rate to prune the modes during the next modeset, configure the pipe at lower link rate and retrian at lower link rate.
This is also required to pass DP CTS tests 4.3.1.3, 4.3.1.4, 4.3.1.6.
Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com Signed-off-by: Manasi Navare manasi.d.navare@intel.com --- drivers/gpu/drm/i915/intel_ddi.c | 14 ++++++++++- drivers/gpu/drm/i915/intel_dp.c | 35 +++++++++++++++++++++++++-- drivers/gpu/drm/i915/intel_dp_link_training.c | 12 ++++++--- drivers/gpu/drm/i915/intel_drv.h | 6 ++++- 4 files changed, 60 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index 7f7741c..cccb301 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -1642,6 +1642,8 @@ static void intel_ddi_pre_enable_dp(struct intel_encoder *encoder, struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); enum port port = intel_ddi_get_encoder_port(encoder); + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_connector *connector = &intel_connector->base;
intel_dp_set_link_params(intel_dp, link_rate, lane_count, link_mst); @@ -1652,7 +1654,17 @@ static void intel_ddi_pre_enable_dp(struct intel_encoder *encoder, intel_prepare_dp_ddi_buffers(encoder); intel_ddi_init_dp_buf_reg(encoder); intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON); - intel_dp_start_link_train(intel_dp); + if (!intel_dp_start_link_train(intel_dp)) { + DRM_ERROR("Link Training failed at link rate = %d, lane count = %d", + link_rate, lane_count); + intel_dp->link_train_failed = true; + intel_dp->link_train_failed_link_rate = link_rate; + intel_dp->link_train_failed_lane_count = lane_count; + /* Schedule a Hotplug Uevent to userspace to start modeset */ + schedule_work(&connector->i915_modeset_retry_work); + } else + connector->link_train_retry = false; + if (port != PORT_A || INTEL_GEN(dev_priv) >= 9) intel_dp_stop_link_train(intel_dp); } diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index aae7f82..78246ba 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -313,6 +313,7 @@ static int intel_dp_link_rate_index(struct intel_dp *intel_dp, int *common_rates int target_clock = mode->clock; int max_rate, mode_rate, max_lanes, max_link_clock; int max_dotclk; + int common_rates[DP_MAX_SUPPORTED_RATES] = {};
max_dotclk = intel_dp_downstream_max_dotclock(intel_dp);
@@ -326,8 +327,26 @@ static int intel_dp_link_rate_index(struct intel_dp *intel_dp, int *common_rates target_clock = fixed_mode->clock; }
- max_link_clock = intel_dp_max_link_rate(intel_dp); - max_lanes = intel_dp_max_lane_count(intel_dp); + /* Prune the modes based on the link rate that failed */ + if (intel_dp->link_train_failed_link_rate) { + intel_dp->link_rate_index = intel_dp_link_rate_index(intel_dp, + common_rates, + intel_dp->link_train_failed_link_rate); + if (intel_dp->link_rate_index > 0) { + max_link_clock = common_rates[intel_dp->link_rate_index - 1]; + max_lanes = intel_dp_max_lane_count(intel_dp); + } else { + /* Here we can lower the lane count, but that is DP 1.3 not 1.2 */ + DRM_ERROR(" No Valid Mode Supported for this Link"); + connector->link_train_retry = false; + intel_dp->link_train_failed_link_rate = 0; + intel_dp->link_rate_index = -1; + } + } + else { + max_link_clock = intel_dp_max_link_rate(intel_dp); + max_lanes = intel_dp_max_lane_count(intel_dp); + }
max_rate = intel_dp_max_data_rate(max_link_clock, max_lanes); mode_rate = intel_dp_link_required(target_clock, 18); @@ -1610,6 +1629,14 @@ static int intel_dp_compute_bpp(struct intel_dp *intel_dp, if (adjusted_mode->flags & DRM_MODE_FLAG_DBLCLK) return false;
+ /* Fall back to lower link rate in case of failure in previous modeset */ + if (intel_dp->link_train_failed_link_rate) { + min_lane_count = max_lane_count; + min_clock = max_clock = intel_dp->link_rate_index - 1; + intel_dp->link_train_failed_link_rate = 0; + intel_dp->link_rate_index = -1; + } + DRM_DEBUG_KMS("DP link computation with max lane count %i " "max bw %d pixel clock %iKHz\n", max_lane_count, common_rates[max_clock], @@ -4394,6 +4421,10 @@ static bool intel_digital_port_connected(struct drm_i915_private *dev_priv, /* Can't disconnect eDP, but you can close the lid... */ if (is_edp(intel_dp)) status = edp_detect(intel_dp); + else if (intel_dp->link_train_failed) { + intel_dp->link_train_failed = false; + goto out; + } else if (intel_digital_port_connected(to_i915(dev), dp_to_dig_port(intel_dp))) status = intel_dp_detect_dpcd(intel_dp); diff --git a/drivers/gpu/drm/i915/intel_dp_link_training.c b/drivers/gpu/drm/i915/intel_dp_link_training.c index 0048b52..10f81ab 100644 --- a/drivers/gpu/drm/i915/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/intel_dp_link_training.c @@ -310,9 +310,15 @@ void intel_dp_stop_link_train(struct intel_dp *intel_dp) DP_TRAINING_PATTERN_DISABLE); }
-void +bool intel_dp_start_link_train(struct intel_dp *intel_dp) { - intel_dp_link_training_clock_recovery(intel_dp); - intel_dp_link_training_channel_equalization(intel_dp); + bool ret; + + if (intel_dp_link_training_clock_recovery(intel_dp)) { + ret = intel_dp_link_training_channel_equalization(intel_dp); + if (ret) + return true; + } + return false; } diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 5760420..4b4bed6 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -890,6 +890,10 @@ struct intel_dp { uint32_t DP; int link_rate; uint8_t lane_count; + int link_train_failed_link_rate; + uint8_t link_train_failed_lane_count; + int link_rate_index; + bool link_train_failed; uint8_t sink_count; bool link_mst; bool has_audio; @@ -1403,7 +1407,7 @@ bool intel_dp_init_connector(struct intel_digital_port *intel_dig_port, void intel_dp_set_link_params(struct intel_dp *intel_dp, int link_rate, uint8_t lane_count, bool link_mst); -void intel_dp_start_link_train(struct intel_dp *intel_dp); +bool intel_dp_start_link_train(struct intel_dp *intel_dp); void intel_dp_stop_link_train(struct intel_dp *intel_dp); void intel_dp_sink_dpms(struct intel_dp *intel_dp, int mode); void intel_dp_encoder_reset(struct drm_encoder *encoder);
This patch adds support to handle automated DP compliance link training test requests. This patch has been tested with Unigraf DPR-120 DP Compliance device for testing Link Training Compliance. After we get a short pulse Compliance test request, test request values are read and hotplug uevent is sent in order to trigger another modeset during which the pipe is configured and link is enabled for link parameters requested by the test.
Signed-off-by: Manasi Navare manasi.d.navare@intel.com Cc: Jani Nikula jani.nikula@linux.intel.com Cc: Daniel Vetter daniel.vetter@intel.com Cc: Ville Syrjala ville.syrjala@linux.intel.com --- drivers/gpu/drm/i915/intel_dp.c | 72 ++++++++++++++++++++++++++++++++++------ drivers/gpu/drm/i915/intel_drv.h | 2 ++ 2 files changed, 63 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index 78246ba..cd7a59e 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -1588,6 +1588,7 @@ static int intel_dp_compute_bpp(struct intel_dp *intel_dp, /* Conveniently, the link BW constants become indices with a shift...*/ int min_clock = 0; int max_clock; + int link_rate_index; int bpp, mode_rate; int link_avail, link_clock; int common_rates[DP_MAX_SUPPORTED_RATES] = {}; @@ -1636,6 +1637,19 @@ static int intel_dp_compute_bpp(struct intel_dp *intel_dp, intel_dp->link_train_failed_link_rate = 0; intel_dp->link_rate_index = -1; } + /* Use values requested by Compliance Test Request */ + if (intel_dp->compliance_test_link_rate && + intel_dp->compliance_test_lane_count) { + link_rate_index = intel_dp_link_rate_index(intel_dp, + common_rates, + drm_dp_bw_code_to_link_rate(intel_dp->compliance_test_link_rate)); + if (link_rate_index >= 0) + min_clock = max_clock = link_rate_index; + min_lane_count = max_lane_count = intel_dp->compliance_test_lane_count; + + intel_dp->compliance_test_link_rate = 0; + intel_dp->compliance_test_lane_count = 0; + }
DRM_DEBUG_KMS("DP link computation with max lane count %i " "max bw %d pixel clock %iKHz\n", @@ -1684,6 +1698,7 @@ static int intel_dp_compute_bpp(struct intel_dp *intel_dp, } } } + }
return false; @@ -3865,6 +3880,29 @@ int intel_dp_sink_crc(struct intel_dp *intel_dp, u8 *crc) static uint8_t intel_dp_autotest_link_training(struct intel_dp *intel_dp) { uint8_t test_result = DP_TEST_ACK; + int status = 0; + /* (DP CTS 1.2) + * 4.3.1.11 + */ + /* Read the TEST_LANE_COUNT and TEST_LINK_RTAE fields (DP CTS 3.1.4) */ + status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_LANE_COUNT, + &intel_dp->compliance_test_lane_count); + + if (status <= 0) { + DRM_DEBUG_KMS("Could not read test lane count from " + "reference sink\n"); + return 0; + } + intel_dp->compliance_test_lane_count &= DP_MAX_LANE_COUNT_MASK; + + status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_LINK_RATE, + &intel_dp->compliance_test_link_rate); + if (status <= 0) { + DRM_DEBUG_KMS("Could not read test link rate from " + "refernce sink\n"); + return 0; + } + return test_result; }
@@ -3965,11 +4003,14 @@ static void intel_dp_handle_test_request(struct intel_dp *intel_dp) }
update_status: - status = drm_dp_dpcd_write(&intel_dp->aux, - DP_TEST_RESPONSE, - &response, 1); - if (status <= 0) - DRM_DEBUG_KMS("Could not write test response to sink\n"); + if (intel_dp->compliance_test_type == DP_TEST_LINK_TRAINING) { + status = drm_dp_dpcd_write(&intel_dp->aux, + DP_TEST_RESPONSE, + &response, 1); + if (status <= 0) + DRM_DEBUG_KMS("Could not write test response " + "to sink\n"); + } }
static int @@ -4074,9 +4115,7 @@ static void intel_dp_handle_test_request(struct intel_dp *intel_dp) if (!to_intel_crtc(intel_encoder->base.crtc)->active) return;
- /* if link training is requested we should perform it always */ - if ((intel_dp->compliance_test_type == DP_TEST_LINK_TRAINING) || - (!drm_dp_channel_eq_ok(link_status, intel_dp->lane_count))) { + if ((!drm_dp_channel_eq_ok(link_status, intel_dp->lane_count))) { DRM_DEBUG_KMS("%s: channel EQ not ok, retraining\n", intel_encoder->base.name);
@@ -4101,6 +4140,9 @@ static void intel_dp_handle_test_request(struct intel_dp *intel_dp) intel_dp_short_pulse(struct intel_dp *intel_dp) { struct drm_device *dev = intel_dp_to_dev(intel_dp); + struct intel_encoder *intel_encoder = &dp_to_dig_port(intel_dp)->base; + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_connector *connector = &intel_connector->base; u8 sink_irq_vector = 0; u8 old_sink_count = intel_dp->sink_count; bool ret; @@ -4135,8 +4177,9 @@ static void intel_dp_handle_test_request(struct intel_dp *intel_dp) DP_DEVICE_SERVICE_IRQ_VECTOR, sink_irq_vector);
- if (sink_irq_vector & DP_AUTOMATED_TEST_REQUEST) - DRM_DEBUG_DRIVER("Test request in short pulse not handled\n"); + if (sink_irq_vector & DP_AUTOMATED_TEST_REQUEST) { + intel_dp_handle_test_request(intel_dp); + } if (sink_irq_vector & (DP_CP_IRQ | DP_SINK_SPECIFIC_IRQ)) DRM_DEBUG_DRIVER("CP or sink specific irq unhandled\n"); } @@ -4144,6 +4187,11 @@ static void intel_dp_handle_test_request(struct intel_dp *intel_dp) drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); intel_dp_check_link_status(intel_dp); drm_modeset_unlock(&dev->mode_config.connection_mutex); + if ((intel_dp->compliance_test_type == DP_TEST_LINK_TRAINING)) { + connector->link_train_retry = true; + /* Send a Hotplug Uevent to userspace to start modeset */ + drm_kms_helper_hotplug_event(intel_encoder->base.dev); + }
return true; } @@ -4421,7 +4469,9 @@ static bool intel_digital_port_connected(struct drm_i915_private *dev_priv, /* Can't disconnect eDP, but you can close the lid... */ if (is_edp(intel_dp)) status = edp_detect(intel_dp); - else if (intel_dp->link_train_failed) { + else if (intel_dp->compliance_test_type == DP_TEST_LINK_TRAINING || + intel_dp->link_train_failed) { + intel_dp->compliance_test_type = 0; intel_dp->link_train_failed = false; goto out; } diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 4b4bed6..b598215 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -963,6 +963,8 @@ struct intel_dp { unsigned long compliance_test_type; unsigned long compliance_test_data; bool compliance_test_active; + u8 compliance_test_lane_count; + u8 compliance_test_link_rate; };
struct intel_lspcon {
dri-devel@lists.freedesktop.org