summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/drm_edid.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/drm_edid.c')
-rw-r--r--drivers/gpu/drm/drm_edid.c152
1 files changed, 121 insertions, 31 deletions
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index d87f574feeca..82a4ceed3fcf 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -1342,6 +1342,7 @@ MODULE_PARM_DESC(edid_fixup,
static void drm_get_displayid(struct drm_connector *connector,
struct edid *edid);
+static int validate_displayid(u8 *displayid, int length, int idx);
static int drm_edid_block_checksum(const u8 *raw_edid)
{
@@ -1573,6 +1574,50 @@ static void connector_bad_edid(struct drm_connector *connector,
}
}
+/* Get override or firmware EDID */
+static struct edid *drm_get_override_edid(struct drm_connector *connector)
+{
+ struct edid *override = NULL;
+
+ if (connector->override_edid)
+ override = drm_edid_duplicate(connector->edid_blob_ptr->data);
+
+ if (!override)
+ override = drm_load_edid_firmware(connector);
+
+ return IS_ERR(override) ? NULL : override;
+}
+
+/**
+ * drm_add_override_edid_modes - add modes from override/firmware EDID
+ * @connector: connector we're probing
+ *
+ * Add modes from the override/firmware EDID, if available. Only to be used from
+ * drm_helper_probe_single_connector_modes() as a fallback for when DDC probe
+ * failed during drm_get_edid() and caused the override/firmware EDID to be
+ * skipped.
+ *
+ * Return: The number of modes added or 0 if we couldn't find any.
+ */
+int drm_add_override_edid_modes(struct drm_connector *connector)
+{
+ struct edid *override;
+ int num_modes = 0;
+
+ override = drm_get_override_edid(connector);
+ if (override) {
+ drm_connector_update_edid_property(connector, override);
+ num_modes = drm_add_edid_modes(connector, override);
+ kfree(override);
+
+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s] adding %d modes via fallback override/firmware EDID\n",
+ connector->base.id, connector->name, num_modes);
+ }
+
+ return num_modes;
+}
+EXPORT_SYMBOL(drm_add_override_edid_modes);
+
/**
* drm_do_get_edid - get EDID data using a custom EDID block read function
* @connector: connector we're probing
@@ -1600,15 +1645,10 @@ struct edid *drm_do_get_edid(struct drm_connector *connector,
{
int i, j = 0, valid_extensions = 0;
u8 *edid, *new;
- struct edid *override = NULL;
+ struct edid *override;
- if (connector->override_edid)
- override = drm_edid_duplicate(connector->edid_blob_ptr->data);
-
- if (!override)
- override = drm_load_edid_firmware(connector);
-
- if (!IS_ERR_OR_NULL(override))
+ override = drm_get_override_edid(connector);
+ if (override)
return override;
if ((edid = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
@@ -2887,16 +2927,46 @@ static u8 *drm_find_edid_extension(const struct edid *edid, int ext_id)
return edid_ext;
}
-static u8 *drm_find_cea_extension(const struct edid *edid)
-{
- return drm_find_edid_extension(edid, CEA_EXT);
-}
static u8 *drm_find_displayid_extension(const struct edid *edid)
{
return drm_find_edid_extension(edid, DISPLAYID_EXT);
}
+static u8 *drm_find_cea_extension(const struct edid *edid)
+{
+ int ret;
+ int idx = 1;
+ int length = EDID_LENGTH;
+ struct displayid_block *block;
+ u8 *cea;
+ u8 *displayid;
+
+ /* Look for a top level CEA extension block */
+ cea = drm_find_edid_extension(edid, CEA_EXT);
+ if (cea)
+ return cea;
+
+ /* CEA blocks can also be found embedded in a DisplayID block */
+ displayid = drm_find_displayid_extension(edid);
+ if (!displayid)
+ return NULL;
+
+ ret = validate_displayid(displayid, length, idx);
+ if (ret)
+ return NULL;
+
+ idx += sizeof(struct displayid_hdr);
+ for_each_displayid_db(displayid, block, idx, length) {
+ if (block->tag == DATA_BLOCK_CTA) {
+ cea = (u8 *)block;
+ break;
+ }
+ }
+
+ return cea;
+}
+
/*
* Calculate the alternate clock for the CEA mode
* (60Hz vs. 59.94Hz etc.)
@@ -3620,13 +3690,38 @@ cea_revision(const u8 *cea)
static int
cea_db_offsets(const u8 *cea, int *start, int *end)
{
- /* Data block offset in CEA extension block */
- *start = 4;
- *end = cea[2];
- if (*end == 0)
- *end = 127;
- if (*end < 4 || *end > 127)
- return -ERANGE;
+ /* DisplayID CTA extension blocks and top-level CEA EDID
+ * block header definitions differ in the following bytes:
+ * 1) Byte 2 of the header specifies length differently,
+ * 2) Byte 3 is only present in the CEA top level block.
+ *
+ * The different definitions for byte 2 follow.
+ *
+ * DisplayID CTA extension block defines byte 2 as:
+ * Number of payload bytes
+ *
+ * CEA EDID block defines byte 2 as:
+ * Byte number (decimal) within this block where the 18-byte
+ * DTDs begin. If no non-DTD data is present in this extension
+ * block, the value should be set to 04h (the byte after next).
+ * If set to 00h, there are no DTDs present in this block and
+ * no non-DTD data.
+ */
+ if (cea[0] == DATA_BLOCK_CTA) {
+ *start = 3;
+ *end = *start + cea[2];
+ } else if (cea[0] == CEA_EXT) {
+ /* Data block offset in CEA extension block */
+ *start = 4;
+ *end = cea[2];
+ if (*end == 0)
+ *end = 127;
+ if (*end < 4 || *end > 127)
+ return -ERANGE;
+ } else {
+ return -ENOTSUPP;
+ }
+
return 0;
}
@@ -4569,8 +4664,8 @@ u32 drm_add_display_info(struct drm_connector *connector, const struct edid *edi
* tells us to assume 8 bpc color depth if the EDID doesn't have
* extensions which tell otherwise.
*/
- if ((info->bpc == 0) && (edid->revision < 4) &&
- (edid->input & DRM_EDID_DIGITAL_TYPE_DVI)) {
+ if (info->bpc == 0 && edid->revision == 3 &&
+ edid->input & DRM_EDID_DIGITAL_DFP_1_X) {
info->bpc = 8;
DRM_DEBUG("%s: Assigning DFP sink color depth as %d bpc.\n",
connector->name, info->bpc);
@@ -4729,11 +4824,7 @@ static int add_displayid_detailed_modes(struct drm_connector *connector,
return 0;
idx += sizeof(struct displayid_hdr);
- while (block = (struct displayid_block *)&displayid[idx],
- idx + sizeof(struct displayid_block) <= length &&
- idx + sizeof(struct displayid_block) + block->num_bytes <= length &&
- block->num_bytes > 0) {
- idx += block->num_bytes + sizeof(struct displayid_block);
+ for_each_displayid_db(displayid, block, idx, length) {
switch (block->tag) {
case DATA_BLOCK_TYPE_1_DETAILED_TIMING:
num_modes += add_displayid_detailed_1_modes(connector, block);
@@ -5350,11 +5441,7 @@ static int drm_parse_display_id(struct drm_connector *connector,
return ret;
idx += sizeof(struct displayid_hdr);
- while (block = (struct displayid_block *)&displayid[idx],
- idx + sizeof(struct displayid_block) <= length &&
- idx + sizeof(struct displayid_block) + block->num_bytes <= length &&
- block->num_bytes > 0) {
- idx += block->num_bytes + sizeof(struct displayid_block);
+ for_each_displayid_db(displayid, block, idx, length) {
DRM_DEBUG_KMS("block id 0x%x, rev %d, len %d\n",
block->tag, block->rev, block->num_bytes);
@@ -5367,6 +5454,9 @@ static int drm_parse_display_id(struct drm_connector *connector,
case DATA_BLOCK_TYPE_1_DETAILED_TIMING:
/* handled in mode gathering code. */
break;
+ case DATA_BLOCK_CTA:
+ /* handled in the cea parser code. */
+ break;
default:
DRM_DEBUG_KMS("found DisplayID tag 0x%x, unhandled\n", block->tag);
break;