UPSTREAM: drm/edid: Make the detailed timing CEA/HDMI mode fixup accept up to 5kHz...
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / drm_edid.c
index d5d2c03fd1369b904e31af5184c05e4b9da0f82d..2d6932c9a351223b26aec2a0cc0edbb3dc2a7cfb 100644 (file)
@@ -73,6 +73,8 @@
 #define EDID_QUIRK_FORCE_8BPC                  (1 << 8)
 /* Force 12bpc */
 #define EDID_QUIRK_FORCE_12BPC                 (1 << 9)
+/* Force 6bpc */
+#define EDID_QUIRK_FORCE_6BPC                  (1 << 10)
 
 struct detailed_mode_closure {
        struct drm_connector *connector;
@@ -99,6 +101,9 @@ static struct edid_quirk {
        /* Unknown Acer */
        { "ACR", 2423, EDID_QUIRK_FIRST_DETAILED_PREFERRED },
 
+       /* AEO model 0 reports 8 bpc, but is a 6 bpc panel */
+       { "AEO", 0, EDID_QUIRK_FORCE_6BPC },
+
        /* Belinea 10 15 55 */
        { "MAX", 1516, EDID_QUIRK_PREFER_LARGE_60 },
        { "MAX", 0x77e, EDID_QUIRK_PREFER_LARGE_60 },
@@ -1014,6 +1019,12 @@ static const struct drm_display_mode edid_4k_modes[] = {
                   2160, 2168, 2178, 2250, 0,
                   DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
          .vrefresh = 24, },
+       /* 5 - 3840x2160@60Hz */
+       { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000,
+                  3840, 4016, 4104, 4400, 0,
+                  2160, 2168, 2178, 2250, 0,
+                  DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+         .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, },
 };
 
 /*** DDC fetch and block validation ***/
@@ -2545,6 +2556,33 @@ cea_mode_alternate_clock(const struct drm_display_mode *cea_mode)
        return clock;
 }
 
+static u8 drm_match_cea_mode_clock_tolerance(const struct drm_display_mode *to_match,
+                                            unsigned int clock_tolerance)
+{
+       u8 mode;
+
+       if (!to_match->clock)
+               return 0;
+
+       for (mode = 0; mode < ARRAY_SIZE(edid_cea_modes); mode++) {
+               const struct drm_display_mode *cea_mode = &edid_cea_modes[mode];
+               unsigned int clock1, clock2;
+
+               /* Check both 60Hz and 59.94Hz */
+               clock1 = cea_mode->clock;
+               clock2 = cea_mode_alternate_clock(cea_mode);
+
+               if (abs(to_match->clock - clock1) > clock_tolerance &&
+                   abs(to_match->clock - clock2) > clock_tolerance)
+                       continue;
+
+               if (drm_mode_equal_no_clocks(to_match, cea_mode))
+                       return mode + 1;
+       }
+
+       return 0;
+}
+
 /**
  * drm_match_cea_mode - look for a CEA mode matching given mode
  * @to_match: display mode
@@ -2609,6 +2647,33 @@ hdmi_mode_alternate_clock(const struct drm_display_mode *hdmi_mode)
        return cea_mode_alternate_clock(hdmi_mode);
 }
 
+static u8 drm_match_hdmi_mode_clock_tolerance(const struct drm_display_mode *to_match,
+                                             unsigned int clock_tolerance)
+{
+       u8 mode;
+
+       if (!to_match->clock)
+               return 0;
+
+       for (mode = 0; mode < ARRAY_SIZE(edid_4k_modes); mode++) {
+               const struct drm_display_mode *hdmi_mode = &edid_4k_modes[mode];
+               unsigned int clock1, clock2;
+
+               /* Make sure to also match alternate clocks */
+               clock1 = hdmi_mode->clock;
+               clock2 = hdmi_mode_alternate_clock(hdmi_mode);
+
+               if (abs(to_match->clock - clock1) > clock_tolerance &&
+                   abs(to_match->clock - clock2) > clock_tolerance)
+                       continue;
+
+               if (drm_mode_equal_no_clocks(to_match, hdmi_mode))
+                       return mode + 1;
+       }
+
+       return 0;
+}
+
 /*
  * drm_match_hdmi_mode - look for a HDMI mode matching given mode
  * @to_match: display mode
@@ -3068,6 +3133,21 @@ static bool cea_db_is_hdmi_vsdb(const u8 *db)
        return hdmi_id == HDMI_IEEE_OUI;
 }
 
+static bool cea_db_is_hdmi_hf_vsdb(const u8 *db)
+{
+       int hdmi_id;
+
+       if (cea_db_tag(db) != VENDOR_BLOCK)
+               return false;
+
+       if (cea_db_payload_len(db) < 7)
+               return false;
+
+       hdmi_id = db[1] | (db[2] << 8) | (db[3] << 16);
+
+       return hdmi_id == HDMI_IEEE_OUI_HF;
+}
+
 #define for_each_cea_db(cea, i, start, end) \
        for ((i) = (start); (i) < (end) && (i) + cea_db_payload_len(&(cea)[(i)]) < (end); (i) += cea_db_payload_len(&(cea)[(i)]) + 1)
 
@@ -3119,14 +3199,18 @@ static void fixup_detailed_cea_mode_clock(struct drm_display_mode *mode)
        u8 mode_idx;
        const char *type;
 
-       mode_idx = drm_match_cea_mode(mode) - 1;
+       /*
+        * allow 5kHz clock difference either way to account for
+        * the 10kHz clock resolution limit of detailed timings.
+        */
+       mode_idx = drm_match_cea_mode_clock_tolerance(mode, 5) - 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;
+               mode_idx = drm_match_hdmi_mode_clock_tolerance(mode, 5) - 1;
                if (mode_idx < ARRAY_SIZE(edid_4k_modes)) {
                        type = "HDMI";
                        cea_mode = &edid_4k_modes[mode_idx];
@@ -3190,6 +3274,36 @@ parse_hdmi_vsdb(struct drm_connector *connector, const u8 *db)
                    connector->audio_latency[1]);
 }
 
+static void
+parse_hdmi_hf_vsdb(struct drm_connector *connector, const u8 *db)
+{
+       u8 len = cea_db_payload_len(db);
+
+       if (len < 7)
+               return;
+
+       if (db[4] != 1)
+               return; /* invalid version */
+
+       connector->max_tmds_char = db[5] * 5;
+       connector->scdc_present = db[6] & (1 << 7);
+       connector->rr_capable = db[6] & (1 << 6);
+       connector->flags_3d = db[6] & 0x7;
+       connector->lte_340mcsc_scramble = db[6] & (1 << 3);
+
+       DRM_DEBUG_KMS("HDMI v2: max TMDS clock %d, "
+                       "scdc %s, "
+                       "rr %s, "
+                       "3D flags 0x%x, "
+                       "scramble %s\n",
+                       connector->max_tmds_char,
+                       connector->scdc_present ? "available" : "not available",
+                       connector->rr_capable ? "capable" : "not capable",
+                       connector->flags_3d,
+                       connector->lte_340mcsc_scramble ?
+                               "supported" : "not supported");
+}
+
 static void
 monitor_name(struct detailed_timing *t, void *data)
 {
@@ -3269,6 +3383,9 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid)
                                /* HDMI Vendor-Specific Data Block */
                                if (cea_db_is_hdmi_vsdb(db))
                                        parse_hdmi_vsdb(connector, db);
+                               /* HDMI Forum Vendor-Specific Data Block */
+                               else if (cea_db_is_hdmi_hf_vsdb(db))
+                                       parse_hdmi_hf_vsdb(connector, db);
                                break;
                        default:
                                break;
@@ -3820,6 +3937,9 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
 
        drm_add_display_info(edid, &connector->display_info, connector);
 
+       if (quirks & EDID_QUIRK_FORCE_6BPC)
+               connector->display_info.bpc = 6;
+
        if (quirks & EDID_QUIRK_FORCE_8BPC)
                connector->display_info.bpc = 8;