+// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2012 The Chromium OS Authors.
*
* (C) Copyright 2010
*
- * SPDX-License-Identifier: GPL-2.0+
- *
* Contains stolen code from ddcprobe project which is:
*/
#include <edid.h>
#include <errno.h>
#include <fdtdec.h>
+#include <log.h>
#include <linux/ctype.h>
#include <linux/string.h>
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);
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",
va + vbl, vborder);
}
-int edid_get_timing(u8 *buf, int buf_size, struct display_timing *timing,
- int *panel_bits_per_colourp)
+/**
+ * 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)
{
- struct edid1_info *edid = (struct edid1_info *)buf;
- bool timing_done;
+ 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;
+}
+
+static bool edid_find_valid_timing(void *buf, int count,
+ struct display_timing *timing,
+ bool (*mode_valid)(void *priv,
+ const struct display_timing *timing),
+ void *mode_valid_priv)
+{
+ struct edid_detailed_timing *t = buf;
+ bool found = false;
int i;
+ for (i = 0; i < count && !found; i++, t++)
+ if (EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) != 0) {
+ decode_timing((u8 *)t, timing);
+ if (mode_valid)
+ found = mode_valid(mode_valid_priv,
+ timing);
+ else
+ found = true;
+ }
+
+ return found;
+}
+
+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 found;
+
if (buf_size < sizeof(*edid) || edid_check_info(edid)) {
debug("%s: Invalid buffer\n", __func__);
return -EINVAL;
}
+ if (!EDID1_INFO_VIDEO_INPUT_DIGITAL(*edid)) {
+ debug("%s: Not a digital display\n", __func__);
+ return -ENOSYS;
+ }
+
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;
+ /* Look for detailed timing in base EDID */
+ found = edid_find_valid_timing(edid->monitor_details.descriptor, 4,
+ timing, mode_valid, mode_valid_priv);
+
+ /* Look for detailed timing in CTA-861 Extension Block */
+ if (!found && edid->extension_flag && buf_size >= EDID_EXT_SIZE) {
+ struct edid_cea861_info *info =
+ (struct edid_cea861_info *)(buf + sizeof(*edid));
- desc = &edid->monitor_details.descriptor[i];
- if (desc->zero_flag_1 != 0) {
- decode_timing((u8 *)desc, timing);
- timing_done = true;
- break;
+ if (info->extension_tag == EDID_CEA861_EXTENSION_TAG) {
+ int count = EDID_CEA861_DTD_COUNT(*info);
+ int offset = info->dtd_offset;
+ int size = count * sizeof(struct edid_detailed_timing);
+
+ if (offset >= 4 && offset + size < EDID_SIZE)
+ found = edid_find_valid_timing(
+ (u8 *)info + offset, count, timing,
+ mode_valid, mode_valid_priv);
}
}
- if (!timing_done)
+
+ if (!found)
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);
((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.
*
* @param string The string to be snipped
- * @return the snipped string
+ * Return: the snipped string
*/
static char *snip(char *string)
{
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 */