From: Thierry Reding <tred...@nvidia.com>

Add a helper that will perform link training as described in the
DisplayPort specification.

Signed-off-by: Thierry Reding <treding at nvidia.com>
---
 drivers/gpu/drm/drm_dp_helper.c | 476 +++++++++++++++++++++++++++++++++++++++-
 include/drm/drm_dp_helper.h     |  49 +++++
 2 files changed, 524 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c
index 543349e14481..8968201ea93c 100644
--- a/drivers/gpu/drm/drm_dp_helper.c
+++ b/drivers/gpu/drm/drm_dp_helper.c
@@ -292,6 +292,19 @@ int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
 }
 EXPORT_SYMBOL(drm_dp_dpcd_read_link_status);

+static void drm_dp_link_reset(struct drm_dp_link *link)
+{
+       if (!link)
+               return;
+
+       link->revision = 0;
+       link->edp = 0;
+       link->rate = 0;
+       link->num_lanes = 0;
+       link->capabilities = 0;
+       link->aux_rd_interval = 0;
+}
+
 /**
  * drm_dp_link_probe() - probe a DisplayPort link for capabilities
  * @aux: DisplayPort AUX channel
@@ -308,7 +321,7 @@ int drm_dp_link_probe(struct drm_dp_aux *aux, struct 
drm_dp_link *link)
        u8 values[16], value;
        int err;

-       memset(link, 0, sizeof(*link));
+       drm_dp_link_reset(link);

        err = drm_dp_dpcd_read(aux, DP_DPCD_REV, values, sizeof(values));
        if (err < 0)
@@ -451,6 +464,14 @@ int drm_dp_link_configure(struct drm_dp_aux *aux, struct 
drm_dp_link *link)
        u8 values[2], value = 0;
        int err;

+       if (link->ops && link->ops->configure) {
+               err = link->ops->configure(link);
+               if (err < 0) {
+                       DRM_ERROR("failed to configure DP link: %d\n", err);
+                       return err;
+               }
+       }
+
        values[0] = drm_dp_link_rate_to_bw_code(link->rate);
        values[1] = link->num_lanes;

@@ -727,3 +748,456 @@ void drm_dp_aux_unregister(struct drm_dp_aux *aux)
        i2c_del_adapter(&aux->ddc);
 }
 EXPORT_SYMBOL(drm_dp_aux_unregister);
+
+/**
+ * DOC: Link training
+ *
+ * These functions contain common logic and helpers to implement DisplayPort
+ * link training.
+ */
+
+/**
+ * drm_dp_link_train_init() - initialize DisplayPort link training state
+ * @train: DisplayPort link training state
+ */
+void drm_dp_link_train_init(struct drm_dp_link_train *train)
+{
+       struct drm_dp_link_train_set *request = &train->request;
+       struct drm_dp_link_train_set *adjust = &train->adjust;
+       unsigned int i;
+
+       for (i = 0; i < 4; i++) {
+               request->voltage_swing[i] = 0;
+               adjust->voltage_swing[i] = 0;
+
+               request->pre_emphasis[i] = 0;
+               adjust->pre_emphasis[i] = 0;
+
+               request->post_cursor[i] = 0;
+               adjust->post_cursor[i] = 0;
+       }
+
+       train->pattern = DP_TRAINING_PATTERN_DISABLE;
+       train->clock_recovered = false;
+       train->channel_equalized = false;
+}
+
+static bool drm_dp_link_train_valid(const struct drm_dp_link_train *train)
+{
+       return train->clock_recovered && train->channel_equalized;
+}
+
+static int drm_dp_link_apply_training(struct drm_dp_link *link)
+{
+       struct drm_dp_link_train_set *request = &link->train.request;
+       unsigned int lanes = link->num_lanes, *vs, *pe, *pc, i;
+       struct drm_dp_aux *aux = link->aux;
+       u8 values[4], pattern = 0;
+       int err;
+
+       err = link->ops->apply_training(link);
+       if (err < 0) {
+               DRM_ERROR("failed to apply link training: %d\n", err);
+               return err;
+       }
+
+       vs = request->voltage_swing;
+       pe = request->pre_emphasis;
+       pc = request->post_cursor;
+
+       /* write currently selected voltage-swing and pre-emphasis levels */
+       for (i = 0; i < lanes; i++)
+               values[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL(vs[i]) |
+                           DP_TRAIN_PRE_EMPHASIS_LEVEL(pe[i]);
+
+       err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_SET, values, lanes);
+       if (err < 0) {
+               DRM_ERROR("failed to set training parameters: %d\n", err);
+               return err;
+       }
+
+       /* write currently selected post-cursor level (if supported) */
+       if (link->revision >= 0x12 && link->rate == 540000) {
+               values[0] = values[1] = 0;
+
+               for (i = 0; i < lanes; i++)
+                       values[i / 2] |= DP_LANE_POST_CURSOR(i, pc[i]);
+
+               err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_1_SET2, values,
+                                       DIV_ROUND_UP(lanes, 2));
+               if (err < 0) {
+                       DRM_ERROR("failed to set post-cursor: %d\n", err);
+                       return err;
+               }
+       }
+
+       /* write link pattern */
+       if (link->train.pattern != DP_TRAINING_PATTERN_DISABLE)
+               pattern |= DP_LINK_SCRAMBLING_DISABLE;
+
+       pattern |= link->train.pattern;
+
+       err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, pattern);
+       if (err < 0) {
+               DRM_ERROR("failed to set training pattern: %d\n", err);
+               return err;
+       }
+
+       return 0;
+}
+
+static void drm_dp_link_train_wait(struct drm_dp_link *link)
+{
+       unsigned long min = 0;
+
+       if (link->aux_rd_interval == 0) {
+               switch (link->train.pattern) {
+               case DP_TRAINING_PATTERN_1:
+                       min = 100;
+                       break;
+
+               case DP_TRAINING_PATTERN_2:
+               case DP_TRAINING_PATTERN_3:
+                       min = 400;
+                       break;
+
+               default:
+                       break;
+               }
+       } else {
+               min = link->aux_rd_interval;
+       }
+
+       if (min > 0)
+               usleep_range(min, 2 * min);
+}
+
+static void drm_dp_link_get_adjustments(struct drm_dp_link *link,
+                                       u8 status[DP_LINK_STATUS_SIZE])
+{
+       struct drm_dp_link_train_set *adjust = &link->train.adjust;
+       unsigned int i;
+
+       for (i = 0; i < link->num_lanes; i++) {
+               adjust->voltage_swing[i] =
+                       drm_dp_get_adjust_request_voltage(status, i) >>
+                               DP_TRAIN_VOLTAGE_SWING_SHIFT;
+
+               adjust->pre_emphasis[i] =
+                       drm_dp_get_adjust_request_pre_emphasis(status, i) >>
+                               DP_TRAIN_PRE_EMPHASIS_SHIFT;
+
+               adjust->post_cursor[i] =
+                       drm_dp_get_adjust_request_post_cursor(status, i);
+       }
+}
+
+static void drm_dp_link_train_adjust(struct drm_dp_link_train *train)
+{
+       struct drm_dp_link_train_set *request = &train->request;
+       struct drm_dp_link_train_set *adjust = &train->adjust;
+       unsigned int i;
+
+       for (i = 0; i < 4; i++)
+               if (request->voltage_swing[i] != adjust->voltage_swing[i])
+                       request->voltage_swing[i] = adjust->voltage_swing[i];
+
+       for (i = 0; i < 4; i++)
+               if (request->pre_emphasis[i] != adjust->pre_emphasis[i])
+                       request->pre_emphasis[i] = adjust->pre_emphasis[i];
+
+       for (i = 0; i < 4; i++)
+               if (request->post_cursor[i] != adjust->post_cursor[i])
+                       request->post_cursor[i] = adjust->post_cursor[i];
+}
+
+static int drm_dp_link_recover_clock(struct drm_dp_link *link)
+{
+       u8 status[DP_LINK_STATUS_SIZE];
+       int err;
+
+       err = drm_dp_link_apply_training(link);
+       if (err < 0)
+               return err;
+
+       drm_dp_link_train_wait(link);
+
+       err = drm_dp_dpcd_read_link_status(link->aux, status);
+       if (err < 0) {
+               DRM_ERROR("failed to read link status: %d\n", err);
+               return err;
+       }
+
+       if (!drm_dp_clock_recovery_ok(status, link->num_lanes))
+               drm_dp_link_get_adjustments(link, status);
+       else
+               link->train.clock_recovered = true;
+
+       return 0;
+}
+
+static int drm_dp_link_clock_recovery(struct drm_dp_link *link)
+{
+       unsigned int repeat;
+       int err;
+
+       /* start clock recovery using training pattern 1 */
+       link->train.pattern = DP_TRAINING_PATTERN_1;
+
+       for (repeat = 1; repeat < 5; repeat++) {
+               err = drm_dp_link_recover_clock(link);
+               if (err < 0) {
+                       DRM_ERROR("failed to recover clock: %d\n", err);
+                       return err;
+               }
+
+               drm_dp_link_train_adjust(&link->train);
+
+               if (link->train.clock_recovered)
+                       break;
+       }
+
+       return 0;
+}
+
+static int drm_dp_link_equalize_channel(struct drm_dp_link *link)
+{
+       struct drm_dp_aux *aux = link->aux;
+       u8 status[DP_LINK_STATUS_SIZE];
+       int err;
+
+       err = drm_dp_link_apply_training(link);
+       if (err < 0)
+               return err;
+
+       drm_dp_link_train_wait(link);
+
+       err = drm_dp_dpcd_read_link_status(aux, status);
+       if (err < 0) {
+               DRM_ERROR("failed to read link status: %d\n", err);
+               return err;
+       }
+
+       if (!drm_dp_clock_recovery_ok(status, link->num_lanes)) {
+               DRM_ERROR("clock recovery lost while equalizing channel\n");
+               link->train.clock_recovered = false;
+               return 0;
+       }
+
+       if (!drm_dp_channel_eq_ok(status, link->num_lanes))
+               drm_dp_link_get_adjustments(link, status);
+       else
+               link->train.channel_equalized = true;
+
+       return 0;
+}
+
+static int drm_dp_link_channel_equalization(struct drm_dp_link *link)
+{
+       unsigned int repeat;
+       int err;
+
+       /* start channel equalization using pattern 2 or 3 */
+       if (link->capabilities & DP_LINK_CAP_TPS3)
+               link->train.pattern = DP_TRAINING_PATTERN_3;
+       else
+               link->train.pattern = DP_TRAINING_PATTERN_2;
+
+       for (repeat = 1; repeat < 5; repeat++) {
+               err = drm_dp_link_equalize_channel(link);
+               if (err < 0) {
+                       DRM_ERROR("failed to equalize channel: %d\n", err);
+                       return err;
+               }
+
+               drm_dp_link_train_adjust(&link->train);
+
+               if (link->train.channel_equalized)
+                       break;
+       }
+
+       return 0;
+}
+
+static int drm_dp_link_downgrade(struct drm_dp_link *link)
+{
+       switch (link->rate) {
+       case 162000:
+               return -EINVAL;
+
+       case 270000:
+               link->rate = 162000;
+               break;
+
+       case 540000:
+               link->rate = 270000;
+               return 0;
+       }
+
+       return 0;
+}
+
+static void drm_dp_link_train_disable(struct drm_dp_link *link)
+{
+       int err;
+
+       link->train.pattern = DP_TRAINING_PATTERN_DISABLE;
+
+       err = drm_dp_link_apply_training(link);
+       if (err < 0)
+               DRM_ERROR("failed to disable link training: %d\n", err);
+}
+
+static int drm_dp_link_train_full(struct drm_dp_link *link)
+{
+       int err;
+
+retry:
+       DRM_DEBUG_KMS("full-training link: %u lane%s at %u MHz\n",
+                     link->num_lanes, (link->num_lanes > 1) ? "s" : "",
+                     link->rate / 100);
+
+       err = drm_dp_link_configure(link->aux, link);
+       if (err < 0) {
+               DRM_ERROR("failed to configure DP link: %d\n", err);
+               return err;
+       }
+
+       err = drm_dp_link_clock_recovery(link);
+       if (err < 0) {
+               DRM_ERROR("clock recovery failed: %d\n", err);
+               goto out;
+       }
+
+       if (!link->train.clock_recovered) {
+               DRM_ERROR("clock recovery failed, downgrading link\n");
+
+               err = drm_dp_link_downgrade(link);
+               if (err < 0)
+                       goto out;
+
+               goto retry;
+       }
+
+       DRM_DEBUG_KMS("clock recovery succeeded\n");
+
+       err = drm_dp_link_channel_equalization(link);
+       if (err < 0) {
+               DRM_ERROR("channel equalization failed: %d\n", err);
+               goto out;
+       }
+
+       if (!link->train.channel_equalized) {
+               DRM_ERROR("channel equalization failed, downgrading link\n");
+
+               err = drm_dp_link_downgrade(link);
+               if (err < 0)
+                       goto out;
+
+               goto retry;
+       }
+
+       DRM_DEBUG_KMS("channel equalization succeeded\n");
+
+out:
+       drm_dp_link_train_disable(link);
+       return err;
+}
+
+static int drm_dp_link_train_fast(struct drm_dp_link *link)
+{
+       u8 status[DP_LINK_STATUS_SIZE];
+       int err;
+
+       DRM_DEBUG_KMS("fast-training link: %u lane%s at %u MHz\n",
+                     link->num_lanes, (link->num_lanes > 1) ? "s" : "",
+                     link->rate / 100);
+
+       err = drm_dp_link_configure(link->aux, link);
+       if (err < 0) {
+               DRM_ERROR("failed to configure DP link: %d\n", err);
+               return err;
+       }
+
+       /* transmit training pattern 1 for 500 microseconds */
+       link->train.pattern = DP_TRAINING_PATTERN_1;
+
+       err = drm_dp_link_apply_training(link);
+       if (err < 0)
+               goto out;
+
+       usleep_range(500, 1000);
+
+       /* transmit training pattern 2 or 3 for 500 microseconds */
+       if (link->capabilities & DP_LINK_CAP_TPS3)
+               link->train.pattern = DP_TRAINING_PATTERN_3;
+       else
+               link->train.pattern = DP_TRAINING_PATTERN_2;
+
+       err = drm_dp_link_apply_training(link);
+       if (err < 0)
+               goto out;
+
+       usleep_range(500, 1000);
+
+       err = drm_dp_dpcd_read_link_status(link->aux, status);
+       if (err < 0) {
+               DRM_ERROR("failed to read link status: %d\n", err);
+               goto out;
+       }
+
+       if (!drm_dp_clock_recovery_ok(status, link->num_lanes)) {
+               DRM_ERROR("clock recovery failed\n");
+               err = -EIO;
+       }
+
+       if (!drm_dp_channel_eq_ok(status, link->num_lanes)) {
+               DRM_ERROR("channel equalization failed\n");
+               err = -EIO;
+       }
+
+out:
+       drm_dp_link_train_disable(link);
+       return err;
+}
+
+/**
+ * drm_dp_link_train() - perform DisplayPort link training
+ * @link: a DP link object
+ *
+ * Uses the context stored in the DP link object to perform link training. It
+ * is expected that drivers will call drm_dp_link_probe() to obtain the link
+ * capabilities before performing link training.
+ *
+ * If the sink supports fast link training (no AUX CH handshake) and valid
+ * training settings are available, this function will try to perform fast
+ * link training and fall back to full link training on failure.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int drm_dp_link_train(struct drm_dp_link *link)
+{
+       int err;
+
+       if (link->capabilities & DP_LINK_CAP_FAST_TRAINING) {
+               if (drm_dp_link_train_valid(&link->train)) {
+                       err = drm_dp_link_train_fast(link);
+                       if (err < 0)
+                               DRM_ERROR("fast link training failed: %d\n",
+                                         err);
+                       else
+                               return 0;
+               } else {
+                       DRM_DEBUG_KMS("training parameters not available\n");
+               }
+       } else {
+               DRM_DEBUG_KMS("fast link training not supported\n");
+       }
+
+       err = drm_dp_link_train_full(link);
+       if (err < 0)
+               DRM_ERROR("full link training failed: %d\n", err);
+
+       return err;
+}
+EXPORT_SYMBOL(drm_dp_link_train);
diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
index 600347f8cdd4..d041bb00d6a0 100644
--- a/include/drm/drm_dp_helper.h
+++ b/include/drm/drm_dp_helper.h
@@ -265,12 +265,14 @@
 # define DP_TRAIN_VOLTAGE_SWING_LEVEL_1 (1 << 0)
 # define DP_TRAIN_VOLTAGE_SWING_LEVEL_2 (2 << 0)
 # define DP_TRAIN_VOLTAGE_SWING_LEVEL_3 (3 << 0)
+# define DP_TRAIN_VOLTAGE_SWING_LEVEL(x) ((x) << 0)

 # define DP_TRAIN_PRE_EMPHASIS_MASK        (3 << 3)
 # define DP_TRAIN_PRE_EMPH_LEVEL_0             (0 << 3)
 # define DP_TRAIN_PRE_EMPH_LEVEL_1             (1 << 3)
 # define DP_TRAIN_PRE_EMPH_LEVEL_2             (2 << 3)
 # define DP_TRAIN_PRE_EMPH_LEVEL_3             (3 << 3)
+# define DP_TRAIN_PRE_EMPHASIS_LEVEL(x)                ((x) << 3)

 # define DP_TRAIN_PRE_EMPHASIS_SHIFT       3
 # define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED  (1 << 5)
@@ -308,6 +310,7 @@
 # define DP_LANE02_MAX_POST_CURSOR2_REACHED (1 << 2)
 # define DP_LANE13_POST_CURSOR2_SET_MASK    (3 << 4)
 # define DP_LANE13_MAX_POST_CURSOR2_REACHED (1 << 6)
+# define DP_LANE_POST_CURSOR(i, x)         (((x) & 0x3) << (((i) & 1) << 2))

 #define DP_MSTM_CTRL                       0x111   /* 1.2 */
 # define DP_MST_EN                         (1 << 0)
@@ -751,6 +754,45 @@ static inline ssize_t drm_dp_dpcd_writeb(struct drm_dp_aux 
*aux,
 int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
                                 u8 status[DP_LINK_STATUS_SIZE]);

+/**
+ * struct drm_dp_link_train_set - link training settings
+ * @voltage_swing: per-lane voltage swing
+ * @pre_emphasis: per-lane pre-emphasis
+ * @post_cursor: per-lane post-cursor
+ */
+struct drm_dp_link_train_set {
+       unsigned int voltage_swing[4];
+       unsigned int pre_emphasis[4];
+       unsigned int post_cursor[4];
+};
+
+/**
+ * struct drm_dp_link_train - link training state information
+ * @request: currently requested settings
+ * @adjust: adjustments requested by sink
+ * @pattern: currently requested training pattern
+ * @clock_recovered: flag to track if clock recovery has completed
+ * @channel_equalized: flag to track if channel equalization has completed
+ */
+struct drm_dp_link_train {
+       struct drm_dp_link_train_set request;
+       struct drm_dp_link_train_set adjust;
+
+       unsigned int pattern;
+
+       bool clock_recovered;
+       bool channel_equalized;
+};
+
+void drm_dp_link_train_init(struct drm_dp_link_train *train);
+
+struct drm_dp_link;
+
+struct drm_dp_link_ops {
+       int (*apply_training)(struct drm_dp_link *link);
+       int (*configure)(struct drm_dp_link *link);
+};
+
 /*
  * DisplayPort link
  */
@@ -767,6 +809,11 @@ struct drm_dp_link {
        unsigned int num_lanes;
        unsigned long capabilities;
        unsigned long aux_rd_interval;
+
+       const struct drm_dp_link_ops *ops;
+       struct drm_dp_aux *aux;
+
+       struct drm_dp_link_train train;
 };

 int drm_dp_link_probe(struct drm_dp_aux *aux, struct drm_dp_link *link);
@@ -774,6 +821,8 @@ int drm_dp_link_power_up(struct drm_dp_aux *aux, struct 
drm_dp_link *link);
 int drm_dp_link_power_down(struct drm_dp_aux *aux, struct drm_dp_link *link);
 int drm_dp_link_configure(struct drm_dp_aux *aux, struct drm_dp_link *link);

+int drm_dp_link_train(struct drm_dp_link *link);
+
 int drm_dp_aux_register(struct drm_dp_aux *aux);
 void drm_dp_aux_unregister(struct drm_dp_aux *aux);

-- 
2.4.5

Reply via email to