From: Ben Skeggs 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 Acked-by: Petr Tesarik --- 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 #include #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 +#include +#include + +#include + +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 +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 @@ -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); };