drm/nouveau/disp/dp: maintain link in response to hpd signal
authorBen Skeggs <bskeggs@redhat.com>
Tue, 20 May 2014 00:18:03 +0000 (10:18 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Wed, 11 Jun 2014 06:10:56 +0000 (16:10 +1000)
This previously worked for the most part due to userspace doing a
modeset in response to HPD interrupts.  This will allow us to
properly handle cases where sync is lost for other reasons, or if
userspace isn't caring.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/core/engine/disp/dport.c
drivers/gpu/drm/nouveau/core/engine/disp/dport.h
drivers/gpu/drm/nouveau/core/engine/disp/nv50.c
drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c
drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c
drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h

index 5d4b034804afefe3e2fe1c5f88b26e4f0c30ba52..c2bf611db5932d0cb0cda4ff81fbedf031f507a7 100644 (file)
@@ -102,7 +102,7 @@ dp_set_link_config(struct dp_state *dp)
        if (outp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
                sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;
 
-       return nv_wraux(outp->base.edid, DPCD_LC00, sink, 2);
+       return nv_wraux(outp->base.edid, DPCD_LC00_LINK_BW_SET, sink, 2);
 }
 
 static void
@@ -313,14 +313,16 @@ static const struct dp_rates {
        {}
 };
 
-int
-nouveau_dp_train(struct nvkm_output_dp *outp, u32 datarate)
+void
+nouveau_dp_train(struct work_struct *w)
 {
+       struct nvkm_output_dp *outp = container_of(w, typeof(*outp), lt.work);
        struct nouveau_disp *disp = nouveau_disp(outp);
        const struct dp_rates *cfg = nouveau_dp_rates;
        struct dp_state _dp = {
                .outp = outp,
        }, *dp = &_dp;
+       u32 datarate = 0;
        int ret;
 
        /* bring capabilities within encoder limits */
@@ -342,6 +344,9 @@ nouveau_dp_train(struct nvkm_output_dp *outp, u32 datarate)
        }
        cfg--;
 
+       /* disable link interrupt handling during link training */
+       nouveau_event_put(outp->irq);
+
        /* enable down-spreading and execute pre-train script from vbios */
        dp_link_train_init(dp, outp->dpcd[3] & 0x01);
 
@@ -370,12 +375,16 @@ nouveau_dp_train(struct nvkm_output_dp *outp, u32 datarate)
                }
        }
 
-       /* finish link training */
+       /* finish link training and execute post-train script from vbios */
        dp_set_training_pattern(dp, 0);
        if (ret < 0)
                ERR("link training failed\n");
 
-       /* execute post-train script from vbios */
        dp_link_train_fini(dp);
-       return (ret < 0) ? false : true;
+
+       /* signal completion and enable link interrupt handling */
+       DBG("training complete\n");
+       atomic_set(&outp->lt.done, 1);
+       wake_up(&outp->lt.wait);
+       nouveau_event_get(outp->irq);
 }
index 2712e31c9ce87e185baaa4fdb623f1590fddeb33..5628d2d5ec71a2aed960267ce7fd9f0555098f8c 100644 (file)
@@ -13,8 +13,7 @@
 #define DPCD_RC0E_AUX_RD_INTERVAL                                       0x0000e
 
 /* DPCD Link Configuration */
-#define DPCD_LC00                                                       0x00100
-#define DPCD_LC00_LINK_BW_SET                                              0xff
+#define DPCD_LC00_LINK_BW_SET                                           0x00100
 #define DPCD_LC01                                                       0x00101
 #define DPCD_LC01_ENHANCED_FRAME_EN                                        0x80
 #define DPCD_LC01_LANE_COUNT_SET                                           0x1f
@@ -71,4 +70,6 @@
 #define DPCD_LS0C_LANE1_POST_CURSOR2                                       0x0c
 #define DPCD_LS0C_LANE0_POST_CURSOR2                                       0x03
 
+void nouveau_dp_train(struct work_struct *);
+
 #endif
index a73bc158d38c14fd44cb61d9c3274d8db5e4a6f7..90974cd087c1e0509d928b1e6c9ae27d03b6f4b3 100644 (file)
@@ -1457,6 +1457,24 @@ nv50_disp_intr_unk20_2(struct nv50_disp_priv *priv, int head)
        if (!outp)
                return;
 
+       /* we allow both encoder attach and detach operations to occur
+        * within a single supervisor (ie. modeset) sequence.  the
+        * encoder detach scripts quite often switch off power to the
+        * lanes, which requires the link to be re-trained.
+        *
+        * this is not generally an issue as the sink "must" (heh)
+        * signal an irq when it's lost sync so the driver can
+        * re-train.
+        *
+        * however, on some boards, if one does not configure at least
+        * the gpu side of the link *before* attaching, then various
+        * things can go horribly wrong (PDISP disappearing from mmio,
+        * third supervisor never happens, etc).
+        *
+        * the solution is simply to retrain here, if necessary.  last
+        * i checked, the binary driver userspace does not appear to
+        * trigger this situation (it forces an UPDATE between steps).
+        */
        if (outp->info.type == DCB_OUTPUT_DP) {
                u32 soff = (ffs(outp->info.or) - 1) * 0x08;
                u32 ctrl, datarate;
@@ -1478,7 +1496,8 @@ nv50_disp_intr_unk20_2(struct nv50_disp_priv *priv, int head)
                        break;
                }
 
-               nouveau_dp_train((void *)outp, datarate / soff);
+               if (nvkm_output_dp_train(outp, datarate / soff, true))
+                       ERR("link not trained before attach\n");
        }
 
        exec_clkcmp(priv, head, 0, pclk, &conf);
index 77e7ade11af52e3aba9be8bae80f03ea8288ee97..4eb16e9e7b59429d6ad30815cbfdb446a36e9f26 100644 (file)
@@ -1138,6 +1138,7 @@ nvd0_disp_intr_unk2_2(struct nv50_disp_priv *priv, int head)
        if (!outp)
                return;
 
+       /* see note in nv50_disp_intr_unk20_2() */
        if (outp->info.type == DCB_OUTPUT_DP) {
                u32 sync = nv_rd32(priv, 0x660404 + (head * 0x300));
                switch ((sync & 0x000003c0) >> 6) {
@@ -1149,7 +1150,8 @@ nvd0_disp_intr_unk2_2(struct nv50_disp_priv *priv, int head)
                        break;
                }
 
-               nouveau_dp_train((void *)outp, pclk);
+               if (nvkm_output_dp_train(outp, pclk, true))
+                       ERR("link not trained before attach\n");
        }
 
        exec_clkcmp(priv, head, 0, pclk, &conf);
index 7f558875543650724fb13c1a8a047c8e3218c604..52c299c3d3008eed897aec9aec7943e5c23e1454 100644 (file)
 #include "conn.h"
 #include "dport.h"
 
+int
+nvkm_output_dp_train(struct nvkm_output *base, u32 datarate, bool wait)
+{
+       struct nvkm_output_dp *outp = (void *)base;
+       bool retrain = true;
+       u8 link[2], stat[3];
+       u32 rate;
+       int ret, i;
+
+       /* check that the link is trained at a high enough rate */
+       ret = nv_rdaux(outp->base.edid, DPCD_LC00_LINK_BW_SET, link, 2);
+       if (ret) {
+               DBG("failed to read link config, assuming no sink\n");
+               goto done;
+       }
+
+       rate = link[0] * 27000 * (link[1] & DPCD_LC01_LANE_COUNT_SET);
+       if (rate < ((datarate / 8) * 10)) {
+               DBG("link not trained at sufficient rate\n");
+               goto done;
+       }
+
+       /* check that link is still trained */
+       ret = nv_rdaux(outp->base.edid, DPCD_LS02, stat, 3);
+       if (ret) {
+               DBG("failed to read link status, assuming no sink\n");
+               goto done;
+       }
+
+       if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) {
+               for (i = 0; i < (link[1] & DPCD_LC01_LANE_COUNT_SET); i++) {
+                       u8 lane = (stat[i >> 1] >> ((i & 1) * 4)) & 0x0f;
+                       if (!(lane & DPCD_LS02_LANE0_CR_DONE) ||
+                           !(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
+                           !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) {
+                               DBG("lane %d not equalised\n", lane);
+                               goto done;
+                       }
+               }
+               retrain = false;
+       } else {
+               DBG("no inter-lane alignment\n");
+       }
+
+done:
+       if (retrain || !atomic_read(&outp->lt.done)) {
+               /* no sink, but still need to configure source */
+               if (outp->dpcd[DPCD_RC00_DPCD_REV] == 0x00) {
+                       outp->dpcd[DPCD_RC01_MAX_LINK_RATE] =
+                               outp->base.info.dpconf.link_bw;
+                       outp->dpcd[DPCD_RC02] =
+                               outp->base.info.dpconf.link_nr;
+               }
+               atomic_set(&outp->lt.done, 0);
+               schedule_work(&outp->lt.work);
+       } else {
+               nouveau_event_get(outp->irq);
+       }
+
+       if (wait) {
+               if (!wait_event_timeout(outp->lt.wait,
+                                       atomic_read(&outp->lt.done),
+                                       msecs_to_jiffies(2000)))
+                       ret = -ETIMEDOUT;
+       }
+
+       return ret;
+}
+
 static void
 nvkm_output_dp_enable(struct nvkm_output_dp *outp, bool present)
 {
@@ -38,12 +107,14 @@ nvkm_output_dp_enable(struct nvkm_output_dp *outp, bool present)
                        DBG("aux power -> always\n");
                        outp->present = true;
                }
+               nvkm_output_dp_train(&outp->base, 0, true);
        } else {
                if (outp->present) {
                        nouveau_i2c(port)->release_pad(port);
                        DBG("aux power -> demand\n");
                        outp->present = false;
                }
+               atomic_set(&outp->lt.done, 0);
        }
 }
 
@@ -78,7 +149,7 @@ nvkm_output_dp_service_work(struct work_struct *work)
        }
 
        if (type & NVKM_I2C_IRQ) {
-               nouveau_event_get(outp->irq);
+               nvkm_output_dp_train(&outp->base, 0, true);
                send |= NVKM_HPD_IRQ;
        }
 
@@ -159,6 +230,11 @@ nvkm_output_dp_create_(struct nouveau_object *parent,
 
        DBG("bios dp %02x %02x %02x %02x\n", outp->version, hdr, cnt, len);
 
+       /* link training */
+       INIT_WORK(&outp->lt.work, nouveau_dp_train);
+       init_waitqueue_head(&outp->lt.wait);
+       atomic_set(&outp->lt.done, 0);
+
        /* link maintenance */
        ret = nouveau_event_new(i2c->ntfy, NVKM_I2C_IRQ, outp->base.edid->index,
                                nvkm_output_dp_service, outp, &outp->irq);
index 4c32cf53417bf2f010413fce353d98fd1f94e04f..ff33ba12cb67ce47c52f95ff68aa9d1ee0f74fed 100644 (file)
@@ -18,6 +18,12 @@ struct nvkm_output_dp {
        atomic_t pending;
        bool present;
        u8 dpcd[16];
+
+       struct {
+               struct work_struct work;
+               wait_queue_head_t wait;
+               atomic_t done;
+       } lt;
 };
 
 #define nvkm_output_dp_create(p,e,c,b,i,d)                                     \
@@ -54,6 +60,6 @@ struct nvkm_output_dp_impl {
        int (*drv_ctl)(struct nvkm_output_dp *, int ln, int vs, int pe, int pc);
 };
 
-int nouveau_dp_train(struct nvkm_output_dp *, u32 rate);
+int nvkm_output_dp_train(struct nvkm_output *, u32 rate, bool wait);
 
 #endif