diff --git a/Makefile.am b/Makefile.am
index 4f71a3a..5ce9d58 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,6 +31,10 @@ if BUILD_SHADER_DEBUGGER
 SUBDIRS += debugger
 endif
 
+if HAVE_KERNEL_SRC_DIR
+SUBDIRS += unit-test-helper
+endif
+
 if BUILD_TESTS
 SUBDIRS += tests
 endif
diff --git a/configure.ac b/configure.ac
index 19f6fa4..53e1f6d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -199,6 +199,17 @@ if test "x$with_libunwind" = xyes; then
 			  AC_MSG_ERROR([libunwind not found. Use --without-libunwind to disable libunwind support.]))
 fi
 
+AC_ARG_WITH(kernel-src-dir,
+	    AS_HELP_STRING([--with-kernel-src-dir],
+			   [Kernel src directory, used by the link training test]),
+	    [KERNEL_SRC_DIR="$withval"], [KERNEL_SRC_DIR=""])
+if test -n "$KERNEL_SRC_DIR"; then
+	I915_SRC_DIR="${KERNEL_SRC_DIR}/drivers/gpu/drm/i915"
+	AC_SUBST([KERNEL_SRC_DIR])
+	AC_SUBST([I915_SRC_DIR])
+fi
+AM_CONDITIONAL([HAVE_KERNEL_SRC_DIR], test -n "$KERNEL_SRC_DIR")
+
 # enable debug symbols
 AC_ARG_ENABLE(debug,
 	      AS_HELP_STRING([--disable-debug],
@@ -264,10 +275,16 @@ AC_CONFIG_FILES([
 		 assembler/test/Makefile
 		 assembler/intel-gen4asm.pc
 		 overlay/Makefile
+		 unit-test-helper/Makefile
+		 unit-test-helper/i915/Makefile
 		 ])
 
 AC_CONFIG_FILES([tools/intel_aubdump], [chmod +x tools/intel_aubdump])
 
+if test -n "${KERNEL_SRC_DIR}"; then
+AC_CONFIG_LINKS([unit-test-helper/kernel-src:"${KERNEL_SRC_DIR}"])
+fi
+
 AC_OUTPUT
 
 # Print a summary of the compilation
@@ -287,6 +304,9 @@ echo "       Debugger           : ${enable_debugger}"
 echo "       Python dumper      : ${DUMPER}"
 echo "       Overlay            : X: ${enable_overlay_xlib}, Xv: ${enable_overlay_xvlib}"
 echo ""
+echo " • Variables:"
+echo "       Kernel src dir     : ${KERNEL_SRC_DIR}"
+echo ""
 echo " • API-Documentation      : ${enable_gtk_doc}"
 echo ""
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dcac2f3..9602a67 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -4,6 +4,10 @@ if HAVE_NOUVEAU
     TESTS_progs_M += $(NOUVEAU_TESTS_M)
 endif
 
+if HAVE_KERNEL_SRC_DIR
+    TESTS_progs_M += $(UNIT_TESTS_M)
+endif
+
 if BUILD_TESTS
 test-list.txt: Makefile.sources
 	@echo TESTLIST > $@
@@ -53,6 +57,11 @@ LDADD = ../lib/libintel_tools.la $(PCIACCESS_LIBS) $(DRM_LIBS) $(LIBUNWIND_LIBS)
 LDADD += $(CAIRO_LIBS) $(LIBUDEV_LIBS) $(GLIB_LIBS) -lm
 AM_CFLAGS += $(CAIRO_CFLAGS) $(LIBUDEV_CFLAGS) $(GLIB_CFLAGS)
 
+UNIT_CFLAGS = \
+	-I$(top_srcdir)/unit-test-helper/include \
+	-iquote $(I915_SRC_DIR) \
+	-iquote $(top_builddir)
+
 drm_import_export_CFLAGS = $(AM_CFLAGS) $(THREAD_CFLAGS)
 drm_import_export_LDADD = $(LDADD) -lpthread
 gem_close_race_CFLAGS = $(AM_CFLAGS) $(THREAD_CFLAGS)
@@ -91,5 +100,10 @@ prime_nv_api_CFLAGS = $(AM_CFLAGS) $(DRM_NOUVEAU_CFLAGS)
 prime_nv_api_LDADD = $(LDADD) $(DRM_NOUVEAU_LIBS)
 prime_nv_pcopy_CFLAGS = $(AM_CFLAGS) $(DRM_NOUVEAU_CFLAGS)
 prime_nv_pcopy_LDADD = $(LDADD) $(DRM_NOUVEAU_LIBS)
+
+if HAVE_KERNEL_SRC_DIR
+unit_dp_link_training_CFLAGS = $(UNIT_CFLAGS) $(AM_CFLAGS)
+endif
+
 endif
 
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index ef69299..b33a4a9 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -185,6 +185,16 @@ TESTS_scripts = \
 	tools_test \
 	$(NULL)
 
+UNIT_TESTS_M = \
+	unit_dp_link_training
+
+if HAVE_KERNEL_SRC_DIR
+unit_dp_link_training_SOURCES = \
+	unit_dp_link_training.c \
+	$(top_builddir)/unit-test-helper/i915/intel_dp_link_training.c
+endif
+
+#
 # This target contains testcases which support automagic subtest enumeration
 # from the piglit testrunner with --list-subtests and running individual
 # subtests with --run-subtest <testname>
@@ -194,6 +204,7 @@ TESTS_scripts = \
 multi_kernel_tests = \
 	$(TESTS_progs_M) \
 	$(TESTS_scripts_M) \
+	$(UNIT_TESTS_progs_M) \
 	$(NULL)
 
 # This target is for simple testcase which don't expose any subtest.
diff --git a/tests/unit_dp_link_training.c b/tests/unit_dp_link_training.c
new file mode 100644
index 0000000..683b2bf
--- /dev/null
+++ b/tests/unit_dp_link_training.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright © 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.
+ *
+ * Authors:
+ *    Ander Conselvan de Oliveira <ander.conselvan.de.oliveira@intel.com>
+ *
+ */
+
+#include "igt_core.h"
+
+#include "unit-test-helper/kernel-src/drivers/gpu/drm/i915/intel_dp.h"
+
+
+/**
+ * ARRAY_SIZE:
+ * @arr: static array
+ *
+ * Macro to compute the size of the static array @arr.
+ */
+#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
+
+
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#define container_of(ptr, type, member) ({			\
+	const typeof(((type *)0)->member) * __mptr = (ptr);	\
+	(type *)((char *)__mptr - offsetof(type, member)); })
+
+
+struct sink_device {
+	ssize_t (*dpcd_write)(struct sink_device *sink, unsigned int offset,
+			      void *buffer, size_t size);
+	bool (*get_link_status)(struct sink_device *sink,
+				uint8_t link_status[DP_LINK_STATUS_SIZE]);
+
+	struct {
+		bool lane_count_and_bw_set;
+		bool training_pattern_1_set;
+		bool started_with_non_zero_levels;
+		bool cr_done;
+		bool channel_eq_done;
+
+		uint8_t dpcd[0x3000];
+	} data;
+};
+
+/* Fake sink device implementation */
+
+static uint8_t
+sink_device_lane_count(struct sink_device *sink)
+{
+	return sink->data.dpcd[DP_LANE_COUNT_SET];
+}
+
+static uint8_t
+sink_device_get_training_pattern(struct sink_device *sink)
+{
+	return sink->data.dpcd[DP_TRAINING_PATTERN_SET] & DP_TRAINING_PATTERN_MASK;
+}
+
+static uint8_t
+sink_device_get_voltage_swing(struct sink_device *sink, int lane)
+{
+	return sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] &
+		DP_TRAIN_VOLTAGE_SWING_MASK;
+}
+
+static uint8_t
+sink_device_get_pre_emphasis_level(struct sink_device *sink, int lane)
+{
+	return (sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] &
+		 DP_TRAIN_PRE_EMPHASIS_MASK) >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
+}
+
+static void
+sink_device_check_lane_count_and_bw(struct sink_device *sink)
+{
+	if (sink->data.lane_count_and_bw_set)
+		return;
+
+	igt_assert(sink->data.dpcd[DP_TRAINING_PATTERN_SET] == 0);
+
+	if (sink->data.dpcd[DP_LINK_BW_SET] != 0 &&
+	    sink->data.dpcd[DP_LANE_COUNT_SET] != 0)
+		sink->data.lane_count_and_bw_set = true;
+}
+
+static void
+sink_device_check_pattern_1_set(struct sink_device *sink)
+{
+	if (!sink->data.lane_count_and_bw_set ||
+	    sink->data.training_pattern_1_set)
+		return;
+
+	igt_assert(sink_device_get_training_pattern(sink) <= DP_TRAINING_PATTERN_1);
+
+	if (sink_device_get_training_pattern(sink) != DP_TRAINING_PATTERN_1)
+		return;
+
+	igt_assert(sink->data.dpcd[DP_LINK_BW_SET] == DP_LINK_BW_1_62 ||
+	       sink->data.dpcd[DP_LINK_BW_SET] == DP_LINK_BW_2_7);
+
+	igt_assert(sink->data.dpcd[DP_LANE_COUNT_SET] == 1 ||
+		   sink->data.dpcd[DP_LANE_COUNT_SET] == 2 ||
+		   sink->data.dpcd[DP_LANE_COUNT_SET] == 4);
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		if (sink_device_get_voltage_swing(sink, lane) != DP_TRAIN_VOLTAGE_SWING_LEVEL_0 ||
+		    sink_device_get_pre_emphasis_level(sink, lane) != DP_TRAIN_PRE_EMPH_LEVEL_0)
+			sink->data.started_with_non_zero_levels = true;
+	}
+
+	sink->data.training_pattern_1_set = true;
+}
+
+static void
+sink_device_check_pattern_2_set(struct sink_device *sink)
+{
+	if (!sink->data.cr_done)
+		return;
+
+	igt_assert(sink_device_get_training_pattern(sink) == DP_TRAINING_PATTERN_2);
+}
+
+static void
+sink_device_check_pattern_disable(struct sink_device *sink)
+{
+	if (!sink->data.cr_done || ! sink->data.channel_eq_done)
+		return;
+
+	igt_assert(sink_device_get_training_pattern(sink) == DP_TRAINING_PATTERN_DISABLE);
+}
+
+static ssize_t
+sink_device_dpcd_write(struct sink_device *sink, unsigned int offset,
+		       void *buffer, size_t size)
+{
+	memcpy(sink->data.dpcd + offset, buffer, size);
+
+	sink_device_check_lane_count_and_bw(sink);
+
+	if (!sink->data.cr_done)
+		sink_device_check_pattern_1_set(sink);
+	else if (!sink->data.channel_eq_done)
+		sink_device_check_pattern_2_set(sink);
+	else
+		sink_device_check_pattern_disable(sink);
+
+	return size;
+}
+
+static bool
+sink_device_max_voltage_reached(struct sink_device *sink, int lane)
+{
+	return (sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] & DP_TRAIN_MAX_SWING_REACHED) ==
+		DP_TRAIN_MAX_SWING_REACHED;
+}
+
+static bool
+sink_device_max_pre_emphasis_reached(struct sink_device *sink, int lane)
+{
+	return (sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] & DP_TRAIN_MAX_PRE_EMPHASIS_REACHED) ==
+		DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+}
+
+static void
+sink_device_set_adjust_voltage(struct sink_device *sink,
+			       int lane, uint8_t level)
+{
+	int shift = DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1);
+
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] &=
+		~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << shift);
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
+		level << shift;
+}
+
+static void
+sink_device_set_adjust_pre_emphasis(struct sink_device *sink,
+				    int lane, uint8_t level)
+{
+	int shift = DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1);
+
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] &=
+		~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << shift);
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
+		level << (DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT + shift);
+}
+
+static bool
+sink_device_request_higher_voltage_swing(struct sink_device *sink)
+{
+	bool max_reached = false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		if (sink_device_max_voltage_reached(sink, lane)) {
+			max_reached = true;
+			break;
+		}
+	}
+
+	if (max_reached)
+		return false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		uint8_t new_voltage =
+			sink_device_get_voltage_swing(sink, lane) + 1;
+
+		sink_device_set_adjust_voltage(sink, lane, new_voltage);
+	}
+
+	return true;
+}
+
+static bool
+sink_device_request_higher_pre_emphasis(struct sink_device *sink)
+{
+	bool max_reached = false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		if (sink_device_max_pre_emphasis_reached(sink, lane)) {
+			max_reached = true;
+			break;
+		}
+	}
+
+	if (max_reached)
+		return false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		uint8_t new_pre_emphasis =
+			sink_device_get_pre_emphasis_level(sink, lane) + 1;
+
+		sink_device_set_adjust_pre_emphasis(sink, lane, new_pre_emphasis);
+	}
+
+	return true;
+}
+
+static void
+sink_device_mark_cr_done(struct sink_device *sink)
+{
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++)
+		sink->data.dpcd[DP_LANE0_1_STATUS + (lane >> 1)] |=
+			DP_LANE_CR_DONE << (4 * (lane & 1));
+
+	sink->data.cr_done = true;
+}
+
+static void
+sink_device_mark_channel_eq_done(struct sink_device *sink)
+{
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		uint8_t mask = (DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED);
+		sink->data.dpcd[DP_LANE0_1_STATUS + (lane >> 1)] |=
+			mask << (4 * (lane & 1));
+	}
+
+	sink->data.dpcd[DP_LANE_ALIGN_STATUS_UPDATED] |= DP_INTERLANE_ALIGN_DONE;
+
+	sink->data.channel_eq_done = true;
+}
+
+static bool
+sink_device_get_link_status(struct sink_device *sink,
+			    uint8_t link_status[DP_LINK_STATUS_SIZE])
+{
+	if (!sink->data.cr_done) {
+		if (!sink_device_request_higher_voltage_swing(sink))
+			sink_device_mark_cr_done(sink);
+	} else if (!sink->data.channel_eq_done) {
+		if (!sink_device_request_higher_pre_emphasis(sink))
+			sink_device_mark_channel_eq_done(sink);
+	}
+
+	memcpy(link_status, sink->data.dpcd + DP_LANE0_1_STATUS,
+	       DP_LINK_STATUS_SIZE);
+
+	return true;
+}
+
+static void
+sink_device_reset(struct sink_device *sink, int lanes, uint8_t link_bw)
+{
+	memset(&sink->data, 0, sizeof sink->data);
+	sink->data.dpcd[DP_MAX_LINK_RATE] = link_bw;
+	sink->data.dpcd[DP_MAX_LANE_COUNT] = lanes;
+}
+
+static struct sink_device simple_sink = {
+	.get_link_status = sink_device_get_link_status,
+	.dpcd_write = sink_device_dpcd_write,
+};
+
+/* Glue code */
+
+struct test_intel_dp {
+	struct intel_dp dp;
+	struct sink_device *sink;
+	uint8_t link_bw;
+
+	uint8_t max_voltage;
+	uint8_t max_pre_emphasis;
+};
+
+static struct test_intel_dp *
+to_test_intel_dp(struct intel_dp *dp)
+{
+	return container_of(dp, struct test_intel_dp, dp);
+}
+
+void intel_dp_set_idle_link_train(struct intel_dp *intel_dp)
+{
+}
+
+bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp)
+{
+	return false;
+}
+
+bool
+intel_dp_get_link_status(struct intel_dp *intel_dp, uint8_t link_status[DP_LINK_STATUS_SIZE])
+{
+	struct sink_device *sink = to_test_intel_dp(intel_dp)->sink;
+	return sink->get_link_status(sink, link_status);
+}
+
+void
+intel_dp_update_signal_levels(struct intel_dp *intel_dp)
+{
+}
+
+void intel_dp_compute_rate(struct intel_dp *intel_dp, int port_clock,
+			   uint8_t *link_bw, uint8_t *rate_select)
+{
+	*link_bw = to_test_intel_dp(intel_dp)->link_bw;
+	*rate_select = 0;
+}
+
+void
+intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
+				       uint8_t dp_train_pat)
+{
+}
+
+uint8_t
+intel_dp_voltage_max(struct intel_dp *intel_dp)
+{
+	return to_test_intel_dp(intel_dp)->max_voltage <<
+		DP_TRAIN_VOLTAGE_SWING_SHIFT;
+}
+
+uint8_t
+intel_dp_pre_emphasis_max(struct intel_dp *intel_dp, uint8_t voltage_swing)
+{
+	return to_test_intel_dp(intel_dp)->max_pre_emphasis <<
+		DP_TRAIN_PRE_EMPHASIS_SHIFT;
+}
+
+ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset,
+			  void *buffer, size_t size)
+{
+	struct intel_dp *intel_dp =
+		container_of(aux, struct intel_dp, aux);
+	struct sink_device *sink = to_test_intel_dp(intel_dp)->sink;
+
+	return sink->dpcd_write(sink, offset, buffer, size);
+}
+
+/* --- */
+
+static struct test_intel_dp test_dp;
+
+static void
+do_test(struct sink_device *sink, int lanes, uint8_t link_bw,
+	     uint8_t max_voltage, uint8_t max_pre_emphasis)
+{
+	memset(&test_dp, 0, sizeof test_dp);
+	test_dp.dp.lane_count = lanes;
+	test_dp.link_bw = link_bw;
+	test_dp.sink = sink;
+	test_dp.max_voltage =
+		max_voltage >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
+	test_dp.max_pre_emphasis =
+		max_pre_emphasis >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
+
+	sink_device_reset(sink, lanes, link_bw);
+
+	intel_dp_start_link_train(&test_dp.dp);
+	intel_dp_stop_link_train(&test_dp.dp);
+
+	igt_assert(sink->data.cr_done);
+	igt_assert(sink->data.channel_eq_done);
+
+	for (int lane = 0; lane < test_dp.dp.lane_count; lane++) {
+		uint8_t cur_v = sink_device_get_voltage_swing(sink, lane);
+		uint8_t cur_p = sink_device_get_pre_emphasis_level(sink, lane);
+
+		igt_assert_eq(cur_v, test_dp.max_voltage);
+		igt_assert_eq(cur_p, test_dp.max_pre_emphasis);
+	}
+}
+
+int test_lanes[] = {
+	1, 2, 4,
+};
+
+uint8_t test_bw[] = {
+	DP_LINK_BW_1_62,
+	DP_LINK_BW_2_7,
+};
+
+uint8_t test_max_voltage[] = {
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_0,
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_1,
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_2,
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_3,
+};
+
+uint8_t test_max_pre_emphasis[] = {
+	DP_TRAIN_PRE_EMPH_LEVEL_0,
+	DP_TRAIN_PRE_EMPH_LEVEL_1,
+	DP_TRAIN_PRE_EMPH_LEVEL_2,
+	DP_TRAIN_PRE_EMPH_LEVEL_3,
+};
+
+igt_main
+{
+	for (int lane = 0; lane < ARRAY_SIZE(test_lanes); lane++)
+	for (int bw = 0; bw < ARRAY_SIZE(test_bw); bw++)
+	for (int voltage = 0; voltage < ARRAY_SIZE(test_max_voltage); voltage++)
+	for (int emph = 0; emph < ARRAY_SIZE(test_max_pre_emphasis); emph++)
+		igt_subtest_f("l%d-bw%d-v%d-pe%d",
+			      test_lanes[lane],
+			      test_bw[bw],
+			      test_max_voltage[voltage],
+			      test_max_pre_emphasis[emph])
+			do_test(&simple_sink,
+				test_lanes[lane],
+				test_bw[bw],
+				test_max_voltage[voltage],
+				test_max_pre_emphasis[emph]);
+}
+
+/* TODO: Get rid of this copy of drm_dp_helper functions. */
+
+/*
+ * 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 <drm/drm_dp_helper.h>
+
+static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r)
+{
+        return link_status[r - DP_LANE0_1_STATUS];
+}
+
+static u8 dp_get_lane_status(const 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;
+}
+
+bool drm_dp_channel_eq_ok(const 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 false; 
+        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 false;
+        }
+        return true;
+}
+
+bool drm_dp_clock_recovery_ok(const 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 false;
+        }
+        return true;
+}
+
+u8 drm_dp_get_adjust_request_voltage(const 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);
+
+        return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT;
+}
+
+u8 drm_dp_get_adjust_request_pre_emphasis(const 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;
+}
+
+/* FIXME: */
+static void udelay(unsigned long usecs) {}
+static void mdelay(unsigned long msecs) {}
+
+void drm_dp_link_train_clock_recovery_delay(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) {
+        if (dpcd[DP_TRAINING_AUX_RD_INTERVAL] == 0)
+                udelay(100);
+        else
+                mdelay(dpcd[DP_TRAINING_AUX_RD_INTERVAL] * 4);
+}
+
+void drm_dp_link_train_channel_eq_delay(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) {
+        if (dpcd[DP_TRAINING_AUX_RD_INTERVAL] == 0)
+                udelay(400);
+        else
+                mdelay(dpcd[DP_TRAINING_AUX_RD_INTERVAL] * 4);
+}
+
diff --git a/unit-test-helper/Makefile.am b/unit-test-helper/Makefile.am
new file mode 100644
index 0000000..cf54f39
--- /dev/null
+++ b/unit-test-helper/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = i915
diff --git a/unit-test-helper/i915/Makefile.am b/unit-test-helper/i915/Makefile.am
new file mode 100644
index 0000000..cc14269
--- /dev/null
+++ b/unit-test-helper/i915/Makefile.am
@@ -0,0 +1,9 @@
+if HAVE_KERNEL_SRC_DIR
+
+BUILT_SOURCES = \
+	intel_dp_link_training.c
+
+%.c: $(I915_SRC_DIR)/%.c
+	cp $< $@
+
+endif
diff --git a/unit-test-helper/include/build_magic.h b/unit-test-helper/include/build_magic.h
new file mode 100644
index 0000000..a4a3836
--- /dev/null
+++ b/unit-test-helper/include/build_magic.h
@@ -0,0 +1,27 @@
+#ifndef _IGT_BUILD_MAGIC_H
+#define _IGT_BUILD_MAGIC_H
+
+#ifndef __KERNEL__
+#define __KERNEL__
+#endif
+
+#include "unit-test-helper/kernel-src/tools/include/linux/compiler.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef unsigned long size_t;
+typedef long ssize_t;
+
+struct delayed_work {
+};
+
+struct notifier_block {
+};
+
+#endif /* _IGT_BUILD_MAGIC_H */
diff --git a/unit-test-helper/include/drm/drmP.h b/unit-test-helper/include/drm/drmP.h
new file mode 100644
index 0000000..fe48518
--- /dev/null
+++ b/unit-test-helper/include/drm/drmP.h
@@ -0,0 +1,24 @@
+#ifndef _IGT_DRM_P_H_
+#define _IGT_DRM_P_H_
+
+#include "igt_core.h"
+
+struct drm_device {
+};
+
+struct drm_connector {
+};
+
+struct drm_encoder {
+};
+
+struct drm_crtc {
+};
+
+struct drm_plane {
+};
+
+#define DRM_ERROR(x) igt_warn(x)
+#define DRM_DEBUG_KMS(x) igt_debug(x)
+
+#endif /* _IGT_DRM_P_H_ */
diff --git a/unit-test-helper/include/drm/drm_dp_helper.h b/unit-test-helper/include/drm/drm_dp_helper.h
new file mode 100644
index 0000000..54f297e
--- /dev/null
+++ b/unit-test-helper/include/drm/drm_dp_helper.h
@@ -0,0 +1,14 @@
+#ifndef _IGT_DRM_DP_HELPER_H_
+#define _IGT_DRM_DP_HELPER_H_
+
+#include <build_magic.h>
+
+struct i2c_adapter {
+};
+
+struct mutex {
+};
+
+#include "unit-test-helper/kernel-src/include/drm/drm_dp_helper.h"
+
+#endif /* _IGT_DRM_DP_HELPER_H_ */
diff --git a/unit-test-helper/include/drm/drm_dp_mst_helper.h b/unit-test-helper/include/drm/drm_dp_mst_helper.h
new file mode 100644
index 0000000..ac9d362
--- /dev/null
+++ b/unit-test-helper/include/drm/drm_dp_mst_helper.h
@@ -0,0 +1,7 @@
+#ifndef _IGT_DRM_DP_MST_HELPER_H_
+#define _IGT_DRM_DP_MST_HELPER_H_
+
+struct drm_dp_mst_topology_mgr {
+};
+
+#endif /* _IGT_DRM_DP_MST_HELPER_H_ */
