Merge branch 'master' of git://git.denx.de/u-boot
[oweals/u-boot.git] / common / edid.c
index e66108f24a482b61ae99febd321418e38fe275c3..f99f42dc406743495b2317b270f05f7623a2f26b 100644 (file)
@@ -1,17 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * Copyright (c) 2012 The Chromium OS Authors.
  *
  * (C) Copyright 2010
  * Petr Stetiar <ynezz@true.cz>
  *
- * SPDX-License-Identifier:    GPL-2.0+
- *
  * Contains stolen code from ddcprobe project which is:
  * Copyright (C) Nalin Dahyabhai <bigfun@pobox.com>
  */
 
 #include <common.h>
 #include <edid.h>
+#include <errno.h>
+#include <fdtdec.h>
 #include <linux/ctype.h>
 #include <linux/string.h>
 
@@ -29,6 +30,17 @@ int edid_check_info(struct edid1_info *edid_info)
        return 0;
 }
 
+int edid_check_checksum(u8 *edid_block)
+{
+       u8 checksum = 0;
+       int i;
+
+       for (i = 0; i < 128; i++)
+               checksum += edid_block[i];
+
+       return (checksum == 0) ? 0 : -EINVAL;
+}
+
 int edid_get_ranges(struct edid1_info *edid, unsigned int *hmin,
                    unsigned int *hmax, unsigned int *vmin,
                    unsigned int *vmax)
@@ -53,6 +65,184 @@ int edid_get_ranges(struct edid1_info *edid, unsigned int *hmin,
        return -1;
 }
 
+/* Set all parts of a timing entry to the same value */
+static void set_entry(struct timing_entry *entry, u32 value)
+{
+       entry->min = value;
+       entry->typ = value;
+       entry->max = value;
+}
+
+/**
+ * decode_timing() - Decoding an 18-byte detailed timing record
+ *
+ * @buf:       Pointer to EDID detailed timing record
+ * @timing:    Place to put timing
+ */
+static void decode_timing(u8 *buf, struct display_timing *timing)
+{
+       uint x_mm, y_mm;
+       unsigned int ha, hbl, hso, hspw, hborder;
+       unsigned int va, vbl, vso, vspw, vborder;
+       struct edid_detailed_timing *t = (struct edid_detailed_timing *)buf;
+
+       /* Edid contains pixel clock in terms of 10KHz */
+       set_entry(&timing->pixelclock, (buf[0] + (buf[1] << 8)) * 10000);
+       x_mm = (buf[12] + ((buf[14] & 0xf0) << 4));
+       y_mm = (buf[13] + ((buf[14] & 0x0f) << 8));
+       ha = (buf[2] + ((buf[4] & 0xf0) << 4));
+       hbl = (buf[3] + ((buf[4] & 0x0f) << 8));
+       hso = (buf[8] + ((buf[11] & 0xc0) << 2));
+       hspw = (buf[9] + ((buf[11] & 0x30) << 4));
+       hborder = buf[15];
+       va = (buf[5] + ((buf[7] & 0xf0) << 4));
+       vbl = (buf[6] + ((buf[7] & 0x0f) << 8));
+       vso = ((buf[10] >> 4) + ((buf[11] & 0x0c) << 2));
+       vspw = ((buf[10] & 0x0f) + ((buf[11] & 0x03) << 4));
+       vborder = buf[16];
+
+       set_entry(&timing->hactive, ha);
+       set_entry(&timing->hfront_porch, hso);
+       set_entry(&timing->hback_porch, hbl - hso - hspw);
+       set_entry(&timing->hsync_len, hspw);
+
+       set_entry(&timing->vactive, va);
+       set_entry(&timing->vfront_porch, vso);
+       set_entry(&timing->vback_porch, vbl - vso - vspw);
+       set_entry(&timing->vsync_len, vspw);
+
+       timing->flags = 0;
+       if (EDID_DETAILED_TIMING_FLAG_HSYNC_POLARITY(*t))
+               timing->flags |= DISPLAY_FLAGS_HSYNC_HIGH;
+       else
+               timing->flags |= DISPLAY_FLAGS_HSYNC_LOW;
+       if (EDID_DETAILED_TIMING_FLAG_VSYNC_POLARITY(*t))
+               timing->flags |= DISPLAY_FLAGS_VSYNC_HIGH;
+       else
+               timing->flags |= DISPLAY_FLAGS_VSYNC_LOW;
+
+       if (EDID_DETAILED_TIMING_FLAG_INTERLACED(*t))
+               timing->flags = DISPLAY_FLAGS_INTERLACED;
+
+       debug("Detailed mode clock %u Hz, %d mm x %d mm\n"
+             "               %04x %04x %04x %04x hborder %x\n"
+             "               %04x %04x %04x %04x vborder %x\n",
+             timing->pixelclock.typ,
+             x_mm, y_mm,
+             ha, ha + hso, ha + hso + hspw,
+             ha + hbl, hborder,
+             va, va + vso, va + vso + vspw,
+             va + vbl, vborder);
+}
+
+/**
+ * Check if HDMI vendor specific data block is present in CEA block
+ * @param info CEA extension block
+ * @return true if block is found
+ */
+static bool cea_is_hdmi_vsdb_present(struct edid_cea861_info *info)
+{
+       u8 end, i = 0;
+
+       /* check for end of data block */
+       end = info->dtd_offset;
+       if (end == 0)
+               end = sizeof(info->data);
+       if (end < 4 || end > sizeof(info->data))
+               return false;
+       end -= 4;
+
+       while (i < end) {
+               /* Look for vendor specific data block of appropriate size */
+               if ((EDID_CEA861_DB_TYPE(*info, i) == EDID_CEA861_DB_VENDOR) &&
+                   (EDID_CEA861_DB_LEN(*info, i) >= 5)) {
+                       u8 *db = &info->data[i + 1];
+                       u32 oui = db[0] | (db[1] << 8) | (db[2] << 16);
+
+                       if (oui == HDMI_IEEE_OUI)
+                               return true;
+               }
+               i += EDID_CEA861_DB_LEN(*info, i) + 1;
+       }
+
+       return false;
+}
+
+int edid_get_timing_validate(u8 *buf, int buf_size,
+                            struct display_timing *timing,
+                            int *panel_bits_per_colourp,
+                            bool (*mode_valid)(void *priv,
+                                       const struct display_timing *timing),
+                            void *mode_valid_priv)
+{
+       struct edid1_info *edid = (struct edid1_info *)buf;
+       bool timing_done;
+       int i;
+
+       if (buf_size < sizeof(*edid) || edid_check_info(edid)) {
+               debug("%s: Invalid buffer\n", __func__);
+               return -EINVAL;
+       }
+
+       if (!EDID1_INFO_FEATURE_PREFERRED_TIMING_MODE(*edid)) {
+               debug("%s: No preferred timing\n", __func__);
+               return -ENOENT;
+       }
+
+       /* Look for detailed timing */
+       timing_done = false;
+       for (i = 0; i < 4; i++) {
+               struct edid_monitor_descriptor *desc;
+
+               desc = &edid->monitor_details.descriptor[i];
+               if (desc->zero_flag_1 != 0) {
+                       decode_timing((u8 *)desc, timing);
+                       if (mode_valid)
+                               timing_done = mode_valid(mode_valid_priv,
+                                                        timing);
+                       else
+                               timing_done = true;
+
+                       if (timing_done)
+                               break;
+               }
+       }
+       if (!timing_done)
+               return -EINVAL;
+
+       if (!EDID1_INFO_VIDEO_INPUT_DIGITAL(*edid)) {
+               debug("%s: Not a digital display\n", __func__);
+               return -ENOSYS;
+       }
+       if (edid->version != 1 || edid->revision < 4) {
+               debug("%s: EDID version %d.%d does not have required info\n",
+                     __func__, edid->version, edid->revision);
+               *panel_bits_per_colourp = -1;
+       } else  {
+               *panel_bits_per_colourp =
+                       ((edid->video_input_definition & 0x70) >> 3) + 4;
+       }
+
+       timing->hdmi_monitor = false;
+       if (edid->extension_flag && (buf_size >= EDID_EXT_SIZE)) {
+               struct edid_cea861_info *info =
+                       (struct edid_cea861_info *)(buf + sizeof(*edid));
+
+               if (info->extension_tag == EDID_CEA861_EXTENSION_TAG)
+                       timing->hdmi_monitor = cea_is_hdmi_vsdb_present(info);
+       }
+
+       return 0;
+}
+
+int edid_get_timing(u8 *buf, int buf_size, struct display_timing *timing,
+                   int *panel_bits_per_colourp)
+{
+       return edid_get_timing_validate(buf, buf_size, timing,
+                                       panel_bits_per_colourp, NULL, NULL);
+}
+
+
 /**
  * Snip the tailing whitespace/return of a string.
  *
@@ -122,7 +312,7 @@ static void edid_print_dtd(struct edid_monitor_descriptor *monitor,
 
                h_total = h_active + h_blanking;
                v_total = v_active + v_blanking;
-               if (v_total * h_total)
+               if (v_total > 0 && h_total > 0)
                        vfreq = pixclock / (v_total * h_total);
                else
                        vfreq = 1; /* Error case */