Nouveau currently initializes the output depth from the EDID basic display
parameters and does not expose a max bpc property on HDMI connectors.  As a
result, HDMI deep-color modes advertised by the VSDB are never selected.

Attach an 8..16 max bpc property.  Select 10, 12 or 16 bpc only when
the sink advertises the corresponding RGB 4:4:4 deep-color mode and the
resulting TMDS character rate fits the existing link limit.  Program the
matching head output-resource depth on NVDisplay.

Share the existing TMDS bandwidth helper with the atomic check so the depth
selection observes both sink and GPU limits.

Signed-off-by: Daniel Campos Ramos <[email protected]>
---
 drivers/gpu/drm/nouveau/dispnv50/disp.c     | 57 ++++++++++++++++++++-
 drivers/gpu/drm/nouveau/dispnv50/headc37d.c |  6 +++
 drivers/gpu/drm/nouveau/dispnv50/headc57d.c |  6 +++
 drivers/gpu/drm/nouveau/dispnv50/headca7d.c |  6 +++
 drivers/gpu/drm/nouveau/nouveau_connector.c |  9 ++--
 drivers/gpu/drm/nouveau/nouveau_connector.h |  1 +
 6 files changed, 80 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c 
b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index add19d47..6410b96b 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -375,10 +375,12 @@ nv50_outp_atomic_check_view(struct drm_encoder *encoder,
 }
 
 static void
-nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, struct drm_crtc_state 
*crtc_state)
+nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, struct drm_crtc_state 
*crtc_state,
+                          struct drm_connector_state *conn_state)
 {
        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;
        struct drm_display_mode *mode = &asyh->state.adjusted_mode;
        unsigned int max_rate, mode_rate;
 
@@ -398,6 +400,36 @@ nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, 
struct drm_crtc_state *c
                        asyh->or.bpc -= 2;
                }
                break;
+       case DCB_OUTPUT_TMDS:
+               if (!connector->display_info.is_hdmi)
+                       break;
+
+               max_rate = nouveau_connector_get_tmds_link_bandwidth(connector);
+               asyh->or.bpc = 8;
+
+               if (conn_state->max_requested_bpc >= 16 &&
+                   connector->display_info.edid_hdmi_rgb444_dc_modes & 
DRM_EDID_HDMI_DC_48) {
+                       mode_rate = DIV_ROUND_UP(mode->clock * 16, 8);
+                       if (mode_rate <= max_rate)
+                               asyh->or.bpc = 16;
+               }
+
+               if (asyh->or.bpc == 8 && conn_state->max_requested_bpc >= 12 &&
+                   connector->display_info.edid_hdmi_rgb444_dc_modes & 
DRM_EDID_HDMI_DC_36) {
+                       mode_rate = DIV_ROUND_UP(mode->clock * 12, 8);
+                       if (mode_rate <= max_rate)
+                               asyh->or.bpc = 12;
+               }
+
+               if (asyh->or.bpc == 8 && conn_state->max_requested_bpc >= 10 &&
+                   connector->display_info.edid_hdmi_rgb444_dc_modes & 
DRM_EDID_HDMI_DC_30) {
+                       mode_rate = DIV_ROUND_UP(mode->clock * 10, 8);
+                       if (mode_rate <= max_rate)
+                               asyh->or.bpc = 10;
+               }
+
+               conn_state->max_bpc = asyh->or.bpc;
+               break;
        default:
                break;
        }
@@ -422,7 +454,7 @@ nv50_outp_atomic_check(struct drm_encoder *encoder,
                asyh->or.bpc = connector->display_info.bpc;
 
        /* We might have to reduce the bpc */
-       nv50_outp_atomic_fix_depth(encoder, crtc_state);
+       nv50_outp_atomic_fix_depth(encoder, crtc_state, conn_state);
 
        return 0;
 }
@@ -1776,6 +1808,27 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, 
struct drm_atomic_commit *st
 
        switch (nv_encoder->dcb->type) {
        case DCB_OUTPUT_TMDS:
+               switch (asyh->or.bpc) {
+               case 16:
+                       depth = NV837D_SOR_SET_CONTROL_PIXEL_DEPTH_BPP_48_444;
+                       break;
+               case 12:
+                       depth = NV837D_SOR_SET_CONTROL_PIXEL_DEPTH_BPP_36_444;
+                       break;
+               case 10:
+                       depth = NV837D_SOR_SET_CONTROL_PIXEL_DEPTH_BPP_30_444;
+                       break;
+               case 8:
+                       depth = NV837D_SOR_SET_CONTROL_PIXEL_DEPTH_BPP_24_444;
+                       break;
+               case 6:
+                       depth = NV837D_SOR_SET_CONTROL_PIXEL_DEPTH_BPP_18_444;
+                       break;
+               default:
+                       depth = NV837D_SOR_SET_CONTROL_PIXEL_DEPTH_DEFAULT;
+                       break;
+               }
+
                if (disp->disp->object.oclass != NV50_DISP &&
                    nv_connector->base.display_info.is_hdmi)
                        nv50_hdmi_enable(encoder, nv_crtc, nv_connector, state, 
mode, hda);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/headc37d.c 
b/drivers/gpu/drm/nouveau/dispnv50/headc37d.c
index 2bcb3790..f42d6642 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headc37d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headc37d.c
@@ -39,6 +39,12 @@ headc37d_or(struct nv50_head *head, struct nv50_head_atom 
*asyh)
         *     improved later for deep colour etc.
         */
        switch (asyh->or.depth) {
+       case 9:
+               depth = 8;
+               break;
+       case 8:
+               depth = 7;
+               break;
        case 6: depth = 5; break;
        case 5: depth = 4; break;
        case 2: depth = 1; break;
diff --git a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c 
b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
index 3f8ba495..f1e9f02d 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headc57d.c
@@ -52,6 +52,12 @@ headc57d_or(struct nv50_head *head, struct nv50_head_atom 
*asyh)
         *     improved later for deep colour etc.
         */
        switch (asyh->or.depth) {
+       case 9:
+               depth = 8;
+               break;
+       case 8:
+               depth = 7;
+               break;
        case 6: depth = 5; break;
        case 5: depth = 4; break;
        case 2: depth = 1; break;
diff --git a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c 
b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
index 678cbc78..437633ff 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
@@ -35,6 +35,12 @@ headca7d_or(struct nv50_head *head, struct nv50_head_atom 
*asyh)
        int ret;
 
        switch (asyh->or.depth) {
+       case 9:
+               depth = 
NVCA7D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_PIXEL_DEPTH_BPP_48_444;
+               break;
+       case 8:
+               depth = 
NVCA7D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_PIXEL_DEPTH_BPP_36_444;
+               break;
        case 6:
                depth = 
NVCA7D_HEAD_SET_CONTROL_OUTPUT_RESOURCE_PIXEL_DEPTH_BPP_30_444;
                break;
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c 
b/drivers/gpu/drm/nouveau/nouveau_connector.c
index cc3caf8d..b452e73f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -298,6 +298,9 @@ nouveau_conn_attach_properties(struct drm_connector 
*connector)
                drm_object_attach_property(&connector->base, dev->mode_config.
                                           dvi_i_subconnector_property, 0);
 
+       if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA)
+               drm_connector_attach_max_bpc_property(connector, 8, 16);
+
        /* Add overscan compensation options to digital outputs. */
        if (disp->underscan_property &&
            (connector->connector_type == DRM_MODE_CONNECTOR_DVID ||
@@ -1101,8 +1104,8 @@ nouveau_connector_get_modes(struct drm_connector 
*connector)
        return ret;
 }
 
-static unsigned
-get_tmds_link_bandwidth(struct drm_connector *connector)
+unsigned int
+nouveau_connector_get_tmds_link_bandwidth(struct drm_connector *connector)
 {
        struct nouveau_connector *nv_connector = nouveau_connector(connector);
        struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder;
@@ -1168,7 +1171,7 @@ nouveau_connector_mode_valid(struct drm_connector 
*connector,
                max_clock = 400000;
                break;
        case DCB_OUTPUT_TMDS:
-               max_clock = get_tmds_link_bandwidth(connector);
+               max_clock = 
nouveau_connector_get_tmds_link_bandwidth(connector);
                break;
        case DCB_OUTPUT_ANALOG:
                max_clock = nv_encoder->dcb->crtconf.maxfreq;
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h 
b/drivers/gpu/drm/nouveau/nouveau_connector.h
index eb292d2b..6623b5e1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -216,6 +216,7 @@ extern int nouveau_duallink;
 extern int nouveau_hdmimhz;
 
 void nouveau_conn_attach_properties(struct drm_connector *);
+unsigned int nouveau_connector_get_tmds_link_bandwidth(struct drm_connector 
*connector);
 void nouveau_conn_reset(struct drm_connector *);
 struct drm_connector_state *
 nouveau_conn_atomic_duplicate_state(struct drm_connector *);
-- 
2.53.0

Reply via email to