diff --git a/common/edid.c b/common/edid.c index 00797a9f6d..2adcced730 100644 --- a/common/edid.c +++ b/common/edid.c @@ -2748,6 +2748,24 @@ static void drm_parse_hdmi_forum_vsdb(struct hdmi_edid_data *data, drm_parse_ycbcr420_deep_color_info(data, hf_vsdb); } +/** + * drm_default_rgb_quant_range - default RGB quantization range + * @mode: display mode + * + * Determine the default RGB quantization range for the mode, + * as specified in CEA-861. + * + * Return: The default RGB quantization range for the mode + */ +enum hdmi_quantization_range +drm_default_rgb_quant_range(struct drm_display_mode *mode) +{ + /* All CEA modes other than VIC 1 use limited quantization range. */ + return drm_match_cea_mode(mode) > 1 ? + HDMI_QUANTIZATION_RANGE_LIMITED : + HDMI_QUANTIZATION_RANGE_FULL; +} + static void drm_parse_hdmi_deep_color_info(struct hdmi_edid_data *data, const u8 *hdmi) { @@ -5157,6 +5175,50 @@ static int hdmi_vendor_infoframe_init(struct hdmi_vendor_infoframe *frame) return 0; } +/** + * drm_hdmi_avi_infoframe_quant_range() - fill the HDMI AVI infoframe + * quantization range information + * @frame: HDMI AVI infoframe + * @rgb_quant_range: RGB quantization range (Q) + * @rgb_quant_range_selectable: Sink support selectable RGB quantization range (QS) + */ +void +drm_hdmi_avi_infoframe_quant_range(struct hdmi_avi_infoframe *frame, + struct drm_display_mode *mode, + enum hdmi_quantization_range rgb_quant_range, + bool rgb_quant_range_selectable) +{ + /* + * CEA-861: + * "A Source shall not send a non-zero Q value that does not correspond + * to the default RGB Quantization Range for the transmitted Picture + * unless the Sink indicates support for the Q bit in a Video + * Capabilities Data Block." + * + * HDMI 2.0 recommends sending non-zero Q when it does match the + * default RGB quantization range for the mode, even when QS=0. + */ + if (rgb_quant_range_selectable || + rgb_quant_range == drm_default_rgb_quant_range(mode)) + frame->quantization_range = rgb_quant_range; + else + frame->quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + + /* + * CEA-861-F: + * "When transmitting any RGB colorimetry, the Source should set the + * YQ-field to match the RGB Quantization Range being transmitted + * (e.g., when Limited Range RGB, set YQ=0 or when Full Range RGB, + * set YQ=1) and the Sink shall ignore the YQ-field." + */ + if (rgb_quant_range == HDMI_QUANTIZATION_RANGE_LIMITED) + frame->ycc_quantization_range = + HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + else + frame->ycc_quantization_range = + HDMI_YCC_QUANTIZATION_RANGE_FULL; +} + static enum hdmi_3d_structure s3d_structure_from_display_mode(const struct drm_display_mode *mode) { diff --git a/drivers/video/drm/dw_hdmi.c b/drivers/video/drm/dw_hdmi.c index c95614367f..00a0ba4331 100644 --- a/drivers/video/drm/dw_hdmi.c +++ b/drivers/video/drm/dw_hdmi.c @@ -111,6 +111,12 @@ static const u16 csc_coeff_rgb_in_eitu709[3][4] = { { 0x6756, 0x78ab, 0x2000, 0x0200 } }; +static const u16 csc_coeff_full_to_limited[3][4] = { + { 0x36f7, 0x0000, 0x0000, 0x0040 }, + { 0x0000, 0x36f7, 0x0000, 0x0040 }, + { 0x0000, 0x0000, 0x36f7, 0x0040 } +}; + struct hdmi_vmode { bool mdataenablepolarity; @@ -125,6 +131,7 @@ struct hdmi_data_info { unsigned int enc_out_bus_format; unsigned int enc_in_encoding; unsigned int enc_out_encoding; + unsigned int quant_range; unsigned int pix_repet_factor; struct hdmi_vmode video_mode; }; @@ -327,8 +334,26 @@ static int hdmi_bus_fmt_color_depth(unsigned int bus_format) static int is_color_space_conversion(struct dw_hdmi *hdmi) { - return hdmi->hdmi_data.enc_in_bus_format != - hdmi->hdmi_data.enc_out_bus_format; + struct drm_display_mode *mode = + hdmi->edid_data.preferred_mode; + bool is_cea_default; + + is_cea_default = (drm_match_cea_mode(mode) > 1) && + (hdmi->hdmi_data.quant_range == + HDMI_QUANTIZATION_RANGE_DEFAULT); + + /* + * When output is rgb limited range or default range with + * cea mode, csc should be enabled. + */ + if (hdmi->hdmi_data.enc_in_bus_format != + hdmi->hdmi_data.enc_out_bus_format || + ((hdmi->hdmi_data.quant_range == HDMI_QUANTIZATION_RANGE_LIMITED || + is_cea_default) && + hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format))) + return 1; + + return 0; } static int is_color_space_decimation(struct dw_hdmi *hdmi) @@ -1139,16 +1164,22 @@ static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi) const u16 (*csc_coeff)[3][4] = &csc_coeff_default; unsigned i; u32 csc_scale = 1; + int enc_out_rgb, enc_in_rgb; + + enc_out_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format); + enc_in_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format); if (is_color_space_conversion(hdmi)) { - if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + if (enc_out_rgb == enc_in_rgb) { + csc_coeff = &csc_coeff_full_to_limited; + csc_scale = 0; + } else if (enc_out_rgb) { if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) csc_coeff = &csc_coeff_rgb_out_eitu601; else csc_coeff = &csc_coeff_rgb_out_eitu709; - } else if (hdmi_bus_fmt_is_rgb( - hdmi->hdmi_data.enc_in_bus_format)) { + } else if (enc_in_rgb) { if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) csc_coeff = &csc_coeff_rgb_in_eitu601; @@ -1513,6 +1544,8 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) struct hdmi_avi_infoframe frame; u8 val; bool is_hdmi2 = false; + enum hdmi_quantization_range rgb_quant_range = + hdmi->hdmi_data.quant_range; if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format) || hdmi->edid_data.display_info.hdmi.scdc.supported) @@ -1520,6 +1553,12 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) /* Initialise info frame from DRM mode */ drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, is_hdmi2); + /* + * Ignore monitor selectable quantization, use quantization set + * by the user + */ + drm_hdmi_avi_infoframe_quant_range(&frame, mode, rgb_quant_range, + true); if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) frame.colorspace = HDMI_COLORSPACE_YUV444; else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) @@ -2034,6 +2073,13 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, hdmi->plat_data->input_bus_encoding; else hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT; + + if (hdmi->plat_data->get_quant_range) + hdmi->hdmi_data.quant_range = + hdmi->plat_data->get_quant_range(data); + else + hdmi->hdmi_data.quant_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + /* * According to the dw-hdmi specification 6.4.2 * vp_pr_cd[3:0]: diff --git a/include/edid.h b/include/edid.h index 1ac641c963..7e52b325fd 100644 --- a/include/edid.h +++ b/include/edid.h @@ -847,6 +847,8 @@ bool drm_detect_hdmi_monitor(struct edid *edid); bool drm_detect_monitor_audio(struct edid *edid); int do_cea_modes(struct hdmi_edid_data *data, const u8 *db, u8 len); int drm_do_get_edid(struct ddc_adapter *adap, u8 *edid); +enum hdmi_quantization_range +drm_default_rgb_quant_range(struct drm_display_mode *mode); u8 drm_scdc_readb(struct ddc_adapter *adap, u8 offset, u8 *value); u8 drm_scdc_writeb(struct ddc_adapter *adap, u8 offset, diff --git a/include/linux/dw_hdmi.h b/include/linux/dw_hdmi.h index 8b0b8fa6e2..6a9f473741 100644 --- a/include/linux/dw_hdmi.h +++ b/include/linux/dw_hdmi.h @@ -163,6 +163,7 @@ struct dw_hdmi_plat_data { unsigned long (*get_output_bus_format)(void *data); unsigned long (*get_enc_in_encoding)(void *data); unsigned long (*get_enc_out_encoding)(void *data); + unsigned long (*get_quant_range)(void *data); }; #endif /* __IMX_HDMI_H__ */ diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h index 70a8e36755..005d34a8fc 100644 --- a/include/linux/hdmi.h +++ b/include/linux/hdmi.h @@ -322,6 +322,11 @@ int drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame, struct drm_display_mode *mode, bool is_hdmi2_sink); +void +drm_hdmi_avi_infoframe_quant_range(struct hdmi_avi_infoframe *frame, + struct drm_display_mode *mode, + enum hdmi_quantization_range rgb_quant_range, + bool rgb_quant_range_selectable); u8 drm_match_cea_mode(struct drm_display_mode *to_match); #endif /* _DRM_HDMI_H */