summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/i915/intel_dp_link_training.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-01-17 12:05:31 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2016-01-17 12:05:31 -0800
commita016af2e70bfca23f2f5de7d8708157b86ea374d (patch)
treebfe3c0c6ea9d52d4ec6ea021b0626a53c83e7d9f /drivers/gpu/drm/i915/intel_dp_link_training.c
parente535d74bc50df2357d3253f8f3ca48c66d0d892a (diff)
parentc3b1681375dc6e71d89a3ae00cc3ce9e775a8917 (diff)
Merge tag 'sound-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai: "We've had quite busy weeks in this cycle. Looking at ALSA core, the significant changes are a few fixes wrt timer and sequencer ioctls that have been revealed by fuzzer recently. Other than that, ASoC core got a few updates about DAI link handling, but these are rather straightforward refactoring. In drivers scene, ASoC received quite lots of new drivers in addition to bunch of updates for still ongoing Intel Skylake support and topology API. HD-audio gained a new HDMI/DP hotplug notification via component. FireWire got a pile of code refactoring/updates with SCS.1x driver integration. More highlights are shown below. [ NOTE: this contains also many commits for DRM. This is due to the pull of drm stable branch into sound tree, as the base of i915 audio component work for HD-audio. The highlights below don't contain these DRM changes, as these are supposed to be pulled via drm tree in anyway sooner or later. ] Core: - Handful fixes to harden ALSA timer and sequencer ioctls against races reported by syzkaller fuzzer - Irq description string can be unique to each card; only for HD-audio for now ASoC: - Conversion of the array of DAI links to a list for supporting dynamically adding and removing DAI links - Topology API enhancements to make everything more component based and being able to specify PCM links via topology - Some more fixes for the topology code, though it is still not final and ready for enabling in production; we really need to get to the point where that can be done - A pile of changes for Intel SkyLake drivers which hopefully deliver some useful initial functionality for systems with this chipset, though there is more work still to come - Lots of new features and cleanups for the Renesas drivers - ANC support for WM5110 - New drivers: Imagination Technologies IPs, Atmel class D speaker, Cirrus CS47L24 and WM1831, Dialog DA7128, Realtek RT5659 and RT56156, Rockchip RK3036, TI PC3168A, and AMD ACP - Rename PCM1792a driver to be generic pcm179x HD-Audio: - Use audio component for i915 HDMI/DP hotplug handling - On-demand binding with i915 driver - bdl_pos_adj parameter adjustment for Baytrail controllers - Enable power_save_node for CX20722; this shouldn't lead to regression, hopefully - Kabylake HDMI/DP codec support - Quirks for Lenovo E50-80, Dell Latitude E-series, and other Dell machines - A few code refactoring FireWire: - Lots of code cleanup and refactoring - Integrate the support of SCS.1x devices into snd-oxfw driver; snd-scs1x driver is obsoleted USB-audio: - Fix possible NULL dereference at disconnection - A regression fix for Native Instruments devices Misc: - A few code cleanups of fm801 driver" * tag 'sound-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (722 commits) ALSA: timer: Code cleanup ALSA: timer: Harden slave timer list handling ALSA: hda - Add fixup for Dell Latitidue E6540 ALSA: timer: Fix race among timer ioctls ALSA: hda - add codec support for Kabylake display audio codec ALSA: timer: Fix double unlink of active_list ALSA: usb-audio: Fix mixer ctl regression of Native Instrument devices ALSA: hda - fix the headset mic detection problem for a Dell laptop ALSA: hda - Fix white noise on Dell Latitude E5550 ALSA: hda_intel: add card number to irq description ALSA: seq: Fix race at timer setup and close ALSA: seq: Fix missing NULL check at remove_events ioctl ALSA: usb-audio: Avoid calling usb_autopm_put_interface() at disconnect ASoC: hdac_hdmi: remove unused hdac_hdmi_query_pin_connlist ASoC: AMD: Add missing include file ALSA: hda - Fixup inverted internal mic for Lenovo E50-80 ALSA: usb: Add native DSD support for Oppo HA-1 ASoC: Make aux_dev more like a generic component ASoC: bcm2835: cleanup includes by ordering them alphabetically ASoC: AMD: Manage ACP 2.x SRAM banks power ...
Diffstat (limited to 'drivers/gpu/drm/i915/intel_dp_link_training.c')
-rw-r--r--drivers/gpu/drm/i915/intel_dp_link_training.c323
1 files changed, 323 insertions, 0 deletions
diff --git a/drivers/gpu/drm/i915/intel_dp_link_training.c b/drivers/gpu/drm/i915/intel_dp_link_training.c
new file mode 100644
index 000000000000..88887938e0bf
--- /dev/null
+++ b/drivers/gpu/drm/i915/intel_dp_link_training.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright © 2008-2015 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "intel_drv.h"
+
+static void
+intel_get_adjust_train(struct intel_dp *intel_dp,
+ const uint8_t link_status[DP_LINK_STATUS_SIZE])
+{
+ uint8_t v = 0;
+ uint8_t p = 0;
+ int lane;
+ uint8_t voltage_max;
+ uint8_t preemph_max;
+
+ for (lane = 0; lane < intel_dp->lane_count; lane++) {
+ uint8_t this_v = drm_dp_get_adjust_request_voltage(link_status, lane);
+ uint8_t this_p = drm_dp_get_adjust_request_pre_emphasis(link_status, lane);
+
+ if (this_v > v)
+ v = this_v;
+ if (this_p > p)
+ p = this_p;
+ }
+
+ voltage_max = intel_dp_voltage_max(intel_dp);
+ if (v >= voltage_max)
+ v = voltage_max | DP_TRAIN_MAX_SWING_REACHED;
+
+ preemph_max = intel_dp_pre_emphasis_max(intel_dp, v);
+ if (p >= preemph_max)
+ p = preemph_max | DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+
+ for (lane = 0; lane < 4; lane++)
+ intel_dp->train_set[lane] = v | p;
+}
+
+static bool
+intel_dp_set_link_train(struct intel_dp *intel_dp,
+ uint8_t dp_train_pat)
+{
+ uint8_t buf[sizeof(intel_dp->train_set) + 1];
+ int ret, len;
+
+ intel_dp_program_link_training_pattern(intel_dp, dp_train_pat);
+
+ buf[0] = dp_train_pat;
+ if ((dp_train_pat & DP_TRAINING_PATTERN_MASK) ==
+ DP_TRAINING_PATTERN_DISABLE) {
+ /* don't write DP_TRAINING_LANEx_SET on disable */
+ len = 1;
+ } else {
+ /* DP_TRAINING_LANEx_SET follow DP_TRAINING_PATTERN_SET */
+ memcpy(buf + 1, intel_dp->train_set, intel_dp->lane_count);
+ len = intel_dp->lane_count + 1;
+ }
+
+ ret = drm_dp_dpcd_write(&intel_dp->aux, DP_TRAINING_PATTERN_SET,
+ buf, len);
+
+ return ret == len;
+}
+
+static bool
+intel_dp_reset_link_train(struct intel_dp *intel_dp,
+ uint8_t dp_train_pat)
+{
+ if (!intel_dp->train_set_valid)
+ memset(intel_dp->train_set, 0, sizeof(intel_dp->train_set));
+ intel_dp_set_signal_levels(intel_dp);
+ return intel_dp_set_link_train(intel_dp, dp_train_pat);
+}
+
+static bool
+intel_dp_update_link_train(struct intel_dp *intel_dp)
+{
+ int ret;
+
+ intel_dp_set_signal_levels(intel_dp);
+
+ ret = drm_dp_dpcd_write(&intel_dp->aux, DP_TRAINING_LANE0_SET,
+ intel_dp->train_set, intel_dp->lane_count);
+
+ return ret == intel_dp->lane_count;
+}
+
+/* Enable corresponding port and start training pattern 1 */
+static void
+intel_dp_link_training_clock_recovery(struct intel_dp *intel_dp)
+{
+ int i;
+ uint8_t voltage;
+ int voltage_tries, loop_tries;
+ uint8_t link_config[2];
+ uint8_t link_bw, rate_select;
+
+ if (intel_dp->prepare_link_retrain)
+ intel_dp->prepare_link_retrain(intel_dp);
+
+ intel_dp_compute_rate(intel_dp, intel_dp->link_rate,
+ &link_bw, &rate_select);
+
+ /* Write the link configuration data */
+ link_config[0] = link_bw;
+ link_config[1] = intel_dp->lane_count;
+ if (drm_dp_enhanced_frame_cap(intel_dp->dpcd))
+ link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
+ drm_dp_dpcd_write(&intel_dp->aux, DP_LINK_BW_SET, link_config, 2);
+ if (intel_dp->num_sink_rates)
+ drm_dp_dpcd_write(&intel_dp->aux, DP_LINK_RATE_SET,
+ &rate_select, 1);
+
+ link_config[0] = 0;
+ link_config[1] = DP_SET_ANSI_8B10B;
+ drm_dp_dpcd_write(&intel_dp->aux, DP_DOWNSPREAD_CTRL, link_config, 2);
+
+ intel_dp->DP |= DP_PORT_EN;
+
+ /* clock recovery */
+ if (!intel_dp_reset_link_train(intel_dp,
+ DP_TRAINING_PATTERN_1 |
+ DP_LINK_SCRAMBLING_DISABLE)) {
+ DRM_ERROR("failed to enable link training\n");
+ return;
+ }
+
+ voltage = 0xff;
+ voltage_tries = 0;
+ loop_tries = 0;
+ for (;;) {
+ uint8_t link_status[DP_LINK_STATUS_SIZE];
+
+ drm_dp_link_train_clock_recovery_delay(intel_dp->dpcd);
+ if (!intel_dp_get_link_status(intel_dp, link_status)) {
+ DRM_ERROR("failed to get link status\n");
+ break;
+ }
+
+ if (drm_dp_clock_recovery_ok(link_status, intel_dp->lane_count)) {
+ DRM_DEBUG_KMS("clock recovery OK\n");
+ break;
+ }
+
+ /*
+ * if we used previously trained voltage and pre-emphasis values
+ * and we don't get clock recovery, reset link training values
+ */
+ if (intel_dp->train_set_valid) {
+ DRM_DEBUG_KMS("clock recovery not ok, reset");
+ /* clear the flag as we are not reusing train set */
+ intel_dp->train_set_valid = false;
+ if (!intel_dp_reset_link_train(intel_dp,
+ DP_TRAINING_PATTERN_1 |
+ DP_LINK_SCRAMBLING_DISABLE)) {
+ DRM_ERROR("failed to enable link training\n");
+ return;
+ }
+ continue;
+ }
+
+ /* Check to see if we've tried the max voltage */
+ for (i = 0; i < intel_dp->lane_count; i++)
+ if ((intel_dp->train_set[i] & DP_TRAIN_MAX_SWING_REACHED) == 0)
+ break;
+ if (i == intel_dp->lane_count) {
+ ++loop_tries;
+ if (loop_tries == 5) {
+ DRM_ERROR("too many full retries, give up\n");
+ break;
+ }
+ intel_dp_reset_link_train(intel_dp,
+ DP_TRAINING_PATTERN_1 |
+ DP_LINK_SCRAMBLING_DISABLE);
+ voltage_tries = 0;
+ continue;
+ }
+
+ /* Check to see if we've tried the same voltage 5 times */
+ if ((intel_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage) {
+ ++voltage_tries;
+ if (voltage_tries == 5) {
+ DRM_ERROR("too many voltage retries, give up\n");
+ break;
+ }
+ } else
+ voltage_tries = 0;
+ voltage = intel_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK;
+
+ /* Update training set as requested by target */
+ intel_get_adjust_train(intel_dp, link_status);
+ if (!intel_dp_update_link_train(intel_dp)) {
+ DRM_ERROR("failed to update link training\n");
+ break;
+ }
+ }
+}
+
+static void
+intel_dp_link_training_channel_equalization(struct intel_dp *intel_dp)
+{
+ bool channel_eq = false;
+ int tries, cr_tries;
+ uint32_t training_pattern = DP_TRAINING_PATTERN_2;
+
+ /*
+ * Training Pattern 3 for HBR2 or 1.2 devices that support it.
+ *
+ * Intel platforms that support HBR2 also support TPS3. TPS3 support is
+ * also mandatory for downstream devices that support HBR2.
+ *
+ * Due to WaDisableHBR2 SKL < B0 is the only exception where TPS3 is
+ * supported but still not enabled.
+ */
+ if (intel_dp_source_supports_hbr2(intel_dp) &&
+ drm_dp_tps3_supported(intel_dp->dpcd))
+ training_pattern = DP_TRAINING_PATTERN_3;
+ else if (intel_dp->link_rate == 540000)
+ DRM_ERROR("5.4 Gbps link rate without HBR2/TPS3 support\n");
+
+ /* channel equalization */
+ if (!intel_dp_set_link_train(intel_dp,
+ training_pattern |
+ DP_LINK_SCRAMBLING_DISABLE)) {
+ DRM_ERROR("failed to start channel equalization\n");
+ return;
+ }
+
+ tries = 0;
+ cr_tries = 0;
+ channel_eq = false;
+ for (;;) {
+ uint8_t link_status[DP_LINK_STATUS_SIZE];
+
+ if (cr_tries > 5) {
+ DRM_ERROR("failed to train DP, aborting\n");
+ break;
+ }
+
+ drm_dp_link_train_channel_eq_delay(intel_dp->dpcd);
+ if (!intel_dp_get_link_status(intel_dp, link_status)) {
+ DRM_ERROR("failed to get link status\n");
+ break;
+ }
+
+ /* Make sure clock is still ok */
+ if (!drm_dp_clock_recovery_ok(link_status,
+ intel_dp->lane_count)) {
+ intel_dp->train_set_valid = false;
+ intel_dp_link_training_clock_recovery(intel_dp);
+ intel_dp_set_link_train(intel_dp,
+ training_pattern |
+ DP_LINK_SCRAMBLING_DISABLE);
+ cr_tries++;
+ continue;
+ }
+
+ if (drm_dp_channel_eq_ok(link_status,
+ intel_dp->lane_count)) {
+ channel_eq = true;
+ break;
+ }
+
+ /* Try 5 times, then try clock recovery if that fails */
+ if (tries > 5) {
+ intel_dp->train_set_valid = false;
+ intel_dp_link_training_clock_recovery(intel_dp);
+ intel_dp_set_link_train(intel_dp,
+ training_pattern |
+ DP_LINK_SCRAMBLING_DISABLE);
+ tries = 0;
+ cr_tries++;
+ continue;
+ }
+
+ /* Update training set as requested by target */
+ intel_get_adjust_train(intel_dp, link_status);
+ if (!intel_dp_update_link_train(intel_dp)) {
+ DRM_ERROR("failed to update link training\n");
+ break;
+ }
+ ++tries;
+ }
+
+ intel_dp_set_idle_link_train(intel_dp);
+
+ if (channel_eq) {
+ intel_dp->train_set_valid = true;
+ DRM_DEBUG_KMS("Channel EQ done. DP Training successful\n");
+ }
+}
+
+void intel_dp_stop_link_train(struct intel_dp *intel_dp)
+{
+ intel_dp_set_link_train(intel_dp,
+ DP_TRAINING_PATTERN_DISABLE);
+}
+
+void
+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);
+}