Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 55f083c3 authored by Ben Skeggs's avatar Ben Skeggs
Browse files

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: default avatarBen Skeggs <bskeggs@redhat.com>
parent 13a61757
Loading
Loading
Loading
Loading
+15 −6
Original line number Diff line number Diff line
@@ -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);
}
+3 −2
Original line number Diff line number Diff line
@@ -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
+20 −1
Original line number Diff line number Diff line
@@ -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);
+3 −1
Original line number Diff line number Diff line
@@ -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);
+77 −1
Original line number Diff line number Diff line
@@ -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);
Loading