drm/edid: Add secondary GTF curve support
authorAdam Jackson <ajax@redhat.com>
Mon, 29 Mar 2010 21:43:30 +0000 (21:43 +0000)
committerDave Airlie <airlied@redhat.com>
Tue, 6 Apr 2010 00:40:25 +0000 (10:40 +1000)
Before CVT-R, some monitors would advertise support for an alternative
GTF formula with lower blanking intervals.  Correctly identify such
monitors, and use the alternative formula when generating modes for
them.

Note that we only do this for "standard" timing descriptors (tuples of
hsize in characters / aspect ratio / vertical refresh).  Range-based
mode lists still only refer to the primary GTF curve.  It would be
possible to do better for the latter case, but monitors are required to
support the primary curve over the entire advertised range, so all it
would win you is a lower pixel clock and therefore possibly better image
quality on analog links.

Signed-off-by: Adam Jackson <ajax@redhat.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
drivers/gpu/drm/drm_edid.c
drivers/gpu/drm/drm_modes.c
include/drm/drm_crtc.h

index 6e999bd..3924a7b 100644 (file)
@@ -64,7 +64,8 @@
 
 #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;
@@ -713,6 +714,71 @@ drm_monitor_supports_rb(struct edid *edid)
        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.
@@ -734,8 +800,8 @@ bad_std_timing(u8 a, u8 b)
  * 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;
@@ -745,6 +811,7 @@ drm_mode_std(struct drm_connector *connector, struct std_timing *t,
                >> 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;
@@ -806,6 +873,23 @@ drm_mode_std(struct drm_connector *connector, struct std_timing *t,
        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);
@@ -1042,19 +1126,6 @@ static int add_established_modes(struct drm_connector *connector, struct edid *e
 
        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
@@ -1066,15 +1137,13 @@ static int standard_timing_level(struct edid *edid)
 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++;
@@ -1140,9 +1209,6 @@ range_pixel_clock(struct edid *edid, u8 *t)
        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)
@@ -1339,7 +1405,6 @@ static int add_detailed_modes(struct drm_connector *connector,
 {
        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;
@@ -1370,8 +1435,8 @@ static int add_detailed_modes(struct drm_connector *connector,
                        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++;
index 76d6339..d460b6c 100644 (file)
@@ -276,35 +276,29 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay,
 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
@@ -316,17 +310,9 @@ struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay,
 #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;
@@ -470,7 +456,48 @@ struct drm_display_mode *drm_gtf_mode(struct drm_device *dev, int hdisplay,
 
        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
index f74523a..8eb3630 100644 (file)
@@ -797,6 +797,10 @@ extern struct drm_display_mode *drm_cvt_mode(struct drm_device *dev,
 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);