Commit 3147ce0d authored by Ben Skeggs's avatar Ben Skeggs Committed by Lyude Paul
Browse files

drm/nouveau/disp: move link training out of supervisor

parent 63371650
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ union nvif_outp_args {
#define NVIF_OUTP_V0_DP_AUX_XFER   0x71
#define NVIF_OUTP_V0_DP_RATES      0x72
#define NVIF_OUTP_V0_DP_TRAIN      0x73
#define NVIF_OUTP_V0_DP_DRIVE      0x74
#define NVIF_OUTP_V0_DP_MST_VCPI   0x78

union nvif_outp_detect_args {
@@ -211,6 +212,16 @@ union nvif_outp_dp_train_args {
	} v0;
};

union nvif_outp_dp_drive_args {
	struct nvif_outp_dp_drive_v0 {
		__u8  version;
		__u8  pad01[2];
		__u8  lanes;
		__u8  pe[4];
		__u8  vs[4];
	} v0;
};

union nvif_outp_dp_mst_vcpi_args {
	struct nvif_outp_dp_mst_vcpi_v0 {
		__u8  version;
+1 −0
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ int nvif_outp_dp_rates(struct nvif_outp *, struct nvif_outp_dp_rate *rate, int r
int nvif_outp_dp_train(struct nvif_outp *, u8 dpcd[DP_RECEIVER_CAP_SIZE],
		       u8 lttprs, u8 link_nr, u32 link_bw, bool mst, bool post_lt_adj,
		       bool retrain);
int nvif_outp_dp_drive(struct nvif_outp *, u8 link_nr, u8 pe[4], u8 vs[4]);
int nvif_outp_dp_mst_vcpi(struct nvif_outp *, int head,
			  u8 start_slot, u8 num_slots, u16 pbn, u16 aligned_pbn);
#endif
+115 −7
Original line number Diff line number Diff line
@@ -320,15 +320,83 @@ nouveau_dp_power_down(struct nouveau_encoder *outp)
static bool
nouveau_dp_train_link(struct nouveau_encoder *outp, bool retrain)
{
	int ret;
	struct drm_dp_aux *aux = &outp->conn->aux;
	bool post_lt = false;
	int ret, retries = 0;

	if ( (outp->dp.dpcd[DP_MAX_LANE_COUNT] & 0x20) &&
	    !(outp->dp.dpcd[DP_MAX_DOWNSPREAD] & DP_TPS4_SUPPORTED))
	    post_lt = true;

retry:
	ret = nvif_outp_dp_train(&outp->outp, outp->dp.dpcd,
					      outp->dp.lttpr.nr,
					      outp->dp.lt.nr,
					      outp->dp.lt.bw,
					      outp->dp.lt.mst,
					      false,
					      post_lt,
					      retrain);
	if (ret)
		return false;

	if (post_lt) {
		u8 stat[DP_LINK_STATUS_SIZE];
		u8 prev[2];
		u8 time = 0, adjusts = 0, tmp;

		ret = drm_dp_dpcd_read_phy_link_status(aux, DP_PHY_DPRX, stat);
		if (ret)
			return false;

		for (;;) {
			if (!drm_dp_channel_eq_ok(stat, outp->dp.lt.nr)) {
				ret = 1;
				break;
			}

			if (!(stat[2] & 0x02))
				break;

			msleep(5);
			time += 5;

			memcpy(prev, &stat[4], sizeof(prev));
			ret = drm_dp_dpcd_read_phy_link_status(aux, DP_PHY_DPRX, stat);
			if (ret)
				break;

			if (!memcmp(prev, &stat[4], sizeof(prev))) {
				if (time > 200)
					break;
			} else {
				u8 pe[4], vs[4];

				if (adjusts++ == 6)
					break;

				for (int i = 0; i < outp->dp.lt.nr; i++) {
					pe[i] = drm_dp_get_adjust_request_pre_emphasis(stat, i) >>
							DP_TRAIN_PRE_EMPHASIS_SHIFT;
					vs[i] = drm_dp_get_adjust_request_voltage(stat, i) >>
							DP_TRAIN_VOLTAGE_SWING_SHIFT;
				}

				ret = nvif_outp_dp_drive(&outp->outp, outp->dp.lt.nr, pe, vs);
				if (ret)
					break;

				time = 0;
			}
		}

		if (drm_dp_dpcd_readb(aux, DP_LANE_COUNT_SET, &tmp) == 1) {
			tmp &= ~0x20;
			drm_dp_dpcd_writeb(aux, DP_LANE_COUNT_SET, tmp);
		}
	}

	if (ret == 1 && retries++ < 3)
		goto retry;

	return ret == 0;
}
@@ -336,15 +404,44 @@ nouveau_dp_train_link(struct nouveau_encoder *outp, bool retrain)
bool
nouveau_dp_train(struct nouveau_encoder *outp, bool mst, u32 khz, u8 bpc)
{
	bool ret;
	struct nouveau_drm *drm = nouveau_drm(outp->base.base.dev);
	struct drm_dp_aux *aux = &outp->conn->aux;
	u32 min_rate;
	u8 pwr;
	bool ret = true;

	if (mst)
		min_rate = outp->dp.link_nr * outp->dp.rate[0].rate;
	else
		min_rate = DIV_ROUND_UP(khz * bpc * 3, 8);

	NV_DEBUG(drm, "%s link training (mst:%d min_rate:%d)\n",
		 outp->base.base.name, mst, min_rate);

	mutex_lock(&outp->dp.hpd_irq_lock);

	outp->dp.lt.nr = outp->dp.link_nr;
	outp->dp.lt.bw = 0;
	if (drm_dp_dpcd_readb(aux, DP_SET_POWER, &pwr) == 1) {
		if ((pwr & DP_SET_POWER_MASK) != DP_SET_POWER_D0) {
			pwr &= ~DP_SET_POWER_MASK;
			pwr |=  DP_SET_POWER_D0;
			drm_dp_dpcd_writeb(aux, DP_SET_POWER, pwr);
		}
	}

	for (int nr = outp->dp.link_nr; nr; nr >>= 1) {
		for (int rate = 0; rate < outp->dp.rate_nr; rate++) {
			if (outp->dp.rate[rate].rate * nr >= min_rate) {
				outp->dp.lt.nr = nr;
				outp->dp.lt.bw = outp->dp.rate[rate].rate;
				outp->dp.lt.mst = mst;
	ret = nouveau_dp_train_link(outp, false);
				if (nouveau_dp_train_link(outp, false))
					goto done;
			}
		}
	}

	ret = false;
done:
	mutex_unlock(&outp->dp.hpd_irq_lock);
	return ret;
}
@@ -352,6 +449,17 @@ nouveau_dp_train(struct nouveau_encoder *outp, bool mst, u32 khz, u8 bpc)
static bool
nouveau_dp_link_check_locked(struct nouveau_encoder *outp)
{
	u8 link_status[DP_LINK_STATUS_SIZE];

	if (!outp || !outp->dp.lt.nr)
		return true;

	if (drm_dp_dpcd_read_phy_link_status(&outp->conn->aux, DP_PHY_DPRX, link_status) < 0)
		return false;

	if (drm_dp_channel_eq_ok(link_status, outp->dp.lt.nr))
		return true;

	return nouveau_dp_train_link(outp, true);
}

+16 −0
Original line number Diff line number Diff line
@@ -46,6 +46,22 @@ nvif_outp_dp_mst_vcpi(struct nvif_outp *outp, int head,
	return ret;
}

int
nvif_outp_dp_drive(struct nvif_outp *outp, u8 link_nr, u8 pe[4], u8 vs[4])
{
	struct nvif_outp_dp_drive_v0 args;
	int ret;

	args.version = 0;
	args.lanes   = link_nr;
	memcpy(args.pe, pe, sizeof(args.pe));
	memcpy(args.vs, vs, sizeof(args.vs));

	ret = nvif_object_mthd(&outp->object, NVIF_OUTP_V0_DP_DRIVE, &args, sizeof(args));
	NVIF_ERRON(ret, &outp->object, "[DP_DRIVE lanes:%d]", args.lanes);
	return ret;
}

int
nvif_outp_dp_train(struct nvif_outp *outp, u8 dpcd[DP_RECEIVER_CAP_SIZE], u8 lttprs,
		   u8 link_nr, u32 link_bw, bool mst, bool post_lt_adj, bool retrain)
+38 −110
Original line number Diff line number Diff line
@@ -315,6 +315,8 @@ nvkm_dp_train_link(struct nvkm_outp *outp, int rate)
	sink[1] = ior->dp.nr;
	if (ior->dp.ef)
		sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;
	if (outp->dp.lt.post_adj)
		sink[1] |= 0x20;

	ret = nvkm_wraux(outp->dp.aux, DPCD_LC00_LINK_BW_SET, sink, 2);
	if (ret)
@@ -455,71 +457,58 @@ nvkm_dp_train_init(struct nvkm_outp *outp)
}

static int
nvkm_dp_train_(struct nvkm_outp *outp, bool retrain)
nvkm_dp_drive(struct nvkm_outp *outp, u8 lanes, u8 pe[4], u8 vs[4])
{
	if (retrain) {
		if (!atomic_read(&outp->dp.lt.done))
			return 0;

		return outp->func->acquire(outp);
	}
	struct lt_state lt = {
		.outp = outp,
		.stat[4] = (pe[0] << 2) | (vs[0] << 0) |
			   (pe[1] << 6) | (vs[1] << 4),
		.stat[5] = (pe[2] << 2) | (vs[2] << 0) |
			   (pe[3] << 6) | (vs[3] << 4),
	};

	return 0;
	return nvkm_dp_train_drive(&lt, false);
}

static int
nvkm_dp_train(struct nvkm_outp *outp, u32 dataKBps)
nvkm_dp_train(struct nvkm_outp *outp, bool retrain)
{
	struct nvkm_ior *ior = outp->ior;
	int ret = -EINVAL, nr, rate;
	u8  pwr;
	int ret, rate;

	/* Retraining link?  Skip source configuration, it can mess up the active modeset. */
	if (atomic_read(&outp->dp.lt.done)) {
	for (rate = 0; rate < outp->dp.rates; rate++) {
			if (outp->dp.rate[rate].rate == ior->dp.bw * 27000)
				return nvkm_dp_train_link(outp, ret);
		if (outp->dp.rate[rate].rate == (retrain ? ior->dp.bw : outp->dp.lt.bw) * 27000)
			break;
	}
		WARN_ON(1);

	if (WARN_ON(rate == outp->dp.rates))
		return -EINVAL;
	}

	/* Ensure sink is not in a low-power state. */
	if (!nvkm_rdaux(outp->dp.aux, DPCD_SC00, &pwr, 1)) {
		if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
			pwr &= ~DPCD_SC00_SET_POWER;
			pwr |=  DPCD_SC00_SET_POWER_D0;
			nvkm_wraux(outp->dp.aux, DPCD_SC00, &pwr, 1);
		}
	/* Retraining link?  Skip source configuration, it can mess up the active modeset. */
	if (retrain) {
		mutex_lock(&outp->dp.mutex);
		ret = nvkm_dp_train_link(outp, rate);
		mutex_unlock(&outp->dp.mutex);
		return ret;
	}

	mutex_lock(&outp->dp.mutex);
	OUTP_DBG(outp, "training");

	ior->dp.mst = outp->dp.lt.mst;
	ior->dp.ef = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP;
	ior->dp.nr = 0;
	ior->dp.bw = outp->dp.lt.bw;
	ior->dp.nr = outp->dp.lt.nr;

	/* Link training. */
	OUTP_DBG(outp, "training");
	nvkm_dp_train_init(outp);

	/* Otherwise, loop through all valid link configurations that support the data rate. */
	for (nr = outp->dp.links; ret < 0 && nr; nr >>= 1) {
		for (rate = 0; ret < 0 && rate < outp->dp.rates; rate++) {
			if (outp->dp.rate[rate].rate * nr >= dataKBps || WARN_ON(!ior->dp.nr)) {
				/* Program selected link configuration. */
				ior->dp.bw = outp->dp.rate[rate].rate / 27000;
				ior->dp.nr = nr;
	ret = nvkm_dp_train_links(outp, rate);
			}
		}
	}

	/* Finish up. */
	nvkm_dp_train_fini(outp);
	if (ret < 0)
		OUTP_ERR(outp, "training failed");
	else
		OUTP_DBG(outp, "training done");
	atomic_set(&outp->dp.lt.done, 1);

	mutex_unlock(&outp->dp.mutex);
	return ret;
}

@@ -537,69 +526,10 @@ nvkm_dp_disable(struct nvkm_outp *outp, struct nvkm_ior *ior)
static void
nvkm_dp_release(struct nvkm_outp *outp)
{
	/* Prevent link from being retrained if sink sends an IRQ. */
	atomic_set(&outp->dp.lt.done, 0);
	outp->ior->dp.nr = 0;
}

static int
nvkm_dp_acquire(struct nvkm_outp *outp)
{
	struct nvkm_ior *ior = outp->ior;
	struct nvkm_head *head;
	bool retrain = true;
	u32 datakbps = 0;
	u32 dataKBps;
	u32 linkKBps;
	u8  stat[3];
	int ret, i;

	mutex_lock(&outp->dp.mutex);

	/* Check that link configuration meets current requirements. */
	list_for_each_entry(head, &outp->disp->heads, head) {
		if (ior->asy.head & (1 << head->id)) {
			u32 khz = (head->asy.hz >> ior->asy.rgdiv) / 1000;
			datakbps += khz * head->asy.or.depth;
		}
	}
	nvkm_dp_disable(outp, outp->ior);

	linkKBps = ior->dp.bw * 27000 * ior->dp.nr;
	dataKBps = DIV_ROUND_UP(datakbps, 8);
	OUTP_DBG(outp, "data %d KB/s link %d KB/s mst %d->%d",
		 dataKBps, linkKBps, ior->dp.mst, outp->dp.lt.mst);
	if (linkKBps < dataKBps || ior->dp.mst != outp->dp.lt.mst) {
		OUTP_DBG(outp, "link requirements changed");
		goto done;
	}

	/* Check that link is still trained. */
	ret = nvkm_rdaux(outp->dp.aux, DPCD_LS02, stat, 3);
	if (ret) {
		OUTP_DBG(outp, "failed to read link status, assuming no sink");
		goto done;
	}

	if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) {
		for (i = 0; i < ior->dp.nr; 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)) {
				OUTP_DBG(outp, "lane %d not equalised", lane);
				goto done;
			}
		}
		retrain = false;
	} else {
		OUTP_DBG(outp, "no inter-lane alignment");
	}

done:
	if (retrain || !atomic_read(&outp->dp.lt.done))
		ret = nvkm_dp_train(outp, dataKBps);
	mutex_unlock(&outp->dp.mutex);
	return ret;
	nvkm_outp_release(outp);
}

void
@@ -638,7 +568,6 @@ nvkm_dp_enable(struct nvkm_outp *outp, bool auxpwr)
		OUTP_DBG(outp, "aux power -> demand");
		nvkm_i2c_aux_monitor(aux, false);
		outp->dp.aux_pwr = false;
		atomic_set(&outp->dp.lt.done, 0);

		/* Restore eDP panel GPIO to its prior state if we changed it, as
		 * it could potentially interfere with other outputs.
@@ -677,14 +606,14 @@ nvkm_dp_func = {
	.fini = nvkm_dp_fini,
	.detect = nvkm_outp_detect,
	.inherit = nvkm_outp_inherit,
	.acquire = nvkm_dp_acquire,
	.acquire = nvkm_outp_acquire,
	.release = nvkm_dp_release,
	.disable = nvkm_dp_disable,
	.bl.get = nvkm_outp_bl_get,
	.bl.set = nvkm_outp_bl_set,
	.dp.aux_pwr = nvkm_dp_aux_pwr,
	.dp.aux_xfer = nvkm_dp_aux_xfer,
	.dp.train = nvkm_dp_train_,
	.dp.train = nvkm_dp_train,
	.dp.drive = nvkm_dp_drive,
};

int
@@ -723,6 +652,5 @@ nvkm_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, struct n
	OUTP_DBG(outp, "bios dp %02x %02x %02x %02x", outp->dp.version, hdr, cnt, len);

	mutex_init(&outp->dp.mutex);
	atomic_set(&outp->dp.lt.done, 0);
	return 0;
}
Loading