[v3,2/6] kms/nv50: reject interlaced modes if the hardware doesn't support it

Submitted by Karol Herbst on Aug. 3, 2018, 12:19 p.m.

Details

Message ID 20180803121939.582-3-kherbst@redhat.com
State New
Headers show
Series "improve feature detection" ( rev: 2 ) in Nouveau

Not browsing as part of any series.

Commit Message

Karol Herbst Aug. 3, 2018, 12:19 p.m.
I ran into this issue on a gm204 GPU with a display reporting interlaced
modes. Nvidia dropped those modelines for DP, but not HDMI.

We should do the same on hardware where interlaced modes aren't supported
via DP.

v2: save caps for each sor seperatly
    rework setting the caps in nouveau_encoder
v3: rework struct structure

Signed-off-by: Karol Herbst <kherbst@redhat.com>
---
 drm/nouveau/dispnv50/core.h     | 12 ++++++++++++
 drm/nouveau/dispnv50/core507d.c | 25 +++++++++++++++++++++++++
 drm/nouveau/dispnv50/core907d.c | 22 ++++++++++++++++++++++
 drm/nouveau/dispnv50/core917d.c |  2 ++
 drm/nouveau/dispnv50/disp.c     | 31 +++++++++++++++++++++++--------
 drm/nouveau/nouveau_connector.c |  2 ++
 drm/nouveau/nouveau_encoder.h   |  1 +
 7 files changed, 87 insertions(+), 8 deletions(-)

Patch hide | download patch | download mbox

diff --git a/drm/nouveau/dispnv50/core.h b/drm/nouveau/dispnv50/core.h
index 8470df9d..62ef0d24 100644
--- a/drm/nouveau/dispnv50/core.h
+++ b/drm/nouveau/dispnv50/core.h
@@ -8,6 +8,14 @@  struct nv50_core {
 	struct nv50_dmac chan;
 };
 
+struct nv50_core_caps {
+	struct {
+		struct {
+			bool no_interlace;
+		} dp;
+	} sor[8];
+};
+
 int nv50_core_new(struct nouveau_drm *, struct nv50_core **);
 void nv50_core_del(struct nv50_core **);
 
@@ -17,6 +25,8 @@  struct nv50_core_func {
 	int (*ntfy_wait_done)(struct nouveau_bo *, u32 offset,
 			      struct nvif_device *);
 	void (*update)(struct nv50_core *, u32 *interlock, bool ntfy);
+	bool (*caps_fetch)(struct nv50_disp *);
+	bool (*caps_parse)(struct nv50_disp *, struct nv50_core_caps *);
 
 	const struct nv50_head_func *head;
 	const struct nv50_outp_func {
@@ -32,6 +42,7 @@  void core507d_init(struct nv50_core *);
 void core507d_ntfy_init(struct nouveau_bo *, u32);
 int core507d_ntfy_wait_done(struct nouveau_bo *, u32, struct nvif_device *);
 void core507d_update(struct nv50_core *, u32 *, bool);
+bool core507d_caps_fetch(struct nv50_disp *);
 
 extern const struct nv50_outp_func dac507d;
 extern const struct nv50_outp_func sor507d;
@@ -42,6 +53,7 @@  int core827d_new(struct nouveau_drm *, s32, struct nv50_core **);
 int core907d_new(struct nouveau_drm *, s32, struct nv50_core **);
 extern const struct nv50_outp_func dac907d;
 extern const struct nv50_outp_func sor907d;
+bool core907d_caps_parse(struct nv50_disp *, struct nv50_core_caps *);
 
 int core917d_new(struct nouveau_drm *, s32, struct nv50_core **);
 
diff --git a/drm/nouveau/dispnv50/core507d.c b/drm/nouveau/dispnv50/core507d.c
index e7fcfa6e..116b19db 100644
--- a/drm/nouveau/dispnv50/core507d.c
+++ b/drm/nouveau/dispnv50/core507d.c
@@ -43,6 +43,31 @@  core507d_update(struct nv50_core *core, u32 *interlock, bool ntfy)
 	}
 }
 
+bool
+core507d_caps_fetch(struct nv50_disp *disp)
+{
+	struct nv50_core *core = disp->core;
+	u32 *push;
+	int i;
+
+	push = evo_wait(&core->chan, 6);
+	if (!push)
+		return false;
+
+	for (i = 0; i < 512; ++i)
+		nouveau_bo_wr32(disp->sync, i, 0);
+
+	evo_mthd(push, 0x0088, 1);
+	evo_data(push, core->chan.sync.handle);
+	evo_mthd(push, 0x0084, 1);
+	evo_data(push, 0xc0000000);
+	evo_mthd(push, 0x008c, 1);
+	evo_data(push, 0x00000000);
+	evo_kick(push, &core->chan);
+
+	return true;
+}
+
 int
 core507d_ntfy_wait_done(struct nouveau_bo *bo, u32 offset,
 			struct nvif_device *device)
diff --git a/drm/nouveau/dispnv50/core907d.c b/drm/nouveau/dispnv50/core907d.c
index ef822f81..854aa48e 100644
--- a/drm/nouveau/dispnv50/core907d.c
+++ b/drm/nouveau/dispnv50/core907d.c
@@ -22,12 +22,34 @@ 
 #include "core.h"
 #include "head.h"
 
+#include "nouveau_bo.h"
+
+bool
+core907d_caps_parse(struct nv50_disp *disp, struct nv50_core_caps *caps)
+{
+	struct nv50_core *core = disp->core;
+	int i;
+
+	if (core->func->ntfy_wait_done(disp->sync, 0x10,
+				       disp->core->chan.base.device))
+		return false;
+
+	for (i = 0; i < 8; ++i) {
+		uint32_t data = nouveau_bo_rd32(disp->sync, 0x14 + i * 2);
+		caps->sor[i].dp.no_interlace |= !(data & (1 << 26));
+	}
+
+	return true;
+}
+
 static const struct nv50_core_func
 core907d = {
 	.init = core507d_init,
 	.ntfy_init = core507d_ntfy_init,
 	.ntfy_wait_done = core507d_ntfy_wait_done,
 	.update = core507d_update,
+	.caps_fetch = core507d_caps_fetch,
+	.caps_parse = core907d_caps_parse,
 	.head = &head907d,
 	.dac = &dac907d,
 	.sor = &sor907d,
diff --git a/drm/nouveau/dispnv50/core917d.c b/drm/nouveau/dispnv50/core917d.c
index 392338df..5886c723 100644
--- a/drm/nouveau/dispnv50/core917d.c
+++ b/drm/nouveau/dispnv50/core917d.c
@@ -28,6 +28,8 @@  core917d = {
 	.ntfy_init = core507d_ntfy_init,
 	.ntfy_wait_done = core507d_ntfy_wait_done,
 	.update = core507d_update,
+	.caps_fetch = core507d_caps_fetch,
+	.caps_parse = core907d_caps_parse,
 	.head = &head917d,
 	.dac = &dac907d,
 	.sor = &sor907d,
diff --git a/drm/nouveau/dispnv50/disp.c b/drm/nouveau/dispnv50/disp.c
index f04ab219..fa23d7a2 100644
--- a/drm/nouveau/dispnv50/disp.c
+++ b/drm/nouveau/dispnv50/disp.c
@@ -1409,7 +1409,8 @@  nv50_sor_func = {
 };
 
 static int
-nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
+nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe,
+                struct nv50_core_caps *caps)
 {
 	struct nouveau_connector *nv_connector = nouveau_connector(connector);
 	struct nouveau_drm *drm = nouveau_drm(connector->dev);
@@ -1418,23 +1419,26 @@  nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
 	struct nouveau_encoder *nv_encoder;
 	struct drm_encoder *encoder;
 	u8 ver, hdr, cnt, len;
+	u8 or = ffs(dcbe->or) - 1;
 	u32 data;
 	int type, ret;
 
+	nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
+	if (!nv_encoder)
+		return -ENOMEM;
+	nv_encoder->dcb = dcbe;
+	nv_encoder->update = nv50_sor_update;
+
 	switch (dcbe->type) {
 	case DCB_OUTPUT_LVDS: type = DRM_MODE_ENCODER_LVDS; break;
-	case DCB_OUTPUT_TMDS:
 	case DCB_OUTPUT_DP:
+		nv_encoder->dp.no_interlace = caps->sor[or].dp.no_interlace;
+	case DCB_OUTPUT_TMDS:
 	default:
 		type = DRM_MODE_ENCODER_TMDS;
 		break;
 	}
 
-	nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
-	if (!nv_encoder)
-		return -ENOMEM;
-	nv_encoder->dcb = dcbe;
-	nv_encoder->update = nv50_sor_update;
 
 	encoder = to_drm_encoder(nv_encoder);
 	encoder->possible_crtcs = dcbe->heads;
@@ -2160,6 +2164,7 @@  nv50_display_create(struct drm_device *dev)
 	struct drm_connector *connector, *tmp;
 	struct nv50_disp *disp;
 	struct dcb_output *dcbe;
+	struct nv50_core_caps caps = { 0 };
 	int crtcs, ret, i;
 
 	disp = kzalloc(sizeof(*disp), GFP_KERNEL);
@@ -2215,6 +2220,16 @@  nv50_display_create(struct drm_device *dev)
 			goto out;
 	}
 
+	/* fetch caps */
+	if (disp->core->func->caps_fetch && disp->core->func->caps_parse) {
+		if (!disp->core->func->caps_fetch(disp) ||
+		    !disp->core->func->caps_parse(disp, &caps)) {
+			ret = -EIO;
+			NV_ERROR(drm, "Failed to fetch display capabilities.\n");
+			goto out;
+		}
+	}
+
 	/* create encoder/connector objects based on VBIOS DCB table */
 	for (i = 0, dcbe = &dcb->entry[0]; i < dcb->entries; i++, dcbe++) {
 		connector = nouveau_connector_create(dev, dcbe->connector);
@@ -2226,7 +2241,7 @@  nv50_display_create(struct drm_device *dev)
 			case DCB_OUTPUT_TMDS:
 			case DCB_OUTPUT_LVDS:
 			case DCB_OUTPUT_DP:
-				ret = nv50_sor_create(connector, dcbe);
+				ret = nv50_sor_create(connector, dcbe, &caps);
 				break;
 			case DCB_OUTPUT_ANALOG:
 				ret = nv50_dac_create(connector, dcbe);
diff --git a/drm/nouveau/nouveau_connector.c b/drm/nouveau/nouveau_connector.c
index 5cc94944..074e6d52 100644
--- a/drm/nouveau/nouveau_connector.c
+++ b/drm/nouveau/nouveau_connector.c
@@ -1041,6 +1041,8 @@  nouveau_connector_mode_valid(struct drm_connector *connector,
 	case DCB_OUTPUT_TV:
 		return get_slave_funcs(encoder)->mode_valid(encoder, mode);
 	case DCB_OUTPUT_DP:
+                if (mode->flags & DRM_MODE_FLAG_INTERLACE && nv_encoder->dp.no_interlace)
+                   return MODE_NO_INTERLACE;
 		max_clock  = nv_encoder->dp.link_nr;
 		max_clock *= nv_encoder->dp.link_bw;
 		clock = clock * (connector->display_info.bpc * 3) / 10;
diff --git a/drm/nouveau/nouveau_encoder.h b/drm/nouveau/nouveau_encoder.h
index dfe095ad..f74af5ce 100644
--- a/drm/nouveau/nouveau_encoder.h
+++ b/drm/nouveau/nouveau_encoder.h
@@ -63,6 +63,7 @@  struct nouveau_encoder {
 		struct {
 			int link_nr;
 			int link_bw;
+			bool no_interlace;
 		} dp;
 	};