edid: Add a function to read detailed monitor timings
authorSimon Glass <sjg@chromium.org>
Wed, 15 Apr 2015 03:03:37 +0000 (21:03 -0600)
committerTom Warren <twarren@nvidia.com>
Wed, 13 May 2015 16:24:10 +0000 (09:24 -0700)
For digital displays (such as EDP LCDs) we would like to read the EDID
information and use that to set display timings. Provide a function to do
this.

Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Tom Warren <twarren@nvidia.com>
common/edid.c
include/edid.h

index df797fcdd5bae35e1eaea9c2f6d3d631c0bff73a..e08e4209202e0ffa852ea75c2848ba0fc5a28238 100644 (file)
@@ -13,6 +13,7 @@
 #include <common.h>
 #include <edid.h>
 #include <errno.h>
+#include <fdtdec.h>
 #include <linux/ctype.h>
 #include <linux/string.h>
 
@@ -65,6 +66,110 @@ 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;
+
+       /* 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);
+
+       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);
+}
+
+int edid_get_timing(u8 *buf, int buf_size, struct display_timing *timing,
+                   int *panel_bits_per_colourp)
+{
+       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);
+                       timing_done = true;
+                       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;
+       }
+
+       return 0;
+}
+
 /**
  * Snip the tailing whitespace/return of a string.
  *
index 18ec1d5ab0cfb38345905b681e760daba60df112..88b4b7d85429df20b440b9e6c743d679df1b030d 100644 (file)
@@ -15,6 +15,9 @@
 
 #include <linux/types.h>
 
+/* Size of the EDID data */
+#define EDID_SIZE      128
+
 #define GET_BIT(_x, _pos) \
        (((_x) >> (_pos)) & 1)
 #define GET_BITS(_x, _pos_msb, _pos_lsb) \
@@ -287,4 +290,20 @@ int edid_get_ranges(struct edid1_info *edid, unsigned int *hmin,
                    unsigned int *hmax, unsigned int *vmin,
                    unsigned int *vmax);
 
+struct display_timing;
+
+/**
+ * edid_get_timing() - Get basic digital display parameters
+ *
+ * @param buf          Buffer containing EDID data
+ * @param buf_size     Size of buffer in bytes
+ * @param timing       Place to put preferring timing information
+ * @param panel_bits_per_colourp       Place to put the number of bits per
+ *                     colour supported by the panel. This will be set to
+ *                     -1 if not available
+ * @return 0 if timings are OK, -ve on error
+ */
+int edid_get_timing(u8 *buf, int buf_size, struct display_timing *timing,
+                   int *panel_bits_per_colourp);
+
 #endif /* __EDID_H_ */