#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;
        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
  * monitors fill with ascii space (0x20) instead.
  * and convert them into a real mode using CVT/GTF/DMT.
  */
 static struct drm_display_mode *
-drm_mode_std(struct drm_connector *connector, struct std_timing *t,
-            int revision, int timing_level)
+drm_mode_std(struct drm_connector *connector, struct edid *edid,
+            struct std_timing *t, int revision)
 {
        struct drm_device *dev = connector->dev;
        struct drm_display_mode *m, *mode = NULL;
                >> 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;
        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);
 
        return modes;
 }
-/**
- * stanard_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;
-               return LEVEL_GTF;
-       }
-       return LEVEL_DMT;
-}
 
 /**
  * add_standard_modes - get std. modes from EDID and add them
 static int add_standard_modes(struct drm_connector *connector, struct edid *edid)
 {
        int i, modes = 0;
-       int timing_level;
-
-       timing_level = standard_timing_level(edid);
 
        for (i = 0; i < EDID_STD_TIMINGS; i++) {
                struct drm_display_mode *newmode;
 
-               newmode = drm_mode_std(connector, &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 t[9] * 10000 + 5001;
 }
 
-/*
- * XXX fix this for GTF secondary curve formula
- */
 static bool
 mode_in_range(struct drm_display_mode *mode, struct edid *edid,
              struct detailed_timing *timing)
 {
        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 drm_display_mode *newmode;
        struct drm_device *dev = connector->dev;
                        struct drm_display_mode *newmode;
 
                        std = &data->data.timings[i];
-                       newmode = drm_mode_std(connector, std, edid->revision,
-                                              timing_level);
+                       newmode = drm_mode_std(connector, edid, std,
+                                              edid->revision);
                        if (newmode) {
                                drm_mode_probed_add(connector, newmode);
                                modes++;
 
 EXPORT_SYMBOL(drm_cvt_mode);
 
 /**
- * drm_gtf_mode - create the modeline based on GTF algorithm
+ * drm_gtf_mode_complex - create the modeline based on full GTF algorithm
  *
  * @dev                :drm device
  * @hdisplay   :hdisplay size
  * @vdisplay   :vdisplay size
  * @vrefresh   :vrefresh rate.
  * @interlaced :whether the interlace is supported
- * @margins    :whether the margin is supported
+ * @margins    :desired margin size
+ * @GTF_[MCKJ]  :extended GTF formula parameters
  *
  * LOCKING.
  * none.
  *
- * return the modeline based on GTF algorithm
- *
- * This function is to create the modeline based on the GTF algorithm.
- * Generalized Timing Formula is derived from:
- *     GTF Spreadsheet by Andy Morrish (1/5/97)
- *     available at http://www.vesa.org
+ * return the modeline based on full GTF algorithm.
  *
- * And it is copied from the file of xserver/hw/xfree86/modes/xf86gtf.c.
- * What I have done is to translate it by using integer calculation.
- * I also refer to the function of fb_get_mode in the file of
- * drivers/video/fbmon.c
+ * GTF feature blocks specify C and J in multiples of 0.5, so we pass them
+ * in here multiplied by two.  For a C of 40, pass in 80.
  */
-struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay,
-                                     int vdisplay, int vrefresh,
-                                     bool interlaced, int margins)
-{
-       /* 1) top/bottom margin size (% of height) - default: 1.8, */
+struct drm_display_mode *
+drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay,
+                    int vrefresh, bool interlaced, int margins,
+                    int GTF_M, int GTF_2C, int GTF_K, int GTF_2J)
+{      /* 1) top/bottom margin size (% of height) - default: 1.8, */
 #define        GTF_MARGIN_PERCENTAGE           18
        /* 2) character cell horizontal granularity (pixels) - default 8 */
 #define        GTF_CELL_GRAN                   8
 #define H_SYNC_PERCENT                 8
        /* min time of vsync + back porch (microsec) */
 #define MIN_VSYNC_PLUS_BP              550
-       /* blanking formula gradient */
-#define GTF_M                          600
-       /* blanking formula offset */
-#define GTF_C                          40
-       /* blanking formula scaling factor */
-#define GTF_K                          128
-       /* blanking formula scaling factor */
-#define GTF_J                          20
        /* C' and M' are part of the Blanking Duty Cycle computation */
-#define GTF_C_PRIME            (((GTF_C - GTF_J) * GTF_K / 256) + GTF_J)
-#define GTF_M_PRIME            (GTF_K * GTF_M / 256)
+#define GTF_C_PRIME    ((((GTF_2C - GTF_2J) * GTF_K / 256) + GTF_2J) / 2)
+#define GTF_M_PRIME    (GTF_K * GTF_M / 256)
        struct drm_display_mode *drm_mode;
        unsigned int hdisplay_rnd, vdisplay_rnd, vfieldrate_rqd;
        int top_margin, bottom_margin;
 
        return drm_mode;
 }
+EXPORT_SYMBOL(drm_gtf_mode_complex);
+
+/**
+ * drm_gtf_mode - create the modeline based on GTF algorithm
+ *
+ * @dev                :drm device
+ * @hdisplay   :hdisplay size
+ * @vdisplay   :vdisplay size
+ * @vrefresh   :vrefresh rate.
+ * @interlaced :whether the interlace is supported
+ * @margins    :whether the margin is supported
+ *
+ * LOCKING.
+ * none.
+ *
+ * return the modeline based on GTF algorithm
+ *
+ * This function is to create the modeline based on the GTF algorithm.
+ * Generalized Timing Formula is derived from:
+ *     GTF Spreadsheet by Andy Morrish (1/5/97)
+ *     available at http://www.vesa.org
+ *
+ * And it is copied from the file of xserver/hw/xfree86/modes/xf86gtf.c.
+ * What I have done is to translate it by using integer calculation.
+ * I also refer to the function of fb_get_mode in the file of
+ * drivers/video/fbmon.c
+ *
+ * Standard GTF parameters:
+ * M = 600
+ * C = 40
+ * K = 128
+ * J = 20
+ */
+struct drm_display_mode *
+drm_gtf_mode(struct drm_device *dev, int hdisplay, int vdisplay, int vrefresh,
+            bool lace, int margins)
+{
+       return drm_gtf_mode_complex(dev, hdisplay, vdisplay, vrefresh, lace,
+                                   margins, 600, 40 * 2, 128, 20 * 2);
+}
 EXPORT_SYMBOL(drm_gtf_mode);
+
 /**
  * drm_mode_set_name - set the name on a mode
  * @mode: name will be set in this mode
 
 extern struct drm_display_mode *drm_gtf_mode(struct drm_device *dev,
                                int hdisplay, int vdisplay, int vrefresh,
                                bool interlaced, int margins);
+extern struct drm_display_mode *drm_gtf_mode_complex(struct drm_device *dev,
+                               int hdisplay, int vdisplay, int vrefresh,
+                               bool interlaced, int margins, int GTF_M,
+                               int GTF_2C, int GTF_K, int GTF_2J);
 extern int drm_add_modes_noedid(struct drm_connector *connector,
                                int hdisplay, int vdisplay);