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)
 {
        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.)
 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;
 }
 
                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;
 
 #define DATA_BLOCK_DISPLAY_INTERFACE 0x0f
 #define DATA_BLOCK_STEREO_DISPLAY_INTERFACE 0x10
 #define DATA_BLOCK_TILED_DISPLAY 0x12
+#define DATA_BLOCK_CTA 0x81
 
 #define DATA_BLOCK_VENDOR_SPECIFIC 0x7f
 
        struct displayid_block base;
        struct displayid_detailed_timings_1 timings[0];
 };
+
+#define for_each_displayid_db(displayid, block, idx, length) \
+       for ((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), \
+            (block) = (struct displayid_block *)&(displayid)[idx])
+
 #endif