drm/edid: Fix up clock for CEA/HDMI modes specified via detailed timings
authorVille Syrjälä <ville.syrjala@linux.intel.com>
Thu, 8 Oct 2015 08:43:32 +0000 (11:43 +0300)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Tue, 20 Oct 2015 10:01:28 +0000 (12:01 +0200)
EDID detailed timings have a resolution of 10kHz for the pixel clock, so
they can't represent certain CEA/HDMI modes accurately. If we see a mode
coming in via detailed timings which otherwise matches one of the
CEA/HDMI modes except the clock is just a bit off, let's assume that the
intention was for that mode to be one of the CEA/HDMI modes and go ahead
and fix up the clock to match the CEA/HDMI spec exactly (well, as close
as we can get with the 1 kHz resolution we use).

This should help code that's looking for an exact clock match (eg. i915
audio N/CTS setup).

Cc: Adam Jackson <ajax@redhat.com>
Cc: Clint Taylor <clinton.a.taylor@intel.com>
Cc: Libin Yang <libin.yang@intel.com>
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: Adam Jackson <ajax@redhat.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
drivers/gpu/drm/drm_edid.c

index d895556..977915c 100644 (file)
@@ -2418,6 +2418,8 @@ add_cvt_modes(struct drm_connector *connector, struct edid *edid)
        return closure.modes;
 }
 
+static void fixup_detailed_cea_mode_clock(struct drm_display_mode *mode);
+
 static void
 do_detailed_mode(struct detailed_timing *timing, void *c)
 {
@@ -2434,6 +2436,13 @@ do_detailed_mode(struct detailed_timing *timing, void *c)
                if (closure->preferred)
                        newmode->type |= DRM_MODE_TYPE_PREFERRED;
 
+               /*
+                * Detailed modes are limited to 10kHz pixel clock resolution,
+                * so fix up anything that looks like CEA/HDMI mode, but the clock
+                * is just slightly off.
+                */
+               fixup_detailed_cea_mode_clock(newmode);
+
                drm_mode_probed_add(closure->connector, newmode);
                closure->modes++;
                closure->preferred = 0;
@@ -3103,6 +3112,45 @@ add_cea_modes(struct drm_connector *connector, struct edid *edid)
        return modes;
 }
 
+static void fixup_detailed_cea_mode_clock(struct drm_display_mode *mode)
+{
+       const struct drm_display_mode *cea_mode;
+       int clock1, clock2, clock;
+       u8 mode_idx;
+       const char *type;
+
+       mode_idx = drm_match_cea_mode(mode) - 1;
+       if (mode_idx < ARRAY_SIZE(edid_cea_modes)) {
+               type = "CEA";
+               cea_mode = &edid_cea_modes[mode_idx];
+               clock1 = cea_mode->clock;
+               clock2 = cea_mode_alternate_clock(cea_mode);
+       } else {
+               mode_idx = drm_match_hdmi_mode(mode) - 1;
+               if (mode_idx < ARRAY_SIZE(edid_4k_modes)) {
+                       type = "HDMI";
+                       cea_mode = &edid_4k_modes[mode_idx];
+                       clock1 = cea_mode->clock;
+                       clock2 = hdmi_mode_alternate_clock(cea_mode);
+               } else {
+                       return;
+               }
+       }
+
+       /* pick whichever is closest */
+       if (abs(mode->clock - clock1) < abs(mode->clock - clock2))
+               clock = clock1;
+       else
+               clock = clock2;
+
+       if (mode->clock == clock)
+               return;
+
+       DRM_DEBUG("detailed mode matches %s VIC %d, adjusting clock %d -> %d\n",
+                 type, mode_idx + 1, mode->clock, clock);
+       mode->clock = clock;
+}
+
 static void
 parse_hdmi_vsdb(struct drm_connector *connector, const u8 *db)
 {