Received: by 10.192.165.156 with SMTP id m28csp1272031imm; Wed, 11 Apr 2018 16:00:05 -0700 (PDT) X-Google-Smtp-Source: AIpwx48ceXiO36U3zF9Zvj2GEpYjA/H4U6ble+KSfO9g/fxSIM0hnSm1weJKxUtTENaAq5zBhJ8J X-Received: by 2002:a17:902:2be4:: with SMTP id l91-v6mr7252455plb.102.1523487605231; Wed, 11 Apr 2018 16:00:05 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1523487605; cv=none; d=google.com; s=arc-20160816; b=MRX8iZdPyJXdMNIqKFZ5zNxhtWQX3SAeTjr8TIYu/3MvYjCrW61jK5fWtHi+7Yi2Wn aItbbXsoRLdgL3hMK0Y2PkKYOmkukTcvwRz2M4QxTJJ3/OlZR5F86wCBDtSSeXXg340K odXow8J4CIAC389OqyX1yyf4doGBTBDrGfwMeM6sQam/YuUuCQskFWcHq1oVQXR0gf0P 38f5Br+H2pgbpNLTPPd0K6MZwJp8ZUZLIsrkpPCZAh5sBFII7eGknEMUMNJ45U1moquO VehM8WeNG8sI5L28lmFE8EA1HlIjQCts/1iTDU5zKq28VeKhf1ACMSafisQ/lVdTHHGW yccg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :arc-authentication-results; bh=sjBzuMd5oesW2+5uxXTMqk8caLGfTUqUefNRW4MUC1k=; b=ASstg5AhkSwOz/jiL/3oyOuMcMGKiZjeikPxw6h/CsBzlvuMFzl9K+szyXw/Wv1BXQ ilV5hIy6hXEhz0YorOqYPRaIh9gzIfK9DGocBve2OavTK3Dfv0T9hXYVlFD66GBe2vgm hUIvmyOH9pFaqd1lG15GgFYea+hrlN6VNKy1iFHpQL79YDpLB3d+l8LzNl5NE1ur2HWj oX8OC8lSA3uDpJcJgNeQz8ayG4N/DiAujcSMZk74ToJBXYg+/DrFGf88/gv6MGebUNRX c7G21Ls8ZRvPiH3U0RK55btDpoeVY0VK9MoFluI9iCwCz0W7gvJa9Qpmr3+d2IxNy6Lk 7vIA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=redhat.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id c66si1551699pfb.278.2018.04.11.15.59.28; Wed, 11 Apr 2018 16:00:05 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=redhat.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752824AbeDKWz5 (ORCPT + 99 others); Wed, 11 Apr 2018 18:55:57 -0400 Received: from mx3-rdu2.redhat.com ([66.187.233.73]:36318 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1752660AbeDKWzg (ORCPT ); Wed, 11 Apr 2018 18:55:36 -0400 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id BF10E414DF3C; Wed, 11 Apr 2018 22:55:35 +0000 (UTC) Received: from malachite.bss.redhat.com (dhcp-10-20-1-55.bss.redhat.com [10.20.1.55]) by smtp.corp.redhat.com (Postfix) with ESMTP id 6BF0F2026E03; Wed, 11 Apr 2018 22:55:35 +0000 (UTC) From: Lyude Paul To: intel-gfx@lists.freedesktop.org Cc: Manasi Navare , =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= , Jani Nikula , Joonas Lahtinen , Rodrigo Vivi , David Airlie , dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org Subject: [PATCH v7 10/10] drm/i915: Implement proper fallback training for MST Date: Wed, 11 Apr 2018 18:54:47 -0400 Message-Id: <20180411225501.26751-11-lyude@redhat.com> In-Reply-To: <20180411225501.26751-1-lyude@redhat.com> References: <20180411225501.26751-1-lyude@redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Scanned-By: MIMEDefang 2.78 on 10.11.54.4 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.5]); Wed, 11 Apr 2018 22:55:35 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.5]); Wed, 11 Apr 2018 22:55:35 +0000 (UTC) for IP:'10.11.54.4' DOMAIN:'int-mx04.intmail.prod.int.rdu2.redhat.com' HELO:'smtp.corp.redhat.com' FROM:'lyude@redhat.com' RCPT:'' Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org For a while we actually haven't had any way of retraining MST links with fallback link parameters like we do with SST. While uncommon, certain setups such as my Caldigit TS3 + EVGA MST hub require this since otherwise, they end up getting stuck in an infinite MST retraining loop. MST retraining is somewhat different then SST retraining. While it's possible during the normal link retraining sequence for a hub to indicate bad link status, it's also possible for a hub to only indicate this status through ESI messages and it's possible for this to happen after the initial link training succeeds. This can lead to a pattern that looks like this: - Train MST link - Training completes successfully - MST hub sets Channel EQ failed bit in ESI - Retraining starts - Retraining completes successfully - MST hub sets Channel EQ failed bit in ESI again - Rinse and repeat In these situations, we need to be able to actually trigger fallback link training from the ESI handler as well, along with using the ESI handler during retraining to figure out whether or not our retraining actually succeeded. This gets a bit more complicated since we have to ensure that we don't block the ESI handler at all while doing retraining. If we do, due to DisplayPort's general issues with being sensitive to IRQ latency most MST hubs will just stop responding to us if their interrupts aren't handled in a timely manner. So: move retraining into it's own separate handler. Running in a separate handler allows us to avoid stalling the ESI during link retraining, and we can have the ESI signal that the channel EQ bit was cleared through a simple completion struct. Additionally, we take care to stick as much of this into the SST retraining path as possible since sharing is caring. V4: - Move all MST retraining work into hotplug_work - Grab the correct power wells when retraining. - Loop through MST encoders in intel_dp_get_crtc_mask(), quicker/easier than connectors V7: - Fix CHECKPATCH errors Signed-off-by: Lyude Paul Cc: Manasi Navare Cc: Ville Syrjälä --- drivers/gpu/drm/i915/intel_ddi.c | 10 +- drivers/gpu/drm/i915/intel_dp.c | 370 ++++++++++++++++++++------ drivers/gpu/drm/i915/intel_dp_link_training.c | 6 +- drivers/gpu/drm/i915/intel_dp_mst.c | 28 +- drivers/gpu/drm/i915/intel_drv.h | 7 + 5 files changed, 329 insertions(+), 92 deletions(-) diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index 92cb26b18a9b..7474300cad01 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -3042,6 +3042,7 @@ static bool intel_ddi_hotplug(struct intel_encoder *encoder, struct intel_connector *connector) { struct drm_modeset_acquire_ctx ctx; + struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); bool changed; int ret; @@ -3063,9 +3064,16 @@ static bool intel_ddi_hotplug(struct intel_encoder *encoder, break; } + if (ret == -EINVAL) + ret = intel_dp_handle_train_failure(intel_dp); + drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); - WARN(ret, "Acquiring modeset locks failed with %i\n", ret); + + if (ret == -EIO) + changed = true; + else + WARN(ret, "Acquiring modeset locks failed with %i\n", ret); return changed; } diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index fbb467bc227d..c62d03c69ec3 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -45,6 +45,8 @@ #define DP_DPRX_ESI_LEN 14 +#define DP_MST_RETRAIN_TIMEOUT (msecs_to_jiffies(100)) + /* Compliance test status bits */ #define INTEL_DP_RESOLUTION_SHIFT_MASK 0 #define INTEL_DP_RESOLUTION_PREFERRED (1 << INTEL_DP_RESOLUTION_SHIFT_MASK) @@ -2760,6 +2762,7 @@ static void intel_disable_dp(struct intel_encoder *encoder, struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); intel_dp->link_trained = false; + intel_dp->link_is_bad = false; if (old_crtc_state->has_audio) intel_audio_codec_disable(encoder, @@ -4205,8 +4208,134 @@ static void intel_dp_handle_test_request(struct intel_dp *intel_dp) DRM_DEBUG_KMS("Could not write test response to sink\n"); } +/* Get a mask of the CRTCs that are running on the given intel_dp struct. For + * MST, this returns a crtc mask containing all of the CRTCs driving + * downstream sinks, for SST it just returns a mask of the attached + * connector's CRTC. + */ +int +intel_dp_get_crtc_mask(struct intel_dp *intel_dp) +{ + struct drm_device *dev = dp_to_dig_port(intel_dp)->base.base.dev; + struct drm_connector *connector; + struct drm_crtc *crtc; + int crtc_mask = 0; + + WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex)); + + if (intel_dp->is_mst) { + struct intel_dp_mst_encoder *mst_enc; + struct intel_connector *intel_connector; + struct drm_connector_state *conn_state; + int i; + + for (i = 0; i < ARRAY_SIZE(intel_dp->mst_encoders); i++) { + mst_enc = intel_dp->mst_encoders[i]; + + intel_connector = mst_enc->connector; + if (!intel_connector) + continue; + + conn_state = intel_connector->base.state; + if (conn_state->crtc) + crtc_mask |= drm_crtc_mask(conn_state->crtc); + } + } else if (intel_dp->attached_connector) { + connector = &intel_dp->attached_connector->base; + crtc = connector->state->crtc; + + if (crtc) + crtc_mask |= drm_crtc_mask(crtc); + } + + return crtc_mask; +} + +static bool +intel_dp_needs_link_retrain(struct intel_dp *intel_dp, + const u8 esi[DP_DPRX_ESI_LEN]) +{ + u8 buf[DP_LINK_STATUS_SIZE]; + const u8 *link_status = NULL; + + if (intel_dp->link_is_bad) + return false; + + if (intel_dp->is_mst) { + if (!intel_dp->active_mst_links) + return false; + + if (esi) { + link_status = &esi[10]; + } else { + struct completion *retrain_completion = + &intel_dp->mst_retrain_completion; + /* We're not running from the ESI handler, so wait a + * little bit to see if the ESI handler lets us know + * that the link status is OK + */ + if (wait_for_completion_timeout(retrain_completion, + DP_MST_RETRAIN_TIMEOUT)) + return false; + } + } else { + if (intel_dp->link_trained) + return false; + if (!intel_dp_get_link_status(intel_dp, buf)) + return false; + + link_status = buf; + } + + /* + * Validate the cached values of intel_dp->link_rate and + * intel_dp->lane_count before attempting to retrain. + */ + if (!intel_dp_link_params_valid(intel_dp, intel_dp->link_rate, + intel_dp->lane_count)) + return false; + + if (link_status) { + return !drm_dp_channel_eq_ok(link_status, + intel_dp->lane_count); + } else { + return true; + } +} + +static inline bool +intel_dp_mst_needs_retrain(struct intel_dp *intel_dp, + const u8 esi[DP_DPRX_ESI_LEN]) +{ + struct drm_device *dev = dp_to_dig_port(intel_dp)->base.base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + + if (intel_dp_needs_link_retrain(intel_dp, esi)) { + DRM_DEBUG_KMS("Channel EQ failing\n"); + + if (completion_done(&intel_dp->mst_retrain_completion)) { + reinit_completion(&intel_dp->mst_retrain_completion); + DRM_DEBUG_KMS("Starting retrain\n"); + + return true; + } else if (!work_busy(&dev_priv->hotplug.hotplug_work)) { + /* Retraining was probably interrupted (usually from + * one of the CRTCs being in a modeset during a + * previous retrain attempt) + */ + return true; + } + + } else if (!completion_done(&intel_dp->mst_retrain_completion)) { + DRM_DEBUG_KMS("Channel EQ stable\n"); + complete_all(&intel_dp->mst_retrain_completion); + } + + return false; +} + static int -intel_dp_check_mst_status(struct intel_dp *intel_dp) +intel_dp_check_mst_status(struct intel_dp *intel_dp, bool *need_retrain) { bool bret; @@ -4215,16 +4344,14 @@ intel_dp_check_mst_status(struct intel_dp *intel_dp) int ret = 0; int retry; bool handled; + *need_retrain = false; bret = intel_dp_get_sink_irq_esi(intel_dp, esi); go_again: if (bret == true) { - - /* check link status - esi[10] = 0x200c */ - if (intel_dp->active_mst_links && - !drm_dp_channel_eq_ok(&esi[10], intel_dp->lane_count)) { - DRM_DEBUG_KMS("channel EQ not ok, retraining\n"); - intel_dp_start_link_train(intel_dp); - intel_dp_stop_link_train(intel_dp); + if (!*need_retrain) { + *need_retrain = + intel_dp_mst_needs_retrain(intel_dp, + esi); } DRM_DEBUG_KMS("got esi %3ph\n", esi); @@ -4262,29 +4389,6 @@ intel_dp_check_mst_status(struct intel_dp *intel_dp) return -EINVAL; } -static bool -intel_dp_needs_link_retrain(struct intel_dp *intel_dp) -{ - u8 link_status[DP_LINK_STATUS_SIZE]; - - if (!intel_dp->link_trained) - return false; - - if (!intel_dp_get_link_status(intel_dp, link_status)) - return false; - - /* - * Validate the cached values of intel_dp->link_rate and - * intel_dp->lane_count before attempting to retrain. - */ - if (!intel_dp_link_params_valid(intel_dp, intel_dp->link_rate, - intel_dp->lane_count)) - return false; - - /* Retrain if Channel EQ or CR not ok */ - return !drm_dp_channel_eq_ok(link_status, intel_dp->lane_count); -} - /* * If display is now connected check links status, * there has been known issues of link loss triggering @@ -4300,68 +4404,131 @@ intel_dp_needs_link_retrain(struct intel_dp *intel_dp) int intel_dp_retrain_link(struct intel_encoder *encoder, struct drm_modeset_acquire_ctx *ctx) { - struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); - struct intel_connector *connector = intel_dp->attached_connector; - struct drm_connector_state *conn_state; - struct intel_crtc_state *crtc_state; - struct intel_crtc *crtc; + struct drm_crtc *crtc; + struct intel_crtc *intel_crtc; + enum pipe pch_transcoder; + int crtc_mask, retry_count = 0; int ret; - /* FIXME handle the MST connectors as well */ - - if (!connector || connector->base.status != connector_status_connected) - return 0; - ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex, ctx); if (ret) return ret; - conn_state = connector->base.state; + crtc_mask = intel_dp_get_crtc_mask(intel_dp); + for_each_intel_crtc_mask(dev, intel_crtc, crtc_mask) { + struct drm_crtc_state *crtc_state; + struct intel_crtc_state *intel_crtc_state; - crtc = to_intel_crtc(conn_state->crtc); - if (!crtc) - return 0; - - ret = drm_modeset_lock(&crtc->base.mutex, ctx); - if (ret) - return ret; + crtc = &intel_crtc->base; + ret = drm_modeset_lock(&crtc->mutex, ctx); + if (ret) + return ret; - crtc_state = to_intel_crtc_state(crtc->base.state); + crtc_state = crtc->state; + intel_crtc_state = to_intel_crtc_state(crtc_state); + WARN_ON(!intel_crtc_has_dp_encoder(intel_crtc_state)); - WARN_ON(!intel_crtc_has_dp_encoder(crtc_state)); + if (crtc_state->commit && + !try_wait_for_completion(&crtc_state->commit->hw_done)) { + DRM_DEBUG_KMS("Not all CRTCs ready yet\n"); + return 0; + } - if (!crtc_state->base.active) + if (!crtc_state->active) + crtc_mask &= ~drm_crtc_mask(crtc); + } + if (!crtc_mask) return 0; - if (conn_state->commit && - !try_wait_for_completion(&conn_state->commit->hw_done)) + if (!intel_dp_needs_link_retrain(intel_dp, NULL)) return 0; - if (!intel_dp_needs_link_retrain(intel_dp)) - return 0; + for_each_intel_crtc_mask(dev, intel_crtc, crtc_mask) { + pch_transcoder = intel_crtc_pch_transcoder(intel_crtc); - /* Suppress underruns caused by re-training */ - intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, false); - if (crtc->config->has_pch_encoder) - intel_set_pch_fifo_underrun_reporting(dev_priv, - intel_crtc_pch_transcoder(crtc), false); + intel_set_cpu_fifo_underrun_reporting(dev_priv, + intel_crtc->pipe, + false); - intel_dp_start_link_train(intel_dp); - intel_dp_stop_link_train(intel_dp); + if (intel_crtc->config->has_pch_encoder) { + intel_set_pch_fifo_underrun_reporting(dev_priv, + pch_transcoder, + false); + } + } + + do { + if (++retry_count > 5) { + DRM_DEBUG_KMS("Too many retries, can't retrain\n"); + return -EINVAL; + } + + intel_dp_start_link_train(intel_dp); + intel_dp_stop_link_train(intel_dp); + } while (intel_dp_needs_link_retrain(intel_dp, NULL)); + + /* Wait for things to become stable */ + for_each_intel_crtc_mask(dev, intel_crtc, crtc_mask) + intel_wait_for_vblank(dev_priv, intel_crtc->pipe); + + /* Now that we know everything is OK, finally re-enable underrun + * reporting + */ + for_each_intel_crtc_mask(dev, intel_crtc, crtc_mask) { + pch_transcoder = intel_crtc_pch_transcoder(intel_crtc); - /* Keep underrun reporting disabled until things are stable */ - intel_wait_for_vblank(dev_priv, crtc->pipe); + intel_set_cpu_fifo_underrun_reporting(dev_priv, + intel_crtc->pipe, + true); - intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true); - if (crtc->config->has_pch_encoder) - intel_set_pch_fifo_underrun_reporting(dev_priv, - intel_crtc_pch_transcoder(crtc), true); + if (intel_crtc->config->has_pch_encoder) { + intel_set_pch_fifo_underrun_reporting(dev_priv, + pch_transcoder, + true); + } + } return 0; } +int intel_dp_handle_train_failure(struct intel_dp *intel_dp) +{ + int ret; + int max_link_rate, max_lane_count; + + if (intel_dp->link_is_bad) + return 0; + intel_dp->link_is_bad = true; + + /* Sometimes sinks will change their RX caps in response to a link + * retraining failure, so only fallback the link rate if we need to + */ + ret = intel_dp_get_dpcd(intel_dp); + if (!ret) { + DRM_ERROR("IO error while reading dpcd from sink\n"); + return -EIO; + } + + max_link_rate = intel_dp_max_link_rate(intel_dp); + max_lane_count = intel_dp_max_lane_count(intel_dp); + if (intel_dp->link_rate == max_link_rate && + intel_dp->lane_count == max_lane_count) { + if (intel_dp_get_link_train_fallback_values(intel_dp, + max_link_rate, + max_lane_count)) { + DRM_ERROR("No possible fallback values for link retraining\n"); + return -EINVAL; + } + } + + schedule_work(&intel_dp->modeset_retry_work); + return 0; +} + /* * If display is now connected check links status, * there has been known issues of link loss triggering @@ -4378,6 +4545,7 @@ static bool intel_dp_hotplug(struct intel_encoder *encoder, struct intel_connector *connector) { struct drm_modeset_acquire_ctx ctx; + struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); bool changed; int ret; @@ -4396,9 +4564,16 @@ static bool intel_dp_hotplug(struct intel_encoder *encoder, break; } + if (ret == -EINVAL) + ret = intel_dp_handle_train_failure(intel_dp); + drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); - WARN(ret, "Acquiring modeset locks failed with %i\n", ret); + + if (ret == -EIO) + changed = true; + else + WARN(ret, "Acquiring modeset locks failed with %i\n", ret); return changed; } @@ -4459,7 +4634,7 @@ intel_dp_short_pulse(struct intel_dp *intel_dp) } /* defer to the hotplug work for link retraining if needed */ - if (intel_dp_needs_link_retrain(intel_dp)) + if (intel_dp_needs_link_retrain(intel_dp, NULL)) return false; if (intel_dp->compliance.test_type == DP_TEST_LINK_TRAINING) { @@ -5379,6 +5554,7 @@ intel_dp_hpd_pulse(struct intel_digital_port *intel_dig_port, bool long_hpd) struct intel_dp *intel_dp = &intel_dig_port->dp; struct drm_i915_private *dev_priv = to_i915(intel_dp_to_dev(intel_dp)); enum irqreturn ret = IRQ_NONE; + bool need_retrain = false; if (long_hpd && intel_dig_port->base.type == INTEL_OUTPUT_EDP) { /* @@ -5405,7 +5581,8 @@ intel_dp_hpd_pulse(struct intel_digital_port *intel_dig_port, bool long_hpd) intel_display_power_get(dev_priv, intel_dp->aux_power_domain); if (intel_dp->is_mst) { - if (intel_dp_check_mst_status(intel_dp) == -EINVAL) { + if (intel_dp_check_mst_status(intel_dp, + &need_retrain) == -EINVAL) { /* * If we were in MST mode, and device is not * there, get out of MST mode @@ -5417,6 +5594,8 @@ intel_dp_hpd_pulse(struct intel_digital_port *intel_dig_port, bool long_hpd) intel_dp->is_mst); intel_dp->detect_done = false; goto put_power; + } else if (need_retrain) { + goto put_power; } } @@ -6251,21 +6430,35 @@ static void intel_dp_modeset_retry_work_fn(struct work_struct *work) { struct intel_dp *intel_dp = container_of(work, typeof(*intel_dp), modeset_retry_work); - struct drm_connector *connector = &intel_dp->attached_connector->base; + struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = intel_dig_port->base.base.dev; + struct drm_connector *connector; + int max_link_rate, max_lane_count; - DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, - connector->name); + mutex_lock(&dev->mode_config.mutex); - /* Grab the locks before changing connector property*/ - mutex_lock(&connector->dev->mode_config.mutex); - /* Set connector link status to BAD and send a Uevent to notify - * userspace to do a modeset. + /* Set the connector link status of all (possibly downstream) ports to + * BAD and send a Uevent to notify userspace to do a modeset. */ - drm_mode_connector_set_link_status_property(connector, - DRM_MODE_LINK_STATUS_BAD); - mutex_unlock(&connector->dev->mode_config.mutex); + if (intel_dp->is_mst) { + max_link_rate = intel_dp_max_link_rate(intel_dp); + max_lane_count = intel_dp_max_lane_count(intel_dp); + drm_dp_mst_topology_mgr_lower_link_rate(&intel_dp->mst_mgr, + max_link_rate, + max_lane_count); + } else { + connector = &intel_dp->attached_connector->base; + + DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + drm_mode_connector_set_link_status_property(connector, + DRM_MODE_LINK_STATUS_BAD); + } + + mutex_unlock(&dev->mode_config.mutex); + /* Send Hotplug uevent so userspace can reprobe */ - drm_kms_helper_hotplug_event(connector->dev); + drm_kms_helper_hotplug_event(dev); } bool @@ -6283,6 +6476,8 @@ intel_dp_init_connector(struct intel_digital_port *intel_dig_port, /* Initialize the work for modeset in case of link train failure */ INIT_WORK(&intel_dp->modeset_retry_work, intel_dp_modeset_retry_work_fn); + init_completion(&intel_dp->mst_retrain_completion); + complete_all(&intel_dp->mst_retrain_completion); if (WARN(intel_dig_port->max_lanes < 1, "Not enough lanes (%d) for DP on port %c\n", @@ -6499,6 +6694,7 @@ void intel_dp_mst_resume(struct drm_device *dev) { struct drm_i915_private *dev_priv = to_i915(dev); int i; + bool need_retrain = false; for (i = 0; i < I915_MAX_PORTS; i++) { struct intel_digital_port *intel_dig_port = dev_priv->hotplug.irq_port[i]; @@ -6508,7 +6704,11 @@ void intel_dp_mst_resume(struct drm_device *dev) continue; ret = drm_dp_mst_topology_mgr_resume(&intel_dig_port->dp.mst_mgr); - if (ret) - intel_dp_check_mst_status(&intel_dig_port->dp); + if (ret) { + intel_dp_check_mst_status(&intel_dig_port->dp, + &need_retrain); + if (need_retrain) + drm_kms_helper_hotplug_event(dev); + } } } diff --git a/drivers/gpu/drm/i915/intel_dp_link_training.c b/drivers/gpu/drm/i915/intel_dp_link_training.c index 2cfa58ce1f95..aa6cfccdab0f 100644 --- a/drivers/gpu/drm/i915/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/intel_dp_link_training.c @@ -336,11 +336,7 @@ intel_dp_start_link_train(struct intel_dp *intel_dp) intel_connector->base.base.id, intel_connector->base.name, intel_dp->link_rate, intel_dp->lane_count); - if (!intel_dp_get_link_train_fallback_values(intel_dp, - intel_dp->link_rate, - intel_dp->lane_count)) - /* Schedule a Hotplug Uevent to userspace to start modeset */ - schedule_work(&intel_dp->modeset_retry_work); + intel_dp_handle_train_failure(intel_dp); } else { DRM_ERROR("[CONNECTOR:%d:%s] Link Training failed at link rate = %d, lane count = %d", intel_connector->base.base.id, diff --git a/drivers/gpu/drm/i915/intel_dp_mst.c b/drivers/gpu/drm/i915/intel_dp_mst.c index 31b37722cd89..f61950073f05 100644 --- a/drivers/gpu/drm/i915/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/intel_dp_mst.c @@ -156,13 +156,37 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder, } static int intel_dp_mst_atomic_check(struct drm_connector *connector, - struct drm_connector_state *new_conn_state) + struct drm_connector_state *new_conn_state) { struct drm_atomic_state *state = new_conn_state->state; struct drm_connector_state *old_conn_state; struct drm_crtc_state *new_crtc_state; + struct intel_dp *intel_dp = to_intel_connector(connector)->mst_port; + struct drm_dp_mst_topology_state *mst_state; + struct intel_dp_mst_topology_state *intel_mst_state; int ret = 0; + /* We can't retrain anything if the hub isn't there anymore */ + if (intel_dp) { + ret = drm_dp_atomic_mst_check_retrain(new_conn_state, + &intel_dp->mst_mgr); + + /* This commit invokes a retrain, reset the link rate */ + if (ret == 1) { + mst_state = + drm_atomic_dp_mst_get_topology_state(state, + &intel_dp->mst_mgr); + intel_mst_state = + to_intel_dp_mst_topology_state(mst_state); + + intel_mst_state->link_rate = 0; + intel_mst_state->lane_count = 0; + ret = 0; + } else if (ret < 0) { + return ret; + } + } + old_conn_state = drm_atomic_get_old_connector_state(state, connector); /* Only free VCPI here if we're not going to be detaching the @@ -230,7 +254,9 @@ static void intel_mst_post_disable_dp(struct intel_encoder *encoder, intel_mst->connector = NULL; if (intel_dp->active_mst_links == 0) { + intel_dp->link_is_bad = false; intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_OFF); + intel_dig_port->base.post_disable(&intel_dig_port->base, old_crtc_state, NULL); } diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index eccb4bd042c3..9567a0e71424 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -1128,6 +1128,11 @@ struct intel_dp { /* mst connector list */ struct intel_dp_mst_encoder *mst_encoders[I915_MAX_PIPES]; struct drm_dp_mst_topology_mgr mst_mgr; + struct completion mst_retrain_completion; + /* Set when retraining the link at the current parameters is + * impossible + */ + bool link_is_bad; uint32_t (*get_aux_clock_divider)(struct intel_dp *dp, int index); /* @@ -1649,6 +1654,7 @@ void intel_dp_start_link_train(struct intel_dp *intel_dp); void intel_dp_stop_link_train(struct intel_dp *intel_dp); int intel_dp_retrain_link(struct intel_encoder *encoder, struct drm_modeset_acquire_ctx *ctx); +int intel_dp_handle_train_failure(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); void intel_dp_encoder_suspend(struct intel_encoder *intel_encoder); @@ -1701,6 +1707,7 @@ void intel_dp_compute_rate(struct intel_dp *intel_dp, int port_clock, bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp); bool intel_dp_get_link_status(struct intel_dp *intel_dp, uint8_t link_status[DP_LINK_STATUS_SIZE]); +int intel_dp_get_crtc_mask(struct intel_dp *intel_dp); static inline unsigned int intel_dp_unused_lane_mask(int lane_count) { -- 2.14.3