From 55f083c33feb7231c7574a64cd01b0477715a370 Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Tue, 20 May 2014 10:18:03 +1000 Subject: [PATCH] drm/nouveau/disp/dp: maintain link in response to hpd signal 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 --- .../gpu/drm/nouveau/core/engine/disp/dport.c | 21 +++-- .../gpu/drm/nouveau/core/engine/disp/dport.h | 5 +- .../gpu/drm/nouveau/core/engine/disp/nv50.c | 21 ++++- .../gpu/drm/nouveau/core/engine/disp/nvd0.c | 4 +- .../gpu/drm/nouveau/core/engine/disp/outpdp.c | 78 ++++++++++++++++++- .../gpu/drm/nouveau/core/engine/disp/outpdp.h | 8 +- 6 files changed, 125 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/dport.c b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c index 5d4b034804af..c2bf611db593 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/dport.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/dport.c @@ -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); } diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/dport.h b/drivers/gpu/drm/nouveau/core/engine/disp/dport.h index 2712e31c9ce8..5628d2d5ec71 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/dport.h +++ b/drivers/gpu/drm/nouveau/core/engine/disp/dport.h @@ -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 diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c index a73bc158d38c..90974cd087c1 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nv50.c @@ -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); diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c index 77e7ade11af5..4eb16e9e7b59 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/nvd0.c @@ -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); diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c index 7f5588755436..52c299c3d300 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c +++ b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.c @@ -28,6 +28,75 @@ #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); diff --git a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h index 4c32cf53417b..ff33ba12cb67 100644 --- a/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h +++ b/drivers/gpu/drm/nouveau/core/engine/disp/outpdp.h @@ -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 -- 2.20.1