Blob Blame History Raw
From: Ben Skeggs <bskeggs@redhat.com>
Date: Tue, 8 May 2018 20:39:47 +1000
Subject: drm/nouveau/kms/nv50-: handle degamma LUT from window channels
Git-commit: 119608a7f3f1ef899f1f98d05306340b92834836
Patch-mainline: v4.18-rc1
References: FATE#326289 FATE#326079 FATE#326049 FATE#322398 FATE#326166

Required to eventually support DRM colour management APIs, and to
support Volta.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Acked-by: Petr Tesarik <ptesarik@suse.com>
---
 drivers/gpu/drm/nouveau/dispnv50/Kbuild     |    1 
 drivers/gpu/drm/nouveau/dispnv50/atom.h     |   25 +++-
 drivers/gpu/drm/nouveau/dispnv50/base.h     |    3 
 drivers/gpu/drm/nouveau/dispnv50/base507c.c |   44 +++++--
 drivers/gpu/drm/nouveau/dispnv50/base827c.c |    4 
 drivers/gpu/drm/nouveau/dispnv50/base907c.c |   43 +++++++
 drivers/gpu/drm/nouveau/dispnv50/disp.c     |   11 +
 drivers/gpu/drm/nouveau/dispnv50/head.c     |  158 ++++++++++------------------
 drivers/gpu/drm/nouveau/dispnv50/head.h     |   18 +--
 drivers/gpu/drm/nouveau/dispnv50/head507d.c |   29 +++--
 drivers/gpu/drm/nouveau/dispnv50/head827d.c |   17 +--
 drivers/gpu/drm/nouveau/dispnv50/head907d.c |   33 +++--
 drivers/gpu/drm/nouveau/dispnv50/head917d.c |   34 +++++-
 drivers/gpu/drm/nouveau/dispnv50/lut.c      |   95 ++++++++++++++++
 drivers/gpu/drm/nouveau/dispnv50/lut.h      |   15 ++
 drivers/gpu/drm/nouveau/dispnv50/wndw.c     |   93 +++++++++++++++-
 drivers/gpu/drm/nouveau/dispnv50/wndw.h     |    8 +
 17 files changed, 461 insertions(+), 170 deletions(-)

--- a/drivers/gpu/drm/nouveau/dispnv50/Kbuild
+++ b/drivers/gpu/drm/nouveau/dispnv50/Kbuild
@@ -1,4 +1,5 @@
 nouveau-y += dispnv50/disp.o
+nouveau-y += dispnv50/lut.o
 
 nouveau-y += dispnv50/core.o
 nouveau-y += dispnv50/core507d.o
--- a/drivers/gpu/drm/nouveau/dispnv50/atom.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/atom.h
@@ -17,6 +17,11 @@ struct nv50_head_atom {
 	struct drm_crtc_state state;
 
 	struct {
+		u32 mask;
+		u32 olut;
+	} wndw;
+
+	struct {
 		u16 iW;
 		u16 iH;
 		u16 oW;
@@ -47,8 +52,9 @@ struct nv50_head_atom {
 		bool visible;
 		u32 handle;
 		u64 offset:40;
-		u8  mode:4;
-	} ilut;
+		u8 buffer:1;
+		u8 mode:4;
+	} olut;
 
 	struct {
 		bool visible;
@@ -107,7 +113,7 @@ struct nv50_head_atom {
 
 	union nv50_head_atom_mask {
 		struct {
-			bool ilut:1;
+			bool olut:1;
 			bool core:1;
 			bool curs:1;
 			bool view:1;
@@ -136,6 +142,7 @@ nv50_head_atom_get(struct drm_atomic_sta
 struct nv50_wndw_atom {
 	struct drm_plane_state state;
 
+	struct drm_property_blob *ilut;
 	bool visible;
 
 	struct {
@@ -152,8 +159,14 @@ struct nv50_wndw_atom {
 	} sema;
 
 	struct {
-		u8 enable:2;
-	} lut;
+		u32 handle;
+		struct {
+			u64 offset:40;
+			u8  buffer:1;
+			u8  enable:2;
+			u8  mode:4;
+		} i;
+	} xlut;
 
 	struct {
 		u8  mode:2;
@@ -180,8 +193,8 @@ struct nv50_wndw_atom {
 		struct {
 			bool ntfy:1;
 			bool sema:1;
+			bool xlut:1;
 			bool image:1;
-			bool lut:1;
 			bool point:1;
 		};
 		u8 mask;
--- a/drivers/gpu/drm/nouveau/dispnv50/base.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/base.h
@@ -17,8 +17,9 @@ void base507c_ntfy_reset(struct nouveau_
 void base507c_ntfy_set(struct nv50_wndw *, struct nv50_wndw_atom *);
 void base507c_ntfy_clr(struct nv50_wndw *);
 int base507c_ntfy_wait_begun(struct nouveau_bo *, u32, struct nvif_device *);
+void base507c_xlut_set(struct nv50_wndw *, struct nv50_wndw_atom *);
+void base507c_xlut_clr(struct nv50_wndw *);
 void base507c_image_clr(struct nv50_wndw *);
-void base507c_lut(struct nv50_wndw *, struct nv50_wndw_atom *);
 void base507c_update(struct nv50_wndw *, u32 *);
 
 int base827c_new(struct nouveau_drm *, int, s32, struct nv50_wndw **);
--- a/drivers/gpu/drm/nouveau/dispnv50/base507c.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/base507c.c
@@ -40,17 +40,6 @@ base507c_update(struct nv50_wndw *wndw,
 }
 
 void
-base507c_lut(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
-{
-	u32 *push;
-	if ((push = evo_wait(&wndw->wndw, 2))) {
-		evo_mthd(push, 0x00e0, 1);
-		evo_data(push, asyw->lut.enable << 30);
-		evo_kick(push, &wndw->wndw);
-	}
-}
-
-void
 base507c_image_clr(struct nv50_wndw *wndw)
 {
 	u32 *push;
@@ -86,6 +75,28 @@ base507c_image_set(struct nv50_wndw *wnd
 	}
 }
 
+void
+base507c_xlut_clr(struct nv50_wndw *wndw)
+{
+	u32 *push;
+	if ((push = evo_wait(&wndw->wndw, 2))) {
+		evo_mthd(push, 0x00e0, 1);
+		evo_data(push, 0x00000000);
+		evo_kick(push, &wndw->wndw);
+	}
+}
+
+void
+base507c_xlut_set(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
+{
+	u32 *push;
+	if ((push = evo_wait(&wndw->wndw, 2))) {
+		evo_mthd(push, 0x00e0, 1);
+		evo_data(push, 0x40000000);
+		evo_kick(push, &wndw->wndw);
+	}
+}
+
 int
 base507c_ntfy_wait_begun(struct nouveau_bo *bo, u32 offset,
 			 struct nvif_device *device)
@@ -177,14 +188,17 @@ base507c_acquire(struct nv50_wndw *wndw,
 	if (ret)
 		return ret;
 
+	if (!wndw->func->ilut) {
+		if ((asyh->base.cpp != 1) ^ (fb->format->cpp[0] != 1))
+			asyh->state.color_mgmt_changed = true;
+	}
+
 	asyh->base.depth = fb->format->depth;
 	asyh->base.cpp = fb->format->cpp[0];
 	asyh->base.x = asyw->state.src.x1 >> 16;
 	asyh->base.y = asyw->state.src.y1 >> 16;
 	asyh->base.w = asyw->state.fb->width;
 	asyh->base.h = asyw->state.fb->height;
-
-	asyw->lut.enable = 1;
 	return 0;
 }
 
@@ -213,9 +227,11 @@ base507c = {
 	.ntfy_set = base507c_ntfy_set,
 	.ntfy_clr = base507c_ntfy_clr,
 	.ntfy_wait_begun = base507c_ntfy_wait_begun,
+	.olut_core = 1,
+	.xlut_set = base507c_xlut_set,
+	.xlut_clr = base507c_xlut_clr,
 	.image_set = base507c_image_set,
 	.image_clr = base507c_image_clr,
-	.lut = base507c_lut,
 	.update = base507c_update,
 };
 
--- a/drivers/gpu/drm/nouveau/dispnv50/base827c.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/base827c.c
@@ -53,9 +53,11 @@ base827c = {
 	.ntfy_set = base507c_ntfy_set,
 	.ntfy_clr = base507c_ntfy_clr,
 	.ntfy_wait_begun = base507c_ntfy_wait_begun,
+	.olut_core = 1,
+	.xlut_set = base507c_xlut_set,
+	.xlut_clr = base507c_xlut_clr,
 	.image_set = base827c_image_set,
 	.image_clr = base507c_image_clr,
-	.lut = base507c_lut,
 	.update = base507c_update,
 };
 
--- a/drivers/gpu/drm/nouveau/dispnv50/base907c.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/base907c.c
@@ -43,6 +43,44 @@ base907c_image_set(struct nv50_wndw *wnd
 	}
 }
 
+static void
+base907c_xlut_clr(struct nv50_wndw *wndw)
+{
+	u32 *push;
+	if ((push = evo_wait(&wndw->wndw, 6))) {
+		evo_mthd(push, 0x00e0, 1);
+		evo_data(push, 0x00000000);
+		evo_mthd(push, 0x00e8, 1);
+		evo_data(push, 0x00000000);
+		evo_mthd(push, 0x00fc, 1);
+		evo_data(push, 0x00000000);
+		evo_kick(push, &wndw->wndw);
+	}
+}
+
+static void
+base907c_xlut_set(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
+{
+	u32 *push;
+	if ((push = evo_wait(&wndw->wndw, 6))) {
+		evo_mthd(push, 0x00e0, 3);
+		evo_data(push, asyw->xlut.i.enable << 30 |
+			       asyw->xlut.i.mode << 24);
+		evo_data(push, asyw->xlut.i.offset >> 8);
+		evo_data(push, 0x40000000);
+		evo_mthd(push, 0x00fc, 1);
+		evo_data(push, asyw->xlut.handle);
+		evo_kick(push, &wndw->wndw);
+	}
+}
+
+static void
+base907c_ilut(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
+{
+	asyw->xlut.i.mode = 7;
+	asyw->xlut.i.enable = 2;
+}
+
 static const struct nv50_wndw_func
 base907c = {
 	.acquire = base507c_acquire,
@@ -53,9 +91,12 @@ base907c = {
 	.ntfy_set = base507c_ntfy_set,
 	.ntfy_clr = base507c_ntfy_clr,
 	.ntfy_wait_begun = base507c_ntfy_wait_begun,
+	.ilut = base907c_ilut,
+	.olut_core = true,
+	.xlut_set = base907c_xlut_set,
+	.xlut_clr = base907c_xlut_clr,
 	.image_set = base907c_image_set,
 	.image_clr = base507c_image_clr,
-	.lut = base507c_lut,
 	.update = base507c_update,
 };
 
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -1971,8 +1971,19 @@ nv50_disp_atomic_check(struct drm_device
 	struct nv50_atom *atom = nv50_atom(state);
 	struct drm_connector_state *old_connector_state, *new_connector_state;
 	struct drm_connector *connector;
+	struct drm_crtc_state *new_crtc_state;
+	struct drm_crtc *crtc;
 	int ret, i;
 
+	/* We need to handle colour management on a per-plane basis. */
+	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
+		if (new_crtc_state->color_mgmt_changed) {
+			ret = drm_atomic_add_affected_planes(state, crtc);
+			if (ret)
+				return ret;
+		}
+	}
+
 	ret = drm_atomic_helper_check(dev, state);
 	if (ret)
 		return ret;
--- a/drivers/gpu/drm/nouveau/dispnv50/head.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.c
@@ -30,56 +30,6 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
 #include "nouveau_connector.h"
-#include "nouveau_bo.h"
-
-static void
-nv50_head_lut_load(struct drm_property_blob *blob, int mode,
-		   struct nouveau_bo *nvbo)
-{
-	struct drm_color_lut *in = (struct drm_color_lut *)blob->data;
-	void __iomem *lut = (u8 *)nvbo_kmap_obj_iovirtual(nvbo);
-	const int size = blob->length / sizeof(*in);
-	int bits, shift, i;
-	u16 zero, r, g, b;
-
-	/* This can't happen.. But it shuts the compiler up. */
-	if (WARN_ON(size != 256))
-		return;
-
-	switch (mode) {
-	case 0: /* LORES. */
-	case 1: /* HIRES. */
-		bits = 11;
-		shift = 3;
-		zero = 0x0000;
-		break;
-	case 7: /* INTERPOLATE_257_UNITY_RANGE. */
-		bits = 14;
-		shift = 0;
-		zero = 0x6000;
-		break;
-	default:
-		WARN_ON(1);
-		return;
-	}
-
-	for (i = 0; i < size; i++) {
-		r = (drm_color_lut_extract(in[i].  red, bits) + zero) << shift;
-		g = (drm_color_lut_extract(in[i].green, bits) + zero) << shift;
-		b = (drm_color_lut_extract(in[i]. blue, bits) + zero) << shift;
-		writew(r, lut + (i * 0x08) + 0);
-		writew(g, lut + (i * 0x08) + 2);
-		writew(b, lut + (i * 0x08) + 4);
-	}
-
-	/* INTERPOLATE modes require a "next" entry to interpolate with,
-	 * so we replicate the last entry to deal with this for now.
-	 */
-	writew(r, lut + (i * 0x08) + 0);
-	writew(g, lut + (i * 0x08) + 2);
-	writew(b, lut + (i * 0x08) + 4);
-}
-
 void
 nv50_head_flush_clr(struct nv50_head *head,
 		    struct nv50_head_atom *asyh, bool flush)
@@ -87,7 +37,7 @@ nv50_head_flush_clr(struct nv50_head *he
 	union nv50_head_atom_mask clr = {
 		.mask = asyh->clr.mask & ~(flush ? 0 : asyh->set.mask),
 	};
-	if (clr.ilut) head->func->ilut_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);
 }
@@ -97,16 +47,14 @@ nv50_head_flush_set(struct nv50_head *he
 {
 	if (asyh->set.view   ) head->func->view    (head, asyh);
 	if (asyh->set.mode   ) head->func->mode    (head, asyh);
-	if (asyh->set.ilut   ) {
-		struct nouveau_bo *nvbo = head->ilut.nvbo[head->ilut.next];
-		struct drm_property_blob *blob = asyh->state.gamma_lut;
-		if (blob)
-			nv50_head_lut_load(blob, asyh->ilut.mode, nvbo);
-		asyh->ilut.offset = nvbo->bo.offset;
-		head->ilut.next ^= 1;
-		head->func->ilut_set(head, asyh);
-	}
 	if (asyh->set.core   ) head->func->core_set(head, asyh);
+	if (asyh->set.olut   ) {
+		asyh->olut.offset = nv50_lut_load(&head->olut,
+						  asyh->olut.mode <= 1,
+						  asyh->olut.buffer,
+						  asyh->state.gamma_lut);
+		head->func->olut_set(head, asyh);
+	}
 	if (asyh->set.curs   ) head->func->curs_set(head, asyh);
 	if (asyh->set.base   ) head->func->base    (head, asyh);
 	if (asyh->set.ovly   ) head->func->ovly    (head, asyh);
@@ -240,35 +188,37 @@ nv50_head_atomic_check_view(struct nv50_
 	asyh->set.view = true;
 }
 
-static void
+static int
 nv50_head_atomic_check_lut(struct nv50_head *head,
-			   struct nv50_head_atom *armh,
 			   struct nv50_head_atom *asyh)
 {
 	struct nv50_disp *disp = nv50_disp(head->base.base.dev);
+	struct drm_property_blob *olut = asyh->state.gamma_lut;
 
-	/* An I8 surface without an input LUT makes no sense, and
-	 * EVO will throw an error if you try.
-	 *
-	 * Legacy clients actually cause this due to the order in
-	 * which they call ioctls, so we will enable the LUT with
-	 * whatever contents the buffer already contains to avoid
-	 * triggering the error check.
-	 */
-	if (!asyh->state.gamma_lut && asyh->base.cpp != 1) {
-		asyh->ilut.handle = 0;
-		asyh->clr.ilut = armh->ilut.visible;
-		return;
+	/* Determine whether core output LUT should be enabled. */
+	if (olut) {
+		/* Check if any window(s) have stolen the core output LUT
+		 * to as an input LUT for legacy gamma + I8 colour format.
+		 */
+		if (asyh->wndw.olut) {
+			/* If any window has stolen the core output LUT,
+			 * all of them must.
+			 */
+			if (asyh->wndw.olut != asyh->wndw.mask)
+				return -EINVAL;
+			olut = NULL;
+		}
 	}
 
-	if (disp->disp->object.oclass < GF110_DISP) {
-		asyh->ilut.mode = (asyh->base.cpp == 1) ? 0 : 1;
-		asyh->set.ilut = true;
-	} else {
-		asyh->ilut.mode = 7;
-		asyh->set.ilut = asyh->state.color_mgmt_changed;
+	if (!olut) {
+		asyh->olut.handle = 0;
+		return 0;
 	}
-	asyh->ilut.handle = disp->core->chan.vram.handle;
+
+	asyh->olut.handle = disp->core->chan.vram.handle;
+	asyh->olut.buffer = !asyh->olut.buffer;
+	head->func->olut(head, asyh);
+	return 0;
 }
 
 static void
@@ -360,9 +310,13 @@ nv50_head_atomic_check(struct drm_crtc *
 			nv50_head_atomic_check_mode(head, asyh);
 
 		if (asyh->state.color_mgmt_changed ||
-		    asyh->base.cpp != armh->base.cpp)
-			nv50_head_atomic_check_lut(head, armh, asyh);
-		asyh->ilut.visible = asyh->ilut.handle != 0;
+		    memcmp(&armh->wndw, &asyh->wndw, sizeof(asyh->wndw))) {
+			int ret = nv50_head_atomic_check_lut(head, asyh);
+			if (ret)
+				return ret;
+
+			asyh->olut.visible = asyh->olut.handle != 0;
+		}
 
 		if (asyc) {
 			if (asyc->set.scaler)
@@ -373,13 +327,16 @@ nv50_head_atomic_check(struct drm_crtc *
 				nv50_head_atomic_check_procamp(armh, asyh, asyc);
 		}
 
-		if (head->func->core_calc)
+		if (head->func->core_calc) {
 			head->func->core_calc(head, asyh);
+			if (!asyh->core.visible)
+				asyh->olut.visible = false;
+		}
 
 		asyh->set.base = armh->base.cpp != asyh->base.cpp;
 		asyh->set.ovly = armh->ovly.cpp != asyh->ovly.cpp;
 	} else {
-		asyh->ilut.visible = false;
+		asyh->olut.visible = false;
 		asyh->core.visible = false;
 		asyh->curs.visible = false;
 		asyh->base.cpp = 0;
@@ -402,11 +359,19 @@ nv50_head_atomic_check(struct drm_crtc *
 		if (armh->curs.visible) {
 			asyh->clr.curs = true;
 		}
+
+		if (asyh->olut.visible) {
+			if (memcmp(&armh->olut, &asyh->olut, sizeof(asyh->olut)))
+				asyh->set.olut = true;
+		} else
+		if (armh->olut.visible) {
+			asyh->clr.olut = true;
+		}
 	} else {
-		asyh->clr.ilut = armh->ilut.visible;
+		asyh->clr.olut = armh->olut.visible;
 		asyh->clr.core = armh->core.visible;
 		asyh->clr.curs = armh->curs.visible;
-		asyh->set.ilut = asyh->ilut.visible;
+		asyh->set.olut = asyh->olut.visible;
 		asyh->set.core = asyh->core.visible;
 		asyh->set.curs = asyh->curs.visible;
 	}
@@ -438,9 +403,10 @@ nv50_head_atomic_duplicate_state(struct
 	if (!(asyh = kmalloc(sizeof(*asyh), GFP_KERNEL)))
 		return NULL;
 	__drm_atomic_helper_crtc_duplicate_state(crtc, &asyh->state);
+	asyh->wndw = armh->wndw;
 	asyh->view = armh->view;
 	asyh->mode = armh->mode;
-	asyh->ilut = armh->ilut;
+	asyh->olut = armh->olut;
 	asyh->core = armh->core;
 	asyh->curs = armh->curs;
 	asyh->base = armh->base;
@@ -477,11 +443,7 @@ static void
 nv50_head_destroy(struct drm_crtc *crtc)
 {
 	struct nv50_head *head = nv50_head(crtc);
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(head->ilut.nvbo); i++)
-		nouveau_bo_unmap_unpin_unref(&head->ilut.nvbo[i]);
-
+	nv50_lut_fini(&head->olut);
 	drm_crtc_cleanup(crtc);
 	kfree(head);
 }
@@ -505,7 +467,7 @@ nv50_head_create(struct drm_device *dev,
 	struct nv50_head *head;
 	struct nv50_wndw *curs, *wndw;
 	struct drm_crtc *crtc;
-	int ret, i;
+	int ret;
 
 	head = kzalloc(sizeof(*head), GFP_KERNEL);
 	if (!head)
@@ -527,10 +489,8 @@ nv50_head_create(struct drm_device *dev,
 	drm_crtc_helper_add(crtc, &nv50_head_help);
 	drm_mode_crtc_set_gamma_size(crtc, 256);
 
-	for (i = 0; i < ARRAY_SIZE(head->ilut.nvbo); i++) {
-		ret = nouveau_bo_new_pin_map(&drm->client, 1025 * 8, 0x100,
-					     TTM_PL_FLAG_VRAM,
-					     &head->ilut.nvbo[i]);
+	if (head->func->olut_set) {
+		ret = nv50_lut_init(disp, &drm->client.mmu, &head->olut);
 		if (ret)
 			goto out;
 	}
--- a/drivers/gpu/drm/nouveau/dispnv50/head.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.h
@@ -3,16 +3,14 @@
 #define nv50_head(c) container_of((c), struct nv50_head, base.base)
 #include "disp.h"
 #include "atom.h"
+#include "lut.h"
 
 #include "nouveau_crtc.h"
 
 struct nv50_head {
 	const struct nv50_head_func *func;
 	struct nouveau_crtc base;
-	struct {
-		struct nouveau_bo *nvbo[2];
-		int next;
-	} ilut;
+	struct nv50_lut olut;
 };
 
 int nv50_head_create(struct drm_device *, int index);
@@ -22,8 +20,9 @@ void nv50_head_flush_clr(struct nv50_hea
 struct nv50_head_func {
 	void (*view)(struct nv50_head *, struct nv50_head_atom *);
 	void (*mode)(struct nv50_head *, struct nv50_head_atom *);
-	void (*ilut_set)(struct nv50_head *, struct nv50_head_atom *);
-	void (*ilut_clr)(struct nv50_head *);
+	void (*olut)(struct nv50_head *, struct nv50_head_atom *);
+	void (*olut_set)(struct nv50_head *, struct nv50_head_atom *);
+	void (*olut_clr)(struct nv50_head *);
 	void (*core_calc)(struct nv50_head *, struct nv50_head_atom *);
 	void (*core_set)(struct nv50_head *, struct nv50_head_atom *);
 	void (*core_clr)(struct nv50_head *);
@@ -39,6 +38,7 @@ struct nv50_head_func {
 extern const struct nv50_head_func head507d;
 void head507d_view(struct nv50_head *, struct nv50_head_atom *);
 void head507d_mode(struct nv50_head *, struct nv50_head_atom *);
+void head507d_olut(struct nv50_head *, struct nv50_head_atom *);
 void head507d_core_calc(struct nv50_head *, struct nv50_head_atom *);
 void head507d_core_clr(struct nv50_head *);
 void head507d_base(struct nv50_head *, struct nv50_head_atom *);
@@ -51,13 +51,13 @@ extern const struct nv50_head_func head8
 extern const struct nv50_head_func head907d;
 void head907d_view(struct nv50_head *, struct nv50_head_atom *);
 void head907d_mode(struct nv50_head *, struct nv50_head_atom *);
-void head907d_ilut_set(struct nv50_head *, struct nv50_head_atom *);
-void head907d_ilut_clr(struct nv50_head *);
+void head907d_olut(struct nv50_head *, struct nv50_head_atom *);
+void head907d_olut_set(struct nv50_head *, struct nv50_head_atom *);
+void head907d_olut_clr(struct nv50_head *);
 void head907d_core_set(struct nv50_head *, struct nv50_head_atom *);
 void head907d_core_clr(struct nv50_head *);
 void head907d_curs_set(struct nv50_head *, struct nv50_head_atom *);
 void head907d_curs_clr(struct nv50_head *);
-void head907d_base(struct nv50_head *, struct nv50_head_atom *);
 void head907d_ovly(struct nv50_head *, struct nv50_head_atom *);
 void head907d_procamp(struct nv50_head *, struct nv50_head_atom *);
 void head907d_or(struct nv50_head *, struct nv50_head_atom *);
--- a/drivers/gpu/drm/nouveau/dispnv50/head507d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head507d.c
@@ -165,6 +165,7 @@ head507d_core_set(struct nv50_head *head
 		 * without also updating HeadSetOffsetCursor.
 		 */
 		asyh->set.curs = asyh->curs.visible;
+		asyh->set.olut = asyh->olut.handle != 0;
 	}
 }
 
@@ -178,8 +179,8 @@ head507d_core_calc(struct nv50_head *hea
 		asyh->core.w = asyh->base.w;
 		asyh->core.h = asyh->base.h;
 	} else
-	if ((asyh->core.visible = asyh->curs.visible) ||
-	    (asyh->core.visible = asyh->ilut.visible)) {
+	if ((asyh->core.visible = (asyh->ovly.cpp != 0)) ||
+	    (asyh->core.visible = asyh->curs.visible)) {
 		/*XXX: We need to either find some way of having the
 		 *     primary base layer appear black, while still
 		 *     being able to display the other layers, or we
@@ -200,31 +201,40 @@ head507d_core_calc(struct nv50_head *hea
 }
 
 static void
-head507d_ilut_clr(struct nv50_head *head)
+head507d_olut_clr(struct nv50_head *head)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
 	u32 *push;
 	if ((push = evo_wait(core, 2))) {
 		evo_mthd(push, 0x0840 + (head->base.index * 0x400), 1);
-		evo_data(push, 0x40000000);
+		evo_data(push, 0x00000000);
 		evo_kick(push, core);
 	}
 }
 
 static void
-head507d_ilut_set(struct nv50_head *head, struct nv50_head_atom *asyh)
+head507d_olut_set(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
 	u32 *push;
 	if ((push = evo_wait(core, 3))) {
 		evo_mthd(push, 0x0840 + (head->base.index * 0x400), 2);
-		evo_data(push, 0x80000000 | asyh->ilut.mode << 30);
-		evo_data(push, asyh->ilut.offset >> 8);
+		evo_data(push, 0x80000000 | asyh->olut.mode << 30);
+		evo_data(push, asyh->olut.offset >> 8);
 		evo_kick(push, core);
 	}
 }
 
 void
+head507d_olut(struct nv50_head *head, struct nv50_head_atom *asyh)
+{
+	if (asyh->base.cpp == 1)
+		asyh->olut.mode = 0;
+	else
+		asyh->olut.mode = 1;
+}
+
+void
 head507d_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
@@ -269,8 +279,9 @@ const struct nv50_head_func
 head507d = {
 	.view = head507d_view,
 	.mode = head507d_mode,
-	.ilut_set = head507d_ilut_set,
-	.ilut_clr = head507d_ilut_clr,
+	.olut = head507d_olut,
+	.olut_set = head507d_olut_set,
+	.olut_clr = head507d_olut_clr,
 	.core_calc = head507d_core_calc,
 	.core_set = head507d_core_set,
 	.core_clr = head507d_core_clr,
--- a/drivers/gpu/drm/nouveau/dispnv50/head827d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head827d.c
@@ -74,13 +74,13 @@ head827d_core_set(struct nv50_head *head
 }
 
 static void
-head827d_ilut_clr(struct nv50_head *head)
+head827d_olut_clr(struct nv50_head *head)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
 	u32 *push;
 	if ((push = evo_wait(core, 4))) {
 		evo_mthd(push, 0x0840 + (head->base.index * 0x400), 1);
-		evo_data(push, 0x40000000);
+		evo_data(push, 0x00000000);
 		evo_mthd(push, 0x085c + (head->base.index * 0x400), 1);
 		evo_data(push, 0x00000000);
 		evo_kick(push, core);
@@ -88,16 +88,16 @@ head827d_ilut_clr(struct nv50_head *head
 }
 
 static void
-head827d_ilut_set(struct nv50_head *head, struct nv50_head_atom *asyh)
+head827d_olut_set(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
 	u32 *push;
 	if ((push = evo_wait(core, 5))) {
 		evo_mthd(push, 0x0840 + (head->base.index * 0x400), 2);
-		evo_data(push, 0x80000000 | asyh->ilut.mode << 30);
-		evo_data(push, asyh->ilut.offset >> 8);
+		evo_data(push, 0x80000000 | asyh->olut.mode << 30);
+		evo_data(push, asyh->olut.offset >> 8);
 		evo_mthd(push, 0x085c + (head->base.index * 0x400), 1);
-		evo_data(push, asyh->ilut.handle);
+		evo_data(push, asyh->olut.handle);
 		evo_kick(push, core);
 	}
 }
@@ -106,8 +106,9 @@ const struct nv50_head_func
 head827d = {
 	.view = head507d_view,
 	.mode = head507d_mode,
-	.ilut_set = head827d_ilut_set,
-	.ilut_clr = head827d_ilut_clr,
+	.olut = head507d_olut,
+	.olut_set = head827d_olut_set,
+	.olut_clr = head827d_olut_clr,
 	.core_calc = head507d_core_calc,
 	.core_set = head827d_core_set,
 	.core_clr = head507d_core_clr,
--- a/drivers/gpu/drm/nouveau/dispnv50/head907d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head907d.c
@@ -91,7 +91,7 @@ head907d_ovly(struct nv50_head *head, st
 	}
 }
 
-void
+static void
 head907d_base(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
@@ -182,13 +182,13 @@ head907d_core_set(struct nv50_head *head
 }
 
 void
-head907d_ilut_clr(struct nv50_head *head)
+head907d_olut_clr(struct nv50_head *head)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
 	u32 *push;
 	if ((push = evo_wait(core, 4))) {
-		evo_mthd(push, 0x0440 + (head->base.index * 0x300), 1);
-		evo_data(push, 0x03000000);
+		evo_mthd(push, 0x0448 + (head->base.index * 0x300), 1);
+		evo_data(push, 0x00000000);
 		evo_mthd(push, 0x045c + (head->base.index * 0x300), 1);
 		evo_data(push, 0x00000000);
 		evo_kick(push, core);
@@ -196,23 +196,27 @@ head907d_ilut_clr(struct nv50_head *head
 }
 
 void
-head907d_ilut_set(struct nv50_head *head, struct nv50_head_atom *asyh)
+head907d_olut_set(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
 	u32 *push;
-	if ((push = evo_wait(core, 7))) {
-		evo_mthd(push, 0x0440 + (head->base.index * 0x300), 4);
-		evo_data(push, 0x80000000 | asyh->ilut.mode << 24);
-		evo_data(push, asyh->ilut.offset >> 8);
-		evo_data(push, 0x00000000);
-		evo_data(push, 0x00000000);
+	if ((push = evo_wait(core, 5))) {
+		evo_mthd(push, 0x0448 + (head->base.index * 0x300), 2);
+		evo_data(push, 0x80000000 | asyh->olut.mode << 24);
+		evo_data(push, asyh->olut.offset >> 8);
 		evo_mthd(push, 0x045c + (head->base.index * 0x300), 1);
-		evo_data(push, asyh->ilut.handle);
+		evo_data(push, asyh->olut.handle);
 		evo_kick(push, core);
 	}
 }
 
 void
+head907d_olut(struct nv50_head *head, struct nv50_head_atom *asyh)
+{
+	asyh->olut.mode = 7;
+}
+
+void
 head907d_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
@@ -259,8 +263,9 @@ const struct nv50_head_func
 head907d = {
 	.view = head907d_view,
 	.mode = head907d_mode,
-	.ilut_set = head907d_ilut_set,
-	.ilut_clr = head907d_ilut_clr,
+	.olut = head907d_olut,
+	.olut_set = head907d_olut_set,
+	.olut_clr = head907d_olut_clr,
 	.core_calc = head507d_core_calc,
 	.core_set = head907d_core_set,
 	.core_clr = head907d_core_clr,
--- a/drivers/gpu/drm/nouveau/dispnv50/head917d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head917d.c
@@ -36,18 +36,46 @@ head917d_dither(struct nv50_head *head,
 	}
 }
 
+static void
+head917d_base(struct nv50_head *head, struct nv50_head_atom *asyh)
+{
+	struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->core->chan;
+	u32 bounds = 0;
+	u32 *push;
+
+	if (asyh->base.cpp) {
+		switch (asyh->base.cpp) {
+		case 8: bounds |= 0x00000500; break;
+		case 4: bounds |= 0x00000300; break;
+		case 2: bounds |= 0x00000100; break;
+		case 1: bounds |= 0x00000000; break;
+		default:
+			WARN_ON(1);
+			break;
+		}
+		bounds |= 0x00020001;
+	}
+
+	if ((push = evo_wait(core, 2))) {
+		evo_mthd(push, 0x04d0 + head->base.index * 0x300, 1);
+		evo_data(push, bounds);
+		evo_kick(push, core);
+	}
+}
+
 const struct nv50_head_func
 head917d = {
 	.view = head907d_view,
 	.mode = head907d_mode,
-	.ilut_set = head907d_ilut_set,
-	.ilut_clr = head907d_ilut_clr,
+	.olut = head907d_olut,
+	.olut_set = head907d_olut_set,
+	.olut_clr = head907d_olut_clr,
 	.core_calc = head507d_core_calc,
 	.core_set = head907d_core_set,
 	.core_clr = head907d_core_clr,
 	.curs_set = head907d_curs_set,
 	.curs_clr = head907d_curs_clr,
-	.base = head907d_base,
+	.base = head917d_base,
 	.ovly = head907d_ovly,
 	.dither = head917d_dither,
 	.procamp = head907d_procamp,
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv50/lut.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "lut.h"
+#include "disp.h"
+
+#include <drm/drm_color_mgmt.h>
+#include <drm/drm_mode.h>
+#include <drm/drm_property.h>
+
+#include <nvif/class.h>
+
+u32
+nv50_lut_load(struct nv50_lut *lut, bool legacy, int buffer,
+	      struct drm_property_blob *blob)
+{
+	struct drm_color_lut *in = (struct drm_color_lut *)blob->data;
+	void __iomem *mem = lut->mem[buffer].object.map.ptr;
+	const int size = blob->length / sizeof(*in);
+	int bits, shift, i;
+	u16 zero, r, g, b;
+	u32 addr = lut->mem[buffer].addr;
+
+	/* This can't happen.. But it shuts the compiler up. */
+	if (WARN_ON(size != 256))
+		return 0;
+
+	if (legacy) {
+		bits = 11;
+		shift = 3;
+		zero = 0x0000;
+	} else {
+		bits = 14;
+		shift = 0;
+		zero = 0x6000;
+	}
+
+	for (i = 0; i < size; i++) {
+		r = (drm_color_lut_extract(in[i].  red, bits) + zero) << shift;
+		g = (drm_color_lut_extract(in[i].green, bits) + zero) << shift;
+		b = (drm_color_lut_extract(in[i]. blue, bits) + zero) << shift;
+		writew(r, mem + (i * 0x08) + 0);
+		writew(g, mem + (i * 0x08) + 2);
+		writew(b, mem + (i * 0x08) + 4);
+	}
+
+	/* INTERPOLATE modes require a "next" entry to interpolate with,
+	 * so we replicate the last entry to deal with this for now.
+	 */
+	writew(r, mem + (i * 0x08) + 0);
+	writew(g, mem + (i * 0x08) + 2);
+	writew(b, mem + (i * 0x08) + 4);
+	return addr;
+}
+
+void
+nv50_lut_fini(struct nv50_lut *lut)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(lut->mem); i++)
+		nvif_mem_fini(&lut->mem[i]);
+}
+
+int
+nv50_lut_init(struct nv50_disp *disp, struct nvif_mmu *mmu,
+	      struct nv50_lut *lut)
+{
+	const u32 size = disp->disp->object.oclass < GF110_DISP ? 257 : 1025;
+	int i;
+	for (i = 0; i < ARRAY_SIZE(lut->mem); i++) {
+		int ret = nvif_mem_init_map(mmu, NVIF_MEM_VRAM, size * 8,
+					    &lut->mem[i]);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv50/lut.h
@@ -0,0 +1,15 @@
+#ifndef __NV50_KMS_LUT_H__
+#define __NV50_KMS_LUT_H__
+#include <nvif/mem.h>
+struct drm_property_blob;
+struct nv50_disp;
+
+struct nv50_lut {
+	struct nvif_mem mem[2];
+};
+
+int nv50_lut_init(struct nv50_disp *, struct nvif_mmu *, struct nv50_lut *);
+void nv50_lut_fini(struct nv50_lut *);
+u32 nv50_lut_load(struct nv50_lut *, bool legacy, int buffer,
+		  struct drm_property_blob *);
+#endif
--- a/drivers/gpu/drm/nouveau/dispnv50/wndw.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.c
@@ -116,6 +116,7 @@ nv50_wndw_flush_clr(struct nv50_wndw *wn
 	};
 	if (clr.sema ) wndw->func-> sema_clr(wndw);
 	if (clr.ntfy ) wndw->func-> ntfy_clr(wndw);
+	if (clr.xlut ) wndw->func-> xlut_clr(wndw);
 	if (clr.image) wndw->func->image_clr(wndw);
 
 	interlock[wndw->interlock.type] |= wndw->interlock.data;
@@ -133,7 +134,18 @@ nv50_wndw_flush_set(struct nv50_wndw *wn
 	if (asyw->set.sema ) wndw->func->sema_set (wndw, asyw);
 	if (asyw->set.ntfy ) wndw->func->ntfy_set (wndw, asyw);
 	if (asyw->set.image) wndw->func->image_set(wndw, asyw);
-	if (asyw->set.lut  ) wndw->func->lut      (wndw, asyw);
+
+	if (asyw->set.xlut ) {
+		if (asyw->ilut) {
+			asyw->xlut.i.offset =
+				nv50_lut_load(&wndw->ilut,
+					      asyw->xlut.i.mode <= 1,
+					      asyw->xlut.i.buffer,
+					      asyw->ilut);
+		}
+		wndw->func->xlut_set(wndw, asyw);
+	}
+
 	if (asyw->set.point) {
 		wndw->immd->point(wndw, asyw);
 		wndw->immd->update(wndw, interlock);
@@ -241,7 +253,56 @@ nv50_wndw_atomic_check_acquire(struct nv
 	return wndw->func->acquire(wndw, asyw, asyh);
 }
 
-int
+static void
+nv50_wndw_atomic_check_lut(struct nv50_wndw *wndw,
+			   struct nv50_wndw_atom *armw,
+			   struct nv50_wndw_atom *asyw,
+			   struct nv50_head_atom *asyh)
+{
+	struct drm_property_blob *ilut = asyh->state.degamma_lut;
+
+	/* I8 format without an input LUT makes no sense, and the
+	 * HW error-checks for this.
+	 *
+	 * In order to handle legacy gamma, when there's no input
+	 * LUT we need to steal the output LUT and use it instead.
+	 */
+	if (!ilut && asyw->state.fb->format->format == DRM_FORMAT_C8) {
+		/* This should be an error, but there's legacy clients
+		 * that do a modeset before providing a gamma table.
+		 *
+		 * We keep the window disabled to avoid angering HW.
+		 */
+		if (!(ilut = asyh->state.gamma_lut)) {
+			asyw->visible = false;
+			return;
+		}
+
+		if (wndw->func->ilut)
+			asyh->wndw.olut |= BIT(wndw->id);
+	} else {
+		asyh->wndw.olut &= ~BIT(wndw->id);
+	}
+
+	/* Recalculate LUT state. */
+	memset(&asyw->xlut, 0x00, sizeof(asyw->xlut));
+	if ((asyw->ilut = wndw->func->ilut ? ilut : NULL)) {
+		wndw->func->ilut(wndw, asyw);
+		asyw->xlut.handle = wndw->wndw.vram.handle;
+		asyw->xlut.i.buffer = !asyw->xlut.i.buffer;
+		asyw->set.xlut = true;
+	}
+
+	/* Handle setting base SET_OUTPUT_LUT_LO_ENABLE_USE_CORE_LUT. */
+	if (wndw->func->olut_core &&
+	    (!armw->visible || (armw->xlut.handle && !asyw->xlut.handle)))
+		asyw->set.xlut = true;
+
+	/* Can't do an immediate flip while changing the LUT. */
+	asyh->state.pageflip_flags &= ~DRM_MODE_PAGE_FLIP_ASYNC;
+}
+
+static int
 nv50_wndw_atomic_check(struct drm_plane *plane, struct drm_plane_state *state)
 {
 	struct nouveau_drm *drm = nouveau_drm(plane->dev);
@@ -274,15 +335,26 @@ nv50_wndw_atomic_check(struct drm_plane
 			return PTR_ERR(harm);
 	}
 
+	/* LUT configuration can potentially cause the window to be disabled. */
+	if (asyw->visible && wndw->func->xlut_set &&
+	    (!armw->visible ||
+	     asyh->state.color_mgmt_changed ||
+	     asyw->state.fb->format->format !=
+	     armw->state.fb->format->format))
+		nv50_wndw_atomic_check_lut(wndw, armw, asyw, asyh);
+
 	/* Calculate new window state. */
 	if (asyw->visible) {
 		ret = nv50_wndw_atomic_check_acquire(wndw, modeset,
 						     armw, asyw, asyh);
 		if (ret)
 			return ret;
+
+		asyh->wndw.mask |= BIT(wndw->id);
 	} else
 	if (armw->visible) {
 		nv50_wndw_atomic_check_release(wndw, asyw, harm);
+		harm->wndw.mask &= ~BIT(wndw->id);
 	} else {
 		return 0;
 	}
@@ -294,9 +366,9 @@ nv50_wndw_atomic_check(struct drm_plane
 	if (!asyw->visible || modeset) {
 		asyw->clr.ntfy = armw->ntfy.handle != 0;
 		asyw->clr.sema = armw->sema.handle != 0;
+		asyw->clr.xlut = armw->xlut.handle != 0;
 		if (wndw->func->image_clr)
 			asyw->clr.image = armw->image.handle[0] != 0;
-		asyw->set.lut = wndw->func->lut && asyw->visible;
 	}
 
 	return 0;
@@ -381,9 +453,10 @@ nv50_wndw_atomic_duplicate_state(struct
 	__drm_atomic_helper_plane_duplicate_state(plane, &asyw->state);
 	asyw->sema = armw->sema;
 	asyw->ntfy = armw->ntfy;
+	asyw->ilut = NULL;
+	asyw->xlut = armw->xlut;
 	asyw->image = armw->image;
 	asyw->point = armw->point;
-	asyw->lut = armw->lut;
 	asyw->clr.mask = 0;
 	asyw->set.mask = 0;
 	return &asyw->state;
@@ -417,6 +490,9 @@ nv50_wndw_destroy(struct drm_plane *plan
 	nvif_notify_fini(&wndw->notify);
 	nv50_dmac_destroy(&wndw->wimm);
 	nv50_dmac_destroy(&wndw->wndw);
+
+	nv50_lut_fini(&wndw->ilut);
+
 	drm_plane_cleanup(&wndw->plane);
 	kfree(wndw);
 }
@@ -456,6 +532,9 @@ nv50_wndw_new_(const struct nv50_wndw_fu
 	       enum nv50_disp_interlock_type interlock_type, u32 interlock_data,
 	       struct nv50_wndw **pwndw)
 {
+	struct nouveau_drm *drm = nouveau_drm(dev);
+	struct nvif_mmu *mmu = &drm->client.mmu;
+	struct nv50_disp *disp = nv50_disp(dev);
 	struct nv50_wndw *wndw;
 	int nformat;
 	int ret;
@@ -484,6 +563,12 @@ nv50_wndw_new_(const struct nv50_wndw_fu
 
 	drm_plane_helper_add(&wndw->plane, &nv50_wndw_helper);
 
+	if (wndw->func->ilut) {
+		ret = nv50_lut_init(disp, mmu, &wndw->ilut);
+		if (ret)
+			return ret;
+	}
+
 	wndw->notify.func = nv50_wndw_notify;
 	return 0;
 }
--- a/drivers/gpu/drm/nouveau/dispnv50/wndw.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.h
@@ -3,6 +3,7 @@
 #define nv50_wndw(p) container_of((p), struct nv50_wndw, plane)
 #include "disp.h"
 #include "atom.h"
+#include "lut.h"
 
 #include <nvif/notify.h>
 
@@ -24,6 +25,8 @@ struct nv50_wndw {
 
 	struct drm_plane plane;
 
+	struct nv50_lut ilut;
+
 	struct nv50_dmac wndw;
 	struct nv50_dmac wimm;
 
@@ -61,9 +64,12 @@ struct nv50_wndw_func {
 	void (*ntfy_clr)(struct nv50_wndw *);
 	int (*ntfy_wait_begun)(struct nouveau_bo *, u32 offset,
 			       struct nvif_device *);
+	void (*ilut)(struct nv50_wndw *, struct nv50_wndw_atom *);
+	bool olut_core;
+	void (*xlut_set)(struct nv50_wndw *, struct nv50_wndw_atom *);
+	void (*xlut_clr)(struct nv50_wndw *);
 	void (*image_set)(struct nv50_wndw *, struct nv50_wndw_atom *);
 	void (*image_clr)(struct nv50_wndw *);
-	void (*lut)(struct nv50_wndw *, struct nv50_wndw_atom *);
 
 	void (*update)(struct nv50_wndw *, u32 *interlock);
 };