diff mbox

[i-g-t] Add a link training test

Message ID 1441715280-11623-10-git-send-email-ander.conselvan.de.oliveira@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Ander Conselvan de Oliveira Sept. 8, 2015, 12:28 p.m. UTC
This adds a test that compiles the link training code from i915 into a
separate executable and uses it to train a fake sink device. The test
also uses device information from i915 to exercise the different code
paths for different hardwdare generations.

In order to get the code to compile a lot of stubbing was necessary. It
was also easier to copy a few functions from the drm_dp_helpers instead
of getting the whole thing to compile as part of the test.
---
 link-training-test/Makefile             |  40 +++
 link-training-test/drm_dp_helper.c      | 115 +++++++++
 link-training-test/intel_drv.h          | 148 ++++++++++++
 link-training-test/link_training_test.c | 414 ++++++++++++++++++++++++++++++++
 4 files changed, 717 insertions(+)
 create mode 100644 link-training-test/Makefile
 create mode 100644 link-training-test/drm_dp_helper.c
 create mode 100644 link-training-test/intel_drv.h
 create mode 100644 link-training-test/link_training_test.c

Comments

Ville Syrjala Sept. 8, 2015, 1:11 p.m. UTC | #1
On Tue, Sep 08, 2015 at 03:28:00PM +0300, Ander Conselvan de Oliveira wrote:
> This adds a test that compiles the link training code from i915 into a
> separate executable and uses it to train a fake sink device. The test
> also uses device information from i915 to exercise the different code
> paths for different hardwdare generations.
> 
> In order to get the code to compile a lot of stubbing was necessary. It
> was also easier to copy a few functions from the drm_dp_helpers instead
> of getting the whole thing to compile as part of the test.

Hmm. So the only device spefic information you really need is:
- supported link rates
- max vswing/pre-emph settings
- DDI vs. not

Cooking up a few configurations with varying options would seem fairly
easy without having to pull in the whole device info, and the hw
specific max vswing/pre-emphasis stuff.

> ---
>  link-training-test/Makefile             |  40 +++
>  link-training-test/drm_dp_helper.c      | 115 +++++++++
>  link-training-test/intel_drv.h          | 148 ++++++++++++
>  link-training-test/link_training_test.c | 414 ++++++++++++++++++++++++++++++++
>  4 files changed, 717 insertions(+)
>  create mode 100644 link-training-test/Makefile
>  create mode 100644 link-training-test/drm_dp_helper.c
>  create mode 100644 link-training-test/intel_drv.h
>  create mode 100644 link-training-test/link_training_test.c
> 
> diff --git a/link-training-test/Makefile b/link-training-test/Makefile
> new file mode 100644
> index 0000000..07a9914
> --- /dev/null
> +++ b/link-training-test/Makefile
> @@ -0,0 +1,40 @@
> +KERNEL_SRC_DIR=/home/aconselv/linux
> +
> +# Files copied from i915 source tree
> +COPIED_SOURCES = \
> +	intel_dp_link_training.c \
> +	intel_dev_info.c \
> +	intel_dev_info.h \
> +	i915_reg.h
> +
> +INTEL_DP_LINK_TRAINING_C = $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/intel_dp_link_training.c
> +
> +INCLUDEDIR = \
> +	-I$(KERNEL_SRC_DIR)/include/drm \
> +	-I$(KERNEL_SRC_DIR)/ \
> +	-I.
> +
> +DEFINES = \
> +	-D__KERNEL__
> +
> +HEADER_FILES = \
> +	intel_drv.h
> +
> +SOURCE_FILES = \
> +	intel_dp_link_training.c \
> +	link_training_test.c \
> +	drm_dp_helper.c
> +
> +all: link_training_test
> +
> +#intel_dp_link_training.c: $(INTEL_DP_LINK_TRAINING_C)
> +#	cp $(INTEL_DP_LINK_TRAINING_C) .
> +
> +$(COPIED_SOURCES): %: $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/%
> +	cp $< $@
> +
> +link_training_test: $(COPIED_SOURCES) $(SOURCE_FILES) $(HEADER_FILES)
> +	gcc -g3 -O0 -std=gnu99 -o link_training_test $(SOURCE_FILES) $(INCLUDEDIR) $(DEFINES)
> +
> +clean:
> +	rm link_training_test $(COPIED_SOURCES)
> diff --git a/link-training-test/drm_dp_helper.c b/link-training-test/drm_dp_helper.c
> new file mode 100644
> index 0000000..a8db7f9
> --- /dev/null
> +++ b/link-training-test/drm_dp_helper.c
> @@ -0,0 +1,115 @@
> +/*
> + * 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 "intel_drv.h"
> +
> +/* TODO: Get rid of this copy of drm_dp_helper functions. */
> +
> +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() {}
> +static void mdelay() {}
> +
> +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/link-training-test/intel_drv.h b/link-training-test/intel_drv.h
> new file mode 100644
> index 0000000..f7a6a6c
> --- /dev/null
> +++ b/link-training-test/intel_drv.h
> @@ -0,0 +1,148 @@
> +/*
> + * Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
> + * Copyright (c) 2007-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.
> + */
> +
> +#ifndef FAKE_INTEL_DRV_H
> +#define FAKE_INTEL_DRV_H
> +
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include <tools/include/linux/compiler.h>
> +
> +typedef unsigned char u8;
> +typedef unsigned short u16;
> +typedef unsigned int u32;
> +typedef unsigned long size_t;
> +typedef long ssize_t;
> +
> +struct drm_device {
> +	void *dev_private;
> +};
> +
> +static inline struct drm_i915_private *to_i915(const struct drm_device *dev)
> +{
> +	return dev->dev_private;
> +}
> +
> +#define BUILD_BUG()
> +
> +#include "intel_dev_info.h"
> +
> +struct drm_i915_private {
> +	struct intel_device_info info;
> +	bool edp_low_vswing;
> +	enum intel_pch pch_type;
> +};
> +
> +
> +struct i2c_adapter {
> +};
> +
> +struct mutex {
> +};
> +
> +#include <drm_dp_helper.h>
> +
> +#define DRM_ERROR printf
> +#define DRM_DEBUG_KMS printf
> +
> +enum port {
> +        PORT_A = 0,
> +        PORT_B,
> +        PORT_C,
> +        PORT_D,
> +        PORT_E,
> +        I915_MAX_PORTS
> +};
> +#define port_name(p) ((p) + 'A')
> +
> +struct drm_encoder {
> +	void *dev;
> +};
> +
> +struct intel_encoder {
> +	struct drm_encoder base;
> +};
> +
> +struct intel_dp {
> +	int link_rate;
> +	int lane_count;
> +	uint8_t link_bw;
> +	uint8_t num_sink_rates;
> +	uint8_t train_set[4];
> +	bool train_set_valid;
> +	struct drm_dp_aux aux;
> +	uint8_t dpcd[DP_RECEIVER_CAP_SIZE];
> +	uint32_t DP;
> +	bool use_tps3;
> +
> +	/* Hold test private data */
> +	void *priv;
> +};
> +
> +struct intel_digital_port {
> +	struct intel_encoder base;
> +	struct intel_dp dp;
> +	enum port port;
> +};
> +
> +#define offsetof(type, member)  __builtin_offsetof (type, member)
> +#define container_of(ptr, type, member) ({			\
> +	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
> +	(type *)( (char *)__mptr - offsetof(type,member) );})
> +
> +static inline struct intel_digital_port *
> +dp_to_dig_port(struct intel_dp *intel_dp)
> +{
> +	return container_of(intel_dp, struct intel_digital_port, dp);
> +}
> +
> +void
> +intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
> +                                      uint8_t dp_train_pat);
> +void
> +intel_dp_update_signal_levels(struct intel_dp *intel_dp);
> +void intel_dp_set_idle_link_train(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);
> +bool
> +intel_dp_get_link_status(struct intel_dp *intel_dp, uint8_t link_status[DP_LINK_STATUS_SIZE]);
> +void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder);
> +void
> +intel_dp_start_link_train(struct intel_dp *intel_dp);
> +void
> +intel_dp_stop_link_train(struct intel_dp *intel_dp);
> +bool intel_dp_source_supports_hbr2(struct drm_device *dev);
> +
> +static inline struct drm_device *intel_dp_to_dev(struct intel_dp *intel_dp)
> +{
> +	struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp);
> +
> +	return intel_dig_port->base.base.dev;
> +}
> +
> +#include "i915_reg.h"
> +
> +#endif /* FAKE_INTEL_DRV_H */
> diff --git a/link-training-test/link_training_test.c b/link-training-test/link_training_test.c
> new file mode 100644
> index 0000000..aa73b9e
> --- /dev/null
> +++ b/link-training-test/link_training_test.c
> @@ -0,0 +1,414 @@
> +/*
> + * 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 <stdlib.h>
> +#include <assert.h>
> +
> +#include "intel_drv.h"
> +#include "i915_reg.h"
> +
> +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;
> +}
> +
> +static void
> +sink_device_check_lane_count_and_bw(struct sink_device *sink)
> +{
> +	if (sink->data.lane_count_and_bw_set)
> +		return;
> +
> +	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;
> +
> +	assert(sink_device_get_training_pattern(sink) <= DP_TRAINING_PATTERN_1);
> +
> +	if (sink_device_get_training_pattern(sink) != DP_TRAINING_PATTERN_1)
> +		return;
> +
> +	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);
> +
> +	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;
> +
> +	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;
> +
> +	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 bool
> +sink_device_request_higher_voltage_swing(struct sink_device *sink)
> +{
> +	bool max_reached;
> +
> +	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++) {
> +		sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
> +			(sink_device_get_voltage_swing(sink, lane) + 1) <<
> +				(DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1));
> +	}
> +
> +	return true;
> +}
> +
> +static bool
> +sink_device_request_higher_pre_emphasis(struct sink_device *sink)
> +{
> +	bool max_reached;
> +
> +	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++) {
> +		sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
> +			(sink_device_get_pre_emphasis_level(sink, lane) + 1) <<
> +				(DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT +
> +				 (DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1)));
> +	}
> +
> +	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 struct sink_device simple_sink = {
> +	.get_link_status = sink_device_get_link_status,
> +	.dpcd_write = sink_device_dpcd_write,
> +};
> +
> +/* Glue code */
> +
> +void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder)
> +{
> +}
> +
> +void intel_dp_set_idle_link_train(struct intel_dp *intel_dp)
> +{
> +}
> +
> +bool intel_dp_source_supports_hbr2(struct drm_device *dev)
> +{
> +	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 = intel_dp->priv;
> +	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 = intel_dp->link_bw;
> +	*rate_select = 0;
> +}
> +
> +void
> +intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
> +				       uint8_t dp_train_pat)
> +{
> +}
> +
> +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 = intel_dp->priv;
> +
> +	return sink->dpcd_write(sink, offset, buffer, size);
> +}
> +
> +/* --- */
> +
> +static struct intel_dp *
> +intel_dp_create(struct drm_device *dev, int lanes, uint8_t link_bw)
> +{
> +	struct intel_digital_port *dig_port;
> +
> +	dig_port = calloc(1, sizeof *dig_port);
> +	dig_port->base.base.dev = dev;
> +	dig_port->dp.lane_count = lanes;
> +	dig_port->dp.link_bw = link_bw;
> +
> +	return &dig_port->dp;
> +}
> +
> +static void
> +intel_dp_destroy(struct intel_dp *intel_dp)
> +{
> +	struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp);
> +	free(dig_port);
> +}
> +
> +/* FIXME: Yikes! */
> +#define BITS_PER_LONG 64
> +#include <include/linux/mod_devicetable.h>
> +#include <i915_pciids.h>
> +#include "intel_dev_info.c"
> +
> +static const struct pci_device_id pciidlist[] = {
> +	INTEL_PCI_IDS,
> +	{0, 0, 0}
> +};
> +
> +struct drm_device *
> +drm_device_for_pci_id(const struct pci_device_id *id)
> +{
> +	struct drm_device *dev;
> +	struct drm_i915_private *dev_priv;
> +
> +	dev = calloc(1, sizeof *dev);
> +	dev_priv = calloc(1, sizeof *dev_priv);
> +	assert(dev && dev_priv);
> +
> +	dev->dev_private = dev_priv;
> +
> +	memcpy(&dev_priv->info, (void *) id->driver_data, sizeof dev_priv->info);
> +	dev_priv->info.device_id = id->device;
> +
> +	/* TODO: set dev_priv->pch_type with an appropriate value */
> +	dev_priv->pch_type = PCH_NONE;
> +
> +	return dev;
> +}
> +
> +void
> +drm_device_destroy(struct drm_device *dev)
> +{
> +	free(dev->dev_private);
> +	free(dev);
> +}
> +
> +int
> +main(int argc, char *argv[])
> +{
> +	const struct pci_device_id *id;
> +
> +	for (id = &pciidlist[0];
> +	     id->vendor != 0 && id->device != 0;
> +	     id++) {
> +		struct drm_device *dev = drm_device_for_pci_id(id);
> +		struct intel_dp *intel_dp =
> +			intel_dp_create(dev, 4, DP_LINK_BW_2_7);
> +
> +		if (IS_GEN2(dev) || IS_PINEVIEW(dev))
> +			continue;
> +
> +		printf("Testing with device id %04x, gen %d\n",
> +		       INTEL_DEVID(dev), INTEL_INFO(dev)->gen);
> +
> +		intel_dp->priv = &simple_sink;
> +		memset(&simple_sink.data, 0, sizeof simple_sink.data);
> +		simple_sink.data.dpcd[DP_MAX_LINK_RATE] = 0x0A;
> +		simple_sink.data.dpcd[DP_MAX_LANE_COUNT] = 0x04;
> +
> +		intel_dp_start_link_train(intel_dp);
> +		intel_dp_stop_link_train(intel_dp);
> +
> +		for (int lane = 0; lane < intel_dp->lane_count; lane++)
> +			printf("lane %i: vswing: %d, pre-emph: %d\n", lane,
> +			       sink_device_get_voltage_swing(&simple_sink, lane),
> +			       sink_device_get_pre_emphasis_level(&simple_sink, lane));
> +		printf("\n");
> +
> +		intel_dp_destroy(intel_dp);
> +		drm_device_destroy(dev);
> +	}
> +
> +	return 0;
> +}
> -- 
> 2.4.3
Ander Conselvan de Oliveira Sept. 8, 2015, 1:26 p.m. UTC | #2
On Tue, 2015-09-08 at 16:11 +0300, Ville Syrjälä wrote:
> On Tue, Sep 08, 2015 at 03:28:00PM +0300, Ander Conselvan de Oliveira wrote:
> > This adds a test that compiles the link training code from i915 into a
> > separate executable and uses it to train a fake sink device. The test
> > also uses device information from i915 to exercise the different code
> > paths for different hardwdare generations.
> > 
> > In order to get the code to compile a lot of stubbing was necessary. It
> > was also easier to copy a few functions from the drm_dp_helpers instead
> > of getting the whole thing to compile as part of the test.
> 
> Hmm. So the only device spefic information you really need is:
> - supported link rates
> - max vswing/pre-emph settings
> - DDI vs. not
> 
> Cooking up a few configurations with varying options would seem fairly
> easy without having to pull in the whole device info, and the hw
> specific max vswing/pre-emphasis stuff.

Yeah. I actually implemented it first with a custom pair of intel_dp_{voltage,pre_emphasis}_max. The
device info was an exercise to see if it was doable to pull that info from the driver instead of
duplicating it locally, but it is certainly not worth it for this case.

Ander

> 
> > ---
> >  link-training-test/Makefile             |  40 +++
> >  link-training-test/drm_dp_helper.c      | 115 +++++++++
> >  link-training-test/intel_drv.h          | 148 ++++++++++++
> >  link-training-test/link_training_test.c | 414 ++++++++++++++++++++++++++++++++
> >  4 files changed, 717 insertions(+)
> >  create mode 100644 link-training-test/Makefile
> >  create mode 100644 link-training-test/drm_dp_helper.c
> >  create mode 100644 link-training-test/intel_drv.h
> >  create mode 100644 link-training-test/link_training_test.c
> > 
> > diff --git a/link-training-test/Makefile b/link-training-test/Makefile
> > new file mode 100644
> > index 0000000..07a9914
> > --- /dev/null
> > +++ b/link-training-test/Makefile
> > @@ -0,0 +1,40 @@
> > +KERNEL_SRC_DIR=/home/aconselv/linux
> > +
> > +# Files copied from i915 source tree
> > +COPIED_SOURCES = \
> > +	intel_dp_link_training.c \
> > +	intel_dev_info.c \
> > +	intel_dev_info.h \
> > +	i915_reg.h
> > +
> > +INTEL_DP_LINK_TRAINING_C = $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/intel_dp_link_training.c
> > +
> > +INCLUDEDIR = \
> > +	-I$(KERNEL_SRC_DIR)/include/drm \
> > +	-I$(KERNEL_SRC_DIR)/ \
> > +	-I.
> > +
> > +DEFINES = \
> > +	-D__KERNEL__
> > +
> > +HEADER_FILES = \
> > +	intel_drv.h
> > +
> > +SOURCE_FILES = \
> > +	intel_dp_link_training.c \
> > +	link_training_test.c \
> > +	drm_dp_helper.c
> > +
> > +all: link_training_test
> > +
> > +#intel_dp_link_training.c: $(INTEL_DP_LINK_TRAINING_C)
> > +#	cp $(INTEL_DP_LINK_TRAINING_C) .
> > +
> > +$(COPIED_SOURCES): %: $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/%
> > +	cp $< $@
> > +
> > +link_training_test: $(COPIED_SOURCES) $(SOURCE_FILES) $(HEADER_FILES)
> > +	gcc -g3 -O0 -std=gnu99 -o link_training_test $(SOURCE_FILES) $(INCLUDEDIR) $(DEFINES)
> > +
> > +clean:
> > +	rm link_training_test $(COPIED_SOURCES)
> > diff --git a/link-training-test/drm_dp_helper.c b/link-training-test/drm_dp_helper.c
> > new file mode 100644
> > index 0000000..a8db7f9
> > --- /dev/null
> > +++ b/link-training-test/drm_dp_helper.c
> > @@ -0,0 +1,115 @@
> > +/*
> > + * 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 "intel_drv.h"
> > +
> > +/* TODO: Get rid of this copy of drm_dp_helper functions. */
> > +
> > +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() {}
> > +static void mdelay() {}
> > +
> > +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/link-training-test/intel_drv.h b/link-training-test/intel_drv.h
> > new file mode 100644
> > index 0000000..f7a6a6c
> > --- /dev/null
> > +++ b/link-training-test/intel_drv.h
> > @@ -0,0 +1,148 @@
> > +/*
> > + * Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
> > + * Copyright (c) 2007-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.
> > + */
> > +
> > +#ifndef FAKE_INTEL_DRV_H
> > +#define FAKE_INTEL_DRV_H
> > +
> > +#include <stdio.h>
> > +#include <stdint.h>
> > +#include <stdbool.h>
> > +#include <string.h>
> > +#include <tools/include/linux/compiler.h>
> > +
> > +typedef unsigned char u8;
> > +typedef unsigned short u16;
> > +typedef unsigned int u32;
> > +typedef unsigned long size_t;
> > +typedef long ssize_t;
> > +
> > +struct drm_device {
> > +	void *dev_private;
> > +};
> > +
> > +static inline struct drm_i915_private *to_i915(const struct drm_device *dev)
> > +{
> > +	return dev->dev_private;
> > +}
> > +
> > +#define BUILD_BUG()
> > +
> > +#include "intel_dev_info.h"
> > +
> > +struct drm_i915_private {
> > +	struct intel_device_info info;
> > +	bool edp_low_vswing;
> > +	enum intel_pch pch_type;
> > +};
> > +
> > +
> > +struct i2c_adapter {
> > +};
> > +
> > +struct mutex {
> > +};
> > +
> > +#include <drm_dp_helper.h>
> > +
> > +#define DRM_ERROR printf
> > +#define DRM_DEBUG_KMS printf
> > +
> > +enum port {
> > +        PORT_A = 0,
> > +        PORT_B,
> > +        PORT_C,
> > +        PORT_D,
> > +        PORT_E,
> > +        I915_MAX_PORTS
> > +};
> > +#define port_name(p) ((p) + 'A')
> > +
> > +struct drm_encoder {
> > +	void *dev;
> > +};
> > +
> > +struct intel_encoder {
> > +	struct drm_encoder base;
> > +};
> > +
> > +struct intel_dp {
> > +	int link_rate;
> > +	int lane_count;
> > +	uint8_t link_bw;
> > +	uint8_t num_sink_rates;
> > +	uint8_t train_set[4];
> > +	bool train_set_valid;
> > +	struct drm_dp_aux aux;
> > +	uint8_t dpcd[DP_RECEIVER_CAP_SIZE];
> > +	uint32_t DP;
> > +	bool use_tps3;
> > +
> > +	/* Hold test private data */
> > +	void *priv;
> > +};
> > +
> > +struct intel_digital_port {
> > +	struct intel_encoder base;
> > +	struct intel_dp dp;
> > +	enum port port;
> > +};
> > +
> > +#define offsetof(type, member)  __builtin_offsetof (type, member)
> > +#define container_of(ptr, type, member) ({			\
> > +	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
> > +	(type *)( (char *)__mptr - offsetof(type,member) );})
> > +
> > +static inline struct intel_digital_port *
> > +dp_to_dig_port(struct intel_dp *intel_dp)
> > +{
> > +	return container_of(intel_dp, struct intel_digital_port, dp);
> > +}
> > +
> > +void
> > +intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
> > +                                      uint8_t dp_train_pat);
> > +void
> > +intel_dp_update_signal_levels(struct intel_dp *intel_dp);
> > +void intel_dp_set_idle_link_train(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);
> > +bool
> > +intel_dp_get_link_status(struct intel_dp *intel_dp, uint8_t link_status[DP_LINK_STATUS_SIZE]);
> > +void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder);
> > +void
> > +intel_dp_start_link_train(struct intel_dp *intel_dp);
> > +void
> > +intel_dp_stop_link_train(struct intel_dp *intel_dp);
> > +bool intel_dp_source_supports_hbr2(struct drm_device *dev);
> > +
> > +static inline struct drm_device *intel_dp_to_dev(struct intel_dp *intel_dp)
> > +{
> > +	struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp);
> > +
> > +	return intel_dig_port->base.base.dev;
> > +}
> > +
> > +#include "i915_reg.h"
> > +
> > +#endif /* FAKE_INTEL_DRV_H */
> > diff --git a/link-training-test/link_training_test.c b/link-training-test/link_training_test.c
> > new file mode 100644
> > index 0000000..aa73b9e
> > --- /dev/null
> > +++ b/link-training-test/link_training_test.c
> > @@ -0,0 +1,414 @@
> > +/*
> > + * 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 <stdlib.h>
> > +#include <assert.h>
> > +
> > +#include "intel_drv.h"
> > +#include "i915_reg.h"
> > +
> > +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;
> > +}
> > +
> > +static void
> > +sink_device_check_lane_count_and_bw(struct sink_device *sink)
> > +{
> > +	if (sink->data.lane_count_and_bw_set)
> > +		return;
> > +
> > +	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;
> > +
> > +	assert(sink_device_get_training_pattern(sink) <= DP_TRAINING_PATTERN_1);
> > +
> > +	if (sink_device_get_training_pattern(sink) != DP_TRAINING_PATTERN_1)
> > +		return;
> > +
> > +	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);
> > +
> > +	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;
> > +
> > +	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;
> > +
> > +	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 bool
> > +sink_device_request_higher_voltage_swing(struct sink_device *sink)
> > +{
> > +	bool max_reached;
> > +
> > +	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++) {
> > +		sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
> > +			(sink_device_get_voltage_swing(sink, lane) + 1) <<
> > +				(DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1));
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +static bool
> > +sink_device_request_higher_pre_emphasis(struct sink_device *sink)
> > +{
> > +	bool max_reached;
> > +
> > +	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++) {
> > +		sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
> > +			(sink_device_get_pre_emphasis_level(sink, lane) + 1) <<
> > +				(DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT +
> > +				 (DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1)));
> > +	}
> > +
> > +	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 struct sink_device simple_sink = {
> > +	.get_link_status = sink_device_get_link_status,
> > +	.dpcd_write = sink_device_dpcd_write,
> > +};
> > +
> > +/* Glue code */
> > +
> > +void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder)
> > +{
> > +}
> > +
> > +void intel_dp_set_idle_link_train(struct intel_dp *intel_dp)
> > +{
> > +}
> > +
> > +bool intel_dp_source_supports_hbr2(struct drm_device *dev)
> > +{
> > +	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 = intel_dp->priv;
> > +	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 = intel_dp->link_bw;
> > +	*rate_select = 0;
> > +}
> > +
> > +void
> > +intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
> > +				       uint8_t dp_train_pat)
> > +{
> > +}
> > +
> > +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 = intel_dp->priv;
> > +
> > +	return sink->dpcd_write(sink, offset, buffer, size);
> > +}
> > +
> > +/* --- */
> > +
> > +static struct intel_dp *
> > +intel_dp_create(struct drm_device *dev, int lanes, uint8_t link_bw)
> > +{
> > +	struct intel_digital_port *dig_port;
> > +
> > +	dig_port = calloc(1, sizeof *dig_port);
> > +	dig_port->base.base.dev = dev;
> > +	dig_port->dp.lane_count = lanes;
> > +	dig_port->dp.link_bw = link_bw;
> > +
> > +	return &dig_port->dp;
> > +}
> > +
> > +static void
> > +intel_dp_destroy(struct intel_dp *intel_dp)
> > +{
> > +	struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp);
> > +	free(dig_port);
> > +}
> > +
> > +/* FIXME: Yikes! */
> > +#define BITS_PER_LONG 64
> > +#include <include/linux/mod_devicetable.h>
> > +#include <i915_pciids.h>
> > +#include "intel_dev_info.c"
> > +
> > +static const struct pci_device_id pciidlist[] = {
> > +	INTEL_PCI_IDS,
> > +	{0, 0, 0}
> > +};
> > +
> > +struct drm_device *
> > +drm_device_for_pci_id(const struct pci_device_id *id)
> > +{
> > +	struct drm_device *dev;
> > +	struct drm_i915_private *dev_priv;
> > +
> > +	dev = calloc(1, sizeof *dev);
> > +	dev_priv = calloc(1, sizeof *dev_priv);
> > +	assert(dev && dev_priv);
> > +
> > +	dev->dev_private = dev_priv;
> > +
> > +	memcpy(&dev_priv->info, (void *) id->driver_data, sizeof dev_priv->info);
> > +	dev_priv->info.device_id = id->device;
> > +
> > +	/* TODO: set dev_priv->pch_type with an appropriate value */
> > +	dev_priv->pch_type = PCH_NONE;
> > +
> > +	return dev;
> > +}
> > +
> > +void
> > +drm_device_destroy(struct drm_device *dev)
> > +{
> > +	free(dev->dev_private);
> > +	free(dev);
> > +}
> > +
> > +int
> > +main(int argc, char *argv[])
> > +{
> > +	const struct pci_device_id *id;
> > +
> > +	for (id = &pciidlist[0];
> > +	     id->vendor != 0 && id->device != 0;
> > +	     id++) {
> > +		struct drm_device *dev = drm_device_for_pci_id(id);
> > +		struct intel_dp *intel_dp =
> > +			intel_dp_create(dev, 4, DP_LINK_BW_2_7);
> > +
> > +		if (IS_GEN2(dev) || IS_PINEVIEW(dev))
> > +			continue;
> > +
> > +		printf("Testing with device id %04x, gen %d\n",
> > +		       INTEL_DEVID(dev), INTEL_INFO(dev)->gen);
> > +
> > +		intel_dp->priv = &simple_sink;
> > +		memset(&simple_sink.data, 0, sizeof simple_sink.data);
> > +		simple_sink.data.dpcd[DP_MAX_LINK_RATE] = 0x0A;
> > +		simple_sink.data.dpcd[DP_MAX_LANE_COUNT] = 0x04;
> > +
> > +		intel_dp_start_link_train(intel_dp);
> > +		intel_dp_stop_link_train(intel_dp);
> > +
> > +		for (int lane = 0; lane < intel_dp->lane_count; lane++)
> > +			printf("lane %i: vswing: %d, pre-emph: %d\n", lane,
> > +			       sink_device_get_voltage_swing(&simple_sink, lane),
> > +			       sink_device_get_pre_emphasis_level(&simple_sink, lane));
> > +		printf("\n");
> > +
> > +		intel_dp_destroy(intel_dp);
> > +		drm_device_destroy(dev);
> > +	}
> > +
> > +	return 0;
> > +}
> > -- 
> > 2.4.3
>
Thomas Wood Sept. 9, 2015, 10:33 a.m. UTC | #3
On 8 September 2015 at 13:28, Ander Conselvan de Oliveira
<ander.conselvan.de.oliveira@intel.com> wrote:
> This adds a test that compiles the link training code from i915 into a
> separate executable and uses it to train a fake sink device. The test
> also uses device information from i915 to exercise the different code
> paths for different hardwdare generations.
>
> In order to get the code to compile a lot of stubbing was necessary. It
> was also easier to copy a few functions from the drm_dp_helpers instead
> of getting the whole thing to compile as part of the test.
> ---
>  link-training-test/Makefile             |  40 +++
>  link-training-test/drm_dp_helper.c      | 115 +++++++++
>  link-training-test/intel_drv.h          | 148 ++++++++++++
>  link-training-test/link_training_test.c | 414 ++++++++++++++++++++++++++++++++
>  4 files changed, 717 insertions(+)
>  create mode 100644 link-training-test/Makefile
>  create mode 100644 link-training-test/drm_dp_helper.c
>  create mode 100644 link-training-test/intel_drv.h
>  create mode 100644 link-training-test/link_training_test.c
>
> diff --git a/link-training-test/Makefile b/link-training-test/Makefile

If this is meant to be part of the test suite, then it needs to be in
the tests directory and use the igt test infrastructure. Otherwise it
should be placed in tools or tools/link-training-test.


> new file mode 100644
> index 0000000..07a9914
> --- /dev/null
> +++ b/link-training-test/Makefile
> @@ -0,0 +1,40 @@
> +KERNEL_SRC_DIR=/home/aconselv/linux

It may be necassary to make the building of this test optional in
configure and provide a way of specifying the kernel source directory.


> +
> +# Files copied from i915 source tree
> +COPIED_SOURCES = \
> +       intel_dp_link_training.c \
> +       intel_dev_info.c \
> +       intel_dev_info.h \
> +       i915_reg.h
> +
> +INTEL_DP_LINK_TRAINING_C = $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/intel_dp_link_training.c
> +
> +INCLUDEDIR = \
> +       -I$(KERNEL_SRC_DIR)/include/drm \
> +       -I$(KERNEL_SRC_DIR)/ \
> +       -I.
> +
> +DEFINES = \
> +       -D__KERNEL__
> +
> +HEADER_FILES = \
> +       intel_drv.h
> +
> +SOURCE_FILES = \
> +       intel_dp_link_training.c \
> +       link_training_test.c \
> +       drm_dp_helper.c
> +
> +all: link_training_test
> +
> +#intel_dp_link_training.c: $(INTEL_DP_LINK_TRAINING_C)
> +#      cp $(INTEL_DP_LINK_TRAINING_C) .
> +
> +$(COPIED_SOURCES): %: $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/%
> +       cp $< $@
> +
> +link_training_test: $(COPIED_SOURCES) $(SOURCE_FILES) $(HEADER_FILES)
> +       gcc -g3 -O0 -std=gnu99 -o link_training_test $(SOURCE_FILES) $(INCLUDEDIR) $(DEFINES)
> +
> +clean:
> +       rm link_training_test $(COPIED_SOURCES)
> diff --git a/link-training-test/drm_dp_helper.c b/link-training-test/drm_dp_helper.c
> new file mode 100644
> index 0000000..a8db7f9
> --- /dev/null
> +++ b/link-training-test/drm_dp_helper.c
> @@ -0,0 +1,115 @@
> +/*
> + * 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 "intel_drv.h"
> +
> +/* TODO: Get rid of this copy of drm_dp_helper functions. */
> +
> +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() {}
> +static void mdelay() {}
> +
> +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/link-training-test/intel_drv.h b/link-training-test/intel_drv.h
> new file mode 100644
> index 0000000..f7a6a6c
> --- /dev/null
> +++ b/link-training-test/intel_drv.h
> @@ -0,0 +1,148 @@
> +/*
> + * Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
> + * Copyright (c) 2007-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.
> + */
> +
> +#ifndef FAKE_INTEL_DRV_H
> +#define FAKE_INTEL_DRV_H
> +
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include <tools/include/linux/compiler.h>
> +
> +typedef unsigned char u8;
> +typedef unsigned short u16;
> +typedef unsigned int u32;
> +typedef unsigned long size_t;
> +typedef long ssize_t;
> +
> +struct drm_device {
> +       void *dev_private;
> +};
> +
> +static inline struct drm_i915_private *to_i915(const struct drm_device *dev)
> +{
> +       return dev->dev_private;
> +}
> +
> +#define BUILD_BUG()
> +
> +#include "intel_dev_info.h"
> +
> +struct drm_i915_private {
> +       struct intel_device_info info;
> +       bool edp_low_vswing;
> +       enum intel_pch pch_type;
> +};
> +
> +
> +struct i2c_adapter {
> +};
> +
> +struct mutex {
> +};
> +
> +#include <drm_dp_helper.h>
> +
> +#define DRM_ERROR printf
> +#define DRM_DEBUG_KMS printf
> +
> +enum port {
> +        PORT_A = 0,
> +        PORT_B,
> +        PORT_C,
> +        PORT_D,
> +        PORT_E,
> +        I915_MAX_PORTS
> +};
> +#define port_name(p) ((p) + 'A')
> +
> +struct drm_encoder {
> +       void *dev;
> +};
> +
> +struct intel_encoder {
> +       struct drm_encoder base;
> +};
> +
> +struct intel_dp {
> +       int link_rate;
> +       int lane_count;
> +       uint8_t link_bw;
> +       uint8_t num_sink_rates;
> +       uint8_t train_set[4];
> +       bool train_set_valid;
> +       struct drm_dp_aux aux;
> +       uint8_t dpcd[DP_RECEIVER_CAP_SIZE];
> +       uint32_t DP;
> +       bool use_tps3;
> +
> +       /* Hold test private data */
> +       void *priv;
> +};
> +
> +struct intel_digital_port {
> +       struct intel_encoder base;
> +       struct intel_dp dp;
> +       enum port port;
> +};
> +
> +#define offsetof(type, member)  __builtin_offsetof (type, member)
> +#define container_of(ptr, type, member) ({                     \
> +       const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
> +       (type *)( (char *)__mptr - offsetof(type,member) );})
> +
> +static inline struct intel_digital_port *
> +dp_to_dig_port(struct intel_dp *intel_dp)
> +{
> +       return container_of(intel_dp, struct intel_digital_port, dp);
> +}
> +
> +void
> +intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
> +                                      uint8_t dp_train_pat);
> +void
> +intel_dp_update_signal_levels(struct intel_dp *intel_dp);
> +void intel_dp_set_idle_link_train(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);
> +bool
> +intel_dp_get_link_status(struct intel_dp *intel_dp, uint8_t link_status[DP_LINK_STATUS_SIZE]);
> +void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder);
> +void
> +intel_dp_start_link_train(struct intel_dp *intel_dp);
> +void
> +intel_dp_stop_link_train(struct intel_dp *intel_dp);
> +bool intel_dp_source_supports_hbr2(struct drm_device *dev);
> +
> +static inline struct drm_device *intel_dp_to_dev(struct intel_dp *intel_dp)
> +{
> +       struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp);
> +
> +       return intel_dig_port->base.base.dev;
> +}
> +
> +#include "i915_reg.h"
> +
> +#endif /* FAKE_INTEL_DRV_H */
> diff --git a/link-training-test/link_training_test.c b/link-training-test/link_training_test.c
> new file mode 100644
> index 0000000..aa73b9e
> --- /dev/null
> +++ b/link-training-test/link_training_test.c
> @@ -0,0 +1,414 @@
> +/*
> + * 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 <stdlib.h>
> +#include <assert.h>
> +
> +#include "intel_drv.h"
> +#include "i915_reg.h"
> +
> +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;
> +}
> +
> +static void
> +sink_device_check_lane_count_and_bw(struct sink_device *sink)
> +{
> +       if (sink->data.lane_count_and_bw_set)
> +               return;
> +
> +       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;
> +
> +       assert(sink_device_get_training_pattern(sink) <= DP_TRAINING_PATTERN_1);
> +
> +       if (sink_device_get_training_pattern(sink) != DP_TRAINING_PATTERN_1)
> +               return;
> +
> +       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);
> +
> +       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;
> +
> +       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;
> +
> +       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 bool
> +sink_device_request_higher_voltage_swing(struct sink_device *sink)
> +{
> +       bool max_reached;
> +
> +       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++) {
> +               sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
> +                       (sink_device_get_voltage_swing(sink, lane) + 1) <<
> +                               (DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1));
> +       }
> +
> +       return true;
> +}
> +
> +static bool
> +sink_device_request_higher_pre_emphasis(struct sink_device *sink)
> +{
> +       bool max_reached;
> +
> +       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++) {
> +               sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
> +                       (sink_device_get_pre_emphasis_level(sink, lane) + 1) <<
> +                               (DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT +
> +                                (DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1)));
> +       }
> +
> +       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 struct sink_device simple_sink = {
> +       .get_link_status = sink_device_get_link_status,
> +       .dpcd_write = sink_device_dpcd_write,
> +};
> +
> +/* Glue code */
> +
> +void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder)
> +{
> +}
> +
> +void intel_dp_set_idle_link_train(struct intel_dp *intel_dp)
> +{
> +}
> +
> +bool intel_dp_source_supports_hbr2(struct drm_device *dev)
> +{
> +       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 = intel_dp->priv;
> +       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 = intel_dp->link_bw;
> +       *rate_select = 0;
> +}
> +
> +void
> +intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
> +                                      uint8_t dp_train_pat)
> +{
> +}
> +
> +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 = intel_dp->priv;
> +
> +       return sink->dpcd_write(sink, offset, buffer, size);
> +}
> +
> +/* --- */
> +
> +static struct intel_dp *
> +intel_dp_create(struct drm_device *dev, int lanes, uint8_t link_bw)
> +{
> +       struct intel_digital_port *dig_port;
> +
> +       dig_port = calloc(1, sizeof *dig_port);
> +       dig_port->base.base.dev = dev;
> +       dig_port->dp.lane_count = lanes;
> +       dig_port->dp.link_bw = link_bw;
> +
> +       return &dig_port->dp;
> +}
> +
> +static void
> +intel_dp_destroy(struct intel_dp *intel_dp)
> +{
> +       struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp);
> +       free(dig_port);
> +}
> +
> +/* FIXME: Yikes! */
> +#define BITS_PER_LONG 64
> +#include <include/linux/mod_devicetable.h>
> +#include <i915_pciids.h>
> +#include "intel_dev_info.c"
> +
> +static const struct pci_device_id pciidlist[] = {
> +       INTEL_PCI_IDS,
> +       {0, 0, 0}
> +};
> +
> +struct drm_device *
> +drm_device_for_pci_id(const struct pci_device_id *id)
> +{
> +       struct drm_device *dev;
> +       struct drm_i915_private *dev_priv;
> +
> +       dev = calloc(1, sizeof *dev);
> +       dev_priv = calloc(1, sizeof *dev_priv);
> +       assert(dev && dev_priv);
> +
> +       dev->dev_private = dev_priv;
> +
> +       memcpy(&dev_priv->info, (void *) id->driver_data, sizeof dev_priv->info);
> +       dev_priv->info.device_id = id->device;
> +
> +       /* TODO: set dev_priv->pch_type with an appropriate value */
> +       dev_priv->pch_type = PCH_NONE;
> +
> +       return dev;
> +}
> +
> +void
> +drm_device_destroy(struct drm_device *dev)
> +{
> +       free(dev->dev_private);
> +       free(dev);
> +}
> +
> +int
> +main(int argc, char *argv[])
> +{
> +       const struct pci_device_id *id;
> +
> +       for (id = &pciidlist[0];
> +            id->vendor != 0 && id->device != 0;
> +            id++) {
> +               struct drm_device *dev = drm_device_for_pci_id(id);
> +               struct intel_dp *intel_dp =
> +                       intel_dp_create(dev, 4, DP_LINK_BW_2_7);
> +
> +               if (IS_GEN2(dev) || IS_PINEVIEW(dev))
> +                       continue;
> +
> +               printf("Testing with device id %04x, gen %d\n",
> +                      INTEL_DEVID(dev), INTEL_INFO(dev)->gen);
> +
> +               intel_dp->priv = &simple_sink;
> +               memset(&simple_sink.data, 0, sizeof simple_sink.data);
> +               simple_sink.data.dpcd[DP_MAX_LINK_RATE] = 0x0A;
> +               simple_sink.data.dpcd[DP_MAX_LANE_COUNT] = 0x04;
> +
> +               intel_dp_start_link_train(intel_dp);
> +               intel_dp_stop_link_train(intel_dp);
> +
> +               for (int lane = 0; lane < intel_dp->lane_count; lane++)
> +                       printf("lane %i: vswing: %d, pre-emph: %d\n", lane,
> +                              sink_device_get_voltage_swing(&simple_sink, lane),
> +                              sink_device_get_pre_emphasis_level(&simple_sink, lane));
> +               printf("\n");
> +
> +               intel_dp_destroy(intel_dp);
> +               drm_device_destroy(dev);
> +       }
> +
> +       return 0;
> +}
> --
> 2.4.3
>
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/intel-gfx
diff mbox

Patch

diff --git a/link-training-test/Makefile b/link-training-test/Makefile
new file mode 100644
index 0000000..07a9914
--- /dev/null
+++ b/link-training-test/Makefile
@@ -0,0 +1,40 @@ 
+KERNEL_SRC_DIR=/home/aconselv/linux
+
+# Files copied from i915 source tree
+COPIED_SOURCES = \
+	intel_dp_link_training.c \
+	intel_dev_info.c \
+	intel_dev_info.h \
+	i915_reg.h
+
+INTEL_DP_LINK_TRAINING_C = $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/intel_dp_link_training.c
+
+INCLUDEDIR = \
+	-I$(KERNEL_SRC_DIR)/include/drm \
+	-I$(KERNEL_SRC_DIR)/ \
+	-I.
+
+DEFINES = \
+	-D__KERNEL__
+
+HEADER_FILES = \
+	intel_drv.h
+
+SOURCE_FILES = \
+	intel_dp_link_training.c \
+	link_training_test.c \
+	drm_dp_helper.c
+
+all: link_training_test
+
+#intel_dp_link_training.c: $(INTEL_DP_LINK_TRAINING_C)
+#	cp $(INTEL_DP_LINK_TRAINING_C) .
+
+$(COPIED_SOURCES): %: $(KERNEL_SRC_DIR)/drivers/gpu/drm/i915/%
+	cp $< $@
+
+link_training_test: $(COPIED_SOURCES) $(SOURCE_FILES) $(HEADER_FILES)
+	gcc -g3 -O0 -std=gnu99 -o link_training_test $(SOURCE_FILES) $(INCLUDEDIR) $(DEFINES)
+
+clean:
+	rm link_training_test $(COPIED_SOURCES)
diff --git a/link-training-test/drm_dp_helper.c b/link-training-test/drm_dp_helper.c
new file mode 100644
index 0000000..a8db7f9
--- /dev/null
+++ b/link-training-test/drm_dp_helper.c
@@ -0,0 +1,115 @@ 
+/*
+ * 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 "intel_drv.h"
+
+/* TODO: Get rid of this copy of drm_dp_helper functions. */
+
+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() {}
+static void mdelay() {}
+
+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/link-training-test/intel_drv.h b/link-training-test/intel_drv.h
new file mode 100644
index 0000000..f7a6a6c
--- /dev/null
+++ b/link-training-test/intel_drv.h
@@ -0,0 +1,148 @@ 
+/*
+ * Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
+ * Copyright (c) 2007-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.
+ */
+
+#ifndef FAKE_INTEL_DRV_H
+#define FAKE_INTEL_DRV_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <tools/include/linux/compiler.h>
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long size_t;
+typedef long ssize_t;
+
+struct drm_device {
+	void *dev_private;
+};
+
+static inline struct drm_i915_private *to_i915(const struct drm_device *dev)
+{
+	return dev->dev_private;
+}
+
+#define BUILD_BUG()
+
+#include "intel_dev_info.h"
+
+struct drm_i915_private {
+	struct intel_device_info info;
+	bool edp_low_vswing;
+	enum intel_pch pch_type;
+};
+
+
+struct i2c_adapter {
+};
+
+struct mutex {
+};
+
+#include <drm_dp_helper.h>
+
+#define DRM_ERROR printf
+#define DRM_DEBUG_KMS printf
+
+enum port {
+        PORT_A = 0,
+        PORT_B,
+        PORT_C,
+        PORT_D,
+        PORT_E,
+        I915_MAX_PORTS
+};
+#define port_name(p) ((p) + 'A')
+
+struct drm_encoder {
+	void *dev;
+};
+
+struct intel_encoder {
+	struct drm_encoder base;
+};
+
+struct intel_dp {
+	int link_rate;
+	int lane_count;
+	uint8_t link_bw;
+	uint8_t num_sink_rates;
+	uint8_t train_set[4];
+	bool train_set_valid;
+	struct drm_dp_aux aux;
+	uint8_t dpcd[DP_RECEIVER_CAP_SIZE];
+	uint32_t DP;
+	bool use_tps3;
+
+	/* Hold test private data */
+	void *priv;
+};
+
+struct intel_digital_port {
+	struct intel_encoder base;
+	struct intel_dp dp;
+	enum port port;
+};
+
+#define offsetof(type, member)  __builtin_offsetof (type, member)
+#define container_of(ptr, type, member) ({			\
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+
+static inline struct intel_digital_port *
+dp_to_dig_port(struct intel_dp *intel_dp)
+{
+	return container_of(intel_dp, struct intel_digital_port, dp);
+}
+
+void
+intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
+                                      uint8_t dp_train_pat);
+void
+intel_dp_update_signal_levels(struct intel_dp *intel_dp);
+void intel_dp_set_idle_link_train(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);
+bool
+intel_dp_get_link_status(struct intel_dp *intel_dp, uint8_t link_status[DP_LINK_STATUS_SIZE]);
+void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder);
+void
+intel_dp_start_link_train(struct intel_dp *intel_dp);
+void
+intel_dp_stop_link_train(struct intel_dp *intel_dp);
+bool intel_dp_source_supports_hbr2(struct drm_device *dev);
+
+static inline struct drm_device *intel_dp_to_dev(struct intel_dp *intel_dp)
+{
+	struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp);
+
+	return intel_dig_port->base.base.dev;
+}
+
+#include "i915_reg.h"
+
+#endif /* FAKE_INTEL_DRV_H */
diff --git a/link-training-test/link_training_test.c b/link-training-test/link_training_test.c
new file mode 100644
index 0000000..aa73b9e
--- /dev/null
+++ b/link-training-test/link_training_test.c
@@ -0,0 +1,414 @@ 
+/*
+ * 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 <stdlib.h>
+#include <assert.h>
+
+#include "intel_drv.h"
+#include "i915_reg.h"
+
+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;
+}
+
+static void
+sink_device_check_lane_count_and_bw(struct sink_device *sink)
+{
+	if (sink->data.lane_count_and_bw_set)
+		return;
+
+	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;
+
+	assert(sink_device_get_training_pattern(sink) <= DP_TRAINING_PATTERN_1);
+
+	if (sink_device_get_training_pattern(sink) != DP_TRAINING_PATTERN_1)
+		return;
+
+	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);
+
+	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;
+
+	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;
+
+	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 bool
+sink_device_request_higher_voltage_swing(struct sink_device *sink)
+{
+	bool max_reached;
+
+	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++) {
+		sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
+			(sink_device_get_voltage_swing(sink, lane) + 1) <<
+				(DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1));
+	}
+
+	return true;
+}
+
+static bool
+sink_device_request_higher_pre_emphasis(struct sink_device *sink)
+{
+	bool max_reached;
+
+	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++) {
+		sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
+			(sink_device_get_pre_emphasis_level(sink, lane) + 1) <<
+				(DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT +
+				 (DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1)));
+	}
+
+	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 struct sink_device simple_sink = {
+	.get_link_status = sink_device_get_link_status,
+	.dpcd_write = sink_device_dpcd_write,
+};
+
+/* Glue code */
+
+void intel_ddi_prepare_link_retrain(struct drm_encoder *encoder)
+{
+}
+
+void intel_dp_set_idle_link_train(struct intel_dp *intel_dp)
+{
+}
+
+bool intel_dp_source_supports_hbr2(struct drm_device *dev)
+{
+	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 = intel_dp->priv;
+	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 = intel_dp->link_bw;
+	*rate_select = 0;
+}
+
+void
+intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
+				       uint8_t dp_train_pat)
+{
+}
+
+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 = intel_dp->priv;
+
+	return sink->dpcd_write(sink, offset, buffer, size);
+}
+
+/* --- */
+
+static struct intel_dp *
+intel_dp_create(struct drm_device *dev, int lanes, uint8_t link_bw)
+{
+	struct intel_digital_port *dig_port;
+
+	dig_port = calloc(1, sizeof *dig_port);
+	dig_port->base.base.dev = dev;
+	dig_port->dp.lane_count = lanes;
+	dig_port->dp.link_bw = link_bw;
+
+	return &dig_port->dp;
+}
+
+static void
+intel_dp_destroy(struct intel_dp *intel_dp)
+{
+	struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp);
+	free(dig_port);
+}
+
+/* FIXME: Yikes! */
+#define BITS_PER_LONG 64
+#include <include/linux/mod_devicetable.h>
+#include <i915_pciids.h>
+#include "intel_dev_info.c"
+
+static const struct pci_device_id pciidlist[] = {
+	INTEL_PCI_IDS,
+	{0, 0, 0}
+};
+
+struct drm_device *
+drm_device_for_pci_id(const struct pci_device_id *id)
+{
+	struct drm_device *dev;
+	struct drm_i915_private *dev_priv;
+
+	dev = calloc(1, sizeof *dev);
+	dev_priv = calloc(1, sizeof *dev_priv);
+	assert(dev && dev_priv);
+
+	dev->dev_private = dev_priv;
+
+	memcpy(&dev_priv->info, (void *) id->driver_data, sizeof dev_priv->info);
+	dev_priv->info.device_id = id->device;
+
+	/* TODO: set dev_priv->pch_type with an appropriate value */
+	dev_priv->pch_type = PCH_NONE;
+
+	return dev;
+}
+
+void
+drm_device_destroy(struct drm_device *dev)
+{
+	free(dev->dev_private);
+	free(dev);
+}
+
+int
+main(int argc, char *argv[])
+{
+	const struct pci_device_id *id;
+
+	for (id = &pciidlist[0];
+	     id->vendor != 0 && id->device != 0;
+	     id++) {
+		struct drm_device *dev = drm_device_for_pci_id(id);
+		struct intel_dp *intel_dp =
+			intel_dp_create(dev, 4, DP_LINK_BW_2_7);
+
+		if (IS_GEN2(dev) || IS_PINEVIEW(dev))
+			continue;
+
+		printf("Testing with device id %04x, gen %d\n",
+		       INTEL_DEVID(dev), INTEL_INFO(dev)->gen);
+
+		intel_dp->priv = &simple_sink;
+		memset(&simple_sink.data, 0, sizeof simple_sink.data);
+		simple_sink.data.dpcd[DP_MAX_LINK_RATE] = 0x0A;
+		simple_sink.data.dpcd[DP_MAX_LANE_COUNT] = 0x04;
+
+		intel_dp_start_link_train(intel_dp);
+		intel_dp_stop_link_train(intel_dp);
+
+		for (int lane = 0; lane < intel_dp->lane_count; lane++)
+			printf("lane %i: vswing: %d, pre-emph: %d\n", lane,
+			       sink_device_get_voltage_swing(&simple_sink, lane),
+			       sink_device_get_pre_emphasis_level(&simple_sink, lane));
+		printf("\n");
+
+		intel_dp_destroy(intel_dp);
+		drm_device_destroy(dev);
+	}
+
+	return 0;
+}