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

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=67030Reviewed-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
...@@ -931,6 +931,36 @@ static const struct drm_display_mode edid_cea_modes[] = { ...@@ -931,6 +931,36 @@ static const struct drm_display_mode edid_cea_modes[] = {
.vrefresh = 100, }, .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 ***/ /*** DDC fetch and block validation ***/
static const u8 edid_header[] = { static const u8 edid_header[] = {
...@@ -2465,6 +2495,68 @@ do_cea_modes(struct drm_connector *connector, const u8 *db, u8 len) ...@@ -2465,6 +2495,68 @@ do_cea_modes(struct drm_connector *connector, const u8 *db, u8 len)
return modes; 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 static int
cea_db_payload_len(const u8 *db) cea_db_payload_len(const u8 *db)
{ {
...@@ -2496,6 +2588,21 @@ cea_db_offsets(const u8 *cea, int *start, int *end) ...@@ -2496,6 +2588,21 @@ cea_db_offsets(const u8 *cea, int *start, int *end)
return 0; 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) \ #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) 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) ...@@ -2519,6 +2626,8 @@ add_cea_modes(struct drm_connector *connector, struct edid *edid)
if (cea_db_tag(db) == VIDEO_BLOCK) if (cea_db_tag(db) == VIDEO_BLOCK)
modes += do_cea_modes(connector, db + 1, dbl); 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) ...@@ -2571,21 +2680,6 @@ monitor_name(struct detailed_timing *t, void *data)
*(u8 **)data = t->data.other_data.data.str.str; *(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 * drm_edid_to_eld - build ELD from EDID
* @connector: connector corresponding to the HDMI/DP sink * @connector: connector corresponding to the HDMI/DP sink
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment