drm/edid: Add both 60Hz and 59.94Hz CEA modes to connector's mode list
authorVille Syrjälä <ville.syrjala@linux.intel.com>
Fri, 31 May 2013 12:23:41 +0000 (15:23 +0300)
committerDave Airlie <airlied@redhat.com>
Mon, 10 Jun 2013 22:51:31 +0000 (08:51 +1000)
Having both modes can be beneficial for video playback cases. If you can
match the video framerate exactly, and the audio and video clocks come
from the same source, you should be able to avoid dropped/repeated
frames without expensive operations such as resampling the audio to
match video output rate.

Rather than add both variants based on the CEA extension short video
descriptors in do_cea_modes(), add only one variant there. Once all
the EDID has been fully probed, do a loop over the entire probed mode
list, during which we add the other variants for all modes that match
CEA modes. This allows us to match modes that didn't come via the CEA
short video descriptors. For example one Samsung TV here doesn't have
the 640x480-60 mode as a SVD, but instead it's specified via a detailed
timing descriptor.

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Dave Airlie <airlied@redhat.com>
drivers/gpu/drm/drm_edid.c

index 9e62bbedb5ad45faa2b7193fad0bd8886ee76b00..e8d17eebde7164bf61885c0b8a26b550cabbc923 100644 (file)
@@ -2321,6 +2321,31 @@ u8 *drm_find_cea_extension(struct edid *edid)
 }
 EXPORT_SYMBOL(drm_find_cea_extension);
 
+/*
+ * Calculate the alternate clock for the CEA mode
+ * (60Hz vs. 59.94Hz etc.)
+ */
+static unsigned int
+cea_mode_alternate_clock(const struct drm_display_mode *cea_mode)
+{
+       unsigned int clock = cea_mode->clock;
+
+       if (cea_mode->vrefresh % 6 != 0)
+               return clock;
+
+       /*
+        * edid_cea_modes contains the 59.94Hz
+        * variant for 240 and 480 line modes,
+        * and the 60Hz variant otherwise.
+        */
+       if (cea_mode->vdisplay == 240 || cea_mode->vdisplay == 480)
+               clock = clock * 1001 / 1000;
+       else
+               clock = DIV_ROUND_UP(clock * 1000, 1001);
+
+       return clock;
+}
+
 /**
  * drm_match_cea_mode - look for a CEA mode matching given mode
  * @to_match: display mode
@@ -2339,21 +2364,9 @@ u8 drm_match_cea_mode(const struct drm_display_mode *to_match)
                const struct drm_display_mode *cea_mode = &edid_cea_modes[mode];
                unsigned int clock1, clock2;
 
-               clock1 = clock2 = cea_mode->clock;
-
                /* Check both 60Hz and 59.94Hz */
-               if (cea_mode->vrefresh % 6 == 0) {
-                       /*
-                        * edid_cea_modes contains the 59.94Hz
-                        * variant for 240 and 480 line modes,
-                        * and the 60Hz variant otherwise.
-                        */
-                       if (cea_mode->vdisplay == 240 ||
-                           cea_mode->vdisplay == 480)
-                               clock1 = clock1 * 1001 / 1000;
-                       else
-                               clock2 = DIV_ROUND_UP(clock2 * 1000, 1001);
-               }
+               clock1 = cea_mode->clock;
+               clock2 = cea_mode_alternate_clock(cea_mode);
 
                if ((KHZ2PICOS(to_match->clock) == KHZ2PICOS(clock1) ||
                     KHZ2PICOS(to_match->clock) == KHZ2PICOS(clock2)) &&
@@ -2364,6 +2377,66 @@ u8 drm_match_cea_mode(const struct drm_display_mode *to_match)
 }
 EXPORT_SYMBOL(drm_match_cea_mode);
 
+static int
+add_alternate_cea_modes(struct drm_connector *connector, struct edid *edid)
+{
+       struct drm_device *dev = connector->dev;
+       struct drm_display_mode *mode, *tmp;
+       LIST_HEAD(list);
+       int modes = 0;
+
+       /* Don't add CEA modes if the CEA extension block is missing */
+       if (!drm_find_cea_extension(edid))
+               return 0;
+
+       /*
+        * Go through all probed modes and create a new mode
+        * with the alternate clock for certain CEA modes.
+        */
+       list_for_each_entry(mode, &connector->probed_modes, head) {
+               const struct drm_display_mode *cea_mode;
+               struct drm_display_mode *newmode;
+               u8 cea_mode_idx = drm_match_cea_mode(mode) - 1;
+               unsigned int clock1, clock2;
+
+               if (cea_mode_idx >= ARRAY_SIZE(edid_cea_modes))
+                       continue;
+
+               cea_mode = &edid_cea_modes[cea_mode_idx];
+
+               clock1 = cea_mode->clock;
+               clock2 = cea_mode_alternate_clock(cea_mode);
+
+               if (clock1 == clock2)
+                       continue;
+
+               if (mode->clock != clock1 && mode->clock != clock2)
+                       continue;
+
+               newmode = drm_mode_duplicate(dev, cea_mode);
+               if (!newmode)
+                       continue;
+
+               /*
+                * The current mode could be either variant. Make
+                * sure to pick the "other" clock for the new mode.
+                */
+               if (mode->clock != clock1)
+                       newmode->clock = clock1;
+               else
+                       newmode->clock = clock2;
+
+               list_add_tail(&newmode->head, &list);
+       }
+
+       list_for_each_entry_safe(mode, tmp, &list, head) {
+               list_del(&mode->head);
+               drm_mode_probed_add(connector, mode);
+               modes++;
+       }
+
+       return modes;
+}
 
 static int
 do_cea_modes (struct drm_connector *connector, u8 *db, u8 len)
@@ -2946,6 +3019,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
        if (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF)
                num_modes += add_inferred_modes(connector, edid);
        num_modes += add_cea_modes(connector, edid);
+       num_modes += add_alternate_cea_modes(connector, edid);
 
        if (quirks & (EDID_QUIRK_PREFER_LARGE_60 | EDID_QUIRK_PREFER_LARGE_75))
                edid_fixup_preferred(connector, quirks);