summaryrefslogtreecommitdiff
path: root/src/drivers/intel
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers/intel')
-rw-r--r--src/drivers/intel/Kconfig20
-rw-r--r--src/drivers/intel/gma/Kconfig24
-rw-r--r--src/drivers/intel/gma/Makefile.inc21
-rw-r--r--src/drivers/intel/gma/drm_dp_helper.c119
-rw-r--r--src/drivers/intel/gma/drm_dp_helper.h239
-rw-r--r--src/drivers/intel/gma/i915.h180
-rw-r--r--src/drivers/intel/gma/intel_dp.c1707
7 files changed, 2310 insertions, 0 deletions
diff --git a/src/drivers/intel/Kconfig b/src/drivers/intel/Kconfig
new file mode 100644
index 0000000000..9954f31e03
--- /dev/null
+++ b/src/drivers/intel/Kconfig
@@ -0,0 +1,20 @@
+##
+## This file is part of the coreboot project.
+##
+## Copyright (C) 2013 Google Inc.
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; version 2 of the License.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+source src/drivers/intel/gma/Kconfig
diff --git a/src/drivers/intel/gma/Kconfig b/src/drivers/intel/gma/Kconfig
new file mode 100644
index 0000000000..1a4212bd49
--- /dev/null
+++ b/src/drivers/intel/gma/Kconfig
@@ -0,0 +1,24 @@
+##
+## This file is part of the coreboot project.
+##
+## Copyright 2013 Google Inc.
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; version 2 of the License.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+config INTEL_DP
+ bool
+ default n
+ help
+ helper functions for intel display port operations
diff --git a/src/drivers/intel/gma/Makefile.inc b/src/drivers/intel/gma/Makefile.inc
new file mode 100644
index 0000000000..c90daa4f37
--- /dev/null
+++ b/src/drivers/intel/gma/Makefile.inc
@@ -0,0 +1,21 @@
+##
+## This file is part of the coreboot project.
+##
+## Copyright (C) 2013 Google Inc.
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; version 2 of the License.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##
+
+ramstage-$(CONFIG_INTEL_DP) += intel_dp.c drm_dp_helper.c
+
diff --git a/src/drivers/intel/gma/drm_dp_helper.c b/src/drivers/intel/gma/drm_dp_helper.c
new file mode 100644
index 0000000000..66072263f4
--- /dev/null
+++ b/src/drivers/intel/gma/drm_dp_helper.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2013 Google Inc.
+ * Copyright © 2009 Keith Packard
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <console/console.h>
+#include <stdint.h>
+#include <delay.h>
+#include <drivers/intel/gma/i915.h>
+#include <string.h>
+
+#include <edid.h>
+
+/* reduced a lot for coreboot. */
+/**
+ * DOC: dp helpers
+ *
+ * These functions contain some common logic and helpers at various
+ * abstraction levels to deal with Display Port sink devices and
+ * related things like DP aux channel transfers, EDID reading over DP
+ * aux channels, decoding certain DPCD blocks, ...
+ */
+
+
+/* Helpers for DP link training */
+static u8 dp_link_status(u8 link_status[DP_LINK_STATUS_SIZE], int r)
+{
+ printk(BIOS_SPEW, "%s: %d, %d, %d\n", __func__,
+ r, r - DP_LANE0_1_STATUS,
+ link_status[r - DP_LANE0_1_STATUS]);
+ return link_status[r - DP_LANE0_1_STATUS];
+}
+
+static u8 dp_get_lane_status(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane)
+{
+ int i = DP_LANE0_1_STATUS + (lane >> 1);
+ int s = (lane & 1) * 4;
+ u8 l = dp_link_status(link_status, i);
+ return (l >> s) & 0xf;
+}
+
+int drm_dp_channel_eq_ok(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane_count)
+{
+ u8 lane_align;
+ u8 lane_status;
+ int lane;
+
+ lane_align = dp_link_status(link_status,
+ DP_LANE_ALIGN_STATUS_UPDATED);
+ if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0)
+ return 0;
+ for (lane = 0; lane < lane_count; lane++) {
+ lane_status = dp_get_lane_status(link_status, lane);
+ if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS)
+ return 0;
+ }
+ return 1;
+}
+
+int drm_dp_clock_recovery_ok(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane_count)
+{
+ int lane;
+ u8 lane_status;
+
+ for (lane = 0; lane < lane_count; lane++) {
+ lane_status = dp_get_lane_status(link_status, lane);
+ if ((lane_status & DP_LANE_CR_DONE) == 0)
+ return 0;
+ }
+ return 1;
+}
+
+u8 drm_dp_get_adjust_request_voltage(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane)
+{
+ int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+ int s = ((lane & 1) ?
+ DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
+ DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
+ u8 l = dp_link_status(link_status, i);
+
+ printk(BIOS_SPEW, "%s: i %d s %d l %d return %d\n",
+ __func__, i, s, l,
+ ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT);
+ return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT;
+}
+
+u8 drm_dp_get_adjust_request_pre_emphasis(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane)
+{
+ int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+ int s = ((lane & 1) ?
+ DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
+ DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
+ u8 l = dp_link_status(link_status, i);
+
+ return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+}
diff --git a/src/drivers/intel/gma/drm_dp_helper.h b/src/drivers/intel/gma/drm_dp_helper.h
new file mode 100644
index 0000000000..f2e06c33f3
--- /dev/null
+++ b/src/drivers/intel/gma/drm_dp_helper.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2013 Google Inc.
+ * Copyright © 2008 Keith Packard
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef _DRM_DP_HELPER_H_
+#define _DRM_DP_HELPER_H_
+
+/* From the VESA DisplayPort spec */
+
+#define AUX_NATIVE_WRITE 0x8
+#define AUX_NATIVE_READ 0x9
+#define AUX_I2C_WRITE 0x0
+#define AUX_I2C_READ 0x1
+#define AUX_I2C_STATUS 0x2
+#define AUX_I2C_MOT 0x4
+
+#define AUX_NATIVE_REPLY_ACK (0x0 << 4)
+#define AUX_NATIVE_REPLY_NACK (0x1 << 4)
+#define AUX_NATIVE_REPLY_DEFER (0x2 << 4)
+#define AUX_NATIVE_REPLY_MASK (0x3 << 4)
+
+#define AUX_I2C_REPLY_ACK (0x0 << 6)
+#define AUX_I2C_REPLY_NACK (0x1 << 6)
+#define AUX_I2C_REPLY_DEFER (0x2 << 6)
+#define AUX_I2C_REPLY_MASK (0x3 << 6)
+
+/* AUX CH addresses */
+/* DPCD */
+#define DP_DPCD_REV 0x000
+
+#define DP_MAX_LINK_RATE 0x001
+
+#define DP_MAX_LANE_COUNT 0x002
+# define DP_MAX_LANE_COUNT_MASK 0x1f
+# define DP_TPS3_SUPPORTED (1 << 6)
+# define DP_ENHANCED_FRAME_CAP (1 << 7)
+
+#define DP_MAX_DOWNSPREAD 0x003
+# define DP_NO_AUX_HANDSHAKE_LINK_TRAINING (1 << 6)
+
+#define DP_NORP 0x004
+
+#define DP_DOWNSTREAMPORT_PRESENT 0x005
+# define DP_DWN_STRM_PORT_PRESENT (1 << 0)
+# define DP_DWN_STRM_PORT_TYPE_MASK 0x06
+/* 00b = DisplayPort */
+/* 01b = Analog */
+/* 10b = TMDS or HDMI */
+/* 11b = Other */
+# define DP_FORMAT_CONVERSION (1 << 3)
+
+#define DP_MAIN_LINK_CHANNEL_CODING 0x006
+
+#define DP_EDP_CONFIGURATION_CAP 0x00d
+#define DP_TRAINING_AUX_RD_INTERVAL 0x00e
+
+#define DP_PSR_SUPPORT 0x070
+# define DP_PSR_IS_SUPPORTED 1
+#define DP_PSR_CAPS 0x071
+# define DP_PSR_NO_TRAIN_ON_EXIT 1
+# define DP_PSR_SETUP_TIME_330 (0 << 1)
+# define DP_PSR_SETUP_TIME_275 (1 << 1)
+# define DP_PSR_SETUP_TIME_220 (2 << 1)
+# define DP_PSR_SETUP_TIME_165 (3 << 1)
+# define DP_PSR_SETUP_TIME_110 (4 << 1)
+# define DP_PSR_SETUP_TIME_55 (5 << 1)
+# define DP_PSR_SETUP_TIME_0 (6 << 1)
+# define DP_PSR_SETUP_TIME_MASK (7 << 1)
+# define DP_PSR_SETUP_TIME_SHIFT 1
+
+/* link configuration */
+#define DP_LINK_BW_SET 0x100
+# define DP_LINK_BW_1_62 0x06
+# define DP_LINK_BW_2_7 0x0a
+# define DP_LINK_BW_5_4 0x14
+
+#define DP_LANE_COUNT_SET 0x101
+# define DP_LANE_COUNT_MASK 0x0f
+# define DP_LANE_COUNT_ENHANCED_FRAME_EN (1 << 7)
+
+#define DP_TRAINING_PATTERN_SET 0x102
+# define DP_TRAINING_PATTERN_DISABLE 0
+# define DP_TRAINING_PATTERN_1 1
+# define DP_TRAINING_PATTERN_2 2
+# define DP_TRAINING_PATTERN_3 3
+# define DP_TRAINING_PATTERN_MASK 0x3
+
+# define DP_LINK_QUAL_PATTERN_DISABLE (0 << 2)
+# define DP_LINK_QUAL_PATTERN_D10_2 (1 << 2)
+# define DP_LINK_QUAL_PATTERN_ERROR_RATE (2 << 2)
+# define DP_LINK_QUAL_PATTERN_PRBS7 (3 << 2)
+# define DP_LINK_QUAL_PATTERN_MASK (3 << 2)
+
+# define DP_RECOVERED_CLOCK_OUT_EN (1 << 4)
+# define DP_LINK_SCRAMBLING_DISABLE (1 << 5)
+
+# define DP_SYMBOL_ERROR_COUNT_BOTH (0 << 6)
+# define DP_SYMBOL_ERROR_COUNT_DISPARITY (1 << 6)
+# define DP_SYMBOL_ERROR_COUNT_SYMBOL (2 << 6)
+# define DP_SYMBOL_ERROR_COUNT_MASK (3 << 6)
+
+#define DP_TRAINING_LANE0_SET 0x103
+#define DP_TRAINING_LANE1_SET 0x104
+#define DP_TRAINING_LANE2_SET 0x105
+#define DP_TRAINING_LANE3_SET 0x106
+
+# define DP_TRAIN_VOLTAGE_SWING_MASK 0x3
+# define DP_TRAIN_VOLTAGE_SWING_SHIFT 0
+# define DP_TRAIN_MAX_SWING_REACHED (1 << 2)
+# define DP_TRAIN_VOLTAGE_SWING_400 (0 << 0)
+# define DP_TRAIN_VOLTAGE_SWING_600 (1 << 0)
+# define DP_TRAIN_VOLTAGE_SWING_800 (2 << 0)
+# define DP_TRAIN_VOLTAGE_SWING_1200 (3 << 0)
+
+# define DP_TRAIN_PRE_EMPHASIS_MASK (3 << 3)
+# define DP_TRAIN_PRE_EMPHASIS_0 (0 << 3)
+# define DP_TRAIN_PRE_EMPHASIS_3_5 (1 << 3)
+# define DP_TRAIN_PRE_EMPHASIS_6 (2 << 3)
+# define DP_TRAIN_PRE_EMPHASIS_9_5 (3 << 3)
+
+# define DP_TRAIN_PRE_EMPHASIS_SHIFT 3
+# define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED (1 << 5)
+
+#define DP_DOWNSPREAD_CTRL 0x107
+# define DP_SPREAD_AMP_0_5 (1 << 4)
+
+#define DP_MAIN_LINK_CHANNEL_CODING_SET 0x108
+# define DP_SET_ANSI_8B10B (1 << 0)
+
+#define DP_PSR_EN_CFG 0x170
+# define DP_PSR_ENABLE (1 << 0)
+# define DP_PSR_MAIN_LINK_ACTIVE (1 << 1)
+# define DP_PSR_CRC_VERIFICATION (1 << 2)
+# define DP_PSR_FRAME_CAPTURE (1 << 3)
+
+#define DP_DEVICE_SERVICE_IRQ_VECTOR 0x201
+# define DP_REMOTE_CONTROL_COMMAND_PENDING (1 << 0)
+# define DP_AUTOMATED_TEST_REQUEST (1 << 1)
+# define DP_CP_IRQ (1 << 2)
+# define DP_SINK_SPECIFIC_IRQ (1 << 6)
+
+#define DP_EDP_CONFIGURATION_SET 0x10a
+
+#define DP_LANE0_1_STATUS 0x202
+#define DP_LANE2_3_STATUS 0x203
+# define DP_LANE_CR_DONE (1 << 0)
+# define DP_LANE_CHANNEL_EQ_DONE (1 << 1)
+# define DP_LANE_SYMBOL_LOCKED (1 << 2)
+
+#define DP_CHANNEL_EQ_BITS (DP_LANE_CR_DONE | \
+ DP_LANE_CHANNEL_EQ_DONE | \
+ DP_LANE_SYMBOL_LOCKED)
+
+#define DP_LANE_ALIGN_STATUS_UPDATED 0x204
+
+#define DP_INTERLANE_ALIGN_DONE (1 << 0)
+#define DP_DOWNSTREAM_PORT_STATUS_CHANGED (1 << 6)
+#define DP_LINK_STATUS_UPDATED (1 << 7)
+
+#define DP_SINK_STATUS 0x205
+
+#define DP_RECEIVE_PORT_0_STATUS (1 << 0)
+#define DP_RECEIVE_PORT_1_STATUS (1 << 1)
+
+#define DP_ADJUST_REQUEST_LANE0_1 0x206
+#define DP_ADJUST_REQUEST_LANE2_3 0x207
+# define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03
+# define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0
+# define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c
+# define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2
+# define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30
+# define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4
+# define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0
+# define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6
+
+#define DP_TEST_REQUEST 0x218
+# define DP_TEST_LINK_TRAINING (1 << 0)
+# define DP_TEST_LINK_PATTERN (1 << 1)
+# define DP_TEST_LINK_EDID_READ (1 << 2)
+# define DP_TEST_LINK_PHY_TEST_PATTERN (1 << 3) /* DPCD >= 1.1 */
+
+#define DP_TEST_LINK_RATE 0x219
+# define DP_LINK_RATE_162 (0x6)
+# define DP_LINK_RATE_27 (0xa)
+
+#define DP_TEST_LANE_COUNT 0x220
+
+#define DP_TEST_PATTERN 0x221
+
+#define DP_TEST_RESPONSE 0x260
+# define DP_TEST_ACK (1 << 0)
+# define DP_TEST_NAK (1 << 1)
+# define DP_TEST_EDID_CHECKSUM_WRITE (1 << 2)
+
+#define DP_SET_POWER 0x600
+# define DP_SET_POWER_D0 0x1
+# define DP_SET_POWER_D3 0x2
+
+#define DP_PSR_ERROR_STATUS 0x2006
+# define DP_PSR_LINK_CRC_ERROR (1 << 0)
+# define DP_PSR_RFB_STORAGE_ERROR (1 << 1)
+
+#define DP_PSR_ESI 0x2007
+# define DP_PSR_CAPS_CHANGE (1 << 0)
+
+#define DP_PSR_STATUS 0x2008
+# define DP_PSR_SINK_INACTIVE 0
+# define DP_PSR_SINK_ACTIVE_SRC_SYNCED 1
+# define DP_PSR_SINK_ACTIVE_RFB 2
+# define DP_PSR_SINK_ACTIVE_SINK_SYNCED 3
+# define DP_PSR_SINK_ACTIVE_RESYNC 4
+# define DP_PSR_SINK_INTERNAL_ERROR 7
+# define DP_PSR_SINK_STATE_MASK 0x07
+
+#define MODE_I2C_START 1
+#define MODE_I2C_WRITE 2
+#define MODE_I2C_READ 4
+#define MODE_I2C_STOP 8
+
+#endif /* _DRM_DP_HELPER_H_ */
diff --git a/src/drivers/intel/gma/i915.h b/src/drivers/intel/gma/i915.h
new file mode 100644
index 0000000000..37570649f9
--- /dev/null
+++ b/src/drivers/intel/gma/i915.h
@@ -0,0 +1,180 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2012 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <drivers/intel/gma/i915_reg.h>
+#include <drivers/intel/gma/drm_dp_helper.h>
+#include <edid.h>
+
+/* port types. We stick with the same defines as the kernel */
+#define INTEL_OUTPUT_UNUSED 0
+#define INTEL_OUTPUT_ANALOG 1
+#define INTEL_OUTPUT_DVO 2
+#define INTEL_OUTPUT_SDVO 3
+#define INTEL_OUTPUT_LVDS 4
+#define INTEL_OUTPUT_TVOUT 5
+#define INTEL_OUTPUT_HDMI 6
+#define INTEL_OUTPUT_DISPLAYPORT 7
+#define INTEL_OUTPUT_EDP 8
+
+/* things that are, strangely, not defined anywhere? */
+#define PCH_PP_UNLOCK 0xabcd0000
+#define WMx_LP_SR_EN (1<<31)
+#define PRB0_TAIL 0x02030
+#define PRB0_HEAD 0x02034
+#define PRB0_START 0x02038
+#define PRB0_CTL 0x0203c
+
+/* debug enums. These are for printks that, due to their place in the
+ * middle of graphics device IO, might change timing. Use with care
+ * or not at all.
+ */
+enum {
+ vio = 2, /* dump every IO */
+ vspin = 4, /* print # of times we spun on a register value */
+};
+
+/* The mainboard must provide these functions. */
+unsigned long io_i915_read32(unsigned long addr);
+void io_i915_write32(unsigned long val, unsigned long addr);
+
+/*
+ * To communicate to and control the extracted-from-kernel code,
+ * we need this struct. It has a counterpart in the ARM code, so
+ * there is a precedent.
+ */
+
+#define DP_RECEIVER_CAP_SIZE 0xf
+#define DP_LINK_STATUS_SIZE 6
+#define DP_LINK_CHECK_TIMEOUT (10 * 1000)
+
+#define DP_LINK_CONFIGURATION_SIZE 9
+
+struct intel_dp {
+ int gen; // 6 for link, 7 for wtm2
+ int has_pch_split; // 1 for link and wtm2
+ int has_pch_cpt; // 1 for everything we know about.
+ int is_haswell;
+ /* output register offset in MMIO space. Usually DP_A */
+ u32 output_reg;
+ /* The initial value of the DP register.
+ * Mainboards can set this to a non-zero
+ * value in the case that there are undetectable
+ * but essential parameters.
+ */
+ u32 DP;
+ uint8_t link_configuration[DP_LINK_CONFIGURATION_SIZE];
+ u32 color_range;
+ /* these paramaters are determined after reading the DPCD */
+ int dpms_mode;
+ uint8_t link_bw;
+ uint8_t lane_count;
+ /* This data is read from the panel via the AUX channel.*/
+ uint8_t dpcd[DP_RECEIVER_CAP_SIZE];
+ int type;
+ int edp;
+ int is_pch_edp;
+ /* state related to training. */
+ uint8_t train_set[4];
+ /* Determined from EDID or coreboot hard-sets. */
+ int panel_power_up_delay;
+ int panel_power_down_delay;
+ int panel_power_cycle_delay;
+ int backlight_on_delay;
+ int backlight_off_delay;
+ int want_panel_vdd;
+ u32 clock;
+ int port;
+ int pipe;
+ int bpp;
+ /* i2c on aux is ... interesting.
+ * Before you do an i2c cycle, you need to set the address.
+ * This requires we remember it from one moment to the next.
+ * Remember it here.
+ */
+ u16 address;
+ /* timing parameters for aux channel IO. They used to compute these on
+ * each and every entry to the functions, which is kind of stupid, and it had
+ * errors anyway.
+ * note: you can get these from watching YABEL output. E.g.:
+ * you see an outb of 0x802300e1 to 64010. the 3 is the precharge
+ * and the e1 is the clock divider.
+ */
+ u32 aux_clock_divider;
+ u32 precharge;
+ /* EDID, raw and processed */
+ u8 rawedid[256];
+ int edidlen;
+ struct edid edid;
+ /* computed values needed for "i915" registers */
+ int bytes_per_pixel;
+ u32 htotal;
+ u32 hblank;
+ u32 hsync;
+ u32 vtotal;
+ u32 vblank;
+ u32 vsync;
+ u32 pfa_sz;
+ u32 pfa_pos;
+ u32 pfa_ctl;
+ u32 pipesrc;
+ u32 stride;
+};
+
+/* we may yet need these. */
+void intel_dp_mode_set(struct intel_dp *intel_dp);
+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_link_down(struct intel_dp *intel_dp);
+
+int intel_dp_i2c_init(struct intel_dp *intel_dp);
+int intel_dp_i2c_aux_ch(struct intel_dp *intel_dp,
+ int mode, uint8_t write_byte, uint8_t *read_byte);
+void
+intel_dp_dpms(struct intel_dp *intel_dp, int mode);
+
+int intel_dp_get_dpcd(struct intel_dp *intel_dp);
+struct edid *intel_dp_get_edid(struct intel_dp *intel_dp);
+
+void intel_dp_sink_dpms(struct intel_dp *intel_dp, int mode);
+void ironlake_edp_pll_on(void);
+void ironlake_edp_panel_vdd_on(struct intel_dp *intel_dp);
+void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp, int sync);
+int intel_dp_get_max_downspread(struct intel_dp *intel_dp, u8 *max_downspread);
+void ironlake_edp_panel_on(struct intel_dp *intel_dp);
+void ironlake_edp_backlight_on(struct intel_dp *intel_dp);
+/* needed only on haswell. */
+void intel_prepare_ddi_buffers(int port, int use_fdi_mode);
+void intel_ddi_prepare_link_retrain(struct intel_dp *intel_dp, int port);
+int intel_dp_aux_ch(struct intel_dp *intel_dp,
+ uint8_t *send, int send_bytes,
+ uint8_t *recv, int recv_size);
+void unpack_aux(u32 src, uint8_t *dst, int dst_bytes);
+
+
+/* drm_dp_helper.c */
+int drm_dp_channel_eq_ok(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane_count);
+int drm_dp_clock_recovery_ok(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane_count);
+u8 drm_dp_get_adjust_request_voltage(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane);
+u8 drm_dp_get_adjust_request_pre_emphasis(u8 link_status[DP_LINK_STATUS_SIZE],
+ int lane);
+
+
diff --git a/src/drivers/intel/gma/intel_dp.c b/src/drivers/intel/gma/intel_dp.c
new file mode 100644
index 0000000000..4b5f7c49c1
--- /dev/null
+++ b/src/drivers/intel/gma/intel_dp.c
@@ -0,0 +1,1707 @@
+/*
+ * Copyright 2013 Google Inc.
+ * Copyright © 2008 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.
+ *
+ * Authors:
+ * Keith Packard <keithp@keithp.com>
+ *
+ */
+
+/* This code was created by the coccinnelle filters in the i915tool,
+ * with some final hand filtering.
+ */
+
+#include <console/console.h>
+#include <stdint.h>
+#include <delay.h>
+#include <drivers/intel/gma/i915.h>
+#include <string.h>
+
+/**
+ * is_edp - is the given port attached to an eDP panel (either CPU or PCH)
+ * @intel_dp: DP struct
+ *
+ * If a CPU or PCH DP output is attached to an eDP panel, this function
+ * will return 1, and 0 otherwise.
+ */
+static int is_edp(struct intel_dp *intel_dp)
+{
+ return intel_dp->type == INTEL_OUTPUT_EDP;
+}
+
+/**
+ * is_pch_edp - is the port on the PCH and attached to an eDP panel?
+ * @intel_dp: DP struct
+ *
+ * Returns 1 if the given DP struct corresponds to a PCH DP port attached
+ * to an eDP panel, 0 otherwise. Helpful for determining whether we
+ * may need FDI resources for a given DP output or not.
+ */
+static int is_pch_edp(struct intel_dp *intel_dp)
+{
+ return intel_dp->is_pch_edp;
+}
+
+/**
+ * is_cpu_edp - is the port on the CPU and attached to an eDP panel?
+ * @intel_dp: DP struct
+ *
+ * Returns 1 if the given DP struct corresponds to a CPU eDP port.
+ */
+static int is_cpu_edp(struct intel_dp *intel_dp)
+{
+ return is_edp(intel_dp) && !is_pch_edp(intel_dp);
+}
+
+static uint32_t
+pack_aux(uint8_t *src, int src_bytes)
+{
+ int i;
+ uint32_t v = 0;
+
+ if (src_bytes > 4)
+ src_bytes = 4;
+ for (i = 0; i < src_bytes; i++)
+ v |= ((uint32_t) src[i]) << ((3-i) * 8);
+ return v;
+}
+
+void
+unpack_aux(uint32_t src, uint8_t *dst, int dst_bytes)
+{
+ int i;
+ if (dst_bytes > 4)
+ dst_bytes = 4;
+ for (i = 0; i < dst_bytes; i++)
+ dst[i] = src >> ((3-i) * 8);
+}
+
+static int ironlake_edp_have_panel_power(struct intel_dp *intel_dp)
+{
+
+ return (io_i915_read32(PCH_PP_STATUS) & PP_ON) != 0;
+}
+
+static int ironlake_edp_have_panel_vdd(struct intel_dp *intel_dp)
+{
+ return (io_i915_read32(PCH_PP_CONTROL) & EDP_FORCE_VDD) != 0;
+}
+
+int
+intel_dp_aux_ch(struct intel_dp *intel_dp,
+ uint8_t *send, int send_bytes,
+ uint8_t *recv, int recv_size)
+{
+ uint32_t output_reg = intel_dp->output_reg;
+ uint32_t ch_ctl = output_reg + 0x10;
+ uint32_t ch_data = ch_ctl + 4;
+ int i;
+ int recv_bytes;
+ uint32_t status;
+ int try;
+
+ /* Try to wait for any previous AUX channel activity */
+ for (try = 0; try < 3; try++) {
+ status = io_i915_read32(ch_ctl);
+ if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0)
+ break;
+ mdelay(1);
+ }
+
+ if (try == 3) {
+ if (1) {
+ status = io_i915_read32(ch_ctl);
+ printk(BIOS_ERR,
+ "dp_aux_ch not started status 0x%08x\n",
+ status);
+ }
+ return -1;
+ }
+
+ /* Must try at least 3 times according to DP spec */
+ for (try = 0; try < 5; try++) {
+ /* Load the send data into the aux channel data registers */
+ for (i = 0; i < send_bytes; i += 4){
+ u32 val, addr;
+ val = pack_aux(send + i, send_bytes - i);
+ addr = ch_data + i;
+ io_i915_write32(val, addr);
+ }
+
+ /* Send the command and wait for it to complete */
+ io_i915_write32(DP_AUX_CH_CTL_SEND_BUSY |
+ DP_AUX_CH_CTL_TIME_OUT_400us |
+ (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) |
+ (intel_dp->precharge << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) |
+ (intel_dp->aux_clock_divider << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT) |
+ DP_AUX_CH_CTL_DONE |
+ DP_AUX_CH_CTL_TIME_OUT_ERROR |
+ DP_AUX_CH_CTL_RECEIVE_ERROR, ch_ctl);
+ for (;;) {
+ status = io_i915_read32(ch_ctl);
+ if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0)
+ break;
+ udelay(100);
+ }
+
+ /* Clear done status and any errors */
+ io_i915_write32(status |
+ DP_AUX_CH_CTL_DONE |
+ DP_AUX_CH_CTL_TIME_OUT_ERROR |
+ DP_AUX_CH_CTL_RECEIVE_ERROR, ch_ctl);
+
+ if (status & (DP_AUX_CH_CTL_TIME_OUT_ERROR |
+ DP_AUX_CH_CTL_RECEIVE_ERROR))
+ continue;
+ if (status & DP_AUX_CH_CTL_DONE)
+ break;
+ }
+
+ if ((status & DP_AUX_CH_CTL_DONE) == 0) {
+ printk(BIOS_ERR, "dp_aux_ch not done status 0x%08x\n", status);
+ return -1;
+ }
+
+ /* Check for timeout or receive error.
+ * Timeouts occur when the sink is not connected
+ */
+ if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) {
+ printk(BIOS_ERR,
+ "dp_aux_ch receive error status 0x%08x\n", status);
+ return -1;
+ }
+
+ /* Timeouts occur when the device isn't connected, so they're
+ * "normal" -- don't fill the kernel log with these */
+ if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) {
+ printk(BIOS_ERR, "dp_aux_ch timeout status 0x%08x\n", status);
+ return -1;
+ }
+
+ /* Unload any bytes sent back from the other side */
+ recv_bytes = ((status & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) >>
+ DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT);
+ if (recv_bytes > recv_size)
+ recv_bytes = recv_size;
+
+ for (i = 0; i < recv_bytes; i += 4)
+ unpack_aux(io_i915_read32(ch_data + i),
+ recv + i, recv_bytes - i);
+
+ return recv_bytes;
+}
+
+/* Write data to the aux channel in native mode */
+static int
+intel_dp_aux_native_write(struct intel_dp *intel_dp,
+ uint16_t address, uint8_t *send, int send_bytes)
+{
+ int ret;
+ uint8_t msg[20];
+ int msg_bytes;
+ uint8_t ack;
+
+ if (send_bytes > 16)
+ return -1;
+ msg[0] = AUX_NATIVE_WRITE << 4;
+ msg[1] = address >> 8;
+ msg[2] = address & 0xff;
+ msg[3] = send_bytes - 1;
+ memcpy(&msg[4], send, send_bytes);
+ msg_bytes = send_bytes + 4;
+ for (;;) {
+ ret = intel_dp_aux_ch(intel_dp, msg, msg_bytes, &ack, 1);
+ if (ret < 0)
+ return ret;
+ if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_ACK)
+ break;
+ else if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_DEFER)
+ udelay(100);
+ else
+ return -1;
+ }
+ return send_bytes;
+}
+
+/* Write a single byte to the aux channel in native mode */
+static int
+intel_dp_aux_native_write_1(struct intel_dp *intel_dp,
+ uint16_t address, uint8_t byte)
+{
+ return intel_dp_aux_native_write(intel_dp, address, &byte, 1);
+}
+
+/* read bytes from a native aux channel */
+static int
+intel_dp_aux_native_read(struct intel_dp *intel_dp,
+ uint16_t address, uint8_t *recv, int recv_bytes)
+{
+ uint8_t msg[4];
+ int msg_bytes;
+ uint8_t reply[20];
+ int reply_bytes;
+ uint8_t ack;
+ int ret;
+
+ msg[0] = AUX_NATIVE_READ << 4;
+ msg[1] = address >> 8;
+ msg[2] = address & 0xff;
+ msg[3] = recv_bytes - 1;
+
+ msg_bytes = 4;
+ reply_bytes = recv_bytes + 1;
+
+ for (;;) {
+ ret = intel_dp_aux_ch(intel_dp, msg, msg_bytes,
+ reply, reply_bytes);
+ if (ret == 0)
+ return -1;
+ if (ret < 0)
+ return ret;
+ ack = reply[0];
+ if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_ACK) {
+ memcpy(recv, reply + 1, ret - 1);
+ return ret - 1;
+ }
+ else if ((ack & AUX_NATIVE_REPLY_MASK) == AUX_NATIVE_REPLY_DEFER)
+ udelay(100);
+ else
+ return -1;
+ }
+}
+
+int
+intel_dp_i2c_aux_ch(struct intel_dp *intel_dp,
+ int mode, uint8_t write_byte, uint8_t *read_byte)
+{
+ uint8_t msg[5];
+ uint8_t reply[2];
+ unsigned retry;
+ int msg_bytes;
+ int reply_bytes;
+ int ret;
+
+ /* Set up the command byte */
+ if (mode & MODE_I2C_READ)
+ msg[0] = AUX_I2C_READ << 4;
+ else
+ msg[0] = AUX_I2C_WRITE << 4;
+
+ if (!(mode & MODE_I2C_STOP))
+ msg[0] |= AUX_I2C_MOT << 4;
+
+ msg[1] = intel_dp->address >> 8;
+ msg[2] = intel_dp->address;
+
+ switch (mode) {
+ case MODE_I2C_WRITE:
+ msg[3] = 0;
+ msg[4] = write_byte;
+ msg_bytes = 5;
+ reply_bytes = 1;
+ break;
+ case MODE_I2C_READ:
+ msg[3] = 0;
+ msg_bytes = 4;
+ reply_bytes = 2;
+ break;
+ default:
+ msg_bytes = 3;
+ reply_bytes = 1;
+ break;
+ }
+
+ for (retry = 0; retry < 5; retry++) {
+ ret = intel_dp_aux_ch(intel_dp,
+ msg, msg_bytes,
+ reply, reply_bytes);
+ if (ret < 0) {
+ printk(BIOS_ERR, "aux_ch failed %d\n", ret);
+ return ret;
+ }
+
+ switch (reply[0] & AUX_NATIVE_REPLY_MASK) {
+ case AUX_NATIVE_REPLY_ACK:
+ /* I2C-over-AUX Reply field is only valid
+ * when paired with AUX ACK.
+ */
+ break;
+ case AUX_NATIVE_REPLY_NACK:
+ printk(BIOS_ERR, "aux_ch native nack\n");
+ return -1;
+ case AUX_NATIVE_REPLY_DEFER:
+ udelay(100);
+ continue;
+ default:
+ printk(BIOS_ERR, "aux_ch invalid native reply 0x%02x\n",
+ reply[0]);
+ return -1;
+ }
+
+ switch (reply[0] & AUX_I2C_REPLY_MASK) {
+ case AUX_I2C_REPLY_ACK:
+ if (mode == MODE_I2C_READ) {
+ *read_byte = reply[1];
+ }
+ return reply_bytes - 1;
+ case AUX_I2C_REPLY_NACK:
+ printk(BIOS_ERR, "aux_i2c nack\n");
+ return -1;
+ case AUX_I2C_REPLY_DEFER:
+ printk(BIOS_ERR, "aux_i2c defer\n");
+ udelay(100);
+ break;
+ default:
+ printk(BIOS_ERR,
+ "aux_i2c invalid reply 0x%02x\n", reply[0]);
+ return -1;
+ }
+ }
+
+ printk(BIOS_ERR, "too many retries, giving up\n");
+ return -1;
+}
+
+int
+intel_dp_i2c_init(struct intel_dp *intel_dp)
+{
+ int ret = 0;
+
+ /* not clear what we need to do here, if anything.
+ * this function was more about setting up the kernel.
+ * it's a handy placeholder, so we leave it in for now.
+ */
+ return ret;
+}
+
+struct intel_dp_m_n {
+ uint32_t tu;
+ uint32_t gmch_m;
+ uint32_t gmch_n;
+ uint32_t link_m;
+ uint32_t link_n;
+};
+
+static void
+intel_reduce_ratio(uint32_t *num, uint32_t *den)
+{
+ while (*num > 0xffffff || *den > 0xffffff) {
+ *num >>= 1;
+ *den >>= 1;
+ }
+}
+
+static void
+intel_dp_compute_m_n(int bpp,
+ int nlanes,
+ int pixel_clock,
+ int link_clock,
+ struct intel_dp_m_n *m_n)
+{
+ m_n->tu = 64;
+ m_n->gmch_m = (pixel_clock * bpp) >> 3;
+ m_n->gmch_n = link_clock * nlanes;
+ intel_reduce_ratio(&m_n->gmch_m, &m_n->gmch_n);
+ m_n->link_m = pixel_clock;
+ m_n->link_n = link_clock;
+ intel_reduce_ratio(&m_n->link_m, &m_n->link_n);
+}
+
+/* not sure. */
+void intel_dp_set_m_n(struct intel_dp *intel_dp);
+
+void
+intel_dp_set_m_n(struct intel_dp *intel_dp)
+{
+ int lane_count;
+ struct intel_dp_m_n m_n;
+ int pipe = intel_dp->pipe;
+
+ lane_count = intel_dp->lane_count;
+
+ /*
+ * Compute the GMCH and Link ratios. The '3' here is
+ * the number of bytes_per_pixel post-LUT, which we always
+ * set up for 8-bits of R/G/B, or 3 bytes total.
+ */
+ intel_dp_compute_m_n(intel_dp->bpp, lane_count,
+ intel_dp->clock, intel_dp->clock, &m_n);
+
+ {
+ io_i915_write32((
+ (m_n.tu - 1) <<
+ PIPE_GMCH_DATA_M_TU_SIZE_SHIFT) |
+ m_n.gmch_m, TRANSDATA_M1(pipe));
+ io_i915_write32(m_n.gmch_n, TRANSDATA_N1(pipe));
+ io_i915_write32(m_n.link_m, TRANSDPLINK_M1(pipe));
+ io_i915_write32(m_n.link_n, TRANSDPLINK_N1(pipe));
+ }
+}
+
+static void ironlake_edp_pll_off(void);
+
+void
+intel_dp_mode_set(struct intel_dp *intel_dp)
+{
+ /* Turn on the eDP PLL if needed */
+ if (is_edp(intel_dp)) {
+ if (!is_pch_edp(intel_dp))
+ ironlake_edp_pll_on();
+ else
+ ironlake_edp_pll_off();
+ }
+
+ /*
+ * There are four kinds of DP registers:
+ *
+ * IBX PCH
+ * SNB CPU
+ * IVB CPU
+ * CPT PCH
+ *
+ * IBX PCH and CPU are the same for almost everything,
+ * except that the CPU DP PLL is configured in this
+ * register
+ *
+ * CPT PCH is quite different, having many bits moved
+ * to the TRANS_DP_CTL register instead. That
+ * configuration happens (oddly) in ironlake_pch_enable
+ */
+
+ /* Preserve the BIOS-computed detected bit. This is
+ * supposed to be read-only.
+ */
+ intel_dp->DP = io_i915_read32(intel_dp->output_reg) & DP_DETECTED;
+ printk(BIOS_SPEW, "%s: initial value is %08lx\n", __func__,
+ (unsigned long)intel_dp->DP);
+ /* | 0 essentially */
+ intel_dp->DP |= DP_VOLTAGE_0_4 | DP_PRE_EMPHASIS_0;
+
+ /* Handle DP bits in common between all three register formats */
+
+ switch (intel_dp->lane_count) {
+ case 1:
+ intel_dp->DP |= DP_PORT_WIDTH_1;
+ break;
+ case 2:
+ intel_dp->DP |= DP_PORT_WIDTH_2;
+ break;
+ case 4:
+ intel_dp->DP |= DP_PORT_WIDTH_4;
+ break;
+ }
+ memset(intel_dp->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE);
+ intel_dp->link_configuration[0] = intel_dp->link_bw;
+ intel_dp->link_configuration[1] = intel_dp->lane_count;
+ intel_dp->link_configuration[8] = DP_SET_ANSI_8B10B;
+ /*
+ * Check for DPCD version > 1.1 and enhanced framing support
+ */
+ if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11 &&
+ (intel_dp->dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP)) {
+ intel_dp->link_configuration[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
+ }
+
+ /* Split out the IBX/CPU vs CPT settings */
+
+ if (is_cpu_edp(intel_dp) && (intel_dp->gen == 7)) {
+ /* what are these? We're not sure.
+ if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC)
+ intel_dp->DP |= DP_SYNC_HS_HIGH;
+
+ if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ */
+ intel_dp->DP |= DP_SYNC_VS_HIGH;
+ /* */
+ intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT;
+
+ if (intel_dp->link_configuration[1] &
+ DP_LANE_COUNT_ENHANCED_FRAME_EN)
+ intel_dp->DP |= DP_ENHANCED_FRAMING;
+
+ intel_dp->DP |= intel_dp->pipe << 29;
+
+ /* don't miss out required setting for eDP */
+ intel_dp->DP |= DP_PLL_ENABLE;
+ if (intel_dp->clock < 200000)
+ intel_dp->DP |= DP_PLL_FREQ_160MHZ;
+ else
+ intel_dp->DP |= DP_PLL_FREQ_270MHZ;
+ } else if (!intel_dp->has_pch_cpt || is_cpu_edp(intel_dp)) {
+ intel_dp->DP |= intel_dp->color_range;
+ intel_dp->DP |= DP_LINK_TRAIN_OFF;
+
+ if (intel_dp->link_configuration[1] &
+ DP_LANE_COUNT_ENHANCED_FRAME_EN)
+ intel_dp->DP |= DP_ENHANCED_FRAMING;
+
+ if (intel_dp->pipe == 1)
+ intel_dp->DP |= DP_PIPEB_SELECT;
+
+ if (is_cpu_edp(intel_dp)) {
+ /* don't miss out required setting for eDP */
+ intel_dp->DP |= DP_PLL_ENABLE;
+ if (intel_dp->clock < 200000)
+ intel_dp->DP |= DP_PLL_FREQ_160MHZ;
+ else
+ intel_dp->DP |= DP_PLL_FREQ_270MHZ;
+ }
+ } else {
+ intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT;
+ }
+}
+
+#define IDLE_ON_MASK (PP_ON | 0 | PP_SEQUENCE_MASK | 0 | PP_SEQUENCE_STATE_MASK)
+#define IDLE_ON_VALUE (PP_ON | 0 | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_ON_IDLE)
+
+#define IDLE_OFF_MASK (PP_ON | 0 | PP_SEQUENCE_MASK | 0 | PP_SEQUENCE_STATE_MASK)
+#define IDLE_OFF_VALUE (0 | 0 | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_OFF_IDLE)
+
+#define IDLE_CYCLE_MASK (PP_ON | 0 | PP_SEQUENCE_MASK | PP_CYCLE_DELAY_ACTIVE | PP_SEQUENCE_STATE_MASK)
+#define IDLE_CYCLE_VALUE (0 | 0 | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_OFF_IDLE)
+
+static void ironlake_wait_panel_status(struct intel_dp *intel_dp,
+ u32 mask,
+ u32 value)
+{
+ int i;
+ u32 status;
+ printk(BIOS_ERR, "[000000.0] [drm:%s], ", __func__);
+ printk(BIOS_ERR, "mask %08lx value %08lx status %08lx control %08lx\n",
+ (unsigned long) mask, (unsigned long) value,
+ (unsigned long)io_i915_read32(PCH_PP_STATUS),
+ (unsigned long)io_i915_read32(PCH_PP_CONTROL));
+
+ for(i = 0, status = io_i915_read32(PCH_PP_STATUS); ((status & mask) != value) && (i < 5000);
+ status = io_i915_read32(PCH_PP_STATUS)){
+ udelay(10);
+ }
+ if (i > 5000){
+ printk(BIOS_ERR,
+ "Panel status timeout: status %08lx control %08lx\n",
+ (unsigned long)io_i915_read32(PCH_PP_STATUS),
+ (unsigned long)io_i915_read32(PCH_PP_CONTROL));
+ }
+}
+
+static void ironlake_wait_panel_on(struct intel_dp *intel_dp)
+{
+ printk(BIOS_ERR, "Wait for panel power on\n");
+ ironlake_wait_panel_status(intel_dp, IDLE_ON_MASK, IDLE_ON_VALUE);
+}
+
+static void ironlake_wait_panel_off(struct intel_dp *intel_dp)
+{
+ printk(BIOS_ERR, "Wait for panel power off time\n");
+ ironlake_wait_panel_status(intel_dp, IDLE_OFF_MASK, IDLE_OFF_VALUE);
+}
+
+static void ironlake_wait_panel_power_cycle(struct intel_dp *intel_dp)
+{
+ printk(BIOS_ERR, "Wait for panel power cycle\n");
+ ironlake_wait_panel_status(intel_dp, IDLE_CYCLE_MASK, IDLE_CYCLE_VALUE);
+}
+
+
+/* Read the current pp_control value, unlocking the register if it
+ * is locked
+ */
+
+static u32 ironlake_get_pp_control(void)
+{
+ u32 control = io_i915_read32(PCH_PP_CONTROL);
+
+ control &= ~PANEL_UNLOCK_MASK;
+ control |= PANEL_UNLOCK_REGS;
+ return control;
+}
+
+void ironlake_edp_panel_vdd_on(struct intel_dp *intel_dp)
+{
+ u32 pp;
+
+ if (!is_edp(intel_dp))
+ return;
+ printk(BIOS_ERR, "Turn eDP VDD on\n");
+
+ if (intel_dp->want_panel_vdd) {
+ printk(BIOS_ERR, "eDP VDD already requested on\n");
+ }
+
+ intel_dp->want_panel_vdd = 1;
+
+ if (ironlake_edp_have_panel_vdd(intel_dp)) {
+ printk(BIOS_ERR, "eDP VDD already on\n");
+ return;
+ }
+
+ if (!ironlake_edp_have_panel_power(intel_dp))
+ ironlake_wait_panel_power_cycle(intel_dp);
+
+ pp = ironlake_get_pp_control();
+ pp |= EDP_FORCE_VDD;
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ // remember this if we need it later. Not sure yet.
+ ////POSTING_READ(PCH_PP_CONTROL);
+ printk(BIOS_ERR, "PCH_PP_STATUS: 0x%08lx PCH_PP_CONTROL: 0x%08lx\n",
+ io_i915_read32(PCH_PP_STATUS), io_i915_read32(PCH_PP_CONTROL));
+
+ /*
+ * If the panel wasn't on, delay before accessing aux channel
+ */
+ if (!ironlake_edp_have_panel_power(intel_dp)) {
+ printk(BIOS_ERR, "eDP was not running\n");
+ mdelay(intel_dp->panel_power_up_delay);
+ }
+}
+
+static void ironlake_panel_vdd_off_sync(struct intel_dp *intel_dp)
+{
+ u32 pp;
+
+ if (!intel_dp->want_panel_vdd && ironlake_edp_have_panel_vdd(intel_dp)) {
+ pp = ironlake_get_pp_control();
+ pp &= ~EDP_FORCE_VDD;
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ ////POSTING_READ(PCH_PP_CONTROL);
+
+ /* Make sure sequencer is idle before allowing subsequent activity */
+ printk(BIOS_ERR, "PCH_PP_STATUS: 0x%08lx PCH_PP_CONTROL: 0x%08lx\n",
+ io_i915_read32(PCH_PP_STATUS), io_i915_read32(PCH_PP_CONTROL));
+
+ mdelay(intel_dp->panel_power_down_delay);
+ }
+}
+
+void ironlake_edp_panel_vdd_off(struct intel_dp *intel_dp, int sync)
+{
+ if (!is_edp(intel_dp))
+ return;
+
+ printk(BIOS_ERR, "Turn eDP VDD off %d\n", intel_dp->want_panel_vdd);
+ if (!intel_dp->want_panel_vdd) {
+ printk(BIOS_ERR, "eDP VDD not forced on");
+ }
+
+ intel_dp->want_panel_vdd = 0;
+
+ if (sync)
+ ironlake_panel_vdd_off_sync(intel_dp);
+}
+
+void ironlake_edp_panel_on(struct intel_dp *intel_dp)
+{
+ u32 pp;
+
+ if (!is_edp(intel_dp))
+ return;
+
+ printk(BIOS_ERR, "Turn eDP power on\n");
+
+ if (ironlake_edp_have_panel_power(intel_dp)) {
+ printk(BIOS_ERR, "eDP power already on\n");
+ return;
+ }
+
+ ironlake_wait_panel_power_cycle(intel_dp);
+
+ pp = ironlake_get_pp_control();
+ if ((intel_dp->gen == 5)) {
+ /* ILK workaround: disable reset around power sequence */
+ pp &= ~PANEL_POWER_RESET;
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ ////POSTING_READ(PCH_PP_CONTROL);
+ }
+
+ pp |= POWER_TARGET_ON;
+ if (!(intel_dp->gen == 5))
+ pp |= PANEL_POWER_RESET;
+
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ ////POSTING_READ(PCH_PP_CONTROL);
+
+ ironlake_wait_panel_on(intel_dp);
+
+ if ((intel_dp->gen == 5)) {
+ pp |= PANEL_POWER_RESET; /* restore panel reset bit */
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ ////POSTING_READ(PCH_PP_CONTROL);
+ }
+}
+
+static void ironlake_edp_panel_off(struct intel_dp *intel_dp)
+{
+ u32 pp;
+
+ if (!is_edp(intel_dp))
+ return;
+
+ printk(BIOS_ERR, "Turn eDP power off\n");
+
+ if (intel_dp->want_panel_vdd) {
+ printk(BIOS_ERR, "Cannot turn power off while VDD is on\n");
+ }
+
+ pp = ironlake_get_pp_control();
+ pp &= ~(POWER_TARGET_ON | EDP_FORCE_VDD |
+ PANEL_POWER_RESET | EDP_BLC_ENABLE);
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ ////POSTING_READ(PCH_PP_CONTROL);
+
+ ironlake_wait_panel_off(intel_dp);
+}
+
+void ironlake_edp_backlight_on(struct intel_dp *intel_dp)
+{
+ u32 pp;
+
+ if (!is_edp(intel_dp))
+ return;
+
+ /*
+ * If we enable the backlight right away following a panel power
+ * on, we may see slight flicker as the panel syncs with the eDP
+ * link. So delay a bit to make sure the image is solid before
+ * allowing it to appear.
+ */
+ mdelay(intel_dp->backlight_on_delay);
+ pp = ironlake_get_pp_control();
+ pp |= EDP_BLC_ENABLE;
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ ////POSTING_READ(PCH_PP_CONTROL);
+}
+
+static void ironlake_edp_backlight_off(struct intel_dp *intel_dp)
+{
+ u32 pp;
+
+ if (!is_edp(intel_dp))
+ return;
+
+ pp = ironlake_get_pp_control();
+ pp &= ~EDP_BLC_ENABLE;
+ io_i915_write32(pp, PCH_PP_CONTROL);
+ ////POSTING_READ(PCH_PP_CONTROL);
+ mdelay(intel_dp->backlight_off_delay);
+}
+
+void ironlake_edp_pll_on(void)
+{
+ u32 dpa_ctl;
+
+ dpa_ctl = io_i915_read32(DP_A);
+ dpa_ctl |= DP_PLL_ENABLE;
+ io_i915_write32(dpa_ctl, DP_A);
+ ////POSTING_READ(DP_A);
+ udelay(200);
+}
+
+static void ironlake_edp_pll_off(void)
+{
+ u32 dpa_ctl;
+
+ dpa_ctl = io_i915_read32(DP_A);
+ dpa_ctl &= ~DP_PLL_ENABLE;
+ io_i915_write32(dpa_ctl, DP_A);
+ ////POSTING_READ(DP_A);
+ udelay(200);
+}
+
+/* If the sink supports it, try to set the power state appropriately */
+void intel_dp_sink_dpms(struct intel_dp *intel_dp, int mode)
+{
+ int ret, i;
+
+ /* Should have a valid DPCD by this point */
+ if (intel_dp->dpcd[DP_DPCD_REV] < 0x11)
+ return;
+
+ /* the convention, for whatever reason, is that mode > 0 means 'off' */
+ if (mode) {
+ ret = intel_dp_aux_native_write_1(intel_dp, DP_SET_POWER,
+ DP_SET_POWER_D3);
+ if (ret != 1) {
+ printk(BIOS_ERR, "failed to write sink power state\n");
+ }
+ } else {
+ /*
+ * When turning on, we need to retry for 1ms to give the sink
+ * time to wake up.
+ */
+ for (i = 0; i < 3; i++) {
+ ret = intel_dp_aux_native_write_1(intel_dp,
+ DP_SET_POWER,
+ DP_SET_POWER_D0);
+ if (ret == 1)
+ break;
+ mdelay(1);
+ }
+ }
+}
+// not sure if needed yet or not.
+void intel_dp_prepare(struct intel_dp *intel_dp);
+void intel_dp_prepare(struct intel_dp *intel_dp)
+{
+
+ ironlake_edp_backlight_off(intel_dp);
+ ironlake_edp_panel_off(intel_dp);
+
+ /* Wake up the sink first */
+ ironlake_edp_panel_vdd_on(intel_dp);
+ intel_dp_sink_dpms(intel_dp, 0);
+ intel_dp_link_down(intel_dp);
+ ironlake_edp_panel_vdd_off(intel_dp, 0);
+
+ /* Make sure the panel is off before trying to
+ * change the mode
+ */
+}
+
+// might be useful.
+void intel_dp_commit(struct intel_dp *intel_dp);
+void intel_dp_commit(struct intel_dp *intel_dp)
+{
+
+ ironlake_edp_panel_vdd_on(intel_dp);
+ intel_dp_sink_dpms(intel_dp, 0);
+ intel_dp_start_link_train(intel_dp);
+ ironlake_edp_panel_on(intel_dp);
+ ironlake_edp_panel_vdd_off(intel_dp, 1);
+ intel_dp_complete_link_train(intel_dp);
+ ironlake_edp_backlight_on(intel_dp);
+
+ intel_dp->dpms_mode = 0;
+}
+
+void
+intel_dp_dpms(struct intel_dp *intel_dp, int mode)
+{
+ uint32_t dp_reg = io_i915_read32(intel_dp->output_reg);
+
+ printk(BIOS_SPEW, "%s: power %s\n", __func__, mode ? "off" : "on");
+ if (mode){
+ ironlake_edp_backlight_off(intel_dp);
+ ironlake_edp_panel_off(intel_dp);
+
+ ironlake_edp_panel_vdd_on(intel_dp);
+ intel_dp_sink_dpms(intel_dp, mode);
+ intel_dp_link_down(intel_dp);
+ ironlake_edp_panel_vdd_off(intel_dp, 0);
+
+ if (is_cpu_edp(intel_dp))
+ ironlake_edp_pll_off();
+ } else {
+ if (is_cpu_edp(intel_dp))
+ ironlake_edp_pll_on();
+
+ ironlake_edp_panel_vdd_on(intel_dp);
+ intel_dp_sink_dpms(intel_dp, mode);
+ if (!(dp_reg & DP_PORT_EN)) {
+ intel_dp_start_link_train(intel_dp);
+ ironlake_edp_panel_on(intel_dp);
+ ironlake_edp_panel_vdd_off(intel_dp, 1);
+ intel_dp_complete_link_train(intel_dp);
+ } else
+ ironlake_edp_panel_vdd_off(intel_dp, 0);
+ ironlake_edp_backlight_on(intel_dp);
+ }
+ intel_dp->dpms_mode = mode;
+}
+
+/*
+ * Native read with retry for link status and receiver capability reads for
+ * cases where the sink may still be asleep.
+ */
+static int
+intel_dp_aux_native_read_retry(struct intel_dp *intel_dp, uint16_t address,
+ uint8_t *recv, int recv_bytes)
+{
+ int ret, i;
+
+ /*
+ * Sinks are *supposed* to come up within 1ms from an off state,
+ * but we're also supposed to retry 3 times per the spec.
+ */
+ for (i = 0; i < 3; i++) {
+ ret = intel_dp_aux_native_read(intel_dp, address, recv,
+ recv_bytes);
+ if (ret == recv_bytes)
+ return ret;
+ mdelay(1);
+ }
+
+ return ret;
+}
+
+/*
+ * Fetch AUX CH registers 0x202 - 0x207 which contain
+ * link status information
+ */
+static int
+intel_dp_get_link_status(struct intel_dp *intel_dp,
+ uint8_t link_status[DP_LINK_STATUS_SIZE])
+{
+ int ret, i;
+
+ ret = intel_dp_aux_native_read_retry(intel_dp,
+ DP_LANE0_1_STATUS,
+ link_status,
+ DP_LINK_STATUS_SIZE);
+
+ printk(BIOS_SPEW, "%s:", __func__);
+ for(i = 0; i < /* !!sizeof(link_status) == 4*/
+ DP_LINK_STATUS_SIZE; i++)
+ printk(BIOS_SPEW, " %02x", link_status[i]);
+ printk(BIOS_SPEW, "\n");
+ return ret;
+}
+
+static uint8_t
+intel_dp_link_status(uint8_t link_status[DP_LINK_STATUS_SIZE],
+ int r)
+{
+ return link_status[r - DP_LANE0_1_STATUS];
+}
+
+#if 0
+static uint8_t
+intel_get_adjust_request_voltage(uint8_t adjust_request[2],
+ int lane)
+{
+ int s = ((lane & 1) ?
+ DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
+ DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
+ uint8_t l = adjust_request[lane>>1];
+
+ return ((l >> s) & 3) << DP_TRAIN_VOLTAGE_SWING_SHIFT;
+}
+
+static uint8_t
+intel_get_adjust_request_pre_emphasis(uint8_t adjust_request[2],
+ int lane)
+{
+ int s = ((lane & 1) ?
+ DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
+ DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
+ uint8_t l = adjust_request[lane>>1];
+
+ return ((l >> s) & 3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+}
+#endif
+const char *voltage_names[] = {
+ "0.4V", "0.6V", "0.8V", "1.2V"
+};
+const char *pre_emph_names[] = {
+ "0dB", "3.5dB", "6dB", "9.5dB"
+};
+const char *link_train_names[] = {
+ "pattern 1", "pattern 2", "idle", "off"
+};
+
+/*
+ * These are source-specific values; current Intel hardware supports
+ * a maximum voltage of 800mV and a maximum pre-emphasis of 6dB
+ */
+
+static uint8_t
+intel_dp_voltage_max(struct intel_dp *intel_dp)
+{
+
+ if ((intel_dp->gen == 7) && is_cpu_edp(intel_dp))
+ return DP_TRAIN_VOLTAGE_SWING_800;
+ else if (intel_dp->has_pch_cpt && !is_cpu_edp(intel_dp))
+ return DP_TRAIN_VOLTAGE_SWING_1200;
+ else
+ return DP_TRAIN_VOLTAGE_SWING_800;
+}
+
+static uint8_t
+intel_dp_pre_emphasis_max(struct intel_dp *intel_dp, uint8_t voltage_swing)
+{
+
+ if (intel_dp->is_haswell){
+ switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) {
+ case DP_TRAIN_VOLTAGE_SWING_400:
+ return DP_TRAIN_PRE_EMPHASIS_9_5;
+ case DP_TRAIN_VOLTAGE_SWING_600:
+ return DP_TRAIN_PRE_EMPHASIS_6;
+ case DP_TRAIN_VOLTAGE_SWING_800:
+ return DP_TRAIN_PRE_EMPHASIS_3_5;
+ case DP_TRAIN_VOLTAGE_SWING_1200:
+ default:
+ return DP_TRAIN_PRE_EMPHASIS_0;
+ }
+ } else if ((intel_dp->gen == 7) && is_cpu_edp(intel_dp)) {
+ switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) {
+ case DP_TRAIN_VOLTAGE_SWING_400:
+ return DP_TRAIN_PRE_EMPHASIS_6;
+ case DP_TRAIN_VOLTAGE_SWING_600:
+ case DP_TRAIN_VOLTAGE_SWING_800:
+ return DP_TRAIN_PRE_EMPHASIS_3_5;
+ default:
+ return DP_TRAIN_PRE_EMPHASIS_0;
+ }
+ } else {
+ switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) {
+ case DP_TRAIN_VOLTAGE_SWING_400:
+ return DP_TRAIN_PRE_EMPHASIS_6;
+ case DP_TRAIN_VOLTAGE_SWING_600:
+ return DP_TRAIN_PRE_EMPHASIS_6;
+ case DP_TRAIN_VOLTAGE_SWING_800:
+ return DP_TRAIN_PRE_EMPHASIS_3_5;
+ case DP_TRAIN_VOLTAGE_SWING_1200:
+ default:
+ return DP_TRAIN_PRE_EMPHASIS_0;
+ }
+ }
+}
+
+static void
+intel_get_adjust_train(struct intel_dp *intel_dp,
+ 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;
+
+ printk(BIOS_SPEW, "%s: set to %s %s%s %s\n",
+ __func__,
+ voltage_names[v&3], pre_emph_names[p&3],
+ v & DP_TRAIN_MAX_SWING_REACHED ? ",max volt swing reached":"",
+ p & DP_TRAIN_MAX_PRE_EMPHASIS_REACHED ?
+ ", max pre emph reached" : "");
+ for (lane = 0; lane < 4; lane++)
+ intel_dp->train_set[lane] = v | p;
+}
+
+static uint32_t
+intel_dp_signal_levels(uint8_t train_set)
+{
+ uint32_t signal_levels = 0;
+
+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) {
+ case DP_TRAIN_VOLTAGE_SWING_400:
+ default:
+ signal_levels |= DP_VOLTAGE_0_4;
+ break;
+ case DP_TRAIN_VOLTAGE_SWING_600:
+ signal_levels |= DP_VOLTAGE_0_6;
+ break;
+ case DP_TRAIN_VOLTAGE_SWING_800:
+ signal_levels |= DP_VOLTAGE_0_8;
+ break;
+ case DP_TRAIN_VOLTAGE_SWING_1200:
+ signal_levels |= DP_VOLTAGE_1_2;
+ break;
+ }
+ switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) {
+ case DP_TRAIN_PRE_EMPHASIS_0:
+ default:
+ signal_levels |= DP_PRE_EMPHASIS_0;
+ break;
+ case DP_TRAIN_PRE_EMPHASIS_3_5:
+ signal_levels |= DP_PRE_EMPHASIS_3_5;
+ break;
+ case DP_TRAIN_PRE_EMPHASIS_6:
+ signal_levels |= DP_PRE_EMPHASIS_6;
+ break;
+ case DP_TRAIN_PRE_EMPHASIS_9_5:
+ signal_levels |= DP_PRE_EMPHASIS_9_5;
+ break;
+ }
+ return signal_levels;
+}
+
+/* Gen6's DP voltage swing and pre-emphasis control */
+static uint32_t
+intel_gen6_edp_signal_levels(uint8_t train_set)
+{
+ int signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK |
+ DP_TRAIN_PRE_EMPHASIS_MASK);
+ switch (signal_levels) {
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_0:
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_0:
+ return EDP_LINK_TRAIN_400_600MV_0DB_SNB_B;
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return EDP_LINK_TRAIN_400MV_3_5DB_SNB_B;
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_6:
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_6:
+ return EDP_LINK_TRAIN_400_600MV_6DB_SNB_B;
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ case DP_TRAIN_VOLTAGE_SWING_800 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return EDP_LINK_TRAIN_600_800MV_3_5DB_SNB_B;
+ case DP_TRAIN_VOLTAGE_SWING_800 | DP_TRAIN_PRE_EMPHASIS_0:
+ case DP_TRAIN_VOLTAGE_SWING_1200 | DP_TRAIN_PRE_EMPHASIS_0:
+ return EDP_LINK_TRAIN_800_1200MV_0DB_SNB_B;
+ default:
+ printk(BIOS_ERR, "[000000.0] [drm:%s], ", __func__);
+ printk(BIOS_ERR, "Unsupported voltage swing/pre-emphasis level:"
+ "0x%x\n", signal_levels);
+ return EDP_LINK_TRAIN_400_600MV_0DB_SNB_B;
+ }
+}
+
+/* Gen7's DP voltage swing and pre-emphasis control */
+static uint32_t
+intel_gen7_edp_signal_levels(uint8_t train_set)
+{
+ int signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK |
+ DP_TRAIN_PRE_EMPHASIS_MASK);
+ switch (signal_levels) {
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_0:
+ return EDP_LINK_TRAIN_400MV_0DB_IVB;
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return EDP_LINK_TRAIN_400MV_3_5DB_IVB;
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_6:
+ return EDP_LINK_TRAIN_400MV_6DB_IVB;
+
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_0:
+ return EDP_LINK_TRAIN_600MV_0DB_IVB;
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return EDP_LINK_TRAIN_600MV_3_5DB_IVB;
+
+ case DP_TRAIN_VOLTAGE_SWING_800 | DP_TRAIN_PRE_EMPHASIS_0:
+ return EDP_LINK_TRAIN_800MV_0DB_IVB;
+ case DP_TRAIN_VOLTAGE_SWING_800 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return EDP_LINK_TRAIN_800MV_3_5DB_IVB;
+
+ default:
+ printk(BIOS_ERR, "[000000.0] [drm:%s], ", __func__);
+ printk(BIOS_ERR, "Unsupported voltage swing/pre-emphasis level:"
+ "0x%x\n", signal_levels);
+ return EDP_LINK_TRAIN_500MV_0DB_IVB;
+ }
+}
+
+/* Gen7.5's (HSW) DP voltage swing and pre-emphasis control */
+static uint32_t
+intel_dp_signal_levels_hsw(uint8_t train_set)
+{
+ int signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK |
+ DP_TRAIN_PRE_EMPHASIS_MASK);
+ switch (signal_levels) {
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_0:
+ return DDI_BUF_EMP_400MV_0DB_HSW;
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return DDI_BUF_EMP_400MV_3_5DB_HSW;
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_6:
+ return DDI_BUF_EMP_400MV_6DB_HSW;
+ case DP_TRAIN_VOLTAGE_SWING_400 | DP_TRAIN_PRE_EMPHASIS_9_5:
+ return DDI_BUF_EMP_400MV_9_5DB_HSW;
+
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_0:
+ return DDI_BUF_EMP_600MV_0DB_HSW;
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return DDI_BUF_EMP_600MV_3_5DB_HSW;
+ case DP_TRAIN_VOLTAGE_SWING_600 | DP_TRAIN_PRE_EMPHASIS_6:
+ return DDI_BUF_EMP_600MV_6DB_HSW;
+
+ case DP_TRAIN_VOLTAGE_SWING_800 | DP_TRAIN_PRE_EMPHASIS_0:
+ return DDI_BUF_EMP_800MV_0DB_HSW;
+ case DP_TRAIN_VOLTAGE_SWING_800 | DP_TRAIN_PRE_EMPHASIS_3_5:
+ return DDI_BUF_EMP_800MV_3_5DB_HSW;
+ default:
+ printk(BIOS_SPEW,
+ "Unsupported voltage swing/pre-emphasis level:"
+ "0x%x\n", signal_levels);
+ return DDI_BUF_EMP_400MV_0DB_HSW;
+ }
+}
+
+
+static uint8_t
+intel_get_lane_status(uint8_t link_status[DP_LINK_STATUS_SIZE],
+ int lane)
+{
+ int s = (lane & 1) * 4;
+ uint8_t l = link_status[lane>>1];
+
+ return (l >> s) & 0xf;
+}
+
+/* Check for clock recovery is done on all channels */
+static int
+intel_clock_recovery_ok(uint8_t link_status[DP_LINK_STATUS_SIZE],
+ int lane_count)
+{
+ int lane;
+ uint8_t lane_status;
+
+ for (lane = 0; lane < lane_count; lane++) {
+ lane_status = intel_get_lane_status(link_status, lane);
+ printk(BIOS_SPEW,
+ "%s: Lane %d, status %02x\n", __func__,
+ lane, lane_status);
+ if ((lane_status & DP_LANE_CR_DONE) == 0)
+ return 0;
+ }
+ return 1;
+}
+
+/* Check to see if channel eq is done on all channels */
+#define CHANNEL_EQ_BITS (DP_LANE_CR_DONE| \
+ DP_LANE_CHANNEL_EQ_DONE| \
+ DP_LANE_SYMBOL_LOCKED)
+static int
+intel_channel_eq_ok(struct intel_dp *intel_dp,
+ uint8_t link_status[DP_LINK_STATUS_SIZE])
+{
+ uint8_t lane_align;
+ uint8_t lane_status;
+ int lane;
+
+ lane_align = intel_dp_link_status(link_status,
+ DP_LANE_ALIGN_STATUS_UPDATED);
+ if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0)
+ return 0;
+ for (lane = 0; lane < intel_dp->lane_count; lane++) {
+ lane_status = intel_get_lane_status(link_status, lane);
+ if ((lane_status & CHANNEL_EQ_BITS) != CHANNEL_EQ_BITS)
+ return 0;
+ }
+ return 1;
+}
+
+static int
+intel_dp_set_link_train(struct intel_dp *intel_dp,
+ uint32_t dp_reg_value,
+ uint8_t dp_train_pat)
+{
+ int ret;
+ u32 temp;
+ int port = intel_dp->port;
+ int i;
+
+ printk(BIOS_SPEW, "%s: dp_reg_value %08lx dp_train_pat %02x\n",
+ __func__, (unsigned long) dp_reg_value, dp_train_pat);
+ if (intel_dp->is_haswell){
+ temp = io_i915_read32(DP_TP_CTL(port));
+
+ if (dp_train_pat & DP_LINK_SCRAMBLING_DISABLE)
+ temp |= DP_TP_CTL_SCRAMBLE_DISABLE;
+ else
+ temp &= ~DP_TP_CTL_SCRAMBLE_DISABLE;
+
+ temp &= ~DP_TP_CTL_LINK_TRAIN_MASK;
+ switch (dp_train_pat & DP_TRAINING_PATTERN_MASK) {
+ case DP_TRAINING_PATTERN_DISABLE:
+ temp |= DP_TP_CTL_LINK_TRAIN_IDLE;
+ io_i915_write32( temp, DP_TP_CTL(port));
+
+ for(i = 0; i < 10; i++){
+ u32 status;
+ status = io_i915_read32(DP_TP_STATUS(port));
+ if (status & DP_TP_STATUS_IDLE_DONE)
+ break;
+ }
+
+ if (i == 10)
+ printk(BIOS_ERR,
+ "Timed out waiting for DP idle patterns\n");
+
+ temp &= ~DP_TP_CTL_LINK_TRAIN_MASK;
+ temp |= DP_TP_CTL_LINK_TRAIN_NORMAL;
+
+ break;
+ case DP_TRAINING_PATTERN_1:
+ temp |= DP_TP_CTL_LINK_TRAIN_PAT1;
+ break;
+ case DP_TRAINING_PATTERN_2:
+ temp |= DP_TP_CTL_LINK_TRAIN_PAT2;
+ break;
+ case DP_TRAINING_PATTERN_3:
+ temp |= DP_TP_CTL_LINK_TRAIN_PAT3;
+ break;
+ }
+ io_i915_write32( temp, DP_TP_CTL(port));
+
+ } else if (intel_dp->has_pch_cpt &&
+ (intel_dp->gen != 7 || !is_cpu_edp(intel_dp))) {
+ dp_reg_value &= ~DP_LINK_TRAIN_MASK_CPT;
+
+ switch (dp_train_pat & DP_TRAINING_PATTERN_MASK) {
+ case DP_TRAINING_PATTERN_DISABLE:
+ dp_reg_value |= DP_LINK_TRAIN_OFF_CPT;
+ break;
+ case DP_TRAINING_PATTERN_1:
+ dp_reg_value |= DP_LINK_TRAIN_PAT_1_CPT;
+ break;
+ case DP_TRAINING_PATTERN_2:
+ dp_reg_value |= DP_LINK_TRAIN_PAT_2_CPT;
+ break;
+ case DP_TRAINING_PATTERN_3:
+ printk(BIOS_ERR,
+ "DP training pattern 3 not supported\n");
+ dp_reg_value |= DP_LINK_TRAIN_PAT_2_CPT;
+ break;
+ }
+
+ } else {
+ dp_reg_value &= ~DP_LINK_TRAIN_MASK;
+
+ switch (dp_train_pat & DP_TRAINING_PATTERN_MASK) {
+ case DP_TRAINING_PATTERN_DISABLE:
+ dp_reg_value |= DP_LINK_TRAIN_OFF;
+ break;
+ case DP_TRAINING_PATTERN_1:
+ dp_reg_value |= DP_LINK_TRAIN_PAT_1;
+ break;
+ case DP_TRAINING_PATTERN_2:
+ dp_reg_value |= DP_LINK_TRAIN_PAT_2;
+ break;
+ case DP_TRAINING_PATTERN_3:
+ printk(BIOS_ERR,"DP training pattern 3 not supported\n");
+ dp_reg_value |= DP_LINK_TRAIN_PAT_2;
+ break;
+ }
+ }
+
+ io_i915_write32( dp_reg_value, intel_dp->output_reg);
+ //POSTING_READ(intel_dp->output_reg);
+
+ intel_dp_aux_native_write_1(intel_dp,
+ DP_TRAINING_PATTERN_SET,
+ dp_train_pat);
+
+ if ((dp_train_pat & DP_TRAINING_PATTERN_MASK) !=
+ DP_TRAINING_PATTERN_DISABLE) {
+ ret = intel_dp_aux_native_write(intel_dp,
+ DP_TRAINING_LANE0_SET,
+ intel_dp->train_set,
+ intel_dp->lane_count);
+ if (ret != intel_dp->lane_count){
+ printk(BIOS_ERR, "%s: wanted %d, got %d\n", __func__,
+ intel_dp->lane_count, ret);
+ return 0;
+ }
+ }
+
+ printk(BIOS_SPEW, "%s: success\n", __func__);
+ return 1;
+}
+
+/* Enable corresponding port and start training pattern 1 */
+void
+intel_dp_start_link_train(struct intel_dp *intel_dp)
+{
+ int i;
+ uint8_t voltage;
+ int clock_recovery = 0;
+ int voltage_tries, loop_tries;
+ u32 reg;
+ uint32_t DP = intel_dp->DP;
+
+ if (intel_dp->is_haswell)
+ intel_ddi_prepare_link_retrain(intel_dp, intel_dp->port);
+
+ /* Write the link configuration data */
+ printk(BIOS_SPEW, "Write the link configuration data\n");
+ intel_dp_aux_native_write(intel_dp, DP_LINK_BW_SET,
+ intel_dp->link_configuration,
+ DP_LINK_CONFIGURATION_SIZE);
+ printk(BIOS_SPEW, "Written\n");
+
+ DP |= DP_PORT_EN;
+
+ memset(intel_dp->train_set, 0, 4);
+ voltage = 0xff;
+ voltage_tries = 0;
+ loop_tries = 0;
+ clock_recovery = 0;
+
+ for (;;) {
+ /* Use intel_dp->train_set[0] to set the voltage and pre emphasis values */
+ uint8_t link_status[DP_LINK_STATUS_SIZE];
+ uint32_t signal_levels;
+
+ if (intel_dp->is_haswell){
+ signal_levels =
+ intel_dp_signal_levels_hsw(intel_dp->train_set[0]);
+ DP = (DP & ~DDI_BUF_EMP_MASK) | signal_levels;
+ printk(BIOS_SPEW, "Haswell: levels %08x DP %08x\n", signal_levels, DP);
+ } else if ((intel_dp->gen == 7) && is_cpu_edp(intel_dp)) {
+ signal_levels = intel_gen7_edp_signal_levels(intel_dp->train_set[0]);
+ DP = (DP & ~EDP_LINK_TRAIN_VOL_EMP_MASK_IVB) | signal_levels;
+ } else if ((intel_dp->gen == 6) && is_cpu_edp(intel_dp)) {
+ signal_levels = intel_gen6_edp_signal_levels(intel_dp->train_set[0]);
+ DP = (DP & ~EDP_LINK_TRAIN_VOL_EMP_MASK_SNB) | signal_levels;
+ } else {
+ signal_levels = intel_dp_signal_levels(intel_dp->train_set[0]);
+ DP = (DP & ~(DP_VOLTAGE_MASK|DP_PRE_EMPHASIS_MASK)) | signal_levels;
+ }
+ printk(BIOS_ERR, "training pattern 1 signal levels %08x\n", signal_levels);
+
+ if (intel_dp->has_pch_cpt && ((intel_dp->gen == 7) || !is_cpu_edp(intel_dp)))
+ reg = DP | DP_LINK_TRAIN_PAT_1_CPT;
+ else
+ reg = DP | DP_LINK_TRAIN_PAT_1;
+
+ if (!intel_dp_set_link_train(intel_dp, reg,
+ DP_TRAINING_PATTERN_1 |
+ DP_LINK_SCRAMBLING_DISABLE))
+ break;
+ /* Set training pattern 1 */
+
+ udelay(100);
+ if (!intel_dp_get_link_status(intel_dp, link_status)) {
+ printk(BIOS_ERR, "failed to get link status\n");
+ break;
+ }
+
+ if (intel_clock_recovery_ok(link_status, intel_dp->lane_count)) {
+ printk(BIOS_ERR, "clock recovery OK\n");
+ clock_recovery = 1;
+ break;
+ }
+
+ /* 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) {
+ printk(BIOS_ERR, "too many full retries, give up\n");
+ break;
+ }
+ printk(BIOS_SPEW, "%s: reset train set\n", __func__);
+ memset(intel_dp->train_set, 0, 4);
+ 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) {
+ printk(BIOS_ERR, "too many voltage retries, give up\n");
+ break;
+ }
+ } else
+ voltage_tries = 0;
+ voltage = intel_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK;
+ printk(BIOS_SPEW, "%s: voltage now %s\n", __func__,
+ voltage_names[voltage]);
+
+ /* Compute new intel_dp->train_set as requested by target */
+ intel_get_adjust_train(intel_dp, link_status);
+ printk(BIOS_SPEW, "%s: new intel train set is "
+ "%02x%02x%02x%02x",
+ __func__,
+ intel_dp->train_set[0], intel_dp->train_set[1],
+ intel_dp->train_set[2], intel_dp->train_set[3]);
+ }
+
+ intel_dp->DP = DP;
+}
+
+void
+intel_dp_complete_link_train(struct intel_dp *intel_dp)
+{
+ int channel_eq = 0;
+ int tries, cr_tries;
+ uint32_t DP = intel_dp->DP;
+
+ /* channel equalization */
+ tries = 0;
+ cr_tries = 0;
+ channel_eq = 0;
+ for (;;) {
+ /* Use intel_dp->train_set[0] to set the voltage and pre emphasis values */
+ uint32_t signal_levels;
+ uint8_t link_status[DP_LINK_STATUS_SIZE];
+
+ if (cr_tries > 5) {
+ printk(BIOS_ERR, "failed to train DP, aborting\n");
+ intel_dp_link_down(intel_dp);
+ break;
+ }
+
+ if (intel_dp->is_haswell) {
+ signal_levels = intel_dp_signal_levels_hsw(intel_dp->train_set[0]);
+ DP = (DP & ~DDI_BUF_EMP_MASK) | signal_levels;
+ printk(BIOS_SPEW, "%s: new DP %08x\n", __func__,
+ DP);
+ } else if ((intel_dp->gen == 7) && is_cpu_edp(intel_dp)) {
+ signal_levels = intel_gen7_edp_signal_levels(intel_dp->train_set[0]);
+ DP = (DP & ~EDP_LINK_TRAIN_VOL_EMP_MASK_IVB) | signal_levels;
+ } else if ((intel_dp->gen == 6) && is_cpu_edp(intel_dp)) {
+ signal_levels = intel_gen6_edp_signal_levels(intel_dp->train_set[0]);
+ DP = (DP & ~EDP_LINK_TRAIN_VOL_EMP_MASK_SNB) | signal_levels;
+ } else {
+ signal_levels = intel_dp_signal_levels(intel_dp->train_set[0]);
+ DP = (DP & ~(DP_VOLTAGE_MASK|DP_PRE_EMPHASIS_MASK)) | signal_levels;
+ }
+
+ /* channel eq pattern */
+ if (!intel_dp_set_link_train(intel_dp, DP,
+ DP_TRAINING_PATTERN_2 |
+ DP_LINK_SCRAMBLING_DISABLE))
+ break;
+
+ udelay(40000); /* was 400 */
+ if (!intel_dp_get_link_status(intel_dp, link_status))
+ break;
+
+ /* Make sure clock is still ok */
+ if (!intel_clock_recovery_ok(link_status, intel_dp->lane_count)) {
+ printk(BIOS_SPEW, "%s: Clock recovery is NOT ok"
+ ": restarting from the start\n", __func__);
+ intel_dp_start_link_train(intel_dp);
+ cr_tries++;
+ continue;
+ }
+
+ if (intel_channel_eq_ok(intel_dp, link_status)) {
+ printk(BIOS_SPEW, "%s: success\n", __func__);
+ channel_eq = 1;
+ break;
+ }
+
+ /* Try 5 times, then try clock recovery if that fails */
+ if (tries > 5) {
+ printk(BIOS_SPEW, "%s: tries > 5,recovering. \n",
+ __func__);
+ intel_dp_link_down(intel_dp);
+ intel_dp_start_link_train(intel_dp);
+ tries = 0;
+ cr_tries++;
+ continue;
+ }
+
+ /* Compute new intel_dp->train_set as requested by target */
+ printk(BIOS_SPEW, "%s: adjust the train\n", __func__);
+ intel_get_adjust_train(intel_dp, link_status);
+ ++tries;
+ }
+
+ //io_i915_write32(reg, intel_dp->output_reg);
+ ////POSTING_READ(intel_dp->output_reg);
+ intel_dp_set_link_train(intel_dp, DP, DP_TRAINING_PATTERN_DISABLE);
+}
+
+void
+intel_dp_link_down(struct intel_dp *intel_dp)
+{
+ uint32_t DP = intel_dp->DP;
+
+ if ((io_i915_read32(intel_dp->output_reg) & DP_PORT_EN) == 0)
+ return;
+
+ if (intel_dp->is_haswell){
+ printk(BIOS_SPEW, "%s: It's a haswell, skip this\n", __func__);
+ return;
+ }
+ if (is_edp(intel_dp)) {
+ DP &= ~DP_PLL_ENABLE;
+ io_i915_write32(DP, intel_dp->output_reg);
+ ////POSTING_READ(intel_dp->output_reg);
+ udelay(100);
+ }
+
+ if (intel_dp->has_pch_cpt && ((intel_dp->gen == 7) || !is_cpu_edp(intel_dp))) {
+ DP &= ~DP_LINK_TRAIN_MASK_CPT;
+ io_i915_write32(DP | DP_LINK_TRAIN_PAT_IDLE_CPT, intel_dp->output_reg);
+ } else {
+ DP &= ~DP_LINK_TRAIN_MASK;
+ io_i915_write32(DP | DP_LINK_TRAIN_PAT_IDLE, intel_dp->output_reg);
+ }
+ ////POSTING_READ(intel_dp->output_reg);
+
+ mdelay(17);
+
+ if (is_edp(intel_dp)) {
+ if (intel_dp->has_pch_cpt && ((intel_dp->gen == 7) || !is_cpu_edp(intel_dp)))
+ DP |= DP_LINK_TRAIN_OFF_CPT;
+ else
+ DP |= DP_LINK_TRAIN_OFF;
+ }
+
+ DP &= ~DP_AUDIO_OUTPUT_ENABLE;
+ io_i915_write32(DP & ~DP_PORT_EN, intel_dp->output_reg);
+ ////POSTING_READ(intel_dp->output_reg);
+ mdelay(600000); //intel_dp->panel_power_down_delay);
+}
+
+int
+intel_dp_get_dpcd(struct intel_dp *intel_dp)
+{
+ int got = 0, want = sizeof(intel_dp->dpcd), rev;
+ got = intel_dp_aux_native_read_retry(intel_dp, 0x000, intel_dp->dpcd,
+ want);
+ if (got < want) {
+ printk(BIOS_SPEW, "%s: got %d, wanted %d\n", __func__, got, want);
+ return 0;
+ }
+
+ rev = intel_dp->dpcd[DP_DPCD_REV];
+ if (!rev){
+ printk(BIOS_SPEW, "%s: intel->dp[DP_DPCD_REV] is 0\n",
+ __func__);
+ return 0;
+ }
+
+ printk(BIOS_SPEW, "DPCD: %02hx%02hx%02hx%02hx%02hx%02hx%02hx%02hx\n",
+ intel_dp->dpcd[0], intel_dp->dpcd[1], intel_dp->dpcd[2],
+ intel_dp->dpcd[3], intel_dp->dpcd[4], intel_dp->dpcd[5],
+ intel_dp->dpcd[6], intel_dp->dpcd[7]);
+ return 1;
+}
+
+/* I have no idea how big max downspread is. 1 byte? Hard as hell to find. */
+int
+intel_dp_get_max_downspread(struct intel_dp *intel_dp, u8 *max_downspread)
+{
+ int got, want = 1;
+ got = intel_dp_aux_native_read_retry(intel_dp, 0x000, max_downspread,
+ want);
+ if (got < want) {
+ printk(BIOS_SPEW, "%s: got %d, wanted %d\n", __func__, got, want);
+ return 0;
+ }
+
+ printk(BIOS_SPEW, "%s: max_downspread is %02x\n", __func__, *max_downspread);
+ return 1;
+}
+