Received: by 10.192.165.156 with SMTP id m28csp1306896imm; Wed, 11 Apr 2018 16:47:33 -0700 (PDT) X-Google-Smtp-Source: AIpwx49EwMAYPA8vO4DhiYRSQVir4Nydwq3gFDdYFs+w0LO8mg4hWpiYdywyjM33MGEt+NbSffzE X-Received: by 10.98.102.65 with SMTP id a62mr5702485pfc.68.1523490453700; Wed, 11 Apr 2018 16:47:33 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1523490453; cv=none; d=google.com; s=arc-20160816; b=rHo7dQbCzqCZjzEqcLUHTYNXOOUgmqx3uAtqDuPnvfsmZtaILUSsk4iVzQQF3u1lw6 VxLIWSFRIg4pVAABKr+P8PpapZ9oMJUu+Ez9ffO+dQMgGI8tDDv0GpB4E0V3WbmnJQV4 B7nEAFhA0go3Y5OabXiUxnYErDzLeiXGgdeczxOqfuYvmoUNiX/rD6MMnGy1NQBFwZj/ FEQ61RTTUW0wU6KD8fG4rTYlerEYd3plOgh9hJhTP3FdXGBMngbXTHNICIjP7DDFBc12 4QMkqTeRz1AyVMQmjvnWyEq7DSXJydnNC6assQ3gRx4Fkg0rW7RBa5q4SO9veiq603T+ n+mg== 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=XyjFWiQSau025lMohwq6EN31omoXIg9MekBV9jaKKWLO52XLjEmbbD31Xf03YfpM9V Yq4DTmw4HkqnNiZ6CIog1lD1wOQvPg0bul2WOBXzG8D7Ux4VFF5ursgQ473rY4agoyfL KwXAZ4Ik+ILJiAyWMZT1NkdkDXp6HCmojmvyNazOKLzWdnWf6cXo6HNB9VrMlIfGBFSC Kz5C5232f5avUl93jv6jyjf9/gz+LIGxGFNvyGQTz7+yX3hIFzV8Dh6akE50xrVlqRc4 y7d7TH4f+hCea006OKEEJ5omBl/yxEy0FFZorHo5Tx1OqkvnDwgNa8PowEz1fgIJHZfD u4Sg== 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 l2-v6si1960884pln.386.2018.04.11.16.46.43; Wed, 11 Apr 2018 16:47:33 -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 S1752796AbeDKXnj (ORCPT + 99 others); Wed, 11 Apr 2018 19:43:39 -0400 Received: from mx3-rdu2.redhat.com ([66.187.233.73]:50312 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1752746AbeDKXnd (ORCPT ); Wed, 11 Apr 2018 19:43:33 -0400 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id BBFC58DC30; Wed, 11 Apr 2018 23:43:32 +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 69CCE10B0F2B; Wed, 11 Apr 2018 23:43:32 +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 v8 10/10] drm/i915: Implement proper fallback training for MST Date: Wed, 11 Apr 2018 19:42:49 -0400 Message-Id: <20180411234302.2896-11-lyude@redhat.com> In-Reply-To: <20180411234302.2896-1-lyude@redhat.com> References: <20180411234302.2896-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.3 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.2]); Wed, 11 Apr 2018 23:43:32 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.11.55.2]); Wed, 11 Apr 2018 23:43:32 +0000 (UTC) for IP:'10.11.54.3' DOMAIN:'int-mx03.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