kms/nv50: reject interlaced modes if the hardware doesn't support it

Submitted by Karol Herbst on July 19, 2018, 12:08 a.m.

Details

Message ID 20180719000853.1787-1-kherbst@redhat.com
State New
Headers show
Series "kms/nv50: reject interlaced modes if the hardware doesn't support it" ( rev: 1 ) in Nouveau

Not browsing as part of any series.

Commit Message

Karol Herbst July 19, 2018, 12:08 a.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.

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

Patch hide | download patch | download mbox

diff --git a/drm/nouveau/dispnv50/core.h b/drm/nouveau/dispnv50/core.h
index 8470df9d..5ff79c89 100644
--- a/drm/nouveau/dispnv50/core.h
+++ b/drm/nouveau/dispnv50/core.h
@@ -8,6 +8,12 @@  struct nv50_core {
 	struct nv50_dmac chan;
 };
 
+struct nv50_core_caps {
+	struct {
+		bool no_interlace;
+	} dp;
+};
+
 int nv50_core_new(struct nouveau_drm *, struct nv50_core **);
 void nv50_core_del(struct nv50_core **);
 
@@ -17,6 +23,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 +40,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 +51,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..2e1c2fe6 100644
--- a/drm/nouveau/dispnv50/core907d.c
+++ b/drm/nouveau/dispnv50/core907d.c
@@ -22,12 +22,35 @@ 
 #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;
+	caps->dp.no_interlace = false;
+
+	if (core->func->ntfy_wait_done(disp->sync, 0x10,
+				       disp->core->chan.base.device))
+		return false;
+
+	for (i = 0x14; i < 0x24; i += 2) {
+		uint32_t data = nouveau_bo_rd32(disp->sync, i);
+		caps->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 1f8bba8f..970dddf6 100644
--- a/drm/nouveau/dispnv50/disp.c
+++ b/drm/nouveau/dispnv50/disp.c
@@ -1384,7 +1384,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);
@@ -1410,6 +1411,7 @@  nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
 		return -ENOMEM;
 	nv_encoder->dcb = dcbe;
 	nv_encoder->update = nv50_sor_update;
+	nv_encoder->dp.no_interlace = caps->dp.no_interlace;
 
 	encoder = to_drm_encoder(nv_encoder);
 	encoder->possible_crtcs = dcbe->heads;
@@ -2132,6 +2134,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);
@@ -2189,6 +2192,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);
@@ -2200,7 +2213,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 7b557c35..b0e1f617 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 3517f920..a9e55096 100644
--- a/drm/nouveau/nouveau_encoder.h
+++ b/drm/nouveau/nouveau_encoder.h
@@ -63,6 +63,7 @@  struct nouveau_encoder {
 			struct nv50_mstm *mstm;
 			int link_nr;
 			int link_bw;
+			bool no_interlace;
 		} dp;
 	};