GA102 and later heads can pack YCbCr 4:2:0 in hardware when the
capability notifier reports the horizontal chroma low-pass and vertical
filters (POSTCOMP_HEAD_HDR_CAPA HCLPF/VFILTER), which is how NVIDIA's
open NVKMS enables it.  Read that per head, and on such heads offer
YCbCr 4:2:0 through the "color format" property and allow 4:2:0 modes.

4:2:0 enables the head's YUV420PACKER, CHROMA_LPF and CHROMA_DOWN_V,
reuses the 4:4:4 pixel depths and GCP encodings, is BT.709 limited
range, and is declared in the AVI InfoFrame.  The sink rules follow
drm_hdmi_state_helper.c: the mode must support 4:2:0 and deep colour
uses the 4:2:0 Deep Color bits; the TMDS character rate is half the
pixel clock.  "Automatic" falls back to 4:2:0 when RGB cannot carry the
mode, a 4:2:0-only mode is rejected on a head that cannot pack it, and
mode validation accounts for the halved rate as
drm_hdmi_connector_mode_valid() does.

The class definitions are subsets of NVIDIA's open-gpu-kernel-modules
clc67d.h and clc673.h.

Not tested on hardware: the only sink available here declares no 4:2:0
support.  The head programming, pixel depths and packets are the ones
exercised by the 4:4:4 and 4:2:2 paths, which were.

Signed-off-by: Daniel Campos Ramos <[email protected]>
---
 drivers/gpu/drm/nouveau/dispnv50/disp.c       | 83 +++++++++++++++----
 drivers/gpu/drm/nouveau/dispnv50/head.c       | 18 +++-
 drivers/gpu/drm/nouveau/dispnv50/head.h       |  3 +
 drivers/gpu/drm/nouveau/dispnv50/headc57d.c   | 13 ++-
 .../drm/nouveau/include/nvhw/class/clc673.h   | 17 ++++
 .../drm/nouveau/include/nvhw/class/clc67d.h   | 16 ++++
 drivers/gpu/drm/nouveau/nouveau_connector.c   | 12 ++-
 7 files changed, 142 insertions(+), 20 deletions(-)
 create mode 100644 drivers/gpu/drm/nouveau/include/nvhw/class/clc673.h
 create mode 100644 drivers/gpu/drm/nouveau/include/nvhw/class/clc67d.h

diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c 
b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index ad8952dfcd71..c52629eaf786 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -423,6 +423,22 @@ nv50_hdmi_sink_supports(const struct drm_display_info 
*info,
                 * which carries up to 12 bpc in the 24-bit container.
                 */
                return (info->color_formats & BIT(fmt)) && bpc <= 12;
+       case DRM_OUTPUT_COLOR_FORMAT_YCBCR420:
+               /* 4:2:0 has its own Deep Color bits (HF-VSDB / Y420CMDB). */
+               if (!drm_mode_is_420(info, mode))
+                       return false;
+               switch (bpc) {
+               case 8:
+                       return true;
+               case 10:
+                       return info->hdmi.y420_dc_modes & 
DRM_EDID_YCBCR420_DC_30;
+               case 12:
+                       return info->hdmi.y420_dc_modes & 
DRM_EDID_YCBCR420_DC_36;
+               case 16:
+                       return info->hdmi.y420_dc_modes & 
DRM_EDID_YCBCR420_DC_48;
+               default:
+                       return false;
+               }
        default:
                return false;
        }
@@ -456,10 +472,37 @@ nv50_hdmi_char_rate(const struct drm_display_mode *mode, 
unsigned int bpc,
        return rate;
 }
 
+/* Deepest depth the sink declares for the format, the user allows and the
+ * TMDS link carries, or 0.
+ */
+static unsigned int
+nv50_hdmi_pick_bpc(const struct drm_connector_state *conn_state,
+                  const struct drm_display_mode *mode,
+                  enum drm_output_color_format fmt, unsigned int max_rate)
+{
+       const struct drm_display_info *info = 
&conn_state->connector->display_info;
+       unsigned int bpc;
+
+       for (bpc = 16; bpc >= 8; bpc -= 2) {
+               unsigned long long rate;
+
+               if (bpc > conn_state->max_requested_bpc ||
+                   !nv50_hdmi_sink_supports(info, mode, fmt, bpc))
+                       continue;
+
+               rate = nv50_hdmi_char_rate(mode, bpc, fmt);
+               if (rate && rate <= max_rate * 1000ULL)
+                       return bpc;
+       }
+
+       return 0;
+}
+
 static int
 nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, struct drm_crtc_state 
*crtc_state,
                           struct drm_connector_state *conn_state)
 {
+       struct nv50_head *head = nv50_head(crtc_state->crtc);
        struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
        struct drm_connector *connector = conn_state->connector;
@@ -497,29 +540,34 @@ nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, 
struct drm_crtc_state *c
                case DRM_CONNECTOR_COLOR_FORMAT_YCBCR422:
                        fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR422;
                        break;
+               case DRM_CONNECTOR_COLOR_FORMAT_YCBCR420:
+                       if (!head->yuv420)
+                               return -EINVAL;
+                       fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR420;
+                       break;
                default:
                        break;
                }
 
-               /* Deepest depth the sink declares for the format, the user
-                * allows and the TMDS link carries.
-                */
                max_rate = nouveau_connector_get_tmds_link_bandwidth(connector);
-               for (bpc = 16; bpc >= 8; bpc -= 2) {
-                       unsigned long long rate;
-
-                       if (bpc > conn_state->max_requested_bpc ||
-                           !nv50_hdmi_sink_supports(info, mode, fmt, bpc))
-                               continue;
+               bpc = nv50_hdmi_pick_bpc(conn_state, mode, fmt, max_rate);
 
-                       rate = nv50_hdmi_char_rate(mode, bpc, fmt);
-                       if (rate && rate <= max_rate * 1000ULL)
-                               break;
+               /* "Automatic" falls back to 4:2:0 when RGB cannot carry the
+                * mode, as in drm_hdmi_state_helper.c.
+                */
+               if (bpc < 8 && fmt == DRM_OUTPUT_COLOR_FORMAT_RGB444 &&
+                   drm_connector_get_color_format(conn_state) == 
DRM_CONNECTOR_COLOR_FORMAT_AUTO &&
+                   head->yuv420 && drm_mode_is_420(info, mode)) {
+                       fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR420;
+                       bpc = nv50_hdmi_pick_bpc(conn_state, mode, fmt, 
max_rate);
                }
 
                if (bpc < 8) {
-                       /* An explicit format request is honoured or refused. */
-                       if (fmt != DRM_OUTPUT_COLOR_FORMAT_RGB444)
+                       /* An explicit format request is honoured or refused,
+                        * and a 4:2:0-only mode cannot fall back to RGB.
+                        */
+                       if (fmt != DRM_OUTPUT_COLOR_FORMAT_RGB444 ||
+                           drm_mode_is_420_only(info, mode))
                                return -EINVAL;
                        bpc = 8;
                }
@@ -532,7 +580,8 @@ nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, 
struct drm_crtc_state *c
                        /* YCbCr is limited range; CTA-861 colorimetry
                         * defaults to BT.709 above 576 lines, BT.601 below.
                         */
-                       asyh->procamp.colorspace = mode->vdisplay > 576 ?
+                       asyh->procamp.colorspace = mode->vdisplay > 576 ||
+                                                  fmt == 
DRM_OUTPUT_COLOR_FORMAT_YCBCR420 ?
                                                   NV50_HEAD_COLORSPACE_YUV_709 
:
                                                   NV50_HEAD_COLORSPACE_YUV_601;
                        asyh->procamp.limited = true;
@@ -1027,7 +1076,9 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct 
nouveau_crtc *nv_crtc,
                                                           
HDMI_QUANTIZATION_RANGE_FULL);
                } else {
                        /* Declare the matrix the head's output CSC applies. */
-                       avi->colorspace = fmt == 
DRM_OUTPUT_COLOR_FORMAT_YCBCR422 ?
+                       avi->colorspace = fmt == 
DRM_OUTPUT_COLOR_FORMAT_YCBCR420 ?
+                                         HDMI_COLORSPACE_YUV420 :
+                                         fmt == 
DRM_OUTPUT_COLOR_FORMAT_YCBCR422 ?
                                          HDMI_COLORSPACE_YUV422 : 
HDMI_COLORSPACE_YUV444;
                        avi->colorimetry = asyh->procamp.colorspace ==
                                           NV50_HEAD_COLORSPACE_YUV_709 ?
diff --git a/drivers/gpu/drm/nouveau/dispnv50/head.c 
b/drivers/gpu/drm/nouveau/dispnv50/head.c
index 7b94c881e8bb..ca48bedbdbe2 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/head.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.c
@@ -30,6 +30,8 @@
 #include <nvif/event.h>
 #include <nvif/cl0046.h>
 
+#include <nvhw/class/clc673.h>
+
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_edid.h>
@@ -371,7 +373,8 @@ nv50_head_atomic_check(struct drm_crtc *crtc, struct 
drm_atomic_commit *state)
                                        asyc->set.dither = true;
                        }
                        if (armh->procamp.colorspace != 
asyh->procamp.colorspace ||
-                           armh->procamp.limited != asyh->procamp.limited)
+                           armh->procamp.limited != asyh->procamp.limited ||
+                           armh->or.format != asyh->or.format)
                                asyh->set.procamp = true;
                } else {
                        if (asyc)
@@ -586,6 +589,19 @@ nv50_head_create(struct drm_device *dev, int index)
        head->disp = disp;
        head->base.index = index;
 
+       /* GA102 and later heads pack YCbCr 4:2:0 in hardware when they have
+        * the horizontal chroma low-pass and vertical filters.
+        */
+       if (disp->disp->object.oclass >= GA102_DISP &&
+           disp->disp->object.oclass < GB202_DISP) {
+               const u32 capa = nvif_rd32(&disp->caps,
+                                          
NVC673_POSTCOMP_HEAD_HDR_CAPA(index));
+
+               head->yuv420 =
+                       NVDEF_TEST(capa, NVC673, POSTCOMP_HEAD_HDR_CAPA, 
HCLPF_PRESENT, ==, TRUE) &&
+                       NVDEF_TEST(capa, NVC673, POSTCOMP_HEAD_HDR_CAPA, 
VFILTER_PRESENT, ==, TRUE);
+       }
+
        if (disp->disp->object.oclass < GF110_DISP)
                funcs = &nv50_head_func;
        else
diff --git a/drivers/gpu/drm/nouveau/dispnv50/head.h 
b/drivers/gpu/drm/nouveau/dispnv50/head.h
index 8bd2fcb1eff5..db4a50a85e79 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/head.h
+++ b/drivers/gpu/drm/nouveau/dispnv50/head.h
@@ -19,6 +19,9 @@ struct nv50_head {
        struct nv50_crc crc;
        struct nv50_lut olut;
        struct nv50_msto *msto;
+
+       /* The head can pack YCbCr 4:2:0 in hardware. */
+       bool yuv420;
 };
 
 struct nv50_head *nv50_head_create(struct drm_device *, int index);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c 
b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
index 937f71f264db..9e152daefc58 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
@@ -26,6 +26,7 @@
 #include <nvif/pushc37b.h>
 
 #include <nvhw/class/clc57d.h>
+#include <nvhw/class/clc67d.h>
 
 static int
 headc57d_display_id(struct nv50_head *head, u32 display_id)
@@ -111,6 +112,7 @@ 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 bool yuv420 = asyh->or.format == DRM_OUTPUT_COLOR_FORMAT_YCBCR420;
        const int i = head->base.index;
        u32 lo = 0x000, y_hi = 0xfff, c_hi = 0xfff;
        int ret;
@@ -126,9 +128,14 @@ headc57d_procamp(struct nv50_head *head, struct 
nv50_head_atom *asyh)
        if (ret)
                return ret;
 
+       /* 4:2:0 filters chroma horizontally and vertically.  CHROMA_DOWN_V
+        * is an NVC67D field and an unused bit on NVC57D; 4:2:0 is only
+        * selected on GA102 and later heads.
+        */
        PUSH_MTHD(push, NVC57D, HEAD_SET_PROCAMP(i),
                  NVVAL(NVC57D, HEAD_SET_PROCAMP, COLOR_SPACE, colorspace) |
-                 NVDEF(NVC57D, HEAD_SET_PROCAMP, CHROMA_LPF, DISABLE) |
+                 NVVAL(NVC57D, HEAD_SET_PROCAMP, CHROMA_LPF, yuv420) |
+                 NVVAL(NVC67D, HEAD_SET_PROCAMP, CHROMA_DOWN_V, yuv420) |
                  NVVAL(NVC57D, HEAD_SET_PROCAMP, DYNAMIC_RANGE,
                        limited ? NVC57D_HEAD_SET_PROCAMP_DYNAMIC_RANGE_CEA :
                                  NVC57D_HEAD_SET_PROCAMP_DYNAMIC_RANGE_VESA));
@@ -288,7 +295,9 @@ headc57d_mode(struct nv50_head *head, struct nv50_head_atom 
*asyh)
 
        //XXX:
        PUSH_NVSQ(push, NVC57D, 0x2074 + (i * 0x400), m->v.blank2e << 16 | 
m->v.blank2s);
-       PUSH_NVSQ(push, NVC57D, 0x2008 + (i * 0x400), m->interlace);
+       PUSH_NVSQ(push, NVC57D, 0x2008 + (i * 0x400), m->interlace |
+                 NVVAL(NVC67D, HEAD_SET_CONTROL, YUV420PACKER,
+                       asyh->or.format == DRM_OUTPUT_COLOR_FORMAT_YCBCR420));
 
        PUSH_MTHD(push, NVC57D, HEAD_SET_PIXEL_CLOCK_FREQUENCY(i),
                  NVVAL(NVC57D, HEAD_SET_PIXEL_CLOCK_FREQUENCY, HERTZ, m->clock 
* 1000));
diff --git a/drivers/gpu/drm/nouveau/include/nvhw/class/clc673.h 
b/drivers/gpu/drm/nouveau/include/nvhw/class/clc673.h
new file mode 100644
index 000000000000..ba901cf7ac28
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/include/nvhw/class/clc673.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2003-2021, NVIDIA CORPORATION. All rights reserved.
+ */
+#ifndef _clc673_h_
+#define _clc673_h_
+
+/* Subset: the per-head post-composition capabilities used by nouveau. */
+#define NVC673_POSTCOMP_HEAD_HDR_CAPA(i)                                      
(0x680+(i)*32) /* RW-4A */
+#define NVC673_POSTCOMP_HEAD_HDR_CAPA_HCLPF_PRESENT                            
             19:19 /* RWIVF */
+#define NVC673_POSTCOMP_HEAD_HDR_CAPA_HCLPF_PRESENT_TRUE                       
        0x00000001 /* RW--V */
+#define NVC673_POSTCOMP_HEAD_HDR_CAPA_HCLPF_PRESENT_FALSE                      
        0x00000000 /* RW--V */
+#define NVC673_POSTCOMP_HEAD_HDR_CAPA_VFILTER_PRESENT                          
             23:23 /* RWIVF */
+#define NVC673_POSTCOMP_HEAD_HDR_CAPA_VFILTER_PRESENT_TRUE                     
        0x00000001 /* RW--V */
+#define NVC673_POSTCOMP_HEAD_HDR_CAPA_VFILTER_PRESENT_FALSE                    
        0x00000000 /* RW--V */
+
+#endif // _clc673_h_
diff --git a/drivers/gpu/drm/nouveau/include/nvhw/class/clc67d.h 
b/drivers/gpu/drm/nouveau/include/nvhw/class/clc67d.h
new file mode 100644
index 000000000000..a659f1f2121c
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/include/nvhw/class/clc67d.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
+ */
+#ifndef _clC67d_h_
+#define _clC67d_h_
+
+/* Subset: the NVC67D additions to NVC57D head methods used by nouveau. */
+#define NVC67D_HEAD_SET_PROCAMP_CHROMA_DOWN_V                                  
 4:4
+#define NVC67D_HEAD_SET_PROCAMP_CHROMA_DOWN_V_DISABLE                          
 (0x00000000)
+#define NVC67D_HEAD_SET_PROCAMP_CHROMA_DOWN_V_ENABLE                           
 (0x00000001)
+#define NVC67D_HEAD_SET_CONTROL_YUV420PACKER                                   
 3:3
+#define NVC67D_HEAD_SET_CONTROL_YUV420PACKER_DISABLE                           
 (0x00000000)
+#define NVC67D_HEAD_SET_CONTROL_YUV420PACKER_ENABLE                            
 (0x00000001)
+
+#endif // _clC67d_h_
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c 
b/drivers/gpu/drm/nouveau/nouveau_connector.c
index 2a9782d6c382..e274251adbb2 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -306,12 +306,17 @@ nouveau_conn_attach_properties(struct drm_connector 
*connector)
                 */
                if (disp->disp.object.oclass >= TU102_DISP &&
                    disp->disp.object.oclass < GB202_DISP) {
-                       const unsigned long formats =
+                       unsigned long formats =
                                BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
                                BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
                                BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
 
                        drm_connector_attach_broadcast_rgb_property(connector);
+                       if (disp->disp.object.oclass >= GA102_DISP) {
+                               /* 4:2:0 is further gated on each head's 
filters. */
+                               formats |= 
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
+                               connector->ycbcr_420_allowed = true;
+                       }
                        drm_connector_attach_color_format_property(connector, 
formats);
                }
        }
@@ -1205,6 +1210,11 @@ nouveau_connector_mode_valid(struct drm_connector 
*connector,
        if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == 
DRM_MODE_FLAG_3D_FRAME_PACKING)
                clock *= 2;
 
+       /* YCbCr 4:2:0 halves the TMDS character rate. */
+       if (clock > max_clock && connector->ycbcr_420_allowed &&
+           drm_mode_is_420(&connector->display_info, mode))
+               clock /= 2;
+
        if (clock < min_clock)
                return MODE_CLOCK_LOW;
        if (clock > max_clock)
-- 
2.53.0

Reply via email to