diff --git a/drivers/gpu/drm/nouveau/dispnv50/crc.c b/drivers/gpu/drm/nouveau/dispnv50/crc.c
index c22b9c3ba7677ea3a06bd6a17e168d678e352677..d3780b5968c6f859f1befc1c535f8f44622e35ec 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/crc.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/crc.c
@@ -366,18 +366,49 @@ void nv50_crc_atomic_start_reporting(struct drm_atomic_state *state)
 	}
 }
 
-void nv50_crc_atomic_check(struct nv50_head *head,
-			   struct nv50_head_atom *asyh,
-			   struct nv50_head_atom *armh)
+int nv50_crc_atomic_check(struct nv50_head *head,
+			  struct nv50_head_atom *asyh,
+			  struct nv50_head_atom *armh)
 {
-	struct nv50_atom *atom = nv50_atom(asyh->state.state);
+	struct drm_atomic_state *state = asyh->state.state;
+	struct drm_device *dev = head->base.base.dev;
+	struct nv50_atom *atom = nv50_atom(state);
+	struct nv50_disp *disp = nv50_disp(dev);
 	bool changed = armh->crc.src != asyh->crc.src;
 
-	if ((armh->crc.src || asyh->crc.src) &&
-	    (drm_atomic_crtc_needs_modeset(&asyh->state) || changed)) {
+	if (!armh->crc.src && !asyh->crc.src) {
+		asyh->set.crc = false;
+		asyh->clr.crc = false;
+		return 0;
+	}
+
+	/* While we don't care about entry tags, Volta+ hw always needs the
+	 * controlling wndw channel programmed to a wndw that's owned by our
+	 * head
+	 */
+	if (asyh->crc.src && disp->disp->object.oclass >= GV100_DISP &&
+	    !(BIT(asyh->crc.wndw) & asyh->wndw.owned)) {
+		if (!asyh->wndw.owned) {
+			/* TODO: once we support flexible channel ownership,
+			 * we should write some code here to handle attempting
+			 * to "steal" a plane: e.g. take a plane that is
+			 * currently not-visible and owned by another head,
+			 * and reassign it to this head. If we fail to do so,
+			 * we shuld reject the mode outright as CRC capture
+			 * then becomes impossible.
+			 */
+			NV_ATOMIC(nouveau_drm(dev),
+				  "No available wndws for CRC readback\n");
+			return -EINVAL;
+		}
+		asyh->crc.wndw = ffs(asyh->wndw.owned) - 1;
+		changed = true;
+	}
+
+	if (drm_atomic_crtc_needs_modeset(&asyh->state) || changed) {
 		asyh->clr.crc = armh->crc.src && armh->state.active;
 		asyh->set.crc = asyh->crc.src && asyh->state.active;
-		if (changed)
+		if (changed && asyh->set.crc)
 			asyh->set.or |= armh->or.crc_raster !=
 					asyh->or.crc_raster;
 		if (asyh->set.crc && asyh->clr.crc)
@@ -386,6 +417,8 @@ void nv50_crc_atomic_check(struct nv50_head *head,
 		asyh->set.crc = false;
 		asyh->clr.crc = false;
 	}
+
+	return 0;
 }
 
 static enum nv50_crc_source_type
@@ -411,7 +444,6 @@ nv50_crc_source_type(struct nouveau_encoder *outp,
 }
 
 void nv50_crc_atomic_set(struct nv50_head *head,
-			 struct nv50_head_atom *armh,
 			 struct nv50_head_atom *asyh)
 {
 	struct drm_crtc *crtc = &head->base.base;
@@ -423,19 +455,15 @@ void nv50_crc_atomic_set(struct nv50_head *head,
 
 	func->set_src(head, outp->or,
 		      nv50_crc_source_type(outp, asyh->crc.src),
-		      NV50_CRC_SOURCE_TYPE_NONE, &crc->ctx[crc->ctx_idx]);
+		      &crc->ctx[crc->ctx_idx], asyh->crc.wndw);
 }
 
-void nv50_crc_atomic_clr(struct nv50_head *head,
-			 struct nv50_head_atom *armh)
+void nv50_crc_atomic_clr(struct nv50_head *head)
 {
 	const struct nv50_crc_func *func =
 		nv50_disp(head->base.base.dev)->core->func->crc;
-	struct nouveau_encoder *outp =
-		nv50_real_outp(nv50_head_atom_get_encoder(armh));
 
-	func->set_src(head, 0, NV50_CRC_SOURCE_TYPE_NONE,
-		      nv50_crc_source_type(outp, armh->crc.src), NULL);
+	func->set_src(head, 0, NV50_CRC_SOURCE_TYPE_NONE, NULL, 0);
 }
 
 #define NV50_CRC_RASTER_ACTIVE   0
diff --git a/drivers/gpu/drm/nouveau/dispnv50/crc.h b/drivers/gpu/drm/nouveau/dispnv50/crc.h
index 6e72f2f9e2eceb4f0dfb09ff094f7a7c3bc3559f..73b2193e44ccdb8f19f51bafedee7372ee8f46ca 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/crc.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/crc.h
@@ -43,13 +43,13 @@ struct nv50_crc_notifier_ctx {
 
 struct nv50_crc_atom {
 	enum nv50_crc_source src;
+	/* Only used for gv100+ */
+	u8 wndw : 4;
 };
 
 struct nv50_crc_func {
-	void (*set_src)(struct nv50_head *, int or,
-			enum nv50_crc_source_type to,
-			enum nv50_crc_source_type from,
-			struct nv50_crc_notifier_ctx *);
+	void (*set_src)(struct nv50_head *, int or, enum nv50_crc_source_type,
+			struct nv50_crc_notifier_ctx *, u32 wndw);
 	void (*set_ctx)(struct nv50_head *, struct nv50_crc_notifier_ctx *);
 	bool (*get_entry)(struct nv50_head *, struct nv50_crc_notifier_ctx *,
 			  enum nv50_crc_source, int idx, u32 *);
@@ -81,14 +81,13 @@ int nv50_crc_verify_source(struct drm_crtc *, const char *, size_t *);
 const char *const *nv50_crc_get_sources(struct drm_crtc *, size_t *);
 int nv50_crc_set_source(struct drm_crtc *, const char *);
 
-void nv50_crc_atomic_check(struct nv50_head *, struct nv50_head_atom *,
-			   struct nv50_head_atom *);
+int nv50_crc_atomic_check(struct nv50_head *, struct nv50_head_atom *,
+			  struct nv50_head_atom *);
 void nv50_crc_atomic_stop_reporting(struct drm_atomic_state *);
 void nv50_crc_atomic_prepare_notifier_contexts(struct drm_atomic_state *);
 void nv50_crc_atomic_start_reporting(struct drm_atomic_state *);
-void nv50_crc_atomic_set(struct nv50_head *, struct nv50_head_atom *armh,
-			 struct nv50_head_atom *asyh);
-void nv50_crc_atomic_clr(struct nv50_head *, struct nv50_head_atom *armh);
+void nv50_crc_atomic_set(struct nv50_head *, struct nv50_head_atom *);
+void nv50_crc_atomic_clr(struct nv50_head *);
 
 extern const struct nv50_crc_func crc907d;
 extern const struct nv50_crc_func crcc37d;
@@ -107,7 +106,7 @@ static inline void nv50_crc_fini(struct nv50_crc *) {}
 static inline void nv50_crc_get_entries(struct nv50_head *) {}
 static inline int nv50_crc_late_register(nv50_head *) { return 0; }
 
-static inline void
+static inline int
 nv50_crc_atomic_check(struct nv50_head *, struct nv50_head_atom *,
 		      struct nv50_head_atom *) {}
 static inline void
@@ -117,10 +116,9 @@ nv50_crc_atomic_prepare_notifier_contexts(struct drm_atomic_state *) {}
 static inline void
 nv50_crc_atomic_start_reporting(struct drm_atomic_state *) {}
 static inline void
-nv50_crc_atomic_set(struct nv50_head *, struct nv50_head_atom *,
-		    struct nv50_head_atom *) {}
+nv50_crc_atomic_set(struct nv50_head *, struct nv50_head_atom *) {}
 static inline void
-nv50_crc_atomic_clr(struct nv50_head *, struct nv50_head_atom *) {}
+nv50_crc_atomic_clr(struct nv50_head *) {}
 
 #endif /* IS_ENABLED(CONFIG_DEBUG_FS) */
 #endif /* !__NV50_CRC_H__ */
diff --git a/drivers/gpu/drm/nouveau/dispnv50/crc907d.c b/drivers/gpu/drm/nouveau/dispnv50/crc907d.c
index 1a76b996e696b52cdd0b02fe3a15b050d30bc4ae..947856315666e8ba27c2603b6626ea1a4cbfdc6c 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/crc907d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/crc907d.c
@@ -19,8 +19,8 @@ struct crc907d_notifier {
 
 static void
 crc907d_set_src(struct nv50_head *head, int or,
-		enum nv50_crc_source_type to, enum nv50_crc_source_type from,
-		struct nv50_crc_notifier_ctx *ctx)
+		enum nv50_crc_source_type source,
+		struct nv50_crc_notifier_ctx *ctx, u32 wndw)
 {
 	struct drm_crtc *crtc = &head->base.base;
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
@@ -28,7 +28,7 @@ crc907d_set_src(struct nv50_head *head, int or,
 	u32 *push;
 	u32 crc_args = 0xfff00000;
 
-	switch (to) {
+	switch (source) {
 	case NV50_CRC_SOURCE_TYPE_SOR:
 		crc_args |= (0x00000f0f + or * 16) << 8;
 		break;
@@ -53,7 +53,7 @@ crc907d_set_src(struct nv50_head *head, int or,
 	if (!push)
 		return;
 
-	if (to) {
+	if (source) {
 		evo_mthd(push, 0x0438 + hoff, 1);
 		evo_data(push, ctx->ntfy.handle);
 		evo_mthd(push, 0x0430 + hoff, 1);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/crcc37d.c b/drivers/gpu/drm/nouveau/dispnv50/crcc37d.c
index 0e99dc1de22a69d49d04366467f8d5953d84ad16..e75d79c459b146549e753e163da1f42515638337 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/crcc37d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/crcc37d.c
@@ -31,16 +31,15 @@ struct crcc37d_notifier {
 
 static void
 crcc37d_set_src(struct nv50_head *head, int or,
-		enum nv50_crc_source_type to, enum nv50_crc_source_type from,
-		struct nv50_crc_notifier_ctx *ctx)
+		enum nv50_crc_source_type source,
+		struct nv50_crc_notifier_ctx *ctx, u32 wndw)
 {
-
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
 	const u32 hoff = head->base.index * 0x400;
 	u32 *push;
 	u32 crc_args;
 
-	switch (to) {
+	switch (source) {
 	case NV50_CRC_SOURCE_TYPE_SOR:
 		crc_args = (0x00000050 + or) << 12;
 		break;
@@ -59,18 +58,14 @@ crcc37d_set_src(struct nv50_head *head, int or,
 	if (!push)
 		return;
 
-	if (to) {
+	if (source) {
 		evo_mthd(push, 0x2180 + hoff, 1);
 		evo_data(push, ctx->ntfy.handle);
-		if (to != NV50_CRC_SOURCE_TYPE_RG) {
-			evo_mthd(push, 0x2184 + hoff, 1);
-			evo_data(push, crc_args);
-		}
+		evo_mthd(push, 0x2184 + hoff, 1);
+		evo_data(push, crc_args | wndw);
 	} else {
-		if (from != NV50_CRC_SOURCE_TYPE_RG) {
-			evo_mthd(push, 0x2184 + hoff, 1);
-			evo_data(push, crc_args);
-		}
+		evo_mthd(push, 0x2184 + hoff, 1);
+		evo_data(push, 0);
 		evo_mthd(push, 0x2180 + hoff, 1);
 		evo_data(push, 0);
 	}
diff --git a/drivers/gpu/drm/nouveau/dispnv50/head.c b/drivers/gpu/drm/nouveau/dispnv50/head.c
index 6c02f721a2799d66dbf18a3a29665cab15c7d0f5..11ac42f6c67e97d67847f6be5525c783b3263094 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/head.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.c
@@ -44,7 +44,7 @@ nv50_head_flush_clr(struct nv50_head *head,
 	union nv50_head_atom_mask clr = {
 		.mask = asyh->clr.mask & ~(flush ? 0 : asyh->set.mask),
 	};
-	if (clr.crc)  nv50_crc_atomic_clr(head, armh);
+	if (clr.crc)  nv50_crc_atomic_clr(head);
 	if (clr.olut) head->func->olut_clr(head);
 	if (clr.core) head->func->core_clr(head);
 	if (clr.curs) head->func->curs_clr(head);
@@ -70,7 +70,7 @@ nv50_head_flush_set(struct nv50_head *head,
 	if (asyh->set.ovly   ) head->func->ovly    (head, asyh);
 	if (asyh->set.dither ) head->func->dither  (head, asyh);
 	if (asyh->set.procamp) head->func->procamp (head, asyh);
-	if (asyh->set.crc    ) nv50_crc_atomic_set (head, armh, asyh);
+	if (asyh->set.crc    ) nv50_crc_atomic_set (head, asyh);
 	if (asyh->set.or     ) head->func->or      (head, asyh);
 }
 
@@ -321,7 +321,7 @@ nv50_head_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state)
 	struct nouveau_conn_atom *asyc = NULL;
 	struct drm_connector_state *conns;
 	struct drm_connector *conn;
-	int i;
+	int i, ret;
 
 	NV_ATOMIC(drm, "%s atomic_check %d\n", crtc->name, asyh->state.active);
 	if (asyh->state.active) {
@@ -416,7 +416,9 @@ nv50_head_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state)
 		asyh->set.curs = asyh->curs.visible;
 	}
 
-	nv50_crc_atomic_check(head, asyh, armh);
+	ret = nv50_crc_atomic_check(head, asyh, armh);
+	if (ret)
+		return ret;
 
 	if (asyh->clr.mask || asyh->set.mask)
 		nv50_atom(asyh->state.state)->lock_core = true;