Contributors: 19
Author Tokens Token Proportion Commits Commit Proportion
Maxime Ripard 2567 82.12% 13 29.55%
Dmitry Eremin-Solenikov 206 6.59% 5 11.36%
Hans de Goede 77 2.46% 2 4.55%
Daniel Vetter 69 2.21% 5 11.36%
Dave Airlie 55 1.76% 3 6.82%
Rob Clark 39 1.25% 2 4.55%
Ville Syrjälä 31 0.99% 2 4.55%
Doug Anderson 30 0.96% 1 2.27%
Liu Ying 15 0.48% 1 2.27%
Radhakrishna Sripada 7 0.22% 1 2.27%
Maarten Lankhorst 7 0.22% 1 2.27%
Andrzej Pietrasiewicz 5 0.16% 1 2.27%
Cristian Ciocaltea 4 0.13% 1 2.27%
Derek Foreman 4 0.13% 1 2.27%
Björn Andersson 3 0.10% 1 2.27%
Thierry Reding 3 0.10% 1 2.27%
Lionel Landwerlin 2 0.06% 1 2.27%
David Turner 1 0.03% 1 2.27%
Chris Wilson 1 0.03% 1 2.27%
Total 3126 44


// SPDX-License-Identifier: MIT

#include <drm/drm_atomic.h>
#include <drm/drm_connector.h>
#include <drm/drm_edid.h>
#include <drm/drm_print.h>

#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>

/**
 * DOC: hdmi helpers
 *
 * These functions contain an implementation of the HDMI specification
 * in the form of KMS helpers.
 *
 * It contains TMDS character rate computation, automatic selection of
 * output formats, infoframes generation, etc.
 *
 * Infoframes Compliance
 * ~~~~~~~~~~~~~~~~~~~~~
 *
 * Drivers using the helpers will expose the various infoframes
 * generated according to the HDMI specification in debugfs.
 *
 * Compliance can then be tested using ``edid-decode`` from the ``v4l-utils`` project
 * (https://git.linuxtv.org/v4l-utils.git/). A sample run would look like:
 *
 * .. code-block:: bash
 *
 *	# edid-decode \
 *		-I /sys/kernel/debug/dri/1/HDMI-A-1/infoframes/audio \
 *		-I /sys/kernel/debug/dri/1/HDMI-A-1/infoframes/avi \
 *		-I /sys/kernel/debug/dri/1/HDMI-A-1/infoframes/hdmi \
 *		-I /sys/kernel/debug/dri/1/HDMI-A-1/infoframes/hdr_drm \
 *		-I /sys/kernel/debug/dri/1/HDMI-A-1/infoframes/spd \
 *		/sys/class/drm/card1-HDMI-A-1/edid \
 *		-c
 *
 *	edid-decode (hex):
 *
 *	00 ff ff ff ff ff ff 00 1e 6d f4 5b 1e ef 06 00
 *	07 20 01 03 80 2f 34 78 ea 24 05 af 4f 42 ab 25
 *	0f 50 54 21 08 00 d1 c0 61 40 45 40 01 01 01 01
 *	01 01 01 01 01 01 98 d0 00 40 a1 40 d4 b0 30 20
 *	3a 00 d1 0b 12 00 00 1a 00 00 00 fd 00 3b 3d 1e
 *	b2 31 00 0a 20 20 20 20 20 20 00 00 00 fc 00 4c
 *	47 20 53 44 51 48 44 0a 20 20 20 20 00 00 00 ff
 *	00 32 30 37 4e 54 52 4c 44 43 34 33 30 0a 01 46
 *
 *	02 03 42 72 23 09 07 07 4d 01 03 04 90 12 13 1f
 *	22 5d 5e 5f 60 61 83 01 00 00 6d 03 0c 00 10 00
 *	b8 3c 20 00 60 01 02 03 67 d8 5d c4 01 78 80 03
 *	e3 0f 00 18 e2 00 6a e3 05 c0 00 e6 06 05 01 52
 *	52 51 11 5d 00 a0 a0 40 29 b0 30 20 3a 00 d1 0b
 *	12 00 00 1a 00 00 00 00 00 00 00 00 00 00 00 00
 *	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 *	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c3
 *
 *	----------------
 *
 *	Block 0, Base EDID:
 *	  EDID Structure Version & Revision: 1.3
 *	  Vendor & Product Identification:
 *	    Manufacturer: GSM
 *	    Model: 23540
 *	    Serial Number: 454430 (0x0006ef1e)
 *	    Made in: week 7 of 2022
 *	  Basic Display Parameters & Features:
 *	    Digital display
 *	    Maximum image size: 47 cm x 52 cm
 *	    Gamma: 2.20
 *	    DPMS levels: Standby Suspend Off
 *	    RGB color display
 *	    First detailed timing is the preferred timing
 *	  Color Characteristics:
 *	    Red  : 0.6835, 0.3105
 *	    Green: 0.2587, 0.6679
 *	    Blue : 0.1445, 0.0585
 *	    White: 0.3134, 0.3291
 *	  Established Timings I & II:
 *	    DMT 0x04:   640x480    59.940476 Hz   4:3     31.469 kHz     25.175000 MHz
 *	    DMT 0x09:   800x600    60.316541 Hz   4:3     37.879 kHz     40.000000 MHz
 *	    DMT 0x10:  1024x768    60.003840 Hz   4:3     48.363 kHz     65.000000 MHz
 *	  Standard Timings:
 *	    DMT 0x52:  1920x1080   60.000000 Hz  16:9     67.500 kHz    148.500000 MHz
 *	    DMT 0x10:  1024x768    60.003840 Hz   4:3     48.363 kHz     65.000000 MHz
 *	    DMT 0x09:   800x600    60.316541 Hz   4:3     37.879 kHz     40.000000 MHz
 *	  Detailed Timing Descriptors:
 *	    DTD 1:  2560x2880   59.966580 Hz   8:9    185.417 kHz    534.000000 MHz (465 mm x 523 mm)
 *	                 Hfront   48 Hsync  32 Hback  240 Hpol P
 *	                 Vfront    3 Vsync  10 Vback  199 Vpol N
 *	    Display Range Limits:
 *	      Monitor ranges (GTF): 59-61 Hz V, 30-178 kHz H, max dotclock 490 MHz
 *	    Display Product Name: 'LG SDQHD'
 *	    Display Product Serial Number: '207NTRLDC430'
 *	  Extension blocks: 1
 *	Checksum: 0x46
 *
 *	----------------
 *
 *	Block 1, CTA-861 Extension Block:
 *	  Revision: 3
 *	  Basic audio support
 *	  Supports YCbCr 4:4:4
 *	  Supports YCbCr 4:2:2
 *	  Native detailed modes: 2
 *	  Audio Data Block:
 *	    Linear PCM:
 *	      Max channels: 2
 *	      Supported sample rates (kHz): 48 44.1 32
 *	      Supported sample sizes (bits): 24 20 16
 *	  Video Data Block:
 *	    VIC   1:   640x480    59.940476 Hz   4:3     31.469 kHz     25.175000 MHz
 *	    VIC   3:   720x480    59.940060 Hz  16:9     31.469 kHz     27.000000 MHz
 *	    VIC   4:  1280x720    60.000000 Hz  16:9     45.000 kHz     74.250000 MHz
 *	    VIC  16:  1920x1080   60.000000 Hz  16:9     67.500 kHz    148.500000 MHz (native)
 *	    VIC  18:   720x576    50.000000 Hz  16:9     31.250 kHz     27.000000 MHz
 *	    VIC  19:  1280x720    50.000000 Hz  16:9     37.500 kHz     74.250000 MHz
 *	    VIC  31:  1920x1080   50.000000 Hz  16:9     56.250 kHz    148.500000 MHz
 *	    VIC  34:  1920x1080   30.000000 Hz  16:9     33.750 kHz     74.250000 MHz
 *	    VIC  93:  3840x2160   24.000000 Hz  16:9     54.000 kHz    297.000000 MHz
 *	    VIC  94:  3840x2160   25.000000 Hz  16:9     56.250 kHz    297.000000 MHz
 *	    VIC  95:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz
 *	    VIC  96:  3840x2160   50.000000 Hz  16:9    112.500 kHz    594.000000 MHz
 *	    VIC  97:  3840x2160   60.000000 Hz  16:9    135.000 kHz    594.000000 MHz
 *	  Speaker Allocation Data Block:
 *	    FL/FR - Front Left/Right
 *	  Vendor-Specific Data Block (HDMI), OUI 00-0C-03:
 *	    Source physical address: 1.0.0.0
 *	    Supports_AI
 *	    DC_36bit
 *	    DC_30bit
 *	    DC_Y444
 *	    Maximum TMDS clock: 300 MHz
 *	    Extended HDMI video details:
 *	      HDMI VICs:
 *	        HDMI VIC 1:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz
 *	        HDMI VIC 2:  3840x2160   25.000000 Hz  16:9     56.250 kHz    297.000000 MHz
 *	        HDMI VIC 3:  3840x2160   24.000000 Hz  16:9     54.000 kHz    297.000000 MHz
 *	  Vendor-Specific Data Block (HDMI Forum), OUI C4-5D-D8:
 *	    Version: 1
 *	    Maximum TMDS Character Rate: 600 MHz
 *	    SCDC Present
 *	    Supports 12-bits/component Deep Color 4:2:0 Pixel Encoding
 *	    Supports 10-bits/component Deep Color 4:2:0 Pixel Encoding
 *	  YCbCr 4:2:0 Capability Map Data Block:
 *	    VIC  96:  3840x2160   50.000000 Hz  16:9    112.500 kHz    594.000000 MHz
 *	    VIC  97:  3840x2160   60.000000 Hz  16:9    135.000 kHz    594.000000 MHz
 *	  Video Capability Data Block:
 *	    YCbCr quantization: No Data
 *	    RGB quantization: Selectable (via AVI Q)
 *	    PT scan behavior: Always Underscanned
 *	    IT scan behavior: Always Underscanned
 *	    CE scan behavior: Always Underscanned
 *	  Colorimetry Data Block:
 *	    BT2020YCC
 *	    BT2020RGB
 *	  HDR Static Metadata Data Block:
 *	    Electro optical transfer functions:
 *	      Traditional gamma - SDR luminance range
 *	      SMPTE ST2084
 *	    Supported static metadata descriptors:
 *	      Static metadata type 1
 *	    Desired content max luminance: 82 (295.365 cd/m^2)
 *	    Desired content max frame-average luminance: 82 (295.365 cd/m^2)
 *	    Desired content min luminance: 81 (0.298 cd/m^2)
 *	  Detailed Timing Descriptors:
 *	    DTD 2:  2560x2880   29.986961 Hz   8:9     87.592 kHz    238.250000 MHz (465 mm x 523 mm)
 *	                 Hfront   48 Hsync  32 Hback   80 Hpol P
 *	                 Vfront    3 Vsync  10 Vback   28 Vpol N
 *	Checksum: 0xc3  Unused space in Extension Block: 43 bytes
 *
 *	----------------
 *
 *	edid-decode 1.29.0-5346
 *	edid-decode SHA: c363e9aa6d70 2025-03-11 11:41:18
 *
 *	Warnings:
 *
 *	Block 1, CTA-861 Extension Block:
 *	  IT Video Formats are overscanned by default, but normally this should be underscanned.
 *	  Video Data Block: VIC 1 and the first DTD are not identical. Is this intended?
 *	  Video Data Block: All VICs are in ascending order, and the first (preferred) VIC <= 4, is that intended?
 *	  Video Capability Data Block: Set Selectable YCbCr Quantization to avoid interop issues.
 *	  Video Capability Data Block: S_PT is equal to S_IT and S_CE, so should be set to 0 instead.
 *	  Colorimetry Data Block: Set the sRGB colorimetry bit to avoid interop issues.
 *	  Display Product Serial Number is set, so the Serial Number in the Base EDID should be 0.
 *	EDID:
 *	  Base EDID: Some timings are out of range of the Monitor Ranges:
 *	    Vertical Freq: 24.000 - 60.317 Hz (Monitor: 59.000 - 61.000 Hz)
 *	    Horizontal Freq: 31.250 - 185.416 kHz (Monitor: 30.000 - 178.000 kHz)
 *	    Maximum Clock: 594.000 MHz (Monitor: 490.000 MHz)
 *
 *	Failures:
 *
 *	Block 1, CTA-861 Extension Block:
 *	  Video Capability Data Block: IT video formats are always underscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to overscanned.
 *	EDID:
 *	  CTA-861: Native progressive timings are a mix of several resolutions.
 *
 *	EDID conformity: FAIL
 *
 *	================
 *
 *	InfoFrame of '/sys/kernel/debug/dri/1/HDMI-A-1/infoframes/audio' was empty.
 *
 *	================
 *
 *	edid-decode InfoFrame (hex):
 *
 *	82 02 0d 31 12 28 04 00 00 00 00 00 00 00 00 00
 *	00
 *
 *	----------------
 *
 *	HDMI InfoFrame Checksum: 0x31
 *
 *	AVI InfoFrame
 *	  Version: 2
 *	  Length: 13
 *	  Y: Color Component Sample Format: RGB
 *	  A: Active Format Information Present: Yes
 *	  B: Bar Data Present: Bar Data not present
 *	  S: Scan Information: Composed for an underscanned display
 *	  C: Colorimetry: No Data
 *	  M: Picture Aspect Ratio: 16:9
 *	  R: Active Portion Aspect Ratio: 8
 *	  ITC: IT Content: No Data
 *	  EC: Extended Colorimetry: xvYCC601
 *	  Q: RGB Quantization Range: Limited Range
 *	  SC: Non-Uniform Picture Scaling: No Known non-uniform scaling
 *	  YQ: YCC Quantization Range: Limited Range
 *	  CN: IT Content Type: Graphics
 *	  PR: Pixel Data Repetition Count: 0
 *	  Line Number of End of Top Bar: 0
 *	  Line Number of Start of Bottom Bar: 0
 *	  Pixel Number of End of Left Bar: 0
 *	  Pixel Number of Start of Right Bar: 0
 *
 *	----------------
 *
 *	AVI InfoFrame conformity: PASS
 *
 *	================
 *
 *	edid-decode InfoFrame (hex):
 *
 *	81 01 05 49 03 0c 00 20 01
 *
 *	----------------
 *
 *	HDMI InfoFrame Checksum: 0x49
 *
 *	Vendor-Specific InfoFrame (HDMI), OUI 00-0C-03
 *	  Version: 1
 *	  Length: 5
 *	  HDMI Video Format: HDMI_VIC is present
 *	  HDMI VIC 1:  3840x2160   30.000000 Hz  16:9     67.500 kHz    297.000000 MHz
 *
 *	----------------
 *
 *	Vendor-Specific InfoFrame (HDMI), OUI 00-0C-03 conformity: PASS
 *
 *	================
 *
 *	InfoFrame of '/sys/kernel/debug/dri/1/HDMI-A-1/infoframes/hdr_drm' was empty.
 *
 *	================
 *
 *	edid-decode InfoFrame (hex):
 *
 *	83 01 19 93 42 72 6f 61 64 63 6f 6d 56 69 64 65
 *	6f 63 6f 72 65 00 00 00 00 00 00 00 09
 *
 *	----------------
 *
 *	HDMI InfoFrame Checksum: 0x93
 *
 *	Source Product Description InfoFrame
 *	  Version: 1
 *	  Length: 25
 *	  Vendor Name: 'Broadcom'
 *	  Product Description: 'Videocore'
 *	  Source Information: PC general
 *
 *	----------------
 *
 *	Source Product Description InfoFrame conformity: PASS
 *
 * Testing
 * ~~~~~~~
 *
 * The helpers have unit testing and can be tested using kunit with:
 *
 * .. code-block:: bash
 *
 *	$ ./tools/testing/kunit/kunit.py run \
 *		--kunitconfig=drivers/gpu/drm/tests \
 *		drm_atomic_helper_connector_hdmi_*
 */

/**
 * __drm_atomic_helper_connector_hdmi_reset() - Initializes all HDMI @drm_connector_state resources
 * @connector: DRM connector
 * @new_conn_state: connector state to reset
 *
 * Initializes all HDMI resources from a @drm_connector_state without
 * actually allocating it. This is useful for HDMI drivers, in
 * combination with __drm_atomic_helper_connector_reset() or
 * drm_atomic_helper_connector_reset().
 */
void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
					      struct drm_connector_state *new_conn_state)
{
	unsigned int max_bpc = connector->max_bpc;

	new_conn_state->max_bpc = max_bpc;
	new_conn_state->max_requested_bpc = max_bpc;
	new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO;
}
EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset);

static const struct drm_display_mode *
connector_state_get_mode(const struct drm_connector_state *conn_state)
{
	struct drm_atomic_state *state;
	struct drm_crtc_state *crtc_state;
	struct drm_crtc *crtc;

	state = conn_state->state;
	if (!state)
		return NULL;

	crtc = conn_state->crtc;
	if (!crtc)
		return NULL;

	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
	if (!crtc_state)
		return NULL;

	return &crtc_state->mode;
}

static bool hdmi_is_limited_range(const struct drm_connector *connector,
				  const struct drm_connector_state *conn_state)
{
	const struct drm_display_info *info = &connector->display_info;
	const struct drm_display_mode *mode =
		connector_state_get_mode(conn_state);

	/*
	 * The Broadcast RGB property only applies to RGB format, and
	 * i915 just assumes limited range for YCbCr output, so let's
	 * just do the same.
	 */
	if (conn_state->hdmi.output_format != HDMI_COLORSPACE_RGB)
		return true;

	if (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_FULL)
		return false;

	if (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED)
		return true;

	if (!info->is_hdmi)
		return false;

	return drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED;
}

static bool
sink_supports_format_bpc(const struct drm_connector *connector,
			 const struct drm_display_info *info,
			 const struct drm_display_mode *mode,
			 unsigned int format, unsigned int bpc)
{
	struct drm_device *dev = connector->dev;
	u8 vic = drm_match_cea_mode(mode);

	/*
	 * CTA-861-F, section 5.4 - Color Coding & Quantization states
	 * that the bpc must be 8, 10, 12 or 16 except for the default
	 * 640x480 VIC1 where the value must be 8.
	 *
	 * The definition of default here is ambiguous but the spec
	 * refers to VIC1 being the default timing in several occasions
	 * so our understanding is that for the default timing (ie,
	 * VIC1), the bpc must be 8.
	 */
	if (vic == 1 && bpc != 8) {
		drm_dbg_kms(dev, "VIC1 requires a bpc of 8, got %u\n", bpc);
		return false;
	}

	if (!info->is_hdmi &&
	    (format != HDMI_COLORSPACE_RGB || bpc != 8)) {
		drm_dbg_kms(dev, "DVI Monitors require an RGB output at 8 bpc\n");
		return false;
	}

	if (!(connector->hdmi.supported_formats & BIT(format))) {
		drm_dbg_kms(dev, "%s format unsupported by the connector.\n",
			    drm_hdmi_connector_get_output_format_name(format));
		return false;
	}

	switch (format) {
	case HDMI_COLORSPACE_RGB:
		drm_dbg_kms(dev, "RGB Format, checking the constraints.\n");

		/*
		 * In some cases, like when the EDID readout fails, or
		 * is not an HDMI compliant EDID for some reason, the
		 * color_formats field will be blank and not report any
		 * format supported. In such a case, assume that RGB is
		 * supported so we can keep things going and light up
		 * the display.
		 */
		if (!(info->color_formats & DRM_COLOR_FORMAT_RGB444))
			drm_warn(dev, "HDMI Sink doesn't support RGB, something's wrong.\n");

		if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
			drm_dbg_kms(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
			return false;
		}

		if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
			drm_dbg_kms(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
			return false;
		}

		drm_dbg_kms(dev, "RGB format supported in that configuration.\n");

		return true;

	case HDMI_COLORSPACE_YUV420:
		/* TODO: YUV420 is unsupported at the moment. */
		drm_dbg_kms(dev, "YUV420 format isn't supported yet.\n");
		return false;

	case HDMI_COLORSPACE_YUV422:
		drm_dbg_kms(dev, "YUV422 format, checking the constraints.\n");

		if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR422)) {
			drm_dbg_kms(dev, "Sink doesn't support YUV422.\n");
			return false;
		}

		if (bpc > 12) {
			drm_dbg_kms(dev, "YUV422 only supports 12 bpc or lower.\n");
			return false;
		}

		/*
		 * HDMI Spec 1.3 - Section 6.5 Pixel Encodings and Color Depth
		 * states that Deep Color is not relevant for YUV422 so we
		 * don't need to check the Deep Color bits in the EDIDs here.
		 */

		drm_dbg_kms(dev, "YUV422 format supported in that configuration.\n");

		return true;

	case HDMI_COLORSPACE_YUV444:
		drm_dbg_kms(dev, "YUV444 format, checking the constraints.\n");

		if (!(info->color_formats & DRM_COLOR_FORMAT_YCBCR444)) {
			drm_dbg_kms(dev, "Sink doesn't support YUV444.\n");
			return false;
		}

		if (bpc == 10 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_30)) {
			drm_dbg_kms(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
			return false;
		}

		if (bpc == 12 && !(info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_36)) {
			drm_dbg_kms(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
			return false;
		}

		drm_dbg_kms(dev, "YUV444 format supported in that configuration.\n");

		return true;
	}

	drm_dbg_kms(dev, "Unsupported pixel format.\n");
	return false;
}

static enum drm_mode_status
hdmi_clock_valid(const struct drm_connector *connector,
		 const struct drm_display_mode *mode,
		 unsigned long long clock)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	const struct drm_display_info *info = &connector->display_info;

	if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000)
		return MODE_CLOCK_HIGH;

	if (funcs && funcs->tmds_char_rate_valid) {
		enum drm_mode_status status;

		status = funcs->tmds_char_rate_valid(connector, mode, clock);
		if (status != MODE_OK)
			return status;
	}

	return MODE_OK;
}

static int
hdmi_compute_clock(const struct drm_connector *connector,
		   struct drm_connector_state *conn_state,
		   const struct drm_display_mode *mode,
		   unsigned int bpc, enum hdmi_colorspace fmt)
{
	enum drm_mode_status status;
	unsigned long long clock;

	clock = drm_hdmi_compute_mode_clock(mode, bpc, fmt);
	if (!clock)
		return -EINVAL;

	status = hdmi_clock_valid(connector, mode, clock);
	if (status != MODE_OK)
		return -EINVAL;

	conn_state->hdmi.tmds_char_rate = clock;

	return 0;
}

static bool
hdmi_try_format_bpc(const struct drm_connector *connector,
		    struct drm_connector_state *conn_state,
		    const struct drm_display_mode *mode,
		    unsigned int bpc, enum hdmi_colorspace fmt)
{
	const struct drm_display_info *info = &connector->display_info;
	struct drm_device *dev = connector->dev;
	int ret;

	drm_dbg_kms(dev, "Trying %s output format\n",
		    drm_hdmi_connector_get_output_format_name(fmt));

	if (!sink_supports_format_bpc(connector, info, mode, fmt, bpc)) {
		drm_dbg_kms(dev, "%s output format not supported with %u bpc\n",
			    drm_hdmi_connector_get_output_format_name(fmt),
			    bpc);
		return false;
	}

	ret = hdmi_compute_clock(connector, conn_state, mode, bpc, fmt);
	if (ret) {
		drm_dbg_kms(dev, "Couldn't compute clock for %s output format and %u bpc\n",
			    drm_hdmi_connector_get_output_format_name(fmt),
			    bpc);
		return false;
	}

	drm_dbg_kms(dev, "%s output format supported with %u (TMDS char rate: %llu Hz)\n",
		    drm_hdmi_connector_get_output_format_name(fmt),
		    bpc, conn_state->hdmi.tmds_char_rate);

	return true;
}

static int
hdmi_compute_format(const struct drm_connector *connector,
		    struct drm_connector_state *conn_state,
		    const struct drm_display_mode *mode,
		    unsigned int bpc)
{
	struct drm_device *dev = connector->dev;

	/*
	 * TODO: Add support for YCbCr420 output for HDMI 2.0 capable
	 * devices, for modes that only support YCbCr420.
	 */
	if (hdmi_try_format_bpc(connector, conn_state, mode, bpc, HDMI_COLORSPACE_RGB)) {
		conn_state->hdmi.output_format = HDMI_COLORSPACE_RGB;
		return 0;
	}

	drm_dbg_kms(dev, "Failed. No Format Supported for that bpc count.\n");

	return -EINVAL;
}

static int
hdmi_compute_config(const struct drm_connector *connector,
		    struct drm_connector_state *conn_state,
		    const struct drm_display_mode *mode)
{
	struct drm_device *dev = connector->dev;
	unsigned int max_bpc = clamp_t(unsigned int,
				       conn_state->max_bpc,
				       8, connector->max_bpc);
	unsigned int bpc;
	int ret;

	for (bpc = max_bpc; bpc >= 8; bpc -= 2) {
		drm_dbg_kms(dev, "Trying with a %d bpc output\n", bpc);

		ret = hdmi_compute_format(connector, conn_state, mode, bpc);
		if (ret)
			continue;

		conn_state->hdmi.output_bpc = bpc;

		drm_dbg_kms(dev,
			    "Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n",
			    mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
			    conn_state->hdmi.output_bpc,
			    drm_hdmi_connector_get_output_format_name(conn_state->hdmi.output_format),
			    conn_state->hdmi.tmds_char_rate);

		return 0;
	}

	return -EINVAL;
}

static int hdmi_generate_avi_infoframe(const struct drm_connector *connector,
				       struct drm_connector_state *conn_state)
{
	const struct drm_display_mode *mode =
		connector_state_get_mode(conn_state);
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.avi;
	struct hdmi_avi_infoframe *frame =
		&infoframe->data.avi;
	bool is_limited_range = conn_state->hdmi.is_limited_range;
	enum hdmi_quantization_range rgb_quant_range =
		is_limited_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL;
	int ret;

	infoframe->set = false;

	ret = drm_hdmi_avi_infoframe_from_display_mode(frame, connector, mode);
	if (ret)
		return ret;

	frame->colorspace = conn_state->hdmi.output_format;

	/*
	 * FIXME: drm_hdmi_avi_infoframe_quant_range() doesn't handle
	 * YUV formats at all at the moment, so if we ever support YUV
	 * formats this needs to be revised.
	 */
	drm_hdmi_avi_infoframe_quant_range(frame, connector, mode, rgb_quant_range);
	drm_hdmi_avi_infoframe_colorimetry(frame, conn_state);
	drm_hdmi_avi_infoframe_bars(frame, conn_state);

	infoframe->set = true;

	return 0;
}

static int hdmi_generate_spd_infoframe(const struct drm_connector *connector,
				       struct drm_connector_state *conn_state)
{
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.spd;
	struct hdmi_spd_infoframe *frame =
		&infoframe->data.spd;
	int ret;

	infoframe->set = false;

	ret = hdmi_spd_infoframe_init(frame,
				      connector->hdmi.vendor,
				      connector->hdmi.product);
	if (ret)
		return ret;

	frame->sdi = HDMI_SPD_SDI_PC;

	infoframe->set = true;

	return 0;
}

static int hdmi_generate_hdr_infoframe(const struct drm_connector *connector,
				       struct drm_connector_state *conn_state)
{
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.hdr_drm;
	struct hdmi_drm_infoframe *frame =
		&infoframe->data.drm;
	int ret;

	infoframe->set = false;

	if (connector->max_bpc < 10)
		return 0;

	if (!conn_state->hdr_output_metadata)
		return 0;

	ret = drm_hdmi_infoframe_set_hdr_metadata(frame, conn_state);
	if (ret)
		return ret;

	infoframe->set = true;

	return 0;
}

static int hdmi_generate_hdmi_vendor_infoframe(const struct drm_connector *connector,
					       struct drm_connector_state *conn_state)
{
	const struct drm_display_info *info = &connector->display_info;
	const struct drm_display_mode *mode =
		connector_state_get_mode(conn_state);
	struct drm_connector_hdmi_infoframe *infoframe =
		&conn_state->hdmi.infoframes.hdmi;
	struct hdmi_vendor_infoframe *frame =
		&infoframe->data.vendor.hdmi;
	int ret;

	infoframe->set = false;

	if (!info->has_hdmi_infoframe)
		return 0;

	ret = drm_hdmi_vendor_infoframe_from_display_mode(frame, connector, mode);
	if (ret)
		return ret;

	infoframe->set = true;

	return 0;
}

static int
hdmi_generate_infoframes(const struct drm_connector *connector,
			 struct drm_connector_state *conn_state)
{
	const struct drm_display_info *info = &connector->display_info;
	int ret;

	if (!info->is_hdmi)
		return 0;

	ret = hdmi_generate_avi_infoframe(connector, conn_state);
	if (ret)
		return ret;

	ret = hdmi_generate_spd_infoframe(connector, conn_state);
	if (ret)
		return ret;

	/*
	 * Audio Infoframes will be generated by ALSA, and updated by
	 * drm_atomic_helper_connector_hdmi_update_audio_infoframe().
	 */

	ret = hdmi_generate_hdr_infoframe(connector, conn_state);
	if (ret)
		return ret;

	ret = hdmi_generate_hdmi_vendor_infoframe(connector, conn_state);
	if (ret)
		return ret;

	return 0;
}

/**
 * drm_atomic_helper_connector_hdmi_check() - Helper to check HDMI connector atomic state
 * @connector: DRM Connector
 * @state: the DRM State object
 *
 * Provides a default connector state check handler for HDMI connectors.
 * Checks that a desired connector update is valid, and updates various
 * fields of derived state.
 *
 * RETURNS:
 * Zero on success, or an errno code otherwise.
 */
int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
					   struct drm_atomic_state *state)
{
	struct drm_connector_state *old_conn_state =
		drm_atomic_get_old_connector_state(state, connector);
	struct drm_connector_state *new_conn_state =
		drm_atomic_get_new_connector_state(state, connector);
	const struct drm_display_mode *mode =
		connector_state_get_mode(new_conn_state);
	int ret;

	if (!new_conn_state->crtc || !new_conn_state->best_encoder)
		return 0;

	new_conn_state->hdmi.is_limited_range = hdmi_is_limited_range(connector, new_conn_state);

	ret = hdmi_compute_config(connector, new_conn_state, mode);
	if (ret)
		return ret;

	ret = hdmi_generate_infoframes(connector, new_conn_state);
	if (ret)
		return ret;

	if (old_conn_state->hdmi.broadcast_rgb != new_conn_state->hdmi.broadcast_rgb ||
	    old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc ||
	    old_conn_state->hdmi.output_format != new_conn_state->hdmi.output_format) {
		struct drm_crtc *crtc = new_conn_state->crtc;
		struct drm_crtc_state *crtc_state;

		crtc_state = drm_atomic_get_crtc_state(state, crtc);
		if (IS_ERR(crtc_state))
			return PTR_ERR(crtc_state);

		crtc_state->mode_changed = true;
	}

	return 0;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_check);

/**
 * drm_hdmi_connector_mode_valid() - Check if mode is valid for HDMI connector
 * @connector: DRM connector to validate the mode
 * @mode: Display mode to validate
 *
 * Generic .mode_valid implementation for HDMI connectors.
 */
enum drm_mode_status
drm_hdmi_connector_mode_valid(struct drm_connector *connector,
			      const struct drm_display_mode *mode)
{
	unsigned long long clock;

	clock = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB);
	if (!clock)
		return MODE_ERROR;

	return hdmi_clock_valid(connector, mode, clock);
}
EXPORT_SYMBOL(drm_hdmi_connector_mode_valid);

static int clear_device_infoframe(struct drm_connector *connector,
				  enum hdmi_infoframe_type type)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_device *dev = connector->dev;
	int ret;

	drm_dbg_kms(dev, "Clearing infoframe type 0x%x\n", type);

	if (!funcs || !funcs->clear_infoframe) {
		drm_dbg_kms(dev, "Function not implemented, bailing.\n");
		return 0;
	}

	ret = funcs->clear_infoframe(connector, type);
	if (ret) {
		drm_dbg_kms(dev, "Call failed: %d\n", ret);
		return ret;
	}

	return 0;
}

static int clear_infoframe(struct drm_connector *connector,
			   struct drm_connector_hdmi_infoframe *old_frame)
{
	int ret;

	ret = clear_device_infoframe(connector, old_frame->data.any.type);
	if (ret)
		return ret;

	return 0;
}

static int write_device_infoframe(struct drm_connector *connector,
				  union hdmi_infoframe *frame)
{
	const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs;
	struct drm_device *dev = connector->dev;
	u8 buffer[HDMI_INFOFRAME_SIZE(MAX)];
	int ret;
	int len;

	drm_dbg_kms(dev, "Writing infoframe type %x\n", frame->any.type);

	if (!funcs || !funcs->write_infoframe) {
		drm_dbg_kms(dev, "Function not implemented, bailing.\n");
		return -EINVAL;
	}

	len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer));
	if (len < 0)
		return len;

	ret = funcs->write_infoframe(connector, frame->any.type, buffer, len);
	if (ret) {
		drm_dbg_kms(dev, "Call failed: %d\n", ret);
		return ret;
	}

	return 0;
}

static int write_infoframe(struct drm_connector *connector,
			   struct drm_connector_hdmi_infoframe *new_frame)
{
	int ret;

	ret = write_device_infoframe(connector, &new_frame->data);
	if (ret)
		return ret;

	return 0;
}

static int write_or_clear_infoframe(struct drm_connector *connector,
				    struct drm_connector_hdmi_infoframe *old_frame,
				    struct drm_connector_hdmi_infoframe *new_frame)
{
	if (new_frame->set)
		return write_infoframe(connector, new_frame);

	if (old_frame->set && !new_frame->set)
		return clear_infoframe(connector, old_frame);

	return 0;
}

/**
 * drm_atomic_helper_connector_hdmi_update_infoframes - Update the Infoframes
 * @connector: A pointer to the HDMI connector
 * @state: The HDMI connector state to generate the infoframe from
 *
 * This function is meant for HDMI connector drivers to write their
 * infoframes. It will typically be used in a
 * @drm_connector_helper_funcs.atomic_enable implementation.
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector,
						       struct drm_atomic_state *state)
{
	struct drm_connector_state *old_conn_state =
		drm_atomic_get_old_connector_state(state, connector);
	struct drm_connector_state *new_conn_state =
		drm_atomic_get_new_connector_state(state, connector);
	struct drm_display_info *info = &connector->display_info;
	int ret;

	if (!info->is_hdmi)
		return 0;

	mutex_lock(&connector->hdmi.infoframes.lock);

	ret = write_or_clear_infoframe(connector,
				       &old_conn_state->hdmi.infoframes.avi,
				       &new_conn_state->hdmi.infoframes.avi);
	if (ret)
		goto out;

	if (connector->hdmi.infoframes.audio.set) {
		ret = write_infoframe(connector,
				      &connector->hdmi.infoframes.audio);
		if (ret)
			goto out;
	}

	ret = write_or_clear_infoframe(connector,
				       &old_conn_state->hdmi.infoframes.hdr_drm,
				       &new_conn_state->hdmi.infoframes.hdr_drm);
	if (ret)
		goto out;

	ret = write_or_clear_infoframe(connector,
				       &old_conn_state->hdmi.infoframes.spd,
				       &new_conn_state->hdmi.infoframes.spd);
	if (ret)
		goto out;

	if (info->has_hdmi_infoframe) {
		ret = write_or_clear_infoframe(connector,
					       &old_conn_state->hdmi.infoframes.hdmi,
					       &new_conn_state->hdmi.infoframes.hdmi);
		if (ret)
			goto out;
	}

out:
	mutex_unlock(&connector->hdmi.infoframes.lock);
	return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_infoframes);

/**
 * drm_atomic_helper_connector_hdmi_update_audio_infoframe - Update the Audio Infoframe
 * @connector: A pointer to the HDMI connector
 * @frame: A pointer to the audio infoframe to write
 *
 * This function is meant for HDMI connector drivers to update their
 * audio infoframe. It will typically be used in one of the ALSA hooks
 * (most likely prepare).
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int
drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector,
							struct hdmi_audio_infoframe *frame)
{
	struct drm_connector_hdmi_infoframe *infoframe =
		&connector->hdmi.infoframes.audio;
	struct drm_display_info *info = &connector->display_info;
	int ret;

	if (!info->is_hdmi)
		return 0;

	mutex_lock(&connector->hdmi.infoframes.lock);

	memcpy(&infoframe->data, frame, sizeof(infoframe->data));
	infoframe->set = true;

	ret = write_infoframe(connector, infoframe);

	mutex_unlock(&connector->hdmi.infoframes.lock);

	return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_audio_infoframe);

/**
 * drm_atomic_helper_connector_hdmi_clear_audio_infoframe - Stop sending the Audio Infoframe
 * @connector: A pointer to the HDMI connector
 *
 * This function is meant for HDMI connector drivers to stop sending their
 * audio infoframe. It will typically be used in one of the ALSA hooks
 * (most likely shutdown).
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(struct drm_connector *connector)
{
	struct drm_connector_hdmi_infoframe *infoframe =
		&connector->hdmi.infoframes.audio;
	struct drm_display_info *info = &connector->display_info;
	int ret;

	if (!info->is_hdmi)
		return 0;

	mutex_lock(&connector->hdmi.infoframes.lock);

	infoframe->set = false;

	ret = clear_infoframe(connector, infoframe);

	memset(&infoframe->data, 0, sizeof(infoframe->data));

	mutex_unlock(&connector->hdmi.infoframes.lock);

	return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_clear_audio_infoframe);

static void
drm_atomic_helper_connector_hdmi_update(struct drm_connector *connector,
					enum drm_connector_status status)
{
	const struct drm_edid *drm_edid;

	if (status == connector_status_disconnected) {
		// TODO: also handle CEC and scramber, HDMI sink disconnected.
		drm_connector_hdmi_audio_plugged_notify(connector, false);
		drm_edid_connector_update(connector, NULL);
		return;
	}

	if (connector->hdmi.funcs->read_edid)
		drm_edid = connector->hdmi.funcs->read_edid(connector);
	else
		drm_edid = drm_edid_read(connector);

	drm_edid_connector_update(connector, drm_edid);

	drm_edid_free(drm_edid);

	if (status == connector_status_connected) {
		// TODO: also handle CEC and scramber, HDMI sink is now connected.
		drm_connector_hdmi_audio_plugged_notify(connector, true);
	}
}

/**
 * drm_atomic_helper_connector_hdmi_hotplug - Handle the hotplug event for the HDMI connector
 * @connector: A pointer to the HDMI connector
 * @status: Connection status
 *
 * This function should be called as a part of the .detect() / .detect_ctx()
 * callbacks for all status changes.
 */
void drm_atomic_helper_connector_hdmi_hotplug(struct drm_connector *connector,
					      enum drm_connector_status status)
{
	drm_atomic_helper_connector_hdmi_update(connector, status);
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_hotplug);

/**
 * drm_atomic_helper_connector_hdmi_force - HDMI Connector implementation of the force callback
 * @connector: A pointer to the HDMI connector
 *
 * This function implements the .force() callback for the HDMI connectors. It
 * can either be used directly as the callback or should be called from within
 * the .force() callback implementation to maintain the HDMI-specific
 * connector's data.
 */
void drm_atomic_helper_connector_hdmi_force(struct drm_connector *connector)
{
	drm_atomic_helper_connector_hdmi_update(connector, connector->status);
}
EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_force);