Nouveau always scans out full-range RGB, and asks the AVI InfoFrame to declare full range. drm_hdmi_avi_infoframe_quant_range() may only send a non-default Q when the sink sets QS in its Video Capability Data Block, so a sink without one is told nothing and, for CE video formats such as 1920x1080p60, applies its default limited range to full-range pixels: black levels are crushed and highlights clipped.
Attach the standard "Broadcast RGB" property to HDMI connectors on heads with an output CSC (Turing to Ada), and select limited range the same way drm_hdmi_state_helper.c does: "Full" and "Limited 16:235" are honoured, and "Automatic" follows drm_default_rgb_quant_range(), i.e. limited for CE video formats and full otherwise. The head's output CSC performs the compression and the AVI InfoFrame declares the range that is actually sent. A change of the property forces a modeset, as the range is selected in the encoder's atomic check. This changes the default for CE video formats on those heads from full-range pixels to limited range, matching i915 and the HDMI state helpers. "Full" restores the previous output. Signed-off-by: Daniel Campos Ramos <[email protected]> --- drivers/gpu/drm/nouveau/dispnv50/disp.c | 24 +++++++++++++++++++-- drivers/gpu/drm/nouveau/nouveau_connector.c | 24 ++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c index b216db61330c..54ed903644bf 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c @@ -387,6 +387,14 @@ nv50_outp_link_clock(const struct drm_display_mode *mode) return mode->clock; } +static bool +nv50_outp_has_output_csc(struct drm_encoder *encoder) +{ + const s32 oclass = nv50_disp(encoder->dev)->disp->object.oclass; + + return oclass >= TU102_DISP && oclass < GB202_DISP; +} + static void nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) @@ -442,6 +450,15 @@ nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, struct drm_crtc_state *c } conn_state->max_bpc = asyh->or.bpc; + + /* Broadcast RGB, as in drm_hdmi_state_helper.c: automatic + * selects limited range for CE video formats. + */ + if (nv50_outp_has_output_csc(encoder) && + (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED || + (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_AUTO && + drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED))) + asyh->procamp.limited = true; break; default: break; @@ -825,8 +842,9 @@ nv50_audio_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc, static void nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc, struct nouveau_connector *nv_connector, struct drm_atomic_commit *state, - struct drm_display_mode *mode, bool hda, u8 bpc) + struct drm_display_mode *mode, bool hda, struct nv50_head_atom *asyh) { + const u8 bpc = asyh->or.bpc; struct nouveau_drm *drm = nouveau_drm(encoder->dev); struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); struct drm_hdmi_info *hdmi = &nv_connector->base.display_info.hdmi; @@ -905,6 +923,8 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc, if (!drm_hdmi_avi_infoframe_from_display_mode(&infoframe.avi, &nv_connector->base, mode)) { drm_hdmi_avi_infoframe_quant_range(&infoframe.avi, &nv_connector->base, mode, + asyh->procamp.limited ? + HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL); size = hdmi_infoframe_pack(&infoframe, args->data, data_len); @@ -1877,7 +1897,7 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_commit *st 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, - asyh->or.bpc); + asyh); if (nv_encoder->outp.or.link & 1) { proto = NV507D_SOR_SET_CONTROL_PROTOCOL_SINGLE_TMDS_A; diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index b452e73f03f1..748c2f29d5d1 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -298,9 +298,15 @@ 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) + if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) { drm_connector_attach_max_bpc_property(connector, 8, 16); + /* Only heads with an output CSC can emit limited range. */ + if (disp->disp.object.oclass >= TU102_DISP && + disp->disp.object.oclass < GB202_DISP) + drm_connector_attach_broadcast_rgb_property(connector); + } + /* Add overscan compensation options to digital outputs. */ if (disp->underscan_property && (connector->connector_type == DRM_MODE_CONNECTOR_DVID || @@ -1213,9 +1219,25 @@ static int nouveau_connector_atomic_check(struct drm_connector *connector, struct drm_atomic_commit *state) { struct nouveau_connector *nv_conn = nouveau_connector(connector); + struct drm_connector_state *old_conn_state = + drm_atomic_get_old_connector_state(state, connector); struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, connector); + /* The quantization range is selected during the encoder's atomic + * check, which only runs on a modeset. + */ + if (conn_state->crtc && + old_conn_state->hdmi.broadcast_rgb != conn_state->hdmi.broadcast_rgb) { + struct drm_crtc_state *crtc_state; + + crtc_state = drm_atomic_get_crtc_state(state, conn_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->mode_changed = true; + } + if (!nv_conn->dp_encoder || !nv_conn->dp_encoder->dp.mstm) return 0; -- 2.53.0
