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 <bskeggs@redhat.com>
This commit is contained in:
parent
13a61757db
commit
55f083c33f
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue