Commit 3b52a1f9 authored by Ben Skeggs's avatar Ben Skeggs
Browse files

drm/nouveau/disp/dp: make use of existing output data for link training



Signed-off-by: default avatarBen Skeggs <bskeggs@redhat.com>
parent 2bd651ea
Loading
Loading
Loading
Loading
+64 −103
Original line number Diff line number Diff line
@@ -33,24 +33,13 @@
#include <core/class.h>

#include "dport.h"

#define DBG(fmt, args...) nv_debug(dp->disp, "DP:%04x:%04x: " fmt,             \
				   dp->outp->hasht, dp->outp->hashm, ##args)
#define ERR(fmt, args...) nv_error(dp->disp, "DP:%04x:%04x: " fmt,             \
				   dp->outp->hasht, dp->outp->hashm, ##args)
#include "outpdp.h"

/******************************************************************************
 * link training
 *****************************************************************************/
struct dp_state {
	const struct nouveau_dp_func *func;
	struct nouveau_disp *disp;
	struct dcb_output *outp;
	struct nvbios_dpout info;
	u8 version;
	struct nouveau_i2c_port *aux;
	int head;
	u8  dpcd[16];
	struct nvkm_output_dp *outp;
	int link_nr;
	u32 link_bw;
	u8  stat[6];
@@ -63,14 +52,16 @@ struct dp_state {
static int
dp_set_link_config(struct dp_state *dp)
{
	struct nouveau_disp *disp = dp->disp;
	struct nvkm_output_dp_impl *impl = (void *)nv_oclass(dp->outp);
	struct nvkm_output_dp *outp = dp->outp;
	struct nouveau_disp *disp = nouveau_disp(outp);
	struct nouveau_bios *bios = nouveau_bios(disp);
	struct nvbios_init init = {
		.subdev = nv_subdev(dp->disp),
		.subdev = nv_subdev(disp),
		.bios = bios,
		.offset = 0x0000,
		.outp = dp->outp,
		.crtc = dp->head,
		.outp = &outp->base.info,
		.crtc = -1,
		.execute = 1,
	};
	u32 lnkcmp;
@@ -80,8 +71,8 @@ dp_set_link_config(struct dp_state *dp)
	DBG("%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw);

	/* set desired link configuration on the source */
	if ((lnkcmp = dp->info.lnkcmp)) {
		if (dp->version < 0x30) {
	if ((lnkcmp = dp->outp->info.lnkcmp)) {
		if (outp->version < 0x30) {
			while ((dp->link_bw / 10) < nv_ro16(bios, lnkcmp))
				lnkcmp += 4;
			init.offset = nv_ro16(bios, lnkcmp + 2);
@@ -94,11 +85,11 @@ dp_set_link_config(struct dp_state *dp)
		nvbios_exec(&init);
	}

	ret = dp->func->lnk_ctl(dp->disp, dp->outp, dp->head,
				dp->link_nr, dp->link_bw / 27000,
				dp->dpcd[DPCD_RC02] &
	ret = impl->lnk_ctl(outp, dp->link_nr, dp->link_bw / 27000,
			    outp->dpcd[DPCD_RC02] &
				       DPCD_RC02_ENHANCED_FRAME_CAP);
	if (ret) {
		if (ret < 0)
			ERR("lnk_ctl failed with %d\n", ret);
		return ret;
	}
@@ -106,31 +97,33 @@ dp_set_link_config(struct dp_state *dp)
	/* set desired link configuration on the sink */
	sink[0] = dp->link_bw / 27000;
	sink[1] = dp->link_nr;
	if (dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
	if (outp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
		sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;

	return nv_wraux(dp->aux, DPCD_LC00, sink, 2);
	return nv_wraux(outp->base.edid, DPCD_LC00, sink, 2);
}

static void
dp_set_training_pattern(struct dp_state *dp, u8 pattern)
{
	struct nvkm_output_dp_impl *impl = (void *)nv_oclass(dp->outp);
	struct nvkm_output_dp *outp = dp->outp;
	u8 sink_tp;

	DBG("training pattern %d\n", pattern);
	dp->func->pattern(dp->disp, dp->outp, dp->head, pattern);
	impl->pattern(outp, pattern);

	nv_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1);
	nv_rdaux(outp->base.edid, DPCD_LC02, &sink_tp, 1);
	sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
	sink_tp |= pattern;
	nv_wraux(dp->aux, DPCD_LC02, &sink_tp, 1);
	nv_wraux(outp->base.edid, DPCD_LC02, &sink_tp, 1);
}

static int
dp_link_train_commit(struct dp_state *dp, bool pc)
{
	const struct nouveau_dp_func *func = dp->func;
	struct nouveau_disp *disp = dp->disp;
	struct nvkm_output_dp_impl *impl = (void *)nv_oclass(dp->outp);
	struct nvkm_output_dp *outp = dp->outp;
	int ret, i;

	for (i = 0; i < dp->link_nr; i++) {
@@ -146,15 +139,15 @@ dp_link_train_commit(struct dp_state *dp, bool pc)
		dp->pc2conf[i >> 1] |= 4 << ((i & 1) * 4);

		DBG("config lane %d %02x\n", i, dp->conf[i]);
		func->drv_ctl(disp, dp->outp, dp->head, i, lvsw, lpre);
		impl->drv_ctl(outp, i, lvsw, lpre, 0);
	}

	ret = nv_wraux(dp->aux, DPCD_LC03(0), dp->conf, 4);
	ret = nv_wraux(outp->base.edid, DPCD_LC03(0), dp->conf, 4);
	if (ret)
		return ret;

	if (pc) {
		ret = nv_wraux(dp->aux, DPCD_LC0F, dp->pc2conf, 2);
		ret = nv_wraux(outp->base.edid, DPCD_LC0F, dp->pc2conf, 2);
		if (ret)
			return ret;
	}
@@ -165,19 +158,20 @@ dp_link_train_commit(struct dp_state *dp, bool pc)
static int
dp_link_train_update(struct dp_state *dp, bool pc, u32 delay)
{
	struct nvkm_output_dp *outp = dp->outp;
	int ret;

	if (dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL])
		mdelay(dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4);
	if (outp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL])
		mdelay(outp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4);
	else
		udelay(delay);

	ret = nv_rdaux(dp->aux, DPCD_LS02, dp->stat, 6);
	ret = nv_rdaux(outp->base.edid, DPCD_LS02, dp->stat, 6);
	if (ret)
		return ret;

	if (pc) {
		ret = nv_rdaux(dp->aux, DPCD_LS0C, &dp->pc2stat, 1);
		ret = nv_rdaux(outp->base.edid, DPCD_LS0C, &dp->pc2stat, 1);
		if (ret)
			dp->pc2stat = 0x00;
		DBG("status %6ph pc2 %02x\n", dp->stat, dp->pc2stat);
@@ -225,10 +219,11 @@ dp_link_train_cr(struct dp_state *dp)
static int
dp_link_train_eq(struct dp_state *dp)
{
	struct nvkm_output_dp *outp = dp->outp;
	bool eq_done = false, cr_done = true;
	int tries = 0, i;

	if (dp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED)
	if (outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED)
		dp_set_training_pattern(dp, 3);
	else
		dp_set_training_pattern(dp, 2);
@@ -257,39 +252,45 @@ dp_link_train_eq(struct dp_state *dp)
static void
dp_link_train_init(struct dp_state *dp, bool spread)
{
	struct nvkm_output_dp *outp = dp->outp;
	struct nouveau_disp *disp = nouveau_disp(outp);
	struct nouveau_bios *bios = nouveau_bios(disp);
	struct nvbios_init init = {
		.subdev = nv_subdev(dp->disp),
		.bios = nouveau_bios(dp->disp),
		.outp = dp->outp,
		.crtc = dp->head,
		.subdev = nv_subdev(disp),
		.bios = bios,
		.outp = &outp->base.info,
		.crtc = -1,
		.execute = 1,
	};

	/* set desired spread */
	if (spread)
		init.offset = dp->info.script[2];
		init.offset = outp->info.script[2];
	else
		init.offset = dp->info.script[3];
		init.offset = outp->info.script[3];
	nvbios_exec(&init);

	/* pre-train script */
	init.offset = dp->info.script[0];
	init.offset = outp->info.script[0];
	nvbios_exec(&init);
}

static void
dp_link_train_fini(struct dp_state *dp)
{
	struct nvkm_output_dp *outp = dp->outp;
	struct nouveau_disp *disp = nouveau_disp(outp);
	struct nouveau_bios *bios = nouveau_bios(disp);
	struct nvbios_init init = {
		.subdev = nv_subdev(dp->disp),
		.bios = nouveau_bios(dp->disp),
		.outp = dp->outp,
		.crtc = dp->head,
		.subdev = nv_subdev(disp),
		.bios = bios,
		.outp = &outp->base.info,
		.crtc = -1,
		.execute = 1,
	};

	/* post-train script */
	init.offset = dp->info.script[1],
	init.offset = outp->info.script[1],
	nvbios_exec(&init);
}

@@ -311,65 +312,25 @@ static const struct dp_rates {
};

int
nouveau_dp_train(struct nouveau_disp *disp, const struct nouveau_dp_func *func,
		 struct dcb_output *outp, int head, u32 datarate)
nouveau_dp_train(struct nvkm_output_dp *outp, u32 datarate)
{
	struct nouveau_bios *bios = nouveau_bios(disp);
	struct nouveau_i2c *i2c = nouveau_i2c(disp);
	struct nouveau_disp *disp = nouveau_disp(outp);
	const struct dp_rates *cfg = nouveau_dp_rates;
	struct dp_state _dp = {
		.disp = disp,
		.func = func,
		.outp = outp,
		.head = head,
	}, *dp = &_dp;
	u8  hdr, cnt, len;
	u32 data;
	int ret;

	/* find the bios displayport data relevant to this output */
	data = nvbios_dpout_match(bios, outp->hasht, outp->hashm, &dp->version,
				 &hdr, &cnt, &len, &dp->info);
	if (!data) {
		ERR("bios data not found\n");
		return -EINVAL;
	}

	/* acquire the aux channel and fetch some info about the display */
	if (outp->location)
		dp->aux = i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(outp->extdev));
	else
		dp->aux = i2c->find(i2c, NV_I2C_TYPE_DCBI2C(outp->i2c_index));
	if (!dp->aux) {
		ERR("no aux channel?!\n");
		return -ENODEV;
	}

	ret = nv_rdaux(dp->aux, 0x00000, dp->dpcd, sizeof(dp->dpcd));
	if (ret) {
		/* it's possible the display has been unplugged before we
		 * get here.  we still need to execute the full set of
		 * vbios scripts, and program the OR at a high enough
		 * frequency to satisfy the target mode.  failure to do
		 * so results at best in an UPDATE hanging, and at worst
		 * with PDISP running away to join the circus.
		 */
		dp->dpcd[1] = dp->outp->dpconf.link_bw;
		dp->dpcd[2] = dp->outp->dpconf.link_nr;
		dp->dpcd[3] = 0x00;
		ERR("failed to read DPCD\n");
	}

	/* bring capabilities within encoder limits */
	if (nv_mclass(disp) < NVD0_DISP_CLASS)
		dp->dpcd[2] &= ~DPCD_RC02_TPS3_SUPPORTED;
	if ((dp->dpcd[2] & 0x1f) > dp->outp->dpconf.link_nr) {
		dp->dpcd[2] &= ~DPCD_RC02_MAX_LANE_COUNT;
		dp->dpcd[2] |= dp->outp->dpconf.link_nr;
		outp->dpcd[2] &= ~DPCD_RC02_TPS3_SUPPORTED;
	if ((outp->dpcd[2] & 0x1f) > outp->base.info.dpconf.link_nr) {
		outp->dpcd[2] &= ~DPCD_RC02_MAX_LANE_COUNT;
		outp->dpcd[2] |= outp->base.info.dpconf.link_nr;
	}
	if (dp->dpcd[1] > dp->outp->dpconf.link_bw)
		dp->dpcd[1] = dp->outp->dpconf.link_bw;
	dp->pc2 = dp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED;
	if (outp->dpcd[1] > outp->base.info.dpconf.link_bw)
		outp->dpcd[1] = outp->base.info.dpconf.link_bw;
	dp->pc2 = outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED;

	/* restrict link config to the lowest required rate, if requested */
	if (datarate) {
@@ -380,12 +341,12 @@ nouveau_dp_train(struct nouveau_disp *disp, const struct nouveau_dp_func *func,
	cfg--;

	/* enable down-spreading and execute pre-train script from vbios */
	dp_link_train_init(dp, dp->dpcd[3] & 0x01);
	dp_link_train_init(dp, outp->dpcd[3] & 0x01);

	while (ret = -EIO, (++cfg)->rate) {
		/* select next configuration supported by encoder and sink */
		while (cfg->nr > (dp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT) ||
		       cfg->bw > (dp->dpcd[DPCD_RC01_MAX_LINK_RATE]))
		while (cfg->nr > (outp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT) ||
		       cfg->bw > (outp->dpcd[DPCD_RC01_MAX_LINK_RATE]))
			cfg++;
		dp->link_bw = cfg->bw * 27000;
		dp->link_nr = cfg->nr;
+0 −19
Original line number Diff line number Diff line
@@ -71,23 +71,4 @@
#define DPCD_LS0C_LANE1_POST_CURSOR2                                       0x0c
#define DPCD_LS0C_LANE0_POST_CURSOR2                                       0x03

struct nouveau_disp;
struct dcb_output;

struct nouveau_dp_func {
	int (*pattern)(struct nouveau_disp *, struct dcb_output *,
		       int head, int pattern);
	int (*lnk_ctl)(struct nouveau_disp *, struct dcb_output *, int head,
		       int link_nr, int link_bw, bool enh_frame);
	int (*drv_ctl)(struct nouveau_disp *, struct dcb_output *, int head,
		       int lane, int swing, int preem);
};

extern const struct nouveau_dp_func nv94_sor_dp_func;
extern const struct nouveau_dp_func nvd0_sor_dp_func;
extern const struct nouveau_dp_func nv50_pior_dp_func;

int nouveau_dp_train(struct nouveau_disp *, const struct nouveau_dp_func *,
		     struct dcb_output *, int, u32);

#endif
+0 −1
Original line number Diff line number Diff line
@@ -81,7 +81,6 @@ gm107_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
	priv->sor.power = nv50_sor_power;
	priv->sor.hda_eld = nvd0_hda_eld;
	priv->sor.hdmi = nvd0_hdmi_ctrl;
	priv->sor.dp = &nvd0_sor_dp_func;
	return 0;
}

+2 −5
Original line number Diff line number Diff line
@@ -1471,8 +1471,7 @@ nv50_disp_intr_unk20_2(struct nv50_disp_priv *priv, int head)
			break;
		}

		nouveau_dp_train(&priv->base, priv->sor.dp,
				 &outp->info, head, datarate);
		nouveau_dp_train((void *)outp, datarate);
	}

	exec_clkcmp(priv, head, 0, pclk, &conf);
@@ -1551,8 +1550,7 @@ nv50_disp_intr_unk40_0(struct nv50_disp_priv *priv, int head)
			break;
		}

		nouveau_dp_train(&priv->base, priv->pior.dp,
				 &outp->info, head, datarate);
		nouveau_dp_train((void *)outp, datarate);
	}
}

@@ -1665,7 +1663,6 @@ nv50_disp_ctor(struct nouveau_object *parent, struct nouveau_object *engine,
	priv->dac.sense = nv50_dac_sense;
	priv->sor.power = nv50_sor_power;
	priv->pior.power = nv50_pior_power;
	priv->pior.dp = &nv50_pior_dp_func;
	return 0;
}

+0 −2
Original line number Diff line number Diff line
@@ -45,13 +45,11 @@ struct nv50_disp_priv {
		int (*hda_eld)(struct nv50_disp_priv *, int sor, u8 *, u32);
		int (*hdmi)(struct nv50_disp_priv *, int head, int sor, u32);
		u32 lvdsconf;
		const struct nouveau_dp_func *dp;
	} sor;
	struct {
		int nr;
		int (*power)(struct nv50_disp_priv *, int ext, u32 data);
		u8 type[3];
		const struct nouveau_dp_func *dp;
	} pior;
};

Loading