On Turing and later display classes the final head colour stage is
programmed through HEAD_SET_PROCAMP, the OCSC1 matrix and the output
clamp ranges.  Nouveau only wrote PROCAMP, hardcoded to full-range RGB,
and left OCSC1 and the clamps untouched, so the head could only ever
emit full-range RGB.

Carry an output colour space and a limited-range flag in the head atom
and program all three from it in headc57d_procamp(): full-range RGB
disables OCSC1 and opens the clamps, limited-range RGB and YCbCr load the
matching BT.601/BT.709/BT.2020 limited-range matrix and clamp to 16-235
(16-240 for chroma).  The matrices are the ITU-R coefficients scaled to
limited range, with the register values NVIDIA's open NVKMS uses on the
same classes.

Re-send procamp whenever the output conversion changes on an active head,
and reset it to full-range RGB when the encoder state is recomputed.
Nothing selects anything else yet, so there is no functional change.

Signed-off-by: Daniel Campos Ramos <[email protected]>
---
 drivers/gpu/drm/nouveau/dispnv50/atom.h     |  2 +
 drivers/gpu/drm/nouveau/dispnv50/disp.c     |  5 +-
 drivers/gpu/drm/nouveau/dispnv50/head.c     |  3 +
 drivers/gpu/drm/nouveau/dispnv50/headc57d.c | 74 +++++++++++++++++++--
 4 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/atom.h 
b/drivers/gpu/drm/nouveau/dispnv50/atom.h
index c34a42ea9ac4..64be784be335 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/atom.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/atom.h
@@ -112,6 +112,8 @@ struct nv50_head_atom {
                        u16 cos:12;
                        u16 sin:12;
                } sat;
+               u8 colorspace:2;        /* HEAD_SET_PROCAMP_COLOR_SPACE */
+               bool limited:1;         /* limited quantization range */
        } procamp;
 
        struct {
diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c 
b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index b183e1865bf5..b216db61330c 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -463,8 +463,11 @@ nv50_outp_atomic_check(struct drm_encoder *encoder,
        if (ret)
                return ret;
 
-       if (crtc_state->mode_changed || crtc_state->connectors_changed)
+       if (crtc_state->mode_changed || crtc_state->connectors_changed) {
                asyh->or.bpc = connector->display_info.bpc;
+               asyh->procamp.colorspace = 0; /* RGB */
+               asyh->procamp.limited = false;
+       }
 
        /* We might have to reduce the bpc */
        nv50_outp_atomic_fix_depth(encoder, crtc_state, conn_state);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/head.c 
b/drivers/gpu/drm/nouveau/dispnv50/head.c
index 6a3a93de0cbf..7b94c881e8bb 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/head.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.c
@@ -370,6 +370,9 @@ nv50_head_atomic_check(struct drm_crtc *crtc, struct 
drm_atomic_commit *state)
                                if (armh->base.depth != asyh->base.depth)
                                        asyc->set.dither = true;
                        }
+                       if (armh->procamp.colorspace != 
asyh->procamp.colorspace ||
+                           armh->procamp.limited != asyh->procamp.limited)
+                               asyh->set.procamp = true;
                } else {
                        if (asyc)
                                asyc->set.mask = ~0;
diff --git a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c 
b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
index f1e9f02d177c..9b5c02c86fc9 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
@@ -81,21 +81,87 @@ headc57d_or(struct nv50_head *head, struct nv50_head_atom 
*asyh)
        return 0;
 }
 
+/* OCSC1 coefficients converting the head's RGB output to limited range,
+ * indexed by HEAD_SET_PROCAMP_COLOR_SPACE.  The three rows produce the
+ * red/Cr, green/Y and blue/Cb channels, the last column is the offset,
+ * and values are two's complement with 16 fractional bits.
+ *
+ * These are the ITU-R BT.601, BT.709 and BT.2020 RGB to Y'CbCr matrices
+ * (identity for RGB) scaled by 219/256 for Y' and R'G'B' and by 224/256
+ * for CbCr, with 16/256 and 128/256 offsets.  The register values are the
+ * ones NVIDIA's open NVKMS programs on the same display classes, which
+ * differ from a direct derivation by at most 4 in the last place.
+ */
+static const u32
+headc57d_ocsc1_limited[4][12] = {
+       [NVC57D_HEAD_SET_PROCAMP_COLOR_SPACE_RGB] = {
+               0x00db04, 0x000000, 0x000000, 0x001000,
+               0x000000, 0x00db04, 0x000000, 0x001000,
+               0x000000, 0x000000, 0x00db04, 0x001000,
+       },
+       [NVC57D_HEAD_SET_PROCAMP_COLOR_SPACE_YUV_601] = {
+               0x007000, 0x1fa234, 0x1fedc8, 0x008000,
+               0x00417c, 0x008090, 0x0018f8, 0x001000,
+               0x1fda34, 0x1fb5cc, 0x007000, 0x008000,
+       },
+       [NVC57D_HEAD_SET_PROCAMP_COLOR_SPACE_YUV_709] = {
+               0x007000, 0x1f9a44, 0x1ff5bc, 0x008000,
+               0x002e90, 0x009ca4, 0x000fd0, 0x001000,
+               0x1fe654, 0x1fa9a8, 0x007000, 0x008000,
+       },
+       [NVC57D_HEAD_SET_PROCAMP_COLOR_SPACE_YUV_2020] = {
+               0x007000, 0x1f9900, 0x1ff700, 0x008000,
+               0x003988, 0x00947c, 0x000cfc, 0x001000,
+               0x1fe0b8, 0x1faf44, 0x007000, 0x008000,
+       },
+};
+
 static int
 headc57d_procamp(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
        struct nvif_push *push = 
&nv50_disp(head->base.base.dev)->core->chan.push;
+       const u8 colorspace = asyh->procamp.colorspace;
+       const bool limited = asyh->procamp.limited;
        const int i = head->base.index;
+       u32 lo = 0x000, y_hi = 0xfff, c_hi = 0xfff;
        int ret;
 
-       if ((ret = PUSH_WAIT(push, 2)))
+       /* 12-bit clamp codes: 16..235 for Y' and R'G'B', 16..240 for CbCr. */
+       if (limited) {
+               lo = 0x100;
+               y_hi = 0xeb0;
+               c_hi = colorspace == NVC57D_HEAD_SET_PROCAMP_COLOR_SPACE_RGB ? 
0xeb0 : 0xf00;
+       }
+
+       ret = PUSH_WAIT(push, 20);
+       if (ret)
                return ret;
 
-       //TODO:
        PUSH_MTHD(push, NVC57D, HEAD_SET_PROCAMP(i),
-                 NVDEF(NVC57D, HEAD_SET_PROCAMP, COLOR_SPACE, RGB) |
+                 NVVAL(NVC57D, HEAD_SET_PROCAMP, COLOR_SPACE, colorspace) |
                  NVDEF(NVC57D, HEAD_SET_PROCAMP, CHROMA_LPF, DISABLE) |
-                 NVDEF(NVC57D, HEAD_SET_PROCAMP, DYNAMIC_RANGE, VESA));
+                 NVVAL(NVC57D, HEAD_SET_PROCAMP, DYNAMIC_RANGE,
+                       limited ? NVC57D_HEAD_SET_PROCAMP_DYNAMIC_RANGE_CEA :
+                                 NVC57D_HEAD_SET_PROCAMP_DYNAMIC_RANGE_VESA));
+
+       PUSH_MTHD(push, NVC57D, HEAD_SET_CLAMP_RANGE_GREEN(i),
+                 NVVAL(NVC57D, HEAD_SET_CLAMP_RANGE_GREEN, LOW, lo) |
+                 NVVAL(NVC57D, HEAD_SET_CLAMP_RANGE_GREEN, HIGH, y_hi),
+
+                               HEAD_SET_CLAMP_RANGE_RED_BLUE(i),
+                 NVVAL(NVC57D, HEAD_SET_CLAMP_RANGE_RED_BLUE, LOW, lo) |
+                 NVVAL(NVC57D, HEAD_SET_CLAMP_RANGE_RED_BLUE, HIGH, c_hi));
+
+       if (!limited) {
+               PUSH_MTHD(push, NVC57D, HEAD_SET_OCSC1CONTROL(i),
+                         NVDEF(NVC57D, HEAD_SET_OCSC1CONTROL, ENABLE, 
DISABLE));
+               return 0;
+       }
+
+       PUSH_MTHD(push, NVC57D, HEAD_SET_OCSC1CONTROL(i),
+                 NVDEF(NVC57D, HEAD_SET_OCSC1CONTROL, ENABLE, ENABLE));
+       PUSH_MTHD(push, NVC57D, HEAD_SET_OCSC1COEFFICIENT_C00(i),
+                 headc57d_ocsc1_limited[colorspace], 12);
        return 0;
 }
 
-- 
2.53.0

Reply via email to