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

Commit af85389c authored by Ben Skeggs's avatar Ben Skeggs
Browse files

drm/nouveau/disp: shuffle functions around



Upcoming changes to split OR from output path drastically change the
placement of various operations.

In order to make the real changes clearer, do the moving around part
ahead of time.

Signed-off-by: default avatarBen Skeggs <bskeggs@redhat.com>
parent 639d72e2
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -12,9 +12,8 @@ nvkm-y += nvkm/engine/disp/gm107.o
nvkm-y += nvkm/engine/disp/gm200.o
nvkm-y += nvkm/engine/disp/gp100.o
nvkm-y += nvkm/engine/disp/gp102.o
nvkm-y += nvkm/engine/disp/vga.o

nvkm-y += nvkm/engine/disp/outp.o
nvkm-y += nvkm/engine/disp/outpdp.o
nvkm-y += nvkm/engine/disp/dacnv50.o
nvkm-y += nvkm/engine/disp/piornv50.o
nvkm-y += nvkm/engine/disp/sornv50.o
@@ -22,20 +21,20 @@ nvkm-y += nvkm/engine/disp/sorg94.o
nvkm-y += nvkm/engine/disp/sorgf119.o
nvkm-y += nvkm/engine/disp/sorgm107.o
nvkm-y += nvkm/engine/disp/sorgm200.o
nvkm-y += nvkm/engine/disp/dport.o

nvkm-y += nvkm/engine/disp/conn.o
nvkm-y += nvkm/engine/disp/outp.o
nvkm-y += nvkm/engine/disp/dp.o

nvkm-y += nvkm/engine/disp/hdagt215.o
nvkm-y += nvkm/engine/disp/hdagf119.o

nvkm-y += nvkm/engine/disp/hdmi_infoframe.o
nvkm-y += nvkm/engine/disp/hdmi.o
nvkm-y += nvkm/engine/disp/hdmig84.o
nvkm-y += nvkm/engine/disp/hdmigt215.o
nvkm-y += nvkm/engine/disp/hdmigf119.o
nvkm-y += nvkm/engine/disp/hdmigk104.o

nvkm-y += nvkm/engine/disp/vga.o
nvkm-y += nvkm/engine/disp/conn.o

nvkm-y += nvkm/engine/disp/rootnv04.o
nvkm-y += nvkm/engine/disp/rootnv50.o
+40 −40
Original line number Diff line number Diff line
@@ -30,40 +30,16 @@
#include <nvif/cl5070.h>
#include <nvif/unpack.h>

static const struct nvkm_output_func
nv50_dac_output_func = {
};

int
nv50_dac_power(NV50_DISP_MTHD_V1)
nv50_dac_output_new(struct nvkm_disp *disp, int index,
		    struct dcb_output *dcbE, struct nvkm_output **poutp)
{
	struct nvkm_device *device = disp->base.engine.subdev.device;
	const u32 doff = outp->or * 0x800;
	union {
		struct nv50_disp_dac_pwr_v0 v0;
	} *args = data;
	u32 stat;
	int ret = -ENOSYS;

	nvif_ioctl(object, "disp dac pwr size %d\n", size);
	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
		nvif_ioctl(object, "disp dac pwr vers %d state %d data %d "
				   "vsync %d hsync %d\n",
			   args->v0.version, args->v0.state, args->v0.data,
			   args->v0.vsync, args->v0.hsync);
		stat  = 0x00000040 * !args->v0.state;
		stat |= 0x00000010 * !args->v0.data;
		stat |= 0x00000004 * !args->v0.vsync;
		stat |= 0x00000001 * !args->v0.hsync;
	} else
		return ret;

	nvkm_msec(device, 2000,
		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
			break;
	);
	nvkm_mask(device, 0x61a004 + doff, 0xc000007f, 0x80000000 | stat);
	nvkm_msec(device, 2000,
		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
			break;
	);
	return 0;
	return nvkm_output_new_(&nv50_dac_output_func, disp,
				index, dcbE, poutp);
}

int
@@ -113,14 +89,38 @@ nv50_dac_sense(NV50_DISP_MTHD_V1)
	return 0;
}

static const struct nvkm_output_func
nv50_dac_output_func = {
};

int
nv50_dac_output_new(struct nvkm_disp *disp, int index,
		    struct dcb_output *dcbE, struct nvkm_output **poutp)
nv50_dac_power(NV50_DISP_MTHD_V1)
{
	return nvkm_output_new_(&nv50_dac_output_func, disp,
				index, dcbE, poutp);
	struct nvkm_device *device = disp->base.engine.subdev.device;
	const u32 doff = outp->or * 0x800;
	union {
		struct nv50_disp_dac_pwr_v0 v0;
	} *args = data;
	u32 stat;
	int ret = -ENOSYS;

	nvif_ioctl(object, "disp dac pwr size %d\n", size);
	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
		nvif_ioctl(object, "disp dac pwr vers %d state %d data %d "
				   "vsync %d hsync %d\n",
			   args->v0.version, args->v0.state, args->v0.data,
			   args->v0.vsync, args->v0.hsync);
		stat  = 0x00000040 * !args->v0.state;
		stat |= 0x00000010 * !args->v0.data;
		stat |= 0x00000004 * !args->v0.vsync;
		stat |= 0x00000001 * !args->v0.hsync;
	} else
		return ret;

	nvkm_msec(device, 2000,
		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
			break;
	);
	nvkm_mask(device, 0x61a004 + doff, 0xc000007f, 0x80000000 | stat);
	nvkm_msec(device, 2000,
		if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
			break;
	);
	return 0;
}
+637 −0
Original line number Diff line number Diff line
@@ -21,15 +21,370 @@
 *
 * Authors: Ben Skeggs
 */
#include "outpdp.h"
#include "dp.h"
#include "conn.h"
#include "dport.h"
#include "priv.h"
#include "nv50.h"

#include <subdev/bios.h>
#include <subdev/bios/init.h>
#include <subdev/i2c.h>

#include <nvif/event.h>

struct lt_state {
	struct nvkm_output_dp *outp;
	int link_nr;
	u32 link_bw;
	u8  stat[6];
	u8  conf[4];
	bool pc2;
	u8  pc2stat;
	u8  pc2conf[2];
};

static int
nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay)
{
	struct nvkm_output_dp *outp = lt->outp;
	int ret;

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

	ret = nvkm_rdaux(outp->aux, DPCD_LS02, lt->stat, 6);
	if (ret)
		return ret;

	if (pc) {
		ret = nvkm_rdaux(outp->aux, DPCD_LS0C, &lt->pc2stat, 1);
		if (ret)
			lt->pc2stat = 0x00;
		OUTP_DBG(&outp->base, "status %6ph pc2 %02x",
			 lt->stat, lt->pc2stat);
	} else {
		OUTP_DBG(&outp->base, "status %6ph", lt->stat);
	}

	return 0;
}

static int
nvkm_dp_train_drive(struct lt_state *lt, bool pc)
{
	struct nvkm_output_dp *outp = lt->outp;
	int ret, i;

	for (i = 0; i < lt->link_nr; i++) {
		u8 lane = (lt->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
		u8 lpc2 = (lt->pc2stat >> (i * 2)) & 0x3;
		u8 lpre = (lane & 0x0c) >> 2;
		u8 lvsw = (lane & 0x03) >> 0;
		u8 hivs = 3 - lpre;
		u8 hipe = 3;
		u8 hipc = 3;

		if (lpc2 >= hipc)
			lpc2 = hipc | DPCD_LC0F_LANE0_MAX_POST_CURSOR2_REACHED;
		if (lpre >= hipe) {
			lpre = hipe | DPCD_LC03_MAX_SWING_REACHED; /* yes. */
			lvsw = hivs = 3 - (lpre & 3);
		} else
		if (lvsw >= hivs) {
			lvsw = hivs | DPCD_LC03_MAX_SWING_REACHED;
		}

		lt->conf[i] = (lpre << 3) | lvsw;
		lt->pc2conf[i >> 1] |= lpc2 << ((i & 1) * 4);

		OUTP_DBG(&outp->base, "config lane %d %02x %02x",
			 i, lt->conf[i], lpc2);
		outp->func->drv_ctl(outp, i, lvsw & 3, lpre & 3, lpc2 & 3);
	}

	ret = nvkm_wraux(outp->aux, DPCD_LC03(0), lt->conf, 4);
	if (ret)
		return ret;

	if (pc) {
		ret = nvkm_wraux(outp->aux, DPCD_LC0F, lt->pc2conf, 2);
		if (ret)
			return ret;
	}

	return 0;
}

static void
nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern)
{
	struct nvkm_output_dp *outp = lt->outp;
	u8 sink_tp;

	OUTP_DBG(&outp->base, "training pattern %d", pattern);
	outp->func->pattern(outp, pattern);

	nvkm_rdaux(outp->aux, DPCD_LC02, &sink_tp, 1);
	sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
	sink_tp |= pattern;
	nvkm_wraux(outp->aux, DPCD_LC02, &sink_tp, 1);
}

static int
nvkm_dp_train_eq(struct lt_state *lt)
{
	struct nvkm_output_dp *outp = lt->outp;
	bool eq_done = false, cr_done = true;
	int tries = 0, i;

	if (outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED)
		nvkm_dp_train_pattern(lt, 3);
	else
		nvkm_dp_train_pattern(lt, 2);

	do {
		if ((tries &&
		    nvkm_dp_train_drive(lt, lt->pc2)) ||
		    nvkm_dp_train_sense(lt, lt->pc2, 400))
			break;

		eq_done = !!(lt->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE);
		for (i = 0; i < lt->link_nr && eq_done; i++) {
			u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
			if (!(lane & DPCD_LS02_LANE0_CR_DONE))
				cr_done = false;
			if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
			    !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED))
				eq_done = false;
		}
	} while (!eq_done && cr_done && ++tries <= 5);

	return eq_done ? 0 : -1;
}

static int
nvkm_dp_train_cr(struct lt_state *lt)
{
	bool cr_done = false, abort = false;
	int voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
	int tries = 0, i;

	nvkm_dp_train_pattern(lt, 1);

	do {
		if (nvkm_dp_train_drive(lt, false) ||
		    nvkm_dp_train_sense(lt, false, 100))
			break;

		cr_done = true;
		for (i = 0; i < lt->link_nr; i++) {
			u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
			if (!(lane & DPCD_LS02_LANE0_CR_DONE)) {
				cr_done = false;
				if (lt->conf[i] & DPCD_LC03_MAX_SWING_REACHED)
					abort = true;
				break;
			}
		}

		if ((lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) {
			voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
			tries = 0;
		}
	} while (!cr_done && !abort && ++tries < 5);

	return cr_done ? 0 : -1;
}

static int
nvkm_dp_train_links(struct lt_state *lt)
{
	struct nvkm_output_dp *outp = lt->outp;
	struct nvkm_disp *disp = outp->base.disp;
	struct nvkm_subdev *subdev = &disp->engine.subdev;
	struct nvkm_bios *bios = subdev->device->bios;
	struct nvbios_init init = {
		.subdev = subdev,
		.bios = bios,
		.offset = 0x0000,
		.outp = &outp->base.info,
		.crtc = -1,
		.execute = 1,
	};
	u32 lnkcmp;
	u8 sink[2];
	int ret;

	OUTP_DBG(&outp->base, "%d lanes at %d KB/s", lt->link_nr, lt->link_bw);

	/* Intersect misc. capabilities of the OR and sink. */
	if (disp->engine.subdev.device->chipset < 0xd0)
		outp->dpcd[2] &= ~DPCD_RC02_TPS3_SUPPORTED;
	lt->pc2 = outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED;

	/* Set desired link configuration on the source. */
	if ((lnkcmp = lt->outp->info.lnkcmp)) {
		if (outp->version < 0x30) {
			while ((lt->link_bw / 10) < nvbios_rd16(bios, lnkcmp))
				lnkcmp += 4;
			init.offset = nvbios_rd16(bios, lnkcmp + 2);
		} else {
			while ((lt->link_bw / 27000) < nvbios_rd08(bios, lnkcmp))
				lnkcmp += 3;
			init.offset = nvbios_rd16(bios, lnkcmp + 1);
		}

		nvbios_exec(&init);
	}

	ret = outp->func->lnk_ctl(outp, lt->link_nr, lt->link_bw / 27000,
				  outp->dpcd[DPCD_RC02] &
					     DPCD_RC02_ENHANCED_FRAME_CAP);
	if (ret) {
		if (ret < 0)
			OUTP_ERR(&outp->base, "lnk_ctl failed with %d", ret);
		return ret;
	}

	outp->func->lnk_pwr(outp, lt->link_nr);

	/* Set desired link configuration on the sink. */
	sink[0] = lt->link_bw / 27000;
	sink[1] = lt->link_nr;
	if (outp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
		sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;

	return nvkm_wraux(outp->aux, DPCD_LC00_LINK_BW_SET, sink, 2);
}

static void
nvkm_dp_train_fini(struct lt_state *lt)
{
	struct nvkm_output_dp *outp = lt->outp;
	struct nvkm_disp *disp = outp->base.disp;
	struct nvkm_subdev *subdev = &disp->engine.subdev;
	struct nvbios_init init = {
		.subdev = subdev,
		.bios = subdev->device->bios,
		.outp = &outp->base.info,
		.crtc = -1,
		.execute = 1,
	};

	/* Execute AfterLinkTraining script from DP Info table. */
	init.offset = outp->info.script[1],
	nvbios_exec(&init);
}

static void
nvkm_dp_train_init(struct lt_state *lt, bool spread)
{
	struct nvkm_output_dp *outp = lt->outp;
	struct nvkm_disp *disp = outp->base.disp;
	struct nvkm_subdev *subdev = &disp->engine.subdev;
	struct nvbios_init init = {
		.subdev = subdev,
		.bios = subdev->device->bios,
		.outp = &outp->base.info,
		.crtc = -1,
		.execute = 1,
	};

	/* Execute EnableSpread/DisableSpread script from DP Info table. */
	if (spread)
		init.offset = outp->info.script[2];
	else
		init.offset = outp->info.script[3];
	nvbios_exec(&init);

	/* Execute BeforeLinkTraining script from DP info table. */
	init.offset = outp->info.script[0];
	nvbios_exec(&init);
}

static const struct dp_rates {
	u32 rate;
	u8  bw;
	u8  nr;
} nvkm_dp_rates[] = {
	{ 2160000, 0x14, 4 },
	{ 1080000, 0x0a, 4 },
	{ 1080000, 0x14, 2 },
	{  648000, 0x06, 4 },
	{  540000, 0x0a, 2 },
	{  540000, 0x14, 1 },
	{  324000, 0x06, 2 },
	{  270000, 0x0a, 1 },
	{  162000, 0x06, 1 },
	{}
};

static void
nvkm_dp_train(struct nvkm_output_dp *outp)
{
	struct nv50_disp *disp = nv50_disp(outp->base.disp);
	const struct dp_rates *cfg = nvkm_dp_rates - 1;
	struct lt_state lt = {
		.outp = outp,
	};
	u8  pwr;
	int ret;

	if (!outp->base.info.location && disp->func->sor.magic)
		disp->func->sor.magic(&outp->base);

	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 (outp->dpcd[1] > outp->base.info.dpconf.link_bw)
		outp->dpcd[1] = outp->base.info.dpconf.link_bw;

	/* Ensure sink is not in a low-power state. */
	if (!nvkm_rdaux(outp->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->aux, DPCD_SC00, &pwr, 1);
		}
	}

	/* Link training. */
	nvkm_dp_train_init(&lt, outp->dpcd[3] & 0x01);
	while (ret = -EIO, (++cfg)->rate) {
		/* Skip configurations not supported by both OR and sink. */
		while (cfg->nr > (outp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT) ||
		       cfg->bw > (outp->dpcd[DPCD_RC01_MAX_LINK_RATE]))
			cfg++;
		lt.link_bw = cfg->bw * 27000;
		lt.link_nr = cfg->nr;

		/* Program selected link configuration. */
		ret = nvkm_dp_train_links(&lt);
		if (ret == 0) {
			/* Attempt to train the link in this configuration. */
			memset(lt.stat, 0x00, sizeof(lt.stat));
			if (!nvkm_dp_train_cr(&lt) &&
			    !nvkm_dp_train_eq(&lt))
				break;
		} else
		if (ret) {
			/* nvkm_dp_train_links() handled training, or
			 * we failed to communicate with the sink.
			 */
			break;
		}
	}
	nvkm_dp_train_pattern(&lt, 0);
	nvkm_dp_train_fini(&lt);
	if (ret < 0)
		OUTP_ERR(&outp->base, "link training failed");

	OUTP_DBG(&outp->base, "training complete");
	atomic_set(&outp->lt.done, 1);
}

int
nvkm_output_dp_train(struct nvkm_output *base, u32 datarate)
{
+67 −5
Original line number Diff line number Diff line
#ifndef __NVKM_DISP_DPORT_H__
#define __NVKM_DISP_DPORT_H__
struct nvkm_output_dp;
#ifndef __NVKM_DISP_OUTP_DP_H__
#define __NVKM_DISP_OUTP_DP_H__
#define nvkm_output_dp(p) container_of((p), struct nvkm_output_dp, base)
#include "outp.h"

#include <core/notify.h>
#include <subdev/bios.h>
#include <subdev/bios/dp.h>

struct nvkm_output_dp {
	const struct nvkm_output_dp_func *func;
	struct nvkm_output base;

	struct nvbios_dpout info;
	u8 version;

	struct nvkm_i2c_aux *aux;

	struct nvkm_notify irq;
	struct nvkm_notify hpd;
	bool present;
	u8 dpcd[16];

	struct mutex mutex;
	struct {
		atomic_t done;
		bool mst;
	} lt;
};

struct nvkm_output_dp_func {
	int (*pattern)(struct nvkm_output_dp *, int);
	int (*lnk_pwr)(struct nvkm_output_dp *, int nr);
	int (*lnk_ctl)(struct nvkm_output_dp *, int nr, int bw, bool ef);
	int (*drv_ctl)(struct nvkm_output_dp *, int ln, int vs, int pe, int pc);
	void (*vcpi)(struct nvkm_output_dp *, int head, u8 start_slot,
		     u8 num_slots, u16 pbn, u16 aligned_pbn);
};

int nvkm_output_dp_train(struct nvkm_output *, u32 rate);

int nvkm_output_dp_ctor(const struct nvkm_output_dp_func *, struct nvkm_disp *,
			int index, struct dcb_output *, struct nvkm_i2c_aux *,
			struct nvkm_output_dp *);
int nvkm_output_dp_new_(const struct nvkm_output_dp_func *, struct nvkm_disp *,
			int index, struct dcb_output *,
			struct nvkm_output **);

int nv50_pior_dp_new(struct nvkm_disp *, int, struct dcb_output *,
		     struct nvkm_output **);

int g94_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
		   struct nvkm_output **);
int g94_sor_dp_lnk_pwr(struct nvkm_output_dp *, int);

int gf119_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
		     struct nvkm_output **);
int gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *, int, int, bool);
int gf119_sor_dp_drv_ctl(struct nvkm_output_dp *, int, int, int, int);
void gf119_sor_dp_vcpi(struct nvkm_output_dp *, int, u8, u8, u16, u16);

int gm107_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
		     struct nvkm_output **);
int gm107_sor_dp_pattern(struct nvkm_output_dp *, int);

int gm200_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *,
		     struct nvkm_output **);

/* DPCD Receiver Capabilities */
#define DPCD_RC00_DPCD_REV                                              0x00000
@@ -76,6 +140,4 @@ struct nvkm_output_dp;
#define DPCD_SC00_SET_POWER                                                0x03
#define DPCD_SC00_SET_POWER_D0                                             0x01
#define DPCD_SC00_SET_POWER_D3                                             0x03

void nvkm_dp_train(struct nvkm_output_dp *);
#endif
+0 −401

File deleted.

Preview size limit exceeded, changes collapsed.

Loading