[PATCH] drm/i915: Enable Upfront link training for type-C DP support

From: Durgadoss R
Date: Mon Aug 03 2015 - 11:51:16 EST


To support USB type C alternate DP mode, the display driver needs to know the
number of lanes required by DP panel as well as number of lanes that can be
supported by the type-C cable. Sometimes, the type-C cable may limit the
bandwidth even if Panel can support more lanes. To address these scenarios,
the display driver will start link training with max lanes, and if the link
training fails, the driver then falls back to x2 lanes.

* Since link training is done before modeset, planes are not enabled. Only
encoder and the its associated PLLs are enabled.
* Once link training is done, the encoder and its PLLs are disabled; so that
the subsequent modeset is not aware of these changes.
* As of now, this is tested only on CHV.

Signed-off-by: Durgadoss R <durgadoss.r@xxxxxxxxx>
[BT: add broadwell support and USB-C reverted state detection]
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@xxxxxxxxxx>
---
drivers/gpu/drm/i915/intel_display.c | 163 +++++++++++++++++++++++++++++++++++
drivers/gpu/drm/i915/intel_dp.c | 22 ++++-
drivers/gpu/drm/i915/intel_drv.h | 7 ++
3 files changed, 190 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index 818f3a3..e8ddba0 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -15799,3 +15799,166 @@ void intel_modeset_preclose(struct drm_device *dev, struct drm_file *file)
spin_unlock_irq(&dev->event_lock);
}
}
+
+static bool
+intel_upfront_link_train_config(struct drm_device *dev,
+ struct intel_dp *intel_dp,
+ struct intel_crtc *crtc,
+ uint8_t link_bw,
+ uint8_t lane_count)
+{
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ struct intel_connector *connector = intel_dp->attached_connector;
+ struct intel_encoder *encoder = connector->encoder;
+ bool success;
+
+ intel_dp->link_bw = link_bw;
+ intel_dp->lane_count = lane_count;
+
+ /* Find port clock from link_bw */
+ crtc->config->port_clock = drm_dp_bw_code_to_link_rate(intel_dp->link_bw);
+
+ /* Enable PLL followed by port */
+ if (IS_BROADWELL(dev)) {
+ hsw_dp_set_ddi_pll_sel(crtc->config, intel_dp->link_bw);
+ if (intel_crtc_to_shared_dpll(crtc))
+ intel_enable_shared_dpll(crtc);
+
+ intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true);
+
+ } else if (IS_CHERRYVIEW(dev)) {
+ intel_dp_set_clock(encoder, crtc->config, intel_dp->link_bw);
+ if (encoder->pre_pll_enable)
+ encoder->pre_pll_enable(encoder);
+ if (!intel_pipe_has_type(crtc, INTEL_OUTPUT_DSI))
+ chv_enable_pll(crtc, crtc->config);
+ }
+ if (encoder->pre_enable)
+ encoder->pre_enable(encoder);
+
+ success = intel_dp->train_set_valid;
+
+ /* Reset encoder for next retry or for clean up */
+ if (encoder->post_disable)
+ encoder->post_disable(encoder);
+ if (IS_CHERRYVIEW(dev)) {
+ if (!intel_pipe_has_type(crtc, INTEL_OUTPUT_DSI))
+ chv_disable_pll(dev_priv, crtc->pipe);
+ } else if (IS_BROADWELL(dev)) {
+ intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, false);
+ if (intel_crtc_to_shared_dpll(crtc))
+ intel_disable_shared_dpll(crtc);
+ }
+
+ intel_dp->train_set_valid = false;
+
+ return success;
+}
+
+bool
+intel_upfront_link_train(struct drm_device *dev,
+ struct intel_dp *intel_dp,
+ struct intel_crtc *crtc)
+{
+ struct intel_connector *connector = intel_dp->attached_connector;
+ struct intel_encoder *encoder = connector->encoder;
+ struct intel_digital_port *intel_dig_port = enc_to_dig_port(&encoder->base);
+ uint8_t link_bw, lane_count;
+ bool found = false;
+ bool valid_crtc = !!crtc;
+
+ if (!connector || !encoder) {
+ DRM_DEBUG_KMS("dp connector/encoder is NULL\n");
+ return false;
+ }
+
+ /* Find an unused crtc and use it for link training if no crtc is given */
+ if (!crtc) {
+ for_each_intel_crtc(dev, crtc) {
+ if (intel_crtc_active(&crtc->base))
+ continue;
+
+ /* Make sure the new crtc will work with the encoder */
+ if (!drm_encoder_crtc_ok(&encoder->base, &crtc->base))
+ continue;
+
+ encoder->base.crtc = &crtc->base;
+
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ DRM_ERROR("Could not find crtc for upfront link training\n");
+ goto exit;
+ }
+ }
+
+ DRM_DEBUG_KMS("upfront link training on pipe:%c\n",
+ pipe_name(crtc->pipe));
+
+ /* check the reversed state of the adapter first */
+
+ /* unset the reversal bit */
+ intel_dig_port->saved_port_bits &= ~DDI_BUF_PORT_REVERSAL;
+
+ /* Initialize with Max Link rate supported by panel & one lane */
+ intel_dp->link_bw = intel_dp->dpcd[DP_MAX_LINK_RATE];
+ intel_dp->lane_count = 1;
+
+ /* Check if link training passed; if not, update the reversed state */
+ if (!intel_upfront_link_train_config(dev, intel_dp, crtc,
+ intel_dp->dpcd[DP_MAX_LINK_RATE], 1))
+ intel_dig_port->saved_port_bits |= DDI_BUF_PORT_REVERSAL;
+
+ found = false;
+
+ /* Initialize with Max Link rate & lane count supported by panel */
+ link_bw = intel_dp->dpcd[DP_MAX_LINK_RATE];
+ lane_count = intel_dp->dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
+
+ do {
+ /* Check if link training passed; if so update lane count */
+ if (intel_upfront_link_train_config(dev, intel_dp, crtc,
+ link_bw, lane_count)) {
+ intel_dp->dpcd[DP_MAX_LANE_COUNT] &=
+ ~DP_MAX_LANE_COUNT_MASK;
+ intel_dp->dpcd[DP_MAX_LANE_COUNT] |=
+ intel_dp->lane_count & DP_MAX_LANE_COUNT_MASK;
+
+ found = true;
+ goto exit;
+ }
+
+ DRM_DEBUG_KMS("upfront link training failed. lanes:%d bw:%d\n",
+ intel_dp->lane_count, intel_dp->link_bw);
+
+ /* Go down to the next level and retry link training */
+ if (lane_count == 4) {
+ lane_count = 2;
+ } else if (lane_count == 2) {
+ lane_count = 1;
+ } else if (link_bw == DP_LINK_BW_5_4) {
+ link_bw = DP_LINK_BW_2_7;
+ lane_count = 4;
+ } else if (link_bw == DP_LINK_BW_2_7) {
+ link_bw = DP_LINK_BW_1_62;
+ lane_count = 4;
+ } else {
+ /* Tried all combinations, so exit */
+ break;
+ }
+
+ } while (1);
+
+exit:
+ /* Clear local associations made */
+ if (!valid_crtc)
+ encoder->base.crtc = NULL;
+
+ if (found)
+ DRM_DEBUG_KMS("upfront link training passed. lanes:%d bw:%d\n",
+ intel_dp->lane_count, intel_dp->link_bw);
+
+ return found;
+}
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
index 44f8a32..187800b 100644
--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -1144,7 +1144,7 @@ skl_edp_set_pll_config(struct intel_crtc_state *pipe_config, int link_clock)
pipe_config->dpll_hw_state.ctrl1 = ctrl1;
}

-static void
+void
hsw_dp_set_ddi_pll_sel(struct intel_crtc_state *pipe_config, int link_bw)
{
memset(&pipe_config->dpll_hw_state, 0,
@@ -1202,7 +1202,7 @@ intel_dp_source_rates(struct drm_device *dev, const int **source_rates)
return (DP_LINK_BW_2_7 >> 3) + 1;
}

-static void
+void
intel_dp_set_clock(struct intel_encoder *encoder,
struct intel_crtc_state *pipe_config, int link_bw)
{
@@ -4423,6 +4423,10 @@ g4x_dp_detect(struct intel_dp *intel_dp)
else if (ret == 0)
return connector_status_disconnected;

+ /* Avoid DPCD opertations if status is same */
+ if (intel_dp->attached_connector->base.status == connector_status_connected)
+ return connector_status_connected;
+
return intel_dp_detect_dpcd(intel_dp);
}

@@ -4495,7 +4499,9 @@ intel_dp_detect(struct drm_connector *connector, bool force)
struct intel_dp *intel_dp = intel_attached_dp(connector);
struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp);
struct intel_encoder *intel_encoder = &intel_dig_port->base;
+ struct drm_crtc *crtc = intel_dig_port->base.base.crtc;
struct drm_device *dev = connector->dev;
+ struct intel_crtc *intel_crtc;
enum drm_connector_status status;
enum intel_display_power_domain power_domain;
bool ret;
@@ -4540,6 +4546,18 @@ intel_dp_detect(struct drm_connector *connector, bool force)

if (intel_encoder->type != INTEL_OUTPUT_EDP)
intel_encoder->type = INTEL_OUTPUT_DISPLAYPORT;
+
+ if (intel_encoder->type == INTEL_OUTPUT_DISPLAYPORT &&
+ (IS_BROADWELL(dev) || IS_CHERRYVIEW(dev))) {
+ /* Do not handle connected boot scenario where panel is enabled
+ * by GOP/VBIOS.
+ */
+ if ((connector->status != connector_status_connected) &&
+ !(intel_encoder->connectors_active &&
+ crtc && crtc->enabled))
+ intel_upfront_link_train(dev, intel_dp, NULL);
+ }
+
status = connector_status_connected;

/* Try to read the source of the interrupt */
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 320c9e6..fcc95d5 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -1163,6 +1163,13 @@ bool intel_dp_init_connector(struct intel_digital_port *intel_dig_port,
void intel_dp_start_link_train(struct intel_dp *intel_dp);
void intel_dp_complete_link_train(struct intel_dp *intel_dp);
void intel_dp_stop_link_train(struct intel_dp *intel_dp);
+bool intel_upfront_link_train(struct drm_device *dev,
+ struct intel_dp *intel_dp,
+ struct intel_crtc *crtc);
+void hsw_dp_set_ddi_pll_sel(struct intel_crtc_state *pipe_config, int link_bw);
+void intel_dp_set_clock(struct intel_encoder *encoder,
+ struct intel_crtc_state *pipe_config, int link_bw);
+
void intel_dp_sink_dpms(struct intel_dp *intel_dp, int mode);
void intel_dp_encoder_destroy(struct drm_encoder *encoder);
int intel_dp_sink_crc(struct intel_dp *intel_dp, u8 *crc);
--
2.4.3


Cheers,
Benjamin
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/