* (C) Copyright 2010
* Petr Stetiar <ynezz@true.cz>
*
- * See file CREDITS for list of people who contributed to this
- * project.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
- * MA 02111-1307 USA
+ * 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>
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)
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(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;
+ }
+
+ 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;
+}
+
/**
* Snip the tailing whitespace/return of a 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 */