drm/i915: use GMBUS to manage i2c links
[linux-2.6.git] / drivers / gpu / drm / drm_edid.c
index 58b6793..fd033eb 100644 (file)
  * DEALINGS IN THE SOFTWARE.
  */
 #include <linux/kernel.h>
+#include <linux/slab.h>
 #include <linux/i2c.h>
-#include <linux/i2c-algo-bit.h>
 #include "drmP.h"
 #include "drm_edid.h"
+#include "drm_edid_modes.h"
+
+#define version_greater(edid, maj, min) \
+       (((edid)->version > (maj)) || \
+        ((edid)->version == (maj) && (edid)->revision > (min)))
+
+#define EDID_EST_TIMINGS 16
+#define EDID_STD_TIMINGS 8
+#define EDID_DETAILED_TIMINGS 4
 
 /*
  * EDID blocks out in the wild have a variety of bugs, try to collect
 /* use +hsync +vsync for detailed mode */
 #define EDID_QUIRK_DETAILED_SYNC_PP            (1 << 6)
 
+struct detailed_mode_closure {
+       struct drm_connector *connector;
+       struct edid *edid;
+       bool preferred;
+       u32 quirks;
+       int modes;
+};
 
 #define LEVEL_DMT      0
 #define LEVEL_GTF      1
-#define LEVEL_CVT      2
+#define LEVEL_GTF2     2
+#define LEVEL_CVT      3
 
 static struct edid_quirk {
        char *vendor;
@@ -80,6 +97,8 @@ static struct edid_quirk {
 
        /* Envision Peripherals, Inc. EN-7100e */
        { "EPI", 59264, EDID_QUIRK_135_CLOCK_TOO_HIGH },
+       /* Envision EN2028 */
+       { "EPI", 8232, EDID_QUIRK_PREFER_LARGE_60 },
 
        /* Funai Electronics PM36B */
        { "FCM", 13600, EDID_QUIRK_PREFER_LARGE_75 |
@@ -139,7 +158,10 @@ drm_edid_block_valid(u8 *raw_edid)
                csum += raw_edid[i];
        if (csum) {
                DRM_ERROR("EDID checksum is invalid, remainder is %d\n", csum);
-               goto bad;
+
+               /* allow CEA to slide through, switches mangle this */
+               if (raw_edid[0] != 0x02)
+                       goto bad;
        }
 
        /* per-block-type checks */
@@ -271,7 +293,7 @@ drm_do_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter)
        return block;
 
 carp:
-       dev_warn(&connector->dev->pdev->dev, "%s: EDID block %d invalid.\n",
+       dev_warn(connector->dev->dev, "%s: EDID block %d invalid.\n",
                 drm_get_connector_name(connector), j);
 
 out:
@@ -364,7 +386,6 @@ static u32 edid_get_quirks(struct edid *edid)
 #define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay)
 #define MODE_REFRESH_DIFF(m,r) (abs((m)->vrefresh - target_refresh))
 
-
 /**
  * edid_fixup_preferred - set preferred modes based on quirk list
  * @connector: has mode list to fix up
@@ -411,247 +432,8 @@ static void edid_fixup_preferred(struct drm_connector *connector,
        preferred_mode->type |= DRM_MODE_TYPE_PREFERRED;
 }
 
-/*
- * Add the Autogenerated from the DMT spec.
- * This table is copied from xfree86/modes/xf86EdidModes.c.
- * But the mode with Reduced blank feature is deleted.
- */
-static struct drm_display_mode drm_dmt_modes[] = {
-       /* 640x350@85Hz */
-       { DRM_MODE("640x350", DRM_MODE_TYPE_DRIVER, 31500, 640, 672,
-                  736, 832, 0, 350, 382, 385, 445, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 640x400@85Hz */
-       { DRM_MODE("640x400", DRM_MODE_TYPE_DRIVER, 31500, 640, 672,
-                  736, 832, 0, 400, 401, 404, 445, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 720x400@85Hz */
-       { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 756,
-                  828, 936, 0, 400, 401, 404, 446, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 640x480@60Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25175, 640, 656,
-                  752, 800, 0, 480, 489, 492, 525, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 640x480@72Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664,
-                  704, 832, 0, 480, 489, 492, 520, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 640x480@75Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656,
-                  720, 840, 0, 480, 481, 484, 500, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 640x480@85Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 36000, 640, 696,
-                  752, 832, 0, 480, 481, 484, 509, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 800x600@56Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824,
-                  896, 1024, 0, 600, 601, 603, 625, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 800x600@60Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840,
-                  968, 1056, 0, 600, 601, 605, 628, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 800x600@72Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856,
-                  976, 1040, 0, 600, 637, 643, 666, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 800x600@75Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816,
-                  896, 1056, 0, 600, 601, 604, 625, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 800x600@85Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 56250, 800, 832,
-                  896, 1048, 0, 600, 601, 604, 631, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 848x480@60Hz */
-       { DRM_MODE("848x480", DRM_MODE_TYPE_DRIVER, 33750, 848, 864,
-                  976, 1088, 0, 480, 486, 494, 517, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1024x768@43Hz, interlace */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 44900, 1024, 1032,
-                  1208, 1264, 0, 768, 768, 772, 817, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC |
-                       DRM_MODE_FLAG_INTERLACE) },
-       /* 1024x768@60Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048,
-                  1184, 1344, 0, 768, 771, 777, 806, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 1024x768@70Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048,
-                  1184, 1328, 0, 768, 771, 777, 806, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 1024x768@75Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78750, 1024, 1040,
-                  1136, 1312, 0, 768, 769, 772, 800, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1024x768@85Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 94500, 1024, 1072,
-                  1072, 1376, 0, 768, 769, 772, 808, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1152x864@75Hz */
-       { DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216,
-                  1344, 1600, 0, 864, 865, 868, 900, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x768@60Hz */
-       { DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 79500, 1280, 1344,
-                  1472, 1664, 0, 768, 771, 778, 798, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x768@75Hz */
-       { DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 102250, 1280, 1360,
-                  1488, 1696, 0, 768, 771, 778, 805, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 1280x768@85Hz */
-       { DRM_MODE("1280x768", DRM_MODE_TYPE_DRIVER, 117500, 1280, 1360,
-                  1496, 1712, 0, 768, 771, 778, 809, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x800@60Hz */
-       { DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 83500, 1280, 1352,
-                  1480, 1680, 0, 800, 803, 809, 831, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) },
-       /* 1280x800@75Hz */
-       { DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 106500, 1280, 1360,
-                  1488, 1696, 0, 800, 803, 809, 838, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x800@85Hz */
-       { DRM_MODE("1280x800", DRM_MODE_TYPE_DRIVER, 122500, 1280, 1360,
-                  1496, 1712, 0, 800, 803, 809, 843, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x960@60Hz */
-       { DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 108000, 1280, 1376,
-                  1488, 1800, 0, 960, 961, 964, 1000, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x960@85Hz */
-       { DRM_MODE("1280x960", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1344,
-                  1504, 1728, 0, 960, 961, 964, 1011, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x1024@60Hz */
-       { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 108000, 1280, 1328,
-                  1440, 1688, 0, 1024, 1025, 1028, 1066, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x1024@75Hz */
-       { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296,
-                  1440, 1688, 0, 1024, 1025, 1028, 1066, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1280x1024@85Hz */
-       { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 157500, 1280, 1344,
-                  1504, 1728, 0, 1024, 1025, 1028, 1072, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1360x768@60Hz */
-       { DRM_MODE("1360x768", DRM_MODE_TYPE_DRIVER, 85500, 1360, 1424,
-                  1536, 1792, 0, 768, 771, 777, 795, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1440x1050@60Hz */
-       { DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 121750, 1400, 1488,
-                  1632, 1864, 0, 1050, 1053, 1057, 1089, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1440x1050@75Hz */
-       { DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 156000, 1400, 1504,
-                  1648, 1896, 0, 1050, 1053, 1057, 1099, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1440x1050@85Hz */
-       { DRM_MODE("1400x1050", DRM_MODE_TYPE_DRIVER, 179500, 1400, 1504,
-                  1656, 1912, 0, 1050, 1053, 1057, 1105, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1440x900@60Hz */
-       { DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 106500, 1440, 1520,
-                  1672, 1904, 0, 900, 903, 909, 934, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1440x900@75Hz */
-       { DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 136750, 1440, 1536,
-                  1688, 1936, 0, 900, 903, 909, 942, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1440x900@85Hz */
-       { DRM_MODE("1440x900", DRM_MODE_TYPE_DRIVER, 157000, 1440, 1544,
-                  1696, 1952, 0, 900, 903, 909, 948, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1600x1200@60Hz */
-       { DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 162000, 1600, 1664,
-                  1856, 2160, 0, 1200, 1201, 1204, 1250, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1600x1200@65Hz */
-       { DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 175500, 1600, 1664,
-                  1856, 2160, 0, 1200, 1201, 1204, 1250, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1600x1200@70Hz */
-       { DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 189000, 1600, 1664,
-                  1856, 2160, 0, 1200, 1201, 1204, 1250, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1600x1200@75Hz */
-       { DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 2025000, 1600, 1664,
-                  1856, 2160, 0, 1200, 1201, 1204, 1250, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1600x1200@85Hz */
-       { DRM_MODE("1600x1200", DRM_MODE_TYPE_DRIVER, 229500, 1600, 1664,
-                  1856, 2160, 0, 1200, 1201, 1204, 1250, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1680x1050@60Hz */
-       { DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 146250, 1680, 1784,
-                  1960, 2240, 0, 1050, 1053, 1059, 1089, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1680x1050@75Hz */
-       { DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 187000, 1680, 1800,
-                  1976, 2272, 0, 1050, 1053, 1059, 1099, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1680x1050@85Hz */
-       { DRM_MODE("1680x1050", DRM_MODE_TYPE_DRIVER, 214750, 1680, 1808,
-                  1984, 2288, 0, 1050, 1053, 1059, 1105, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1792x1344@60Hz */
-       { DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 204750, 1792, 1920,
-                  2120, 2448, 0, 1344, 1345, 1348, 1394, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1729x1344@75Hz */
-       { DRM_MODE("1792x1344", DRM_MODE_TYPE_DRIVER, 261000, 1792, 1888,
-                  2104, 2456, 0, 1344, 1345, 1348, 1417, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1853x1392@60Hz */
-       { DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 218250, 1856, 1952,
-                  2176, 2528, 0, 1392, 1393, 1396, 1439, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1856x1392@75Hz */
-       { DRM_MODE("1856x1392", DRM_MODE_TYPE_DRIVER, 288000, 1856, 1984,
-                  2208, 2560, 0, 1392, 1395, 1399, 1500, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1920x1200@60Hz */
-       { DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 193250, 1920, 2056,
-                  2256, 2592, 0, 1200, 1203, 1209, 1245, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1920x1200@75Hz */
-       { DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 245250, 1920, 2056,
-                  2264, 2608, 0, 1200, 1203, 1209, 1255, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1920x1200@85Hz */
-       { DRM_MODE("1920x1200", DRM_MODE_TYPE_DRIVER, 281250, 1920, 2064,
-                  2272, 2624, 0, 1200, 1203, 1209, 1262, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1920x1440@60Hz */
-       { DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 234000, 1920, 2048,
-                  2256, 2600, 0, 1440, 1441, 1444, 1500, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 1920x1440@75Hz */
-       { DRM_MODE("1920x1440", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2064,
-                  2288, 2640, 0, 1440, 1441, 1444, 1500, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 2560x1600@60Hz */
-       { DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 348500, 2560, 2752,
-                  3032, 3504, 0, 1600, 1603, 1609, 1658, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 2560x1600@75HZ */
-       { DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 443250, 2560, 2768,
-                  3048, 3536, 0, 1600, 1603, 1609, 1672, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-       /* 2560x1600@85HZ */
-       { DRM_MODE("2560x1600", DRM_MODE_TYPE_DRIVER, 505250, 2560, 2768,
-                  3048, 3536, 0, 1600, 1603, 1609, 1682, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) },
-};
-static const int drm_num_dmt_modes =
-       sizeof(drm_dmt_modes) / sizeof(struct drm_display_mode);
-
-static struct drm_display_mode *drm_find_dmt(struct drm_device *dev,
-                       int hsize, int vsize, int fresh)
+struct drm_display_mode *drm_mode_find_dmt(struct drm_device *dev,
+                                          int hsize, int vsize, int fresh)
 {
        int i;
        struct drm_display_mode *ptr, *mode;
@@ -669,6 +451,163 @@ static struct drm_display_mode *drm_find_dmt(struct drm_device *dev,
        }
        return mode;
 }
+EXPORT_SYMBOL(drm_mode_find_dmt);
+
+typedef void detailed_cb(struct detailed_timing *timing, void *closure);
+
+static void
+cea_for_each_detailed_block(u8 *ext, detailed_cb *cb, void *closure)
+{
+       int i, n = 0;
+       u8 rev = ext[0x01], d = ext[0x02];
+       u8 *det_base = ext + d;
+
+       switch (rev) {
+       case 0:
+               /* can't happen */
+               return;
+       case 1:
+               /* have to infer how many blocks we have, check pixel clock */
+               for (i = 0; i < 6; i++)
+                       if (det_base[18*i] || det_base[18*i+1])
+                               n++;
+               break;
+       default:
+               /* explicit count */
+               n = min(ext[0x03] & 0x0f, 6);
+               break;
+       }
+
+       for (i = 0; i < n; i++)
+               cb((struct detailed_timing *)(det_base + 18 * i), closure);
+}
+
+static void
+vtb_for_each_detailed_block(u8 *ext, detailed_cb *cb, void *closure)
+{
+       unsigned int i, n = min((int)ext[0x02], 6);
+       u8 *det_base = ext + 5;
+
+       if (ext[0x01] != 1)
+               return; /* unknown version */
+
+       for (i = 0; i < n; i++)
+               cb((struct detailed_timing *)(det_base + 18 * i), closure);
+}
+
+static void
+drm_for_each_detailed_block(u8 *raw_edid, detailed_cb *cb, void *closure)
+{
+       int i;
+       struct edid *edid = (struct edid *)raw_edid;
+
+       if (edid == NULL)
+               return;
+
+       for (i = 0; i < EDID_DETAILED_TIMINGS; i++)
+               cb(&(edid->detailed_timings[i]), closure);
+
+       for (i = 1; i <= raw_edid[0x7e]; i++) {
+               u8 *ext = raw_edid + (i * EDID_LENGTH);
+               switch (*ext) {
+               case CEA_EXT:
+                       cea_for_each_detailed_block(ext, cb, closure);
+                       break;
+               case VTB_EXT:
+                       vtb_for_each_detailed_block(ext, cb, closure);
+                       break;
+               default:
+                       break;
+               }
+       }
+}
+
+static void
+is_rb(struct detailed_timing *t, void *data)
+{
+       u8 *r = (u8 *)t;
+       if (r[3] == EDID_DETAIL_MONITOR_RANGE)
+               if (r[15] & 0x10)
+                       *(bool *)data = true;
+}
+
+/* EDID 1.4 defines this explicitly.  For EDID 1.3, we guess, badly. */
+static bool
+drm_monitor_supports_rb(struct edid *edid)
+{
+       if (edid->revision >= 4) {
+               bool ret;
+               drm_for_each_detailed_block((u8 *)edid, is_rb, &ret);
+               return ret;
+       }
+
+       return ((edid->input & DRM_EDID_INPUT_DIGITAL) != 0);
+}
+
+static void
+find_gtf2(struct detailed_timing *t, void *data)
+{
+       u8 *r = (u8 *)t;
+       if (r[3] == EDID_DETAIL_MONITOR_RANGE && r[10] == 0x02)
+               *(u8 **)data = r;
+}
+
+/* Secondary GTF curve kicks in above some break frequency */
+static int
+drm_gtf2_hbreak(struct edid *edid)
+{
+       u8 *r = NULL;
+       drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
+       return r ? (r[12] * 2) : 0;
+}
+
+static int
+drm_gtf2_2c(struct edid *edid)
+{
+       u8 *r = NULL;
+       drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
+       return r ? r[13] : 0;
+}
+
+static int
+drm_gtf2_m(struct edid *edid)
+{
+       u8 *r = NULL;
+       drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
+       return r ? (r[15] << 8) + r[14] : 0;
+}
+
+static int
+drm_gtf2_k(struct edid *edid)
+{
+       u8 *r = NULL;
+       drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
+       return r ? r[16] : 0;
+}
+
+static int
+drm_gtf2_2j(struct edid *edid)
+{
+       u8 *r = NULL;
+       drm_for_each_detailed_block((u8 *)edid, find_gtf2, &r);
+       return r ? r[17] : 0;
+}
+
+/**
+ * standard_timing_level - get std. timing level(CVT/GTF/DMT)
+ * @edid: EDID block to scan
+ */
+static int standard_timing_level(struct edid *edid)
+{
+       if (edid->revision >= 2) {
+               if (edid->revision >= 4 && (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF))
+                       return LEVEL_CVT;
+               if (drm_gtf2_hbreak(edid))
+                       return LEVEL_GTF2;
+               return LEVEL_GTF;
+       }
+       return LEVEL_DMT;
+}
 
 /*
  * 0 is reserved.  The spec says 0x01 fill for unused timings.  Some old
@@ -690,18 +629,19 @@ bad_std_timing(u8 a, u8 b)
  * Take the standard timing params (in this case width, aspect, and refresh)
  * and convert them into a real mode using CVT/GTF/DMT.
  */
-struct drm_display_mode *drm_mode_std(struct drm_device *dev,
-                                     struct std_timing *t,
-                                     int revision,
-                                     int timing_level)
+static struct drm_display_mode *
+drm_mode_std(struct drm_connector *connector, struct edid *edid,
+            struct std_timing *t, int revision)
 {
-       struct drm_display_mode *mode;
+       struct drm_device *dev = connector->dev;
+       struct drm_display_mode *m, *mode = NULL;
        int hsize, vsize;
        int vrefresh_rate;
        unsigned aspect_ratio = (t->vfreq_aspect & EDID_TIMING_ASPECT_MASK)
                >> EDID_TIMING_ASPECT_SHIFT;
        unsigned vfreq = (t->vfreq_aspect & EDID_TIMING_VFREQ_MASK)
                >> EDID_TIMING_VFREQ_SHIFT;
+       int timing_level = standard_timing_level(edid);
 
        if (bad_std_timing(t->hsize, t->vfreq_aspect))
                return NULL;
@@ -722,18 +662,38 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev,
                vsize = (hsize * 4) / 5;
        else
                vsize = (hsize * 9) / 16;
-       /* HDTV hack */
-       if (hsize == 1360 && vsize == 765 && vrefresh_rate == 60) {
-               mode = drm_cvt_mode(dev, hsize, vsize, vrefresh_rate, 0, 0,
+
+       /* HDTV hack, part 1 */
+       if (vrefresh_rate == 60 &&
+           ((hsize == 1360 && vsize == 765) ||
+            (hsize == 1368 && vsize == 769))) {
+               hsize = 1366;
+               vsize = 768;
+       }
+
+       /*
+        * If this connector already has a mode for this size and refresh
+        * rate (because it came from detailed or CVT info), use that
+        * instead.  This way we don't have to guess at interlace or
+        * reduced blanking.
+        */
+       list_for_each_entry(m, &connector->probed_modes, head)
+               if (m->hdisplay == hsize && m->vdisplay == vsize &&
+                   drm_mode_vrefresh(m) == vrefresh_rate)
+                       return NULL;
+
+       /* HDTV hack, part 2 */
+       if (hsize == 1366 && vsize == 768 && vrefresh_rate == 60) {
+               mode = drm_cvt_mode(dev, 1366, 768, vrefresh_rate, 0, 0,
                                    false);
                mode->hdisplay = 1366;
-               mode->vsync_start = mode->vsync_start - 1;
-               mode->vsync_end = mode->vsync_end - 1;
+               mode->hsync_start = mode->hsync_start - 1;
+               mode->hsync_end = mode->hsync_end - 1;
                return mode;
        }
-       mode = NULL;
+
        /* check whether it can be found in default mode table */
-       mode = drm_find_dmt(dev, hsize, vsize, vrefresh_rate);
+       mode = drm_mode_find_dmt(dev, hsize, vsize, vrefresh_rate);
        if (mode)
                return mode;
 
@@ -743,6 +703,23 @@ struct drm_display_mode *drm_mode_std(struct drm_device *dev,
        case LEVEL_GTF:
                mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0);
                break;
+       case LEVEL_GTF2:
+               /*
+                * This is potentially wrong if there's ever a monitor with
+                * more than one ranges section, each claiming a different
+                * secondary GTF curve.  Please don't do that.
+                */
+               mode = drm_gtf_mode(dev, hsize, vsize, vrefresh_rate, 0, 0);
+               if (drm_mode_hsync(mode) > drm_gtf2_hbreak(edid)) {
+                       kfree(mode);
+                       mode = drm_gtf_mode_complex(dev, hsize, vsize,
+                                                   vrefresh_rate, 0, 0,
+                                                   drm_gtf2_m(edid),
+                                                   drm_gtf2_2c(edid),
+                                                   drm_gtf2_k(edid),
+                                                   drm_gtf2_2j(edid));
+               }
+               break;
        case LEVEL_CVT:
                mode = drm_cvt_mode(dev, hsize, vsize, vrefresh_rate, 0, 0,
                                    false);
@@ -775,13 +752,11 @@ drm_mode_do_interlace_quirk(struct drm_display_mode *mode,
                { 1440,  576 },
                { 2880,  576 },
        };
-       static const int n_sizes =
-               sizeof(cea_interlaced)/sizeof(cea_interlaced[0]);
 
        if (!(pt->misc & DRM_EDID_PT_INTERLACED))
                return;
 
-       for (i = 0; i < n_sizes; i++) {
+       for (i = 0; i < ARRAY_SIZE(cea_interlaced); i++) {
                if ((mode->hdisplay == cea_interlaced[i].w) &&
                    (mode->vdisplay == cea_interlaced[i].h / 2)) {
                        mode->vdisplay *= 2;
@@ -866,10 +841,10 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev,
        if (mode->vsync_end > mode->vtotal)
                mode->vtotal = mode->vsync_end + 1;
 
-       drm_mode_set_name(mode);
-
        drm_mode_do_interlace_quirk(mode, pt);
 
+       drm_mode_set_name(mode);
+
        if (quirks & EDID_QUIRK_DETAILED_SYNC_PP) {
                pt->misc |= DRM_EDID_PT_HSYNC_POSITIVE | DRM_EDID_PT_VSYNC_POSITIVE;
        }
@@ -895,66 +870,180 @@ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev,
        return mode;
 }
 
+static bool
+mode_is_rb(struct drm_display_mode *mode)
+{
+       return (mode->htotal - mode->hdisplay == 160) &&
+              (mode->hsync_end - mode->hdisplay == 80) &&
+              (mode->hsync_end - mode->hsync_start == 32) &&
+              (mode->vsync_start - mode->vdisplay == 3);
+}
+
+static bool
+mode_in_hsync_range(struct drm_display_mode *mode, struct edid *edid, u8 *t)
+{
+       int hsync, hmin, hmax;
+
+       hmin = t[7];
+       if (edid->revision >= 4)
+           hmin += ((t[4] & 0x04) ? 255 : 0);
+       hmax = t[8];
+       if (edid->revision >= 4)
+           hmax += ((t[4] & 0x08) ? 255 : 0);
+       hsync = drm_mode_hsync(mode);
+
+       return (hsync <= hmax && hsync >= hmin);
+}
+
+static bool
+mode_in_vsync_range(struct drm_display_mode *mode, struct edid *edid, u8 *t)
+{
+       int vsync, vmin, vmax;
+
+       vmin = t[5];
+       if (edid->revision >= 4)
+           vmin += ((t[4] & 0x01) ? 255 : 0);
+       vmax = t[6];
+       if (edid->revision >= 4)
+           vmax += ((t[4] & 0x02) ? 255 : 0);
+       vsync = drm_mode_vrefresh(mode);
+
+       return (vsync <= vmax && vsync >= vmin);
+}
+
+static u32
+range_pixel_clock(struct edid *edid, u8 *t)
+{
+       /* unspecified */
+       if (t[9] == 0 || t[9] == 255)
+               return 0;
+
+       /* 1.4 with CVT support gives us real precision, yay */
+       if (edid->revision >= 4 && t[10] == 0x04)
+               return (t[9] * 10000) - ((t[12] >> 2) * 250);
+
+       /* 1.3 is pathetic, so fuzz up a bit */
+       return t[9] * 10000 + 5001;
+}
+
+static bool
+mode_in_range(struct drm_display_mode *mode, struct edid *edid,
+             struct detailed_timing *timing)
+{
+       u32 max_clock;
+       u8 *t = (u8 *)timing;
+
+       if (!mode_in_hsync_range(mode, edid, t))
+               return false;
+
+       if (!mode_in_vsync_range(mode, edid, t))
+               return false;
+
+       if ((max_clock = range_pixel_clock(edid, t)))
+               if (mode->clock > max_clock)
+                       return false;
+
+       /* 1.4 max horizontal check */
+       if (edid->revision >= 4 && t[10] == 0x04)
+               if (t[13] && mode->hdisplay > 8 * (t[13] + (256 * (t[12]&0x3))))
+                       return false;
+
+       if (mode_is_rb(mode) && !drm_monitor_supports_rb(edid))
+               return false;
+
+       return true;
+}
+
 /*
- * Detailed mode info for the EDID "established modes" data to use.
+ * XXX If drm_dmt_modes ever regrows the CVT-R modes (and it will) this will
+ * need to account for them.
  */
-static struct drm_display_mode edid_est_modes[] = {
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 40000, 800, 840,
-                  968, 1056, 0, 600, 601, 605, 628, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@60Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 36000, 800, 824,
-                  896, 1024, 0, 600, 601, 603,  625, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@56Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 656,
-                  720, 840, 0, 480, 481, 484, 500, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@75Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 31500, 640, 664,
-                  704,  832, 0, 480, 489, 491, 520, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@72Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 30240, 640, 704,
-                  768,  864, 0, 480, 483, 486, 525, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@67Hz */
-       { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 25200, 640, 656,
-                  752, 800, 0, 480, 490, 492, 525, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 640x480@60Hz */
-       { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 35500, 720, 738,
-                  846, 900, 0, 400, 421, 423,  449, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 720x400@88Hz */
-       { DRM_MODE("720x400", DRM_MODE_TYPE_DRIVER, 28320, 720, 738,
-                  846,  900, 0, 400, 412, 414, 449, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 720x400@70Hz */
-       { DRM_MODE("1280x1024", DRM_MODE_TYPE_DRIVER, 135000, 1280, 1296,
-                  1440, 1688, 0, 1024, 1025, 1028, 1066, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1280x1024@75Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 78800, 1024, 1040,
-                  1136, 1312, 0,  768, 769, 772, 800, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1024x768@75Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 75000, 1024, 1048,
-                  1184, 1328, 0,  768, 771, 777, 806, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@70Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048,
-                  1184, 1344, 0,  768, 771, 777, 806, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 1024x768@60Hz */
-       { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER,44900, 1024, 1032,
-                  1208, 1264, 0, 768, 768, 776, 817, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_INTERLACE) }, /* 1024x768@43Hz */
-       { DRM_MODE("832x624", DRM_MODE_TYPE_DRIVER, 57284, 832, 864,
-                  928, 1152, 0, 624, 625, 628, 667, 0,
-                  DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) }, /* 832x624@75Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 49500, 800, 816,
-                  896, 1056, 0, 600, 601, 604,  625, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@75Hz */
-       { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 50000, 800, 856,
-                  976, 1040, 0, 600, 637, 643, 666, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 800x600@72Hz */
-       { DRM_MODE("1152x864", DRM_MODE_TYPE_DRIVER, 108000, 1152, 1216,
-                  1344, 1600, 0,  864, 865, 868, 900, 0,
-                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) }, /* 1152x864@75Hz */
-};
+static int
+drm_gtf_modes_for_range(struct drm_connector *connector, struct edid *edid,
+                       struct detailed_timing *timing)
+{
+       int i, modes = 0;
+       struct drm_display_mode *newmode;
+       struct drm_device *dev = connector->dev;
 
-#define EDID_EST_TIMINGS 16
-#define EDID_STD_TIMINGS 8
-#define EDID_DETAILED_TIMINGS 4
+       for (i = 0; i < drm_num_dmt_modes; i++) {
+               if (mode_in_range(drm_dmt_modes + i, edid, timing)) {
+                       newmode = drm_mode_duplicate(dev, &drm_dmt_modes[i]);
+                       if (newmode) {
+                               drm_mode_probed_add(connector, newmode);
+                               modes++;
+                       }
+               }
+       }
+
+       return modes;
+}
+
+static void
+do_inferred_modes(struct detailed_timing *timing, void *c)
+{
+       struct detailed_mode_closure *closure = c;
+       struct detailed_non_pixel *data = &timing->data.other_data;
+       int gtf = (closure->edid->features & DRM_EDID_FEATURE_DEFAULT_GTF);
+
+       if (gtf && data->type == EDID_DETAIL_MONITOR_RANGE)
+               closure->modes += drm_gtf_modes_for_range(closure->connector,
+                                                         closure->edid,
+                                                         timing);
+}
+
+static int
+add_inferred_modes(struct drm_connector *connector, struct edid *edid)
+{
+       struct detailed_mode_closure closure = {
+               connector, edid, 0, 0, 0
+       };
+
+       if (version_greater(edid, 1, 0))
+               drm_for_each_detailed_block((u8 *)edid, do_inferred_modes,
+                                           &closure);
+
+       return closure.modes;
+}
+
+static int
+drm_est3_modes(struct drm_connector *connector, struct detailed_timing *timing)
+{
+       int i, j, m, modes = 0;
+       struct drm_display_mode *mode;
+       u8 *est = ((u8 *)timing) + 5;
+
+       for (i = 0; i < 6; i++) {
+               for (j = 7; j > 0; j--) {
+                       m = (i * 8) + (7 - j);
+                       if (m >= ARRAY_SIZE(est3_modes))
+                               break;
+                       if (est[i] & (1 << j)) {
+                               mode = drm_mode_find_dmt(connector->dev,
+                                                        est3_modes[m].w,
+                                                        est3_modes[m].h,
+                                                        est3_modes[m].r
+                                                        /*, est3_modes[m].rb */);
+                               if (mode) {
+                                       drm_mode_probed_add(connector, mode);
+                                       modes++;
+                               }
+                       }
+               }
+       }
+
+       return modes;
+}
+
+static void
+do_established_modes(struct detailed_timing *timing, void *c)
+{
+       struct detailed_mode_closure *closure = c;
+       struct detailed_non_pixel *data = &timing->data.other_data;
+
+       if (data->type == EDID_DETAIL_EST_TIMINGS)
+               closure->modes += drm_est3_modes(closure->connector, timing);
+}
 
 /**
  * add_established_modes - get est. modes from EDID and add them
@@ -963,15 +1052,19 @@ static struct drm_display_mode edid_est_modes[] = {
  * Each EDID block contains a bitmap of the supported "established modes" list
  * (defined above).  Tease them out and add them to the global modes list.
  */
-static int add_established_modes(struct drm_connector *connector, struct edid *edid)
+static int
+add_established_modes(struct drm_connector *connector, struct edid *edid)
 {
        struct drm_device *dev = connector->dev;
        unsigned long est_bits = edid->established_timings.t1 |
                (edid->established_timings.t2 << 8) |
                ((edid->established_timings.mfg_rsvd & 0x80) << 9);
        int i, modes = 0;
+       struct detailed_mode_closure closure = {
+               connector, edid, 0, 0, 0
+       };
 
-       for (i = 0; i <= EDID_EST_TIMINGS; i++)
+       for (i = 0; i <= EDID_EST_TIMINGS; i++) {
                if (est_bits & (1<<i)) {
                        struct drm_display_mode *newmode;
                        newmode = drm_mode_duplicate(dev, &edid_est_modes[i]);
@@ -980,109 +1073,74 @@ static int add_established_modes(struct drm_connector *connector, struct edid *e
                                modes++;
                        }
                }
+       }
 
-       return modes;
+       if (version_greater(edid, 1, 0))
+                   drm_for_each_detailed_block((u8 *)edid,
+                                               do_established_modes, &closure);
+
+       return modes + closure.modes;
 }
-/**
- * stanard_timing_level - get std. timing level(CVT/GTF/DMT)
- * @edid: EDID block to scan
- */
-static int standard_timing_level(struct edid *edid)
+
+static void
+do_standard_modes(struct detailed_timing *timing, void *c)
 {
-       if (edid->revision >= 2) {
-               if (edid->revision >= 4 && (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF))
-                       return LEVEL_CVT;
-               return LEVEL_GTF;
+       struct detailed_mode_closure *closure = c;
+       struct detailed_non_pixel *data = &timing->data.other_data;
+       struct drm_connector *connector = closure->connector;
+       struct edid *edid = closure->edid;
+
+       if (data->type == EDID_DETAIL_STD_MODES) {
+               int i;
+               for (i = 0; i < 6; i++) {
+                       struct std_timing *std;
+                       struct drm_display_mode *newmode;
+
+                       std = &data->data.timings[i];
+                       newmode = drm_mode_std(connector, edid, std,
+                                              edid->revision);
+                       if (newmode) {
+                               drm_mode_probed_add(connector, newmode);
+                               closure->modes++;
+                       }
+               }
        }
-       return LEVEL_DMT;
 }
 
 /**
  * add_standard_modes - get std. modes from EDID and add them
  * @edid: EDID block to scan
  *
- * Standard modes can be calculated using the CVT standard.  Grab them from
- * @edid, calculate them, and add them to the list.
+ * Standard modes can be calculated using the appropriate standard (DMT,
+ * GTF or CVT. Grab them from @edid and add them to the list.
  */
-static int add_standard_modes(struct drm_connector *connector, struct edid *edid)
+static int
+add_standard_modes(struct drm_connector *connector, struct edid *edid)
 {
-       struct drm_device *dev = connector->dev;
        int i, modes = 0;
-       int timing_level;
-
-       timing_level = standard_timing_level(edid);
+       struct detailed_mode_closure closure = {
+               connector, edid, 0, 0, 0
+       };
 
        for (i = 0; i < EDID_STD_TIMINGS; i++) {
-               struct std_timing *t = &edid->standard_timings[i];
                struct drm_display_mode *newmode;
 
-               newmode = drm_mode_std(dev, &edid->standard_timings[i],
-                                      edid->revision, timing_level);
+               newmode = drm_mode_std(connector, edid,
+                                      &edid->standard_timings[i],
+                                      edid->revision);
                if (newmode) {
                        drm_mode_probed_add(connector, newmode);
                        modes++;
                }
        }
 
-       return modes;
-}
-
-/*
- * XXX fix this for:
- * - GTF secondary curve formula
- * - EDID 1.4 range offsets
- * - CVT extended bits
- */
-static bool
-mode_in_range(struct drm_display_mode *mode, struct detailed_timing *timing)
-{
-       struct detailed_data_monitor_range *range;
-       int hsync, vrefresh;
-
-       range = &timing->data.other_data.data.range;
-
-       hsync = drm_mode_hsync(mode);
-       vrefresh = drm_mode_vrefresh(mode);
-
-       if (hsync < range->min_hfreq_khz || hsync > range->max_hfreq_khz)
-               return false;
-
-       if (vrefresh < range->min_vfreq || vrefresh > range->max_vfreq)
-               return false;
-
-       if (range->pixel_clock_mhz && range->pixel_clock_mhz != 0xff) {
-               /* be forgiving since it's in units of 10MHz */
-               int max_clock = range->pixel_clock_mhz * 10 + 9;
-               max_clock *= 1000;
-               if (mode->clock > max_clock)
-                       return false;
-       }
-
-       return true;
-}
-
-/*
- * XXX If drm_dmt_modes ever regrows the CVT-R modes (and it will) this will
- * need to account for them.
- */
-static int drm_gtf_modes_for_range(struct drm_connector *connector,
-                                  struct detailed_timing *timing)
-{
-       int i, modes = 0;
-       struct drm_display_mode *newmode;
-       struct drm_device *dev = connector->dev;
+       if (version_greater(edid, 1, 0))
+               drm_for_each_detailed_block((u8 *)edid, do_standard_modes,
+                                           &closure);
 
-       for (i = 0; i < drm_num_dmt_modes; i++) {
-               if (mode_in_range(drm_dmt_modes + i, timing)) {
-                       newmode = drm_mode_duplicate(dev, &drm_dmt_modes[i]);
-                       if (newmode) {
-                               drm_mode_probed_add(connector, newmode);
-                               modes++;
-                       }
-               }
-       }
+       /* XXX should also look for standard codes in VTB blocks */
 
-       return modes;
+       return modes + closure.modes;
 }
 
 static int drm_cvt_modes(struct drm_connector *connector,
@@ -1134,251 +1192,78 @@ static int drm_cvt_modes(struct drm_connector *connector,
        return modes;
 }
 
-static const struct {
-       short w;
-       short h;
-       short r;
-       short rb;
-} est3_modes[] = {
-       /* byte 6 */
-       { 640, 350, 85, 0 },
-       { 640, 400, 85, 0 },
-       { 720, 400, 85, 0 },
-       { 640, 480, 85, 0 },
-       { 848, 480, 60, 0 },
-       { 800, 600, 85, 0 },
-       { 1024, 768, 85, 0 },
-       { 1152, 864, 75, 0 },
-       /* byte 7 */
-       { 1280, 768, 60, 1 },
-       { 1280, 768, 60, 0 },
-       { 1280, 768, 75, 0 },
-       { 1280, 768, 85, 0 },
-       { 1280, 960, 60, 0 },
-       { 1280, 960, 85, 0 },
-       { 1280, 1024, 60, 0 },
-       { 1280, 1024, 85, 0 },
-       /* byte 8 */
-       { 1360, 768, 60, 0 },
-       { 1440, 900, 60, 1 },
-       { 1440, 900, 60, 0 },
-       { 1440, 900, 75, 0 },
-       { 1440, 900, 85, 0 },
-       { 1400, 1050, 60, 1 },
-       { 1400, 1050, 60, 0 },
-       { 1400, 1050, 75, 0 },
-       /* byte 9 */
-       { 1400, 1050, 85, 0 },
-       { 1680, 1050, 60, 1 },
-       { 1680, 1050, 60, 0 },
-       { 1680, 1050, 75, 0 },
-       { 1680, 1050, 85, 0 },
-       { 1600, 1200, 60, 0 },
-       { 1600, 1200, 65, 0 },
-       { 1600, 1200, 70, 0 },
-       /* byte 10 */
-       { 1600, 1200, 75, 0 },
-       { 1600, 1200, 85, 0 },
-       { 1792, 1344, 60, 0 },
-       { 1792, 1344, 85, 0 },
-       { 1856, 1392, 60, 0 },
-       { 1856, 1392, 75, 0 },
-       { 1920, 1200, 60, 1 },
-       { 1920, 1200, 60, 0 },
-       /* byte 11 */
-       { 1920, 1200, 75, 0 },
-       { 1920, 1200, 85, 0 },
-       { 1920, 1440, 60, 0 },
-       { 1920, 1440, 75, 0 },
-};
-static const int num_est3_modes = sizeof(est3_modes) / sizeof(est3_modes[0]);
+static void
+do_cvt_mode(struct detailed_timing *timing, void *c)
+{
+       struct detailed_mode_closure *closure = c;
+       struct detailed_non_pixel *data = &timing->data.other_data;
+
+       if (data->type == EDID_DETAIL_CVT_3BYTE)
+               closure->modes += drm_cvt_modes(closure->connector, timing);
+}
 
 static int
-drm_est3_modes(struct drm_connector *connector, struct detailed_timing *timing)
-{
-       int i, j, m, modes = 0;
-       struct drm_display_mode *mode;
-       u8 *est = ((u8 *)timing) + 5;
+add_cvt_modes(struct drm_connector *connector, struct edid *edid)
+{      
+       struct detailed_mode_closure closure = {
+               connector, edid, 0, 0, 0
+       };
 
-       for (i = 0; i < 6; i++) {
-               for (j = 7; j > 0; j--) {
-                       m = (i * 8) + (7 - j);
-                       if (m > num_est3_modes)
-                               break;
-                       if (est[i] & (1 << j)) {
-                               mode = drm_find_dmt(connector->dev,
-                                                   est3_modes[m].w,
-                                                   est3_modes[m].h,
-                                                   est3_modes[m].r
-                                                   /*, est3_modes[m].rb */);
-                               if (mode) {
-                                       drm_mode_probed_add(connector, mode);
-                                       modes++;
-                               }
-                       }
-               }
-       }
+       if (version_greater(edid, 1, 2))
+               drm_for_each_detailed_block((u8 *)edid, do_cvt_mode, &closure);
 
-       return modes;
+       /* XXX should also look for CVT codes in VTB blocks */
+
+       return closure.modes;
 }
 
-static int add_detailed_modes(struct drm_connector *connector,
-                             struct detailed_timing *timing,
-                             struct edid *edid, u32 quirks, int preferred)
+static void
+do_detailed_mode(struct detailed_timing *timing, void *c)
 {
-       int i, modes = 0;
-       struct detailed_non_pixel *data = &timing->data.other_data;
-       int timing_level = standard_timing_level(edid);
-       int gtf = (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF);
+       struct detailed_mode_closure *closure = c;
        struct drm_display_mode *newmode;
-       struct drm_device *dev = connector->dev;
 
        if (timing->pixel_clock) {
-               newmode = drm_mode_detailed(dev, edid, timing, quirks);
+               newmode = drm_mode_detailed(closure->connector->dev,
+                                           closure->edid, timing,
+                                           closure->quirks);
                if (!newmode)
-                       return 0;
+                       return;
 
-               if (preferred)
+               if (closure->preferred)
                        newmode->type |= DRM_MODE_TYPE_PREFERRED;
 
-               drm_mode_probed_add(connector, newmode);
-               return 1;
-       }
-
-       /* other timing types */
-       switch (data->type) {
-       case EDID_DETAIL_MONITOR_RANGE:
-               if (gtf)
-                       modes += drm_gtf_modes_for_range(connector, timing);
-               break;
-       case EDID_DETAIL_STD_MODES:
-               /* Six modes per detailed section */
-               for (i = 0; i < 6; i++) {
-                       struct std_timing *std;
-                       struct drm_display_mode *newmode;
-
-                       std = &data->data.timings[i];
-                       newmode = drm_mode_std(dev, std, edid->revision,
-                                              timing_level);
-                       if (newmode) {
-                               drm_mode_probed_add(connector, newmode);
-                               modes++;
-                       }
-               }
-               break;
-       case EDID_DETAIL_CVT_3BYTE:
-               modes += drm_cvt_modes(connector, timing);
-               break;
-       case EDID_DETAIL_EST_TIMINGS:
-               modes += drm_est3_modes(connector, timing);
-               break;
-       default:
-               break;
+               drm_mode_probed_add(closure->connector, newmode);
+               closure->modes++;
+               closure->preferred = 0;
        }
-
-       return modes;
 }
 
-/**
- * add_detailed_info - get detailed mode info from EDID data
+/*
+ * add_detailed_modes - Add modes from detailed timings
  * @connector: attached connector
  * @edid: EDID block to scan
  * @quirks: quirks to apply
- *
- * Some of the detailed timing sections may contain mode information.  Grab
- * it and add it to the list.
- */
-static int add_detailed_info(struct drm_connector *connector,
-                            struct edid *edid, u32 quirks)
-{
-       int i, modes = 0;
-
-       for (i = 0; i < EDID_DETAILED_TIMINGS; i++) {
-               struct detailed_timing *timing = &edid->detailed_timings[i];
-               int preferred = (i == 0) && (edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING);
-
-               /* In 1.0, only timings are allowed */
-               if (!timing->pixel_clock && edid->version == 1 &&
-                       edid->revision == 0)
-                       continue;
-
-               modes += add_detailed_modes(connector, timing, edid, quirks,
-                                           preferred);
-       }
-
-       return modes;
-}
-
-/**
- * add_detailed_mode_eedid - get detailed mode info from addtional timing
- *                     EDID block
- * @connector: attached connector
- * @edid: EDID block to scan(It is only to get addtional timing EDID block)
- * @quirks: quirks to apply
- *
- * Some of the detailed timing sections may contain mode information.  Grab
- * it and add it to the list.
  */
-static int add_detailed_info_eedid(struct drm_connector *connector,
-                            struct edid *edid, u32 quirks)
+static int
+add_detailed_modes(struct drm_connector *connector, struct edid *edid,
+                  u32 quirks)
 {
-       int i, modes = 0;
-       char *edid_ext = NULL;
-       struct detailed_timing *timing;
-       int edid_ext_num;
-       int start_offset, end_offset;
-       int timing_level;
-
-       if (edid->version == 1 && edid->revision < 3) {
-               /* If the EDID version is less than 1.3, there is no
-                * extension EDID.
-                */
-               return 0;
-       }
-       if (!edid->extensions) {
-               /* if there is no extension EDID, it is unnecessary to
-                * parse the E-EDID to get detailed info
-                */
-               return 0;
-       }
-
-       /* Chose real EDID extension number */
-       edid_ext_num = edid->extensions > DRM_MAX_EDID_EXT_NUM ?
-               DRM_MAX_EDID_EXT_NUM : edid->extensions;
-
-       /* Find CEA extension */
-       for (i = 0; i < edid_ext_num; i++) {
-               edid_ext = (char *)edid + EDID_LENGTH * (i + 1);
-               /* This block is CEA extension */
-               if (edid_ext[0] == 0x02)
-                       break;
-       }
-
-       if (i == edid_ext_num) {
-               /* if there is no additional timing EDID block, return */
-               return 0;
-       }
+       struct detailed_mode_closure closure = {
+               connector,
+               edid,
+               1,
+               quirks,
+               0
+       };
 
-       /* Get the start offset of detailed timing block */
-       start_offset = edid_ext[2];
-       if (start_offset == 0) {
-               /* If the start_offset is zero, it means that neither detailed
-                * info nor data block exist. In such case it is also
-                * unnecessary to parse the detailed timing info.
-                */
-               return 0;
-       }
+       if (closure.preferred && !version_greater(edid, 1, 3))
+               closure.preferred =
+                   (edid->features & DRM_EDID_FEATURE_PREFERRED_TIMING);
 
-       timing_level = standard_timing_level(edid);
-       end_offset = EDID_LENGTH;
-       end_offset -= sizeof(struct detailed_timing);
-       for (i = start_offset; i < end_offset;
-                       i += sizeof(struct detailed_timing)) {
-               timing = (struct detailed_timing *)(edid_ext + i);
-               modes += add_detailed_modes(connector, timing, edid, quirks, 0);
-       }
+       drm_for_each_detailed_block((u8 *)edid, do_detailed_mode, &closure);
 
-       return modes;
+       return closure.modes;
 }
 
 #define HDMI_IDENTIFIER 0x000C03
@@ -1393,7 +1278,7 @@ static int add_detailed_info_eedid(struct drm_connector *connector,
 bool drm_detect_hdmi_monitor(struct edid *edid)
 {
        char *edid_ext = NULL;
-       int i, hdmi_id, edid_ext_num;
+       int i, hdmi_id;
        int start_offset, end_offset;
        bool is_hdmi = false;
 
@@ -1401,19 +1286,15 @@ bool drm_detect_hdmi_monitor(struct edid *edid)
        if (edid == NULL || edid->extensions == 0)
                goto end;
 
-       /* Chose real EDID extension number */
-       edid_ext_num = edid->extensions > DRM_MAX_EDID_EXT_NUM ?
-                      DRM_MAX_EDID_EXT_NUM : edid->extensions;
-
        /* Find CEA extension */
-       for (i = 0; i < edid_ext_num; i++) {
+       for (i = 0; i < edid->extensions; i++) {
                edid_ext = (char *)edid + EDID_LENGTH * (i + 1);
                /* This block is CEA extension */
                if (edid_ext[0] == 0x02)
                        break;
        }
 
-       if (i == edid_ext_num)
+       if (i == edid->extensions)
                goto end;
 
        /* Data block offset in CEA extension block */
@@ -1461,7 +1342,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
                return 0;
        }
        if (!drm_edid_is_valid(edid)) {
-               dev_warn(&connector->dev->pdev->dev, "%s: EDID invalid.\n",
+               dev_warn(connector->dev->dev, "%s: EDID invalid.\n",
                         drm_get_connector_name(connector));
                return 0;
        }
@@ -1478,35 +1359,21 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
         * - established timing codes
         * - modes inferred from GTF or CVT range information
         *
-        * We don't quite implement this yet, but we're close.
+        * We get this pretty much right.
         *
         * XXX order for additional mode types in extension blocks?
         */
-       num_modes += add_detailed_info(connector, edid, quirks);
-       num_modes += add_detailed_info_eedid(connector, edid, quirks);
+       num_modes += add_detailed_modes(connector, edid, quirks);
+       num_modes += add_cvt_modes(connector, edid);
        num_modes += add_standard_modes(connector, edid);
        num_modes += add_established_modes(connector, edid);
+       num_modes += add_inferred_modes(connector, edid);
 
        if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75))
                edid_fixup_preferred(connector, quirks);
 
-       connector->display_info.serration_vsync = (edid->input & DRM_EDID_INPUT_SERRATION_VSYNC) ? 1 : 0;
-       connector->display_info.sync_on_green = (edid->input & DRM_EDID_INPUT_SYNC_ON_GREEN) ? 1 : 0;
-       connector->display_info.composite_sync = (edid->input & DRM_EDID_INPUT_COMPOSITE_SYNC) ? 1 : 0;
-       connector->display_info.separate_syncs = (edid->input & DRM_EDID_INPUT_SEPARATE_SYNCS) ? 1 : 0;
-       connector->display_info.blank_to_black = (edid->input & DRM_EDID_INPUT_BLANK_TO_BLACK) ? 1 : 0;
-       connector->display_info.video_level = (edid->input & DRM_EDID_INPUT_VIDEO_LEVEL) >> 5;
-       connector->display_info.digital = (edid->input & DRM_EDID_INPUT_DIGITAL) ? 1 : 0;
        connector->display_info.width_mm = edid->width_cm * 10;
        connector->display_info.height_mm = edid->height_cm * 10;
-       connector->display_info.gamma = edid->gamma;
-       connector->display_info.gtf_supported = (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF) ? 1 : 0;
-       connector->display_info.standard_color = (edid->features & DRM_EDID_FEATURE_STANDARD_COLOR) ? 1 : 0;
-       connector->display_info.display_type = (edid->features & DRM_EDID_FEATURE_DISPLAY_TYPE) >> 3;
-       connector->display_info.active_off_supported = (edid->features & DRM_EDID_FEATURE_PM_ACTIVE_OFF) ? 1 : 0;
-       connector->display_info.suspend_supported = (edid->features & DRM_EDID_FEATURE_PM_SUSPEND) ? 1 : 0;
-       connector->display_info.standby_supported = (edid->features & DRM_EDID_FEATURE_PM_STANDBY) ? 1 : 0;
-       connector->display_info.gamma = edid->gamma;
 
        return num_modes;
 }