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

Commit 8663bc7c authored by Ben Skeggs's avatar Ben Skeggs
Browse files

drm/nouveau/dp: move all nv50/sor-specific code out of nouveau_dp.c



Off-chip encoders (which we don't support yet anyway), and newer chipsets
(such as NVD9...), will need their own code for this.

Signed-off-by: default avatarBen Skeggs <bskeggs@redhat.com>
parent 8c1dcb65
Loading
Loading
Loading
Loading
+16 −217
Original line number Diff line number Diff line
@@ -161,116 +161,6 @@ out:
	return ret;
}

static u32
dp_link_bw_get(struct drm_device *dev, int or, int link)
{
	u32 ctrl = nv_rd32(dev, 0x614300 + (or * 0x800));
	if (!(ctrl & 0x000c0000))
		return 162000;
	return 270000;
}

static int
dp_lane_count_get(struct drm_device *dev, int or, int link)
{
	u32 ctrl = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link));
	switch (ctrl & 0x000f0000) {
	case 0x00010000: return 1;
	case 0x00030000: return 2;
	default:
		return 4;
	}
}

void
nouveau_dp_tu_update(struct drm_device *dev, int or, int link, u32 clk, u32 bpp)
{
	const u32 symbol = 100000;
	int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0;
	int TU, VTUi, VTUf, VTUa;
	u64 link_data_rate, link_ratio, unk;
	u32 best_diff = 64 * symbol;
	u32 link_nr, link_bw, r;

	/* calculate packed data rate for each lane */
	link_nr = dp_lane_count_get(dev, or, link);
	link_data_rate = (clk * bpp / 8) / link_nr;

	/* calculate ratio of packed data rate to link symbol rate */
	link_bw = dp_link_bw_get(dev, or, link);
	link_ratio = link_data_rate * symbol;
	r = do_div(link_ratio, link_bw);

	for (TU = 64; TU >= 32; TU--) {
		/* calculate average number of valid symbols in each TU */
		u32 tu_valid = link_ratio * TU;
		u32 calc, diff;

		/* find a hw representation for the fraction.. */
		VTUi = tu_valid / symbol;
		calc = VTUi * symbol;
		diff = tu_valid - calc;
		if (diff) {
			if (diff >= (symbol / 2)) {
				VTUf = symbol / (symbol - diff);
				if (symbol - (VTUf * diff))
					VTUf++;

				if (VTUf <= 15) {
					VTUa  = 1;
					calc += symbol - (symbol / VTUf);
				} else {
					VTUa  = 0;
					VTUf  = 1;
					calc += symbol;
				}
			} else {
				VTUa  = 0;
				VTUf  = min((int)(symbol / diff), 15);
				calc += symbol / VTUf;
			}

			diff = calc - tu_valid;
		} else {
			/* no remainder, but the hw doesn't like the fractional
			 * part to be zero.  decrement the integer part and
			 * have the fraction add a whole symbol back
			 */
			VTUa = 0;
			VTUf = 1;
			VTUi--;
		}

		if (diff < best_diff) {
			best_diff = diff;
			bestTU = TU;
			bestVTUa = VTUa;
			bestVTUf = VTUf;
			bestVTUi = VTUi;
			if (diff == 0)
				break;
		}
	}

	if (!bestTU) {
		NV_ERROR(dev, "DP: unable to find suitable config\n");
		return;
	}

	/* XXX close to vbios numbers, but not right */
	unk  = (symbol - link_ratio) * bestTU;
	unk *= link_ratio;
	r = do_div(unk, symbol);
	r = do_div(unk, symbol);
	unk += 6;

	nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x000001fc, bestTU << 2);
	nv_mask(dev, NV50_SOR_DP_SCFG(or, link), 0x010f7f3f, bestVTUa << 24 |
							     bestVTUf << 16 |
							     bestVTUi << 8 |
							     unk);
}

u8 *
nouveau_dp_bios_data(struct drm_device *dev, struct dcb_entry *dcb, u8 **entry)
{
@@ -318,13 +208,10 @@ nouveau_dp_bios_data(struct drm_device *dev, struct dcb_entry *dcb, u8 **entry)
 * link training
 *****************************************************************************/
struct dp_state {
	struct dp_train_func *func;
	struct dcb_entry *dcb;
	u8 *table;
	u8 *entry;
	int auxch;
	int crtc;
	int or;
	int link;
	u8 *dpcd;
	int link_nr;
	u32 link_bw;
@@ -335,100 +222,48 @@ struct dp_state {
static void
dp_set_link_config(struct drm_device *dev, struct dp_state *dp)
{
	int or = dp->or, link = dp->link;
	u8 *entry, sink[2];
	u32 dp_ctrl;
	u16 script;
	u8 sink[2];

	NV_DEBUG_KMS(dev, "%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw);

	/* set selected link rate on source */
	switch (dp->link_bw) {
	case 270000:
		nv_mask(dev, 0x614300 + (or * 0x800), 0x000c0000, 0x00040000);
		sink[0] = DP_LINK_BW_2_7;
		break;
	default:
		nv_mask(dev, 0x614300 + (or * 0x800), 0x000c0000, 0x00000000);
		sink[0] = DP_LINK_BW_1_62;
		break;
	}

	/* offset +0x0a of each dp encoder table entry is a pointer to another
	 * table, that has (among other things) pointers to more scripts that
	 * need to be executed, this time depending on link speed.
	 */
	entry = ROMPTR(dev, dp->entry[10]);
	if (entry) {
		if (dp->table[0] < 0x30) {
			while (dp->link_bw < (ROM16(entry[0]) * 10))
				entry += 4;
			script = ROM16(entry[2]);
		} else {
			while (dp->link_bw < (entry[0] * 27000))
				entry += 3;
			script = ROM16(entry[1]);
		}

		nouveau_bios_run_init_table(dev, script, dp->dcb, dp->crtc);
	}
	/* set desired link configuration on the source */
	dp->func->link_set(dev, dp->dcb, dp->crtc, dp->link_nr, dp->link_bw,
			   dp->dpcd[2] & DP_ENHANCED_FRAME_CAP);

	/* configure lane count on the source */
	dp_ctrl = ((1 << dp->link_nr) - 1) << 16;
	/* inform the sink of the new configuration */
	sink[0] = dp->link_bw / 27000;
	sink[1] = dp->link_nr;
	if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP) {
		dp_ctrl |= 0x00004000;
	if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP)
		sink[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
	}

	nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x001f4000, dp_ctrl);

	/* inform the sink of the new configuration */
	auxch_tx(dev, dp->auxch, 8, DP_LINK_BW_SET, sink, 2);
}

static void
dp_set_training_pattern(struct drm_device *dev, struct dp_state *dp, u8 tp)
dp_set_training_pattern(struct drm_device *dev, struct dp_state *dp, u8 pattern)
{
	u8 sink_tp;

	NV_DEBUG_KMS(dev, "training pattern %d\n", tp);
	NV_DEBUG_KMS(dev, "training pattern %d\n", pattern);

	nv_mask(dev, NV50_SOR_DP_CTRL(dp->or, dp->link), 0x0f000000, tp << 24);
	dp->func->train_set(dev, dp->dcb, pattern);

	auxch_tx(dev, dp->auxch, 9, DP_TRAINING_PATTERN_SET, &sink_tp, 1);
	sink_tp &= ~DP_TRAINING_PATTERN_MASK;
	sink_tp |= tp;
	sink_tp |= pattern;
	auxch_tx(dev, dp->auxch, 8, DP_TRAINING_PATTERN_SET, &sink_tp, 1);
}

static const u8 nv50_lane_map[] = { 16, 8, 0, 24 };
static const u8 nvaf_lane_map[] = { 24, 16, 8, 0 };

static int
dp_link_train_commit(struct drm_device *dev, struct dp_state *dp)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	u32 mask = 0, drv = 0, pre = 0, unk = 0;
	const u8 *shifts;
	int link = dp->link;
	int or = dp->or;
	int i;

	if (dev_priv->chipset != 0xaf)
		shifts = nv50_lane_map;
	else
		shifts = nvaf_lane_map;

	for (i = 0; i < dp->link_nr; i++) {
		u8 *conf = dp->entry + dp->table[4];
		u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
		u8 lpre = (lane & 0x0c) >> 2;
		u8 lvsw = (lane & 0x03) >> 0;

		mask |= 0xff << shifts[i];
		unk |= 1 << (shifts[i] >> 3);

		dp->conf[i] = (lpre << 3) | lvsw;
		if (lvsw == DP_TRAIN_VOLTAGE_SWING_1200)
			dp->conf[i] |= DP_TRAIN_MAX_SWING_REACHED;
@@ -436,41 +271,9 @@ dp_link_train_commit(struct drm_device *dev, struct dp_state *dp)
			dp->conf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;

		NV_DEBUG_KMS(dev, "config lane %d %02x\n", i, dp->conf[i]);

		if (dp->table[0] < 0x30) {
			u8 *last = conf + (dp->entry[4] * dp->table[5]);
			while (lvsw != conf[0] || lpre != conf[1]) {
				conf += dp->table[5];
				if (conf >= last)
					return -EINVAL;
			}

			conf += 2;
		} else {
			/* no lookup table anymore, set entries for each
			 * combination of voltage swing and pre-emphasis
			 * level allowed by the DP spec.
			 */
			switch (lvsw) {
			case 0: lpre += 0; break;
			case 1: lpre += 4; break;
			case 2: lpre += 7; break;
			case 3: lpre += 9; break;
		dp->func->train_adj(dev, dp->dcb, i, lvsw, lpre);
	}

			conf = conf + (lpre * dp->table[5]);
			conf++;
		}

		drv |= conf[0] << shifts[i];
		pre |= conf[1] << shifts[i];
		unk  = (unk & ~0x0000ff00) | (conf[2] << 8);
	}

	nv_mask(dev, NV50_SOR_DP_UNK118(or, link), mask, drv);
	nv_mask(dev, NV50_SOR_DP_UNK120(or, link), mask, pre);
	nv_mask(dev, NV50_SOR_DP_UNK130(or, link), 0x0000ff0f, unk);

	return auxch_tx(dev, dp->auxch, 8, DP_TRAINING_LANE0_SET, dp->conf, 4);
}

@@ -598,7 +401,8 @@ dp_link_train_fini(struct drm_device *dev, struct dp_state *dp)
}

bool
nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate)
nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate,
		      struct dp_train_func *func)
{
	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
	struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
@@ -614,15 +418,10 @@ nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate)
	if (!auxch)
		return false;

	dp.table = nouveau_dp_bios_data(dev, nv_encoder->dcb, &dp.entry);
	if (!dp.table)
		return -EINVAL;

	dp.func = func;
	dp.dcb = nv_encoder->dcb;
	dp.crtc = nv_crtc->index;
	dp.auxch = auxch->drive;
	dp.or = nv_encoder->or;
	dp.link = !(nv_encoder->dcb->sorconf.link & 1);
	dp.dpcd = nv_encoder->dp.dpcd;

	/* some sinks toggle hotplug in response to some of the actions
+0 −8
Original line number Diff line number Diff line
@@ -1162,14 +1162,6 @@ int nouveau_ttm_mmap(struct file *, struct vm_area_struct *);
/* nouveau_hdmi.c */
void nouveau_hdmi_mode_set(struct drm_encoder *, struct drm_display_mode *);

/* nouveau_dp.c */
int nouveau_dp_auxch(struct nouveau_i2c_chan *auxch, int cmd, int addr,
		     uint8_t *data, int data_nr);
bool nouveau_dp_detect(struct drm_encoder *);
bool nouveau_dp_link_train(struct drm_encoder *, u32 datarate);
void nouveau_dp_tu_update(struct drm_device *, int, int, u32, u32);
u8 *nouveau_dp_bios_data(struct drm_device *, struct dcb_entry *, u8 **);

/* nv04_fb.c */
extern int  nv04_fb_vram_init(struct drm_device *);
extern int  nv04_fb_init(struct drm_device *);
+18 −0
Original line number Diff line number Diff line
@@ -32,6 +32,14 @@

#define NV_DPMS_CLEARED 0x80

struct dp_train_func {
	void (*link_set)(struct drm_device *, struct dcb_entry *, int crtc,
			 int nr, u32 bw, bool enhframe);
	void (*train_set)(struct drm_device *, struct dcb_entry *, u8 pattern);
	void (*train_adj)(struct drm_device *, struct dcb_entry *,
			  u8 lane, u8 swing, u8 preem);
};

struct nouveau_encoder {
	struct drm_encoder_slave base;

@@ -78,9 +86,19 @@ get_slave_funcs(struct drm_encoder *enc)
	return to_encoder_slave(enc)->slave_funcs;
}

/* nouveau_dp.c */
int nouveau_dp_auxch(struct nouveau_i2c_chan *auxch, int cmd, int addr,
		     uint8_t *data, int data_nr);
bool nouveau_dp_detect(struct drm_encoder *);
bool nouveau_dp_link_train(struct drm_encoder *, u32 datarate,
			   struct dp_train_func *);
u8 *nouveau_dp_bios_data(struct drm_device *, struct dcb_entry *, u8 **);

struct nouveau_connector *
nouveau_encoder_connector_get(struct nouveau_encoder *encoder);
int nv50_sor_create(struct drm_connector *, struct dcb_entry *);
void nv50_sor_dp_calc_tu(struct drm_device *, int, int, u32, u32);
int nv50_dac_create(struct drm_connector *, struct dcb_entry *);


#endif /* __NOUVEAU_ENCODER_H__ */
+2 −2
Original line number Diff line number Diff line
@@ -863,9 +863,9 @@ nv50_display_unk20_handler(struct drm_device *dev)
	if (type == OUTPUT_DP) {
		int link = !(dcb->dpconf.sor.link & 1);
		if ((mc & 0x000f0000) == 0x00020000)
			nouveau_dp_tu_update(dev, or, link, pclk, 18);
			nv50_sor_dp_calc_tu(dev, or, link, pclk, 18);
		else
			nouveau_dp_tu_update(dev, or, link, pclk, 24);
			nv50_sor_dp_calc_tu(dev, or, link, pclk, 24);
	}

	if (dcb->type != OUTPUT_ANALOG) {
+195 −1
Original line number Diff line number Diff line
@@ -36,6 +36,193 @@
#include "nouveau_crtc.h"
#include "nv50_display.h"

static u32
nv50_sor_dp_lane_map(struct drm_device *dev, struct dcb_entry *dcb, u8 lane)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */
	static const u8 nv50[] = { 16, 8, 0, 24 };
	if (dev_priv->card_type == 0xaf)
		return nvaf[lane];
	return nv50[lane];
}

static void
nv50_sor_dp_train_set(struct drm_device *dev, struct dcb_entry *dcb, u8 pattern)
{
	u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
	nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x0f000000, pattern << 24);
}

static void
nv50_sor_dp_train_adj(struct drm_device *dev, struct dcb_entry *dcb,
		      u8 lane, u8 swing, u8 preem)
{
	u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
	u32 shift = nv50_sor_dp_lane_map(dev, dcb, lane);
	u32 mask = 0x000000ff << shift;
	u8 *table, *entry, *config;

	table = nouveau_dp_bios_data(dev, dcb, &entry);
	if (!table || (table[0] != 0x20 && table[0] != 0x21)) {
		NV_ERROR(dev, "PDISP: unsupported DP table for chipset\n");
		return;
	}

	config = entry + table[4];
	while (config[0] != swing || config[1] != preem) {
		config += table[5];
		if (config >= entry + table[4] + entry[4] * table[5])
			return;
	}

	nv_mask(dev, NV50_SOR_DP_UNK118(or, link), mask, config[2] << shift);
	nv_mask(dev, NV50_SOR_DP_UNK120(or, link), mask, config[3] << shift);
	nv_mask(dev, NV50_SOR_DP_UNK130(or, link), 0x0000ff00, config[4] << 8);
}

static void
nv50_sor_dp_link_set(struct drm_device *dev, struct dcb_entry *dcb, int crtc,
		     int link_nr, u32 link_bw, bool enhframe)
{
	u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
	u32 dpctrl = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)) & ~0x001f4000;
	u32 clksor = nv_rd32(dev, 0x614300 + (or * 0x800)) & ~0x000c0000;
	u8 *table, *entry, mask;
	int i;

	table = nouveau_dp_bios_data(dev, dcb, &entry);
	if (!table || (table[0] != 0x20 && table[0] != 0x21)) {
		NV_ERROR(dev, "PDISP: unsupported DP table for chipset\n");
		return;
	}

	entry = ROMPTR(dev, entry[10]);
	if (entry) {
		while (link_bw < ROM16(entry[0]) * 10)
			entry += 4;

		nouveau_bios_run_init_table(dev, ROM16(entry[2]), dcb, crtc);
	}

	dpctrl |= ((1 << link_nr) - 1) << 16;
	if (enhframe)
		dpctrl |= 0x00004000;

	if (link_bw > 162000)
		clksor |= 0x00040000;

	nv_wr32(dev, 0x614300 + (or * 0x800), clksor);
	nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), dpctrl);

	mask = 0;
	for (i = 0; i < link_nr; i++)
		mask |= 1 << (nv50_sor_dp_lane_map(dev, dcb, i) >> 3);
	nv_mask(dev, NV50_SOR_DP_UNK130(or, link), 0x0000000f, mask);
}

static void
nv50_sor_dp_link_get(struct drm_device *dev, u32 or, u32 link, u32 *nr, u32 *bw)
{
	u32 dpctrl = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)) & 0x000f0000;
	u32 clksor = nv_rd32(dev, 0x614300 + (or * 0x800));
	if (clksor & 0x000c0000)
		*bw = 270000;
	else
		*bw = 162000;

	if      (dpctrl > 0x00030000) *nr = 4;
	else if (dpctrl > 0x00010000) *nr = 2;
	else			      *nr = 1;
}

void
nv50_sor_dp_calc_tu(struct drm_device *dev, int or, int link, u32 clk, u32 bpp)
{
	const u32 symbol = 100000;
	int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0;
	int TU, VTUi, VTUf, VTUa;
	u64 link_data_rate, link_ratio, unk;
	u32 best_diff = 64 * symbol;
	u32 link_nr, link_bw, r;

	/* calculate packed data rate for each lane */
	nv50_sor_dp_link_get(dev, or, link, &link_nr, &link_bw);
	link_data_rate = (clk * bpp / 8) / link_nr;

	/* calculate ratio of packed data rate to link symbol rate */
	link_ratio = link_data_rate * symbol;
	r = do_div(link_ratio, link_bw);

	for (TU = 64; TU >= 32; TU--) {
		/* calculate average number of valid symbols in each TU */
		u32 tu_valid = link_ratio * TU;
		u32 calc, diff;

		/* find a hw representation for the fraction.. */
		VTUi = tu_valid / symbol;
		calc = VTUi * symbol;
		diff = tu_valid - calc;
		if (diff) {
			if (diff >= (symbol / 2)) {
				VTUf = symbol / (symbol - diff);
				if (symbol - (VTUf * diff))
					VTUf++;

				if (VTUf <= 15) {
					VTUa  = 1;
					calc += symbol - (symbol / VTUf);
				} else {
					VTUa  = 0;
					VTUf  = 1;
					calc += symbol;
				}
			} else {
				VTUa  = 0;
				VTUf  = min((int)(symbol / diff), 15);
				calc += symbol / VTUf;
			}

			diff = calc - tu_valid;
		} else {
			/* no remainder, but the hw doesn't like the fractional
			 * part to be zero.  decrement the integer part and
			 * have the fraction add a whole symbol back
			 */
			VTUa = 0;
			VTUf = 1;
			VTUi--;
		}

		if (diff < best_diff) {
			best_diff = diff;
			bestTU = TU;
			bestVTUa = VTUa;
			bestVTUf = VTUf;
			bestVTUi = VTUi;
			if (diff == 0)
				break;
		}
	}

	if (!bestTU) {
		NV_ERROR(dev, "DP: unable to find suitable config\n");
		return;
	}

	/* XXX close to vbios numbers, but not right */
	unk  = (symbol - link_ratio) * bestTU;
	unk *= link_ratio;
	r = do_div(unk, symbol);
	r = do_div(unk, symbol);
	unk += 6;

	nv_mask(dev, NV50_SOR_DP_CTRL(or, link), 0x000001fc, bestTU << 2);
	nv_mask(dev, NV50_SOR_DP_SCFG(or, link), 0x010f7f3f, bestVTUa << 24 |
							     bestVTUf << 16 |
							     bestVTUi << 8 |
							     unk);
}
static void
nv50_sor_disconnect(struct drm_encoder *encoder)
{
@@ -124,9 +311,16 @@ nv50_sor_dpms(struct drm_encoder *encoder, int mode)
			return;

		if (mode == DRM_MODE_DPMS_ON) {
			struct dp_train_func func = {
				.link_set = nv50_sor_dp_link_set,
				.train_set = nv50_sor_dp_train_set,
				.train_adj = nv50_sor_dp_train_adj
			};
			u32 rate = nv_encoder->dp.datarate;
			u8 status = DP_SET_POWER_D0;

			nouveau_dp_auxch(auxch, 8, DP_SET_POWER, &status, 1);
			nouveau_dp_link_train(encoder, nv_encoder->dp.datarate);
			nouveau_dp_link_train(encoder, rate, &func);
		} else {
			u8 status = DP_SET_POWER_D3;
			nouveau_dp_auxch(auxch, 8, DP_SET_POWER, &status, 1);