Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/livep...
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / drm_edid.c
index d895556be4f0339bddcc8beab09f8457a3842185..d5d2c03fd1369b904e31af5184c05e4b9da0f82d 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;
@@ -2529,9 +2538,9 @@ cea_mode_alternate_clock(const struct drm_display_mode *cea_mode)
         * and the 60Hz variant otherwise.
         */
        if (cea_mode->vdisplay == 240 || cea_mode->vdisplay == 480)
-               clock = clock * 1001 / 1000;
+               clock = DIV_ROUND_CLOSEST(clock * 1001, 1000);
        else
-               clock = DIV_ROUND_UP(clock * 1000, 1001);
+               clock = DIV_ROUND_CLOSEST(clock * 1000, 1001);
 
        return clock;
 }
@@ -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)
 {