Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ebe1963 authored by Lespiau, Damien's avatar Lespiau, Damien Committed by Dave Airlie
Browse files

drm/edid: Parse the HDMI CEA block and look for 4k modes



HDMI 1.4 adds 4 "4k x 2k" modes in the the CEA vendor specific block.

With this commit, we now parse this block and expose the 4k modes that
we find there.

v2: Fix the "4096x2160" string (nice catch!), add comments about
    do_hdmi_vsdb_modes() arguments and make it clearer that offset is
    relative to the end of the required fields of the HDMI VSDB
    (Ville Syrjälä)

v3: Fix 'Unknow' typo (Simon Farnsworth)

Signed-off-by: default avatarDamien Lespiau <damien.lespiau@intel.com>
Tested-by: default avatarCancan Feng <cancan.feng@intel.com>
Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=67030


Reviewed-by: default avatarSimon Farnsworth <simon.farnsworth@onelan.co.uk>
Reviewed-by: default avatarVille Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: default avatarThierry Reding <treding@nvidia.com>
Signed-off-by: default avatarDave Airlie <airlied@gmail.com>
parent 13ac3f55
Loading
Loading
Loading
Loading
+109 −15
Original line number Diff line number Diff line
@@ -931,6 +931,36 @@ static const struct drm_display_mode edid_cea_modes[] = {
	 .vrefresh = 100, },
};

/*
 * HDMI 1.4 4k modes.
 */
static const struct drm_display_mode edid_4k_modes[] = {
	/* 1 - 3840x2160@30Hz */
	{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000,
		   3840, 4016, 4104, 4400, 0,
		   2160, 2168, 2178, 2250, 0,
		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
	  .vrefresh = 30, },
	/* 2 - 3840x2160@25Hz */
	{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000,
		   3840, 4896, 4984, 5280, 0,
		   2160, 2168, 2178, 2250, 0,
		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
	  .vrefresh = 25, },
	/* 3 - 3840x2160@24Hz */
	{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000,
		   3840, 5116, 5204, 5500, 0,
		   2160, 2168, 2178, 2250, 0,
		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
	  .vrefresh = 24, },
	/* 4 - 4096x2160@24Hz (SMPTE) */
	{ DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000,
		   4096, 5116, 5204, 5500, 0,
		   2160, 2168, 2178, 2250, 0,
		   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
	  .vrefresh = 24, },
};

/*** DDC fetch and block validation ***/

static const u8 edid_header[] = {
@@ -2465,6 +2495,68 @@ do_cea_modes(struct drm_connector *connector, const u8 *db, u8 len)
	return modes;
}

/*
 * do_hdmi_vsdb_modes - Parse the HDMI Vendor Specific data block
 * @connector: connector corresponding to the HDMI sink
 * @db: start of the CEA vendor specific block
 * @len: length of the CEA block payload, ie. one can access up to db[len]
 *
 * Parses the HDMI VSDB looking for modes to add to @connector.
 */
static int
do_hdmi_vsdb_modes(struct drm_connector *connector, const u8 *db, u8 len)
{
	struct drm_device *dev = connector->dev;
	int modes = 0, offset = 0, i;
	u8 vic_len;

	if (len < 8)
		goto out;

	/* no HDMI_Video_Present */
	if (!(db[8] & (1 << 5)))
		goto out;

	/* Latency_Fields_Present */
	if (db[8] & (1 << 7))
		offset += 2;

	/* I_Latency_Fields_Present */
	if (db[8] & (1 << 6))
		offset += 2;

	/* the declared length is not long enough for the 2 first bytes
	 * of additional video format capabilities */
	offset += 2;
	if (len < (8 + offset))
		goto out;

	vic_len = db[8 + offset] >> 5;

	for (i = 0; i < vic_len && len >= (9 + offset + i); i++) {
		struct drm_display_mode *newmode;
		u8 vic;

		vic = db[9 + offset + i];

		vic--; /* VICs start at 1 */
		if (vic >= ARRAY_SIZE(edid_4k_modes)) {
			DRM_ERROR("Unknown HDMI VIC: %d\n", vic);
			continue;
		}

		newmode = drm_mode_duplicate(dev, &edid_4k_modes[vic]);
		if (!newmode)
			continue;

		drm_mode_probed_add(connector, newmode);
		modes++;
	}

out:
	return modes;
}

static int
cea_db_payload_len(const u8 *db)
{
@@ -2496,6 +2588,21 @@ cea_db_offsets(const u8 *cea, int *start, int *end)
	return 0;
}

static bool cea_db_is_hdmi_vsdb(const u8 *db)
{
	int hdmi_id;

	if (cea_db_tag(db) != VENDOR_BLOCK)
		return false;

	if (cea_db_payload_len(db) < 5)
		return false;

	hdmi_id = db[1] | (db[2] << 8) | (db[3] << 16);

	return hdmi_id == HDMI_IDENTIFIER;
}

#define for_each_cea_db(cea, i, start, end) \
	for ((i) = (start); (i) < (end) && (i) + cea_db_payload_len(&(cea)[(i)]) < (end); (i) += cea_db_payload_len(&(cea)[(i)]) + 1)

@@ -2519,6 +2626,8 @@ add_cea_modes(struct drm_connector *connector, struct edid *edid)

			if (cea_db_tag(db) == VIDEO_BLOCK)
				modes += do_cea_modes(connector, db + 1, dbl);
			else if (cea_db_is_hdmi_vsdb(db))
				modes += do_hdmi_vsdb_modes(connector, db, dbl);
		}
	}

@@ -2571,21 +2680,6 @@ monitor_name(struct detailed_timing *t, void *data)
		*(u8 **)data = t->data.other_data.data.str.str;
}

static bool cea_db_is_hdmi_vsdb(const u8 *db)
{
	int hdmi_id;

	if (cea_db_tag(db) != VENDOR_BLOCK)
		return false;

	if (cea_db_payload_len(db) < 5)
		return false;

	hdmi_id = db[1] | (db[2] << 8) | (db[3] << 16);

	return hdmi_id == HDMI_IDENTIFIER;
}

/**
 * drm_edid_to_eld - build ELD from EDID
 * @connector: connector corresponding to the HDMI/DP sink