// SPDX-License-Identifier: MIT /* * Copyright 2006-2012 Red Hat, Inc. * Copyright 2018-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * * Author: Adam Jackson * Maintainer: Hans Verkuil */ #include #include "edid-decode.h" static const char *bpc444[] = {"6", "8", "10", "12", "14", "16", NULL, NULL}; static const char *bpc4xx[] = {"8", "10", "12", "14", "16", NULL, NULL, NULL}; static const char *audiorates[] = {"32", "44.1", "48", NULL, NULL, NULL, NULL, NULL}; // misc functions static void print_flags(const char *label, unsigned char flag_byte, const char **flags, bool reverse = false) { if (!flag_byte) return; unsigned countflags = 0; printf("%s: ", label); for (unsigned i = 0; i < 8; i++) { if (flag_byte & (1 << (reverse ? 7 - i : i))) { if (countflags) printf(", "); if (flags[i]) printf("%s", flags[i]); else printf("Undefined (%u)", i); countflags++; } } printf("\n"); } void edid_state::check_displayid_datablock_revision(unsigned char hdr, unsigned char valid_flags, unsigned char rev) { unsigned char revision = hdr & 7; unsigned char flags = hdr & ~7 & ~valid_flags; if (revision != rev) warn("Unexpected revision (%u != %u).\n", revision, rev); if (flags) warn("Unexpected flags (0x%02x).\n", flags); } static bool check_displayid_datablock_length(const unsigned char *x, unsigned expectedlenmin = 0, unsigned expectedlenmax = 128 - 2 - 5 - 3, unsigned payloaddumpstart = 0) { unsigned char len = x[2]; if (expectedlenmin == expectedlenmax && len != expectedlenmax) fail("DisplayID payload length is different than expected (%d != %d).\n", len, expectedlenmax); else if (len > expectedlenmax) fail("DisplayID payload length is greater than expected (%d > %d).\n", len, expectedlenmax); else if (len < expectedlenmin) fail("DisplayID payload length is less than expected (%d < %d).\n", len, expectedlenmin); else return true; if (len > payloaddumpstart) hex_block(" ", x + 3 + payloaddumpstart, len - payloaddumpstart); return false; } // tag 0x00 and 0x20 void edid_state::parse_displayid_product_id(const unsigned char *x) { check_displayid_datablock_revision(x[1]); dispid.has_product_identification = true; if (dispid.version >= 0x20) { unsigned oui = (x[3] << 16) | (x[4] << 8) | x[5]; printf(" Vendor OUI %s\n", ouitohex(oui).c_str()); } else { printf(" Vendor ID: %c%c%c\n", x[3], x[4], x[5]); } printf(" Product Code: %u\n", x[6] | (x[7] << 8)); unsigned sn = x[8] | (x[9] << 8) | (x[10] << 16) | (x[11] << 24); if (sn) { if (hide_serial_numbers) printf(" Serial Number: ...\n"); else printf(" Serial Number: %u\n", sn); } unsigned week = x[12]; unsigned year = 2000 + x[13]; printf(" %s: %u", week == 0xff ? "Model Year" : "Year of Manufacture", year); if (week && week <= 0x36) printf(", Week %u", week); printf("\n"); if (x[14]) { char buf[256]; memcpy(buf, x + 15, x[14]); buf[x[14]] = 0; printf(" Product ID: %s\n", buf); } } // tag 0x01 static const char *feature_support_flags[] = { "De-interlacing", "Support ACP, ISRC1, or ISRC2packets", "Fixed pixel format", "Fixed timing", "Power management (DPM)", "Audio input override", "Separate audio inputs provided", "Audio support on video interface" }; static void print_flag_lines(const char *indent, const char *label, unsigned char flag_byte, const char **flags) { if (flag_byte) { printf("%s\n", label); for (int i = 0; i < 8; i++) if (flag_byte & (1 << i)) printf("%s%s\n", indent, flags[i]); } } void edid_state::parse_displayid_parameters(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 12, 12)) return; dispid.has_display_parameters = true; printf(" Image size: %.1f mm x %.1f mm\n", ((x[4] << 8) + x[3]) / 10.0, ((x[6] << 8) + x[5]) / 10.0); printf(" Pixels: %d x %d\n", (x[8] << 8) + x[7], (x[10] << 8) + x[9]); print_flag_lines(" ", " Feature support flags:", x[11], feature_support_flags); if (x[12] != 0xff) printf(" Gamma: %.2f\n", ((x[12] + 100.0) / 100.0)); printf(" Aspect ratio: %.2f\n", ((x[13] + 100.0) / 100.0)); printf(" Dynamic bpc native: %d\n", (x[14] & 0xf) + 1); printf(" Dynamic bpc overall: %d\n", ((x[14] >> 4) & 0xf) + 1); } // tag 0x02 static const char *std_colorspace_ids[] = { "sRGB", "BT.601", "BT.709", "Adobe RGB", "DCI-P3", "NTSC", "EBU", "Adobe Wide Gamut RGB", "DICOM" }; static double fp2d(unsigned short fp) { return fp / 4096.0; } void edid_state::parse_displayid_color_characteristics(const unsigned char *x) { check_displayid_datablock_revision(x[1], 0xf8, 1); unsigned cie_year = (x[1] & 0x80) ? 1976 : 1931; unsigned xfer_id = (x[1] >> 3) & 0x0f; unsigned num_whitepoints = x[3] & 0x0f; unsigned num_primaries = (x[3] >> 4) & 0x07; bool temporal_color = x[3] & 0x80; unsigned offset = 4; printf(" Uses %s color\n", temporal_color ? "temporal" : "spatial"); printf(" Uses %u CIE (x, y) coordinates\n", cie_year); if (xfer_id) { printf(" Associated with Transfer Characteristics Data Block with Identifier %u\n", xfer_id); if (!(dispid.preparsed_xfer_ids & (1 << xfer_id))) fail("Missing Transfer Characteristics Data Block with Identifier %u.\n", xfer_id); } if (!num_primaries) { printf(" Uses color space %s\n", x[4] >= ARRAY_SIZE(std_colorspace_ids) ? "Reserved" : std_colorspace_ids[x[4]]); offset++; } for (unsigned i = 0; i < num_primaries; i++) { unsigned idx = offset + 3 * i; printf(" Primary #%u: (%.4f, %.4f)\n", i, fp2d(x[idx] | ((x[idx + 1] & 0x0f) << 8)), fp2d(((x[idx + 1] & 0xf0) >> 4) | (x[idx + 2] << 4))); } offset += 3 * num_primaries; for (unsigned i = 0; i < num_whitepoints; i++) { unsigned idx = offset + 3 * i; printf(" White point #%u: (%.4f, %.4f)\n", i, fp2d(x[idx] | ((x[idx + 1] & 0x0f) << 8)), fp2d(((x[idx + 1] & 0xf0) >> 4) | (x[idx + 2] << 4))); } } // tag 0x03 and 0x22 void edid_state::parse_displayid_type_1_7_timing(const unsigned char *x, bool type7, unsigned block_rev, bool is_cta) { struct timings t = {}; unsigned hbl, vbl; std::string s("aspect "); dispid.has_type_1_7 = true; t.pixclk_khz = (type7 ? 1 : 10) * (1 + (x[0] + (x[1] << 8) + (x[2] << 16))); switch (x[3] & 0xf) { case 0: s += "1:1"; t.hratio = t.vratio = 1; break; case 1: s += "5:4"; t.hratio = 5; t.vratio = 4; break; case 2: s += "4:3"; t.hratio = 4; t.vratio = 3; break; case 3: s += "15:9"; t.hratio = 15; t.vratio = 9; break; case 4: s += "16:9"; t.hratio = 16; t.vratio = 9; break; case 5: s += "16:10"; t.hratio = 16; t.vratio = 10; break; case 6: s += "64:27"; t.hratio = 64; t.vratio = 27; break; case 7: s += "256:135"; t.hratio = 256; t.vratio = 135; break; default: s += "undefined"; if ((x[3] & 0xf) > (dispid.version <= 0x12 ? 7 : 8)) fail("Unknown aspect 0x%02x.\n", x[3] & 0xf); break; } switch ((x[3] >> 5) & 0x3) { case 0: s += ", no 3D stereo"; break; case 1: s += ", 3D stereo"; break; case 2: s += ", 3D stereo depends on user action"; break; case 3: s += ", reserved"; fail("Reserved stereo 0x03.\n"); break; } if (block_rev >= 2 && (x[3] & 0x80)) s += ", YCbCr 4:2:0"; t.hact = 1 + (x[4] | (x[5] << 8)); hbl = 1 + (x[6] | (x[7] << 8)); t.hfp = 1 + (x[8] | ((x[9] & 0x7f) << 8)); t.hsync = 1 + (x[10] | (x[11] << 8)); t.hbp = hbl - t.hfp - t.hsync; if ((x[9] >> 7) & 0x1) t.pos_pol_hsync = true; t.vact = 1 + (x[12] | (x[13] << 8)); vbl = 1 + (x[14] | (x[15] << 8)); t.vfp = 1 + (x[16] | ((x[17] & 0x7f) << 8)); t.vsync = 1 + (x[18] | (x[19] << 8)); t.vbp = vbl - t.vfp - t.vsync; if ((x[17] >> 7) & 0x1) t.pos_pol_vsync = true; if (x[3] & 0x10) { t.interlaced = true; t.vfp /= 2; t.vsync /= 2; t.vbp /= 2; } if (block_rev < 2 && (x[3] & 0x80)) { s += ", preferred"; dispid.preferred_timings.push_back(timings_ext(t, "DTD", s)); } print_timings(" ", &t, "DTD", s.c_str(), true); if (is_cta) { timings_ext te(t, "DTD", s); cta.vec_vtdbs.push_back(te); // Only use a T7VTDB if is cannot be expressed by a // DTD or a T10VTDB. if (t.hact <= 4095 && t.vact <= 4095 && t.pixclk_khz <= 655360 && !(x[3] & 0xe0)) { fail("This T7VTDB can be represented as an 18-byte DTD.\n"); return; } unsigned htot = t.hact + t.hfp + t.hsync + t.hbp; unsigned vtot = t.vact + t.vfp + t.vsync + t.vbp; unsigned refresh = (t.pixclk_khz * 1000ULL) / (htot * vtot); for (unsigned rb = RB_NONE; rb <= RB_CVT_V3; rb++) { timings cvt_t = calc_cvt_mode(t.hact, t.vact, refresh, rb); if (match_timings(t, cvt_t)) { fail("This T7VTDB can be represented as a T10VTDB.\n"); return; } } timings cvt_t = calc_cvt_mode(t.hact, t.vact, refresh, RB_CVT_V3, false, false, true); if (match_timings(t, cvt_t)) fail("This T7VTDB can be represented as a T10VTDB.\n"); } } // tag 0x04 void edid_state::parse_displayid_type_2_timing(const unsigned char *x) { struct timings t = {}; unsigned hbl, vbl; std::string s("aspect "); t.pixclk_khz = 10 * (1 + (x[0] + (x[1] << 8) + (x[2] << 16))); t.hact = 8 + 8 * (x[4] | ((x[5] & 0x01) << 8)); hbl = 8 + 8 * ((x[5] & 0xfe) >> 1); t.hfp = 8 + 8 * ((x[6] & 0xf0) >> 4); t.hsync = 8 + 8 * (x[6] & 0xf); t.hbp = hbl - t.hfp - t.hsync; if ((x[3] >> 3) & 0x1) t.pos_pol_hsync = true; t.vact = 1 + (x[7] | ((x[8] & 0xf) << 8)); vbl = 1 + x[9]; t.vfp = 1 + (x[10] >> 4); t.vsync = 1 + (x[10] & 0xf); t.vbp = vbl - t.vfp - t.vsync; if ((x[17] >> 2) & 0x1) t.pos_pol_vsync = true; if (x[3] & 0x10) { t.interlaced = true; t.vfp /= 2; t.vsync /= 2; t.vbp /= 2; } calc_ratio(&t); s += std::to_string(t.hratio) + ":" + std::to_string(t.vratio); switch ((x[3] >> 5) & 0x3) { case 0: s += ", no 3D stereo"; break; case 1: s += ", 3D stereo"; break; case 2: s += ", 3D stereo depends on user action"; break; case 3: s += ", reserved"; fail("Reserved stereo 0x03.\n"); break; } if (x[3] & 0x80) { s += ", preferred"; dispid.preferred_timings.push_back(timings_ext(t, "DTD", s)); } print_timings(" ", &t, "DTD", s.c_str(), true); } // tag 0x05 void edid_state::parse_displayid_type_3_timing(const unsigned char *x) { struct timings t = {}; std::string s("aspect "); switch (x[0] & 0xf) { case 0: s += "1:1"; t.hratio = t.vratio = 1; break; case 1: s += "5:4"; t.hratio = 5; t.vratio = 4; break; case 2: s += "4:3"; t.hratio = 4; t.vratio = 3; break; case 3: s += "15:9"; t.hratio = 15; t.vratio = 9; break; case 4: s += "16:9"; t.hratio = 16; t.vratio = 9; break; case 5: s += "16:10"; t.hratio = 16; t.vratio = 10; break; case 6: s += "64:27"; t.hratio = 64; t.vratio = 27; break; case 7: s += "256:135"; t.hratio = 256; t.vratio = 135; break; default: s += "undefined"; if ((x[3] & 0xf) > (dispid.version <= 0x12 ? 7 : 8)) fail("Unknown aspect 0x%02x.\n", x[3] & 0xf); break; } t.rb = ((x[0] & 0x70) >> 4) == 1 ? RB_CVT_V1 : RB_NONE; t.hact = 8 + 8 * x[1]; t.vact = t.hact * t.vratio / t.hratio; edid_cvt_mode(1 + (x[2] & 0x7f), t); if (x[0] & 0x80) { s += ", preferred"; dispid.preferred_timings.push_back(timings_ext(t, "CVT", s)); } print_timings(" ", &t, "CVT", s.c_str()); } // tag 0x06 and 0x23 void edid_state::parse_displayid_type_4_8_timing(unsigned char type, unsigned short id, bool is_cta) { const struct timings *t = NULL; char type_name[16]; switch (type) { case 0: t = find_dmt_id(id); sprintf(type_name, "DMT 0x%02x", id); break; case 1: t = find_vic_id(id); sprintf(type_name, "VIC %3u", id); break; case 2: t = find_hdmi_vic_id(id); sprintf(type_name, "HDMI VIC %u", id); break; default: break; } if (t) print_timings(" ", t, type_name); if (t && is_cta && !cta.t8vtdb.is_valid()) { timings_ext te(*t, type_name, ""); cta.t8vtdb = te; } } // tag 0x09 void edid_state::parse_displayid_video_timing_range_limits(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 15, 15)) return; printf(" Pixel Clock: %.3f-%.3f MHz\n", (double)((x[3] | (x[4] << 8) | (x[5] << 16)) + 1) / 100.0, (double)((x[6] | (x[7] << 8) | (x[8] << 16)) + 1) / 100.0); printf(" Horizontal Frequency: %u-%u kHz\n", x[9], x[10]); printf(" Minimum Horizontal Blanking: %u pixels\n", x[11] | (x[12] << 8)); printf(" Vertical Refresh: %u-%u Hz\n", x[13], x[14]); printf(" Minimum Vertical Blanking: %u lines\n", x[15] | (x[16] << 8)); if (x[17] & 0x80) printf(" Supports Interlaced\n"); if (x[17] & 0x40) printf(" Supports CVT\n"); if (x[17] & 0x20) printf(" Supports CVT Reduced Blanking\n"); if (x[17] & 0x10) printf(" Discrete frequency display device\n"); } // tag 0x0a and 0x0b void edid_state::parse_displayid_string(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (check_displayid_datablock_length(x)) printf(" Text: '%s'\n", extract_string(x + 3, x[2])); } // tag 0x0c void edid_state::parse_displayid_display_device(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 13, 13)) return; printf(" Display Device Technology: "); switch (x[3]) { case 0x00: printf("Monochrome CRT\n"); break; case 0x01: printf("Standard tricolor CRT\n"); break; case 0x02: printf("Other/undefined CRT\n"); break; case 0x10: printf("Passive matrix TN\n"); break; case 0x11: printf("Passive matrix cholesteric LC\n"); break; case 0x12: printf("Passive matrix ferroelectric LC\n"); break; case 0x13: printf("Other passive matrix LC type\n"); break; case 0x14: printf("Active-matrix TN\n"); break; case 0x15: printf("Active-matrix IPS (all types)\n"); break; case 0x16: printf("Active-matrix VA (all types)\n"); break; case 0x17: printf("Active-matrix OCB\n"); break; case 0x18: printf("Active-matrix ferroelectric\n"); break; case 0x1f: printf("Other LC type\n"); break; case 0x20: printf("DC plasma\n"); break; case 0x21: printf("AC plasma\n"); break; } switch (x[3] & 0xf0) { case 0x30: printf("Electroluminescent, except OEL/OLED\n"); break; case 0x40: printf("Inorganic LED\n"); break; case 0x50: printf("Organic LED/OEL\n"); break; case 0x60: printf("FED or sim. \"cold-cathode,\" phosphor-based types\n"); break; case 0x70: printf("Electrophoretic\n"); break; case 0x80: printf("Electrochromic\n"); break; case 0x90: printf("Electromechanical\n"); break; case 0xa0: printf("Electrowetting\n"); break; case 0xf0: printf("Other type not defined here\n"); break; } printf(" Display operating mode: "); switch (x[4] >> 4) { case 0x00: printf("Direct-view reflective, ambient light\n"); break; case 0x01: printf("Direct-view reflective, ambient light, also has light source\n"); break; case 0x02: printf("Direct-view reflective, uses light source\n"); break; case 0x03: printf("Direct-view transmissive, ambient light\n"); break; case 0x04: printf("Direct-view transmissive, ambient light, also has light source\n"); break; case 0x05: printf("Direct-view transmissive, uses light source\n"); break; case 0x06: printf("Direct-view emissive\n"); break; case 0x07: printf("Direct-view transflective, backlight off by default\n"); break; case 0x08: printf("Direct-view transflective, backlight on by default\n"); break; case 0x09: printf("Transparent display, ambient light\n"); break; case 0x0a: printf("Transparent emissive display\n"); break; case 0x0b: printf("Projection device using reflective light modulator\n"); break; case 0x0c: printf("Projection device using transmissive light modulator\n"); break; case 0x0d: printf("Projection device using emissive image transducer\n"); break; default: printf("Reserved\n"); break; } if (x[4] & 0x08) printf(" The backlight may be switched on and off\n"); if (x[4] & 0x04) printf(" The backlight's intensity can be controlled\n"); unsigned w = x[5] | (x[6] << 8); unsigned h = x[7] | (x[8] << 8); if (w && h) { printf(" Display native pixel format: %ux%u\n", w + 1, h + 1); dispid.native_width = w + 1; dispid.native_height = h + 1; } else if (w || h) { fail("Invalid Native Pixel Format %ux%u.\n", w, h); } printf(" Aspect ratio and orientation:\n"); printf(" Aspect Ratio: %.2f\n", (100 + x[9]) / 100.0); unsigned char v = x[0x0a]; printf(" Default Orientation: "); switch ((v & 0xc0) >> 6) { case 0x00: printf("Landscape\n"); break; case 0x01: printf("Portrait\n"); break; case 0x02: printf("Not Fixed\n"); break; case 0x03: printf("Undefined\n"); break; } printf(" Rotation Capability: "); switch ((v & 0x30) >> 4) { case 0x00: printf("None\n"); break; case 0x01: printf("Can rotate 90 degrees clockwise\n"); break; case 0x02: printf("Can rotate 90 degrees counterclockwise\n"); break; case 0x03: printf("Can rotate 90 degrees in either direction)\n"); break; } printf(" Zero Pixel Location: "); switch ((v & 0x0c) >> 2) { case 0x00: printf("Upper Left\n"); break; case 0x01: printf("Upper Right\n"); break; case 0x02: printf("Lower Left\n"); break; case 0x03: printf("Lower Right\n"); break; } printf(" Scan Direction: "); switch (v & 0x03) { case 0x00: printf("Not defined\n"); break; case 0x01: printf("Fast Scan is on the Major (Long) Axis and Slow Scan is on the Minor Axis\n"); break; case 0x02: printf("Fast Scan is on the Minor (Short) Axis and Slow Scan is on the Major Axis\n"); break; case 0x03: printf("Reserved\n"); fail("Scan Direction used the reserved value 0x03.\n"); break; } printf(" Sub-pixel layout/configuration/shape: "); switch (x[0x0b]) { case 0x00: printf("Not defined\n"); break; case 0x01: printf("RGB vertical stripes\n"); break; case 0x02: printf("RGB horizontal stripes\n"); break; case 0x03: printf("Vertical stripes using primary order\n"); break; case 0x04: printf("Horizontal stripes using primary order\n"); break; case 0x05: printf("Quad sub-pixels, red at top left\n"); break; case 0x06: printf("Quad sub-pixels, red at bottom left\n"); break; case 0x07: printf("Delta (triad) RGB sub-pixels\n"); break; case 0x08: printf("Mosaic\n"); break; case 0x09: printf("Quad sub-pixels, RGB + 1 additional color\n"); break; case 0x0a: printf("Five sub-pixels, RGB + 2 additional colors\n"); break; case 0x0b: printf("Six sub-pixels, RGB + 3 additional colors\n"); break; case 0x0c: printf("Clairvoyante, Inc. PenTile Matrix (tm) layout\n"); break; default: printf("Reserved\n"); break; } printf(" Horizontal and vertical dot/pixel pitch: %.2fx%.2f mm\n", (double)(x[0x0c]) / 100.0, (double)(x[0x0d]) / 100.0); printf(" Color bit depth: %u\n", x[0x0e] & 0x0f); v = x[0x0f]; printf(" Response time for %s transition: %u ms\n", (v & 0x80) ? "white-to-black" : "black-to-white", v & 0x7f); } // tag 0x0d void edid_state::parse_displayid_intf_power_sequencing(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 6, 6)) return; printf(" Power Sequence T1 Range: %.1f-%u.0 ms\n", (x[3] >> 4) / 10.0, (x[3] & 0xf) * 2); printf(" Power Sequence T2 Range: 0.0-%u.0 ms\n", (x[4] & 0x3f) * 2); printf(" Power Sequence T3 Range: 0.0-%u.0 ms\n", (x[5] & 0x3f) * 2); printf(" Power Sequence T4 Min: %u.0 ms\n", (x[6] & 0x7f) * 10); printf(" Power Sequence T5 Min: %u.0 ms\n", (x[7] & 0x3f) * 10); printf(" Power Sequence T6 Min: %u.0 ms\n", (x[8] & 0x3f) * 10); } // tag 0x0e void edid_state::parse_displayid_transfer_characteristics(const unsigned char *x) { check_displayid_datablock_revision(x[1], 0xf0, 1); unsigned xfer_id = x[1] >> 4; bool first_is_white = x[3] & 0x80; bool four_param = x[3] & 0x20; if (xfer_id) { printf(" Transfer Characteristics Data Block Identifier: %u\n", xfer_id); if (!(dispid.preparsed_color_ids & (1 << xfer_id))) fail("Missing Color Characteristics Data Block using Identifier %u.\n", xfer_id); } if (first_is_white) printf(" The first curve is the 'white' transfer characteristic\n"); if (x[3] & 0x40) printf(" Individual response curves\n"); unsigned offset = 4; unsigned len = x[2] - 1; for (unsigned i = 0; len; i++) { if ((x[3] & 0x80) && !i) printf(" White curve: "); else printf(" Response curve #%u:", i - first_is_white); unsigned samples = x[offset]; if (four_param) { if (samples != 5) fail("Expected 5 samples.\n"); printf(" A0=%u A1=%u A2=%u A3=%u Gamma=%.2f\n", x[offset + 1], x[offset + 2], x[offset + 3], x[offset + 4], (double)(x[offset + 5] + 100.0) / 100.0); samples++; } else { double sum = 0; // The spec is not very clear about the number of samples: // should this be interpreted as the actual number of // samples stored in this Data Block, or as the number of // samples in the curve, but where the last sample is not // actually stored since it is always 0x3ff. // // The ATP Manager interprets this as the latter, so that's // what we implement here. for (unsigned j = offset + 1; j < offset + samples; j++) { sum += x[j]; printf(" %.2f", sum * 100.0 / 1023.0); } printf(" 100.00\n"); } offset += samples; len -= samples; } } // tag 0x0f void edid_state::parse_displayid_display_intf(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 10, 10)) return; dispid.has_display_interface_features = true; printf(" Interface Type: "); switch (x[3] >> 4) { case 0x00: switch (x[3] & 0xf) { case 0x00: printf("Analog 15HD/VGA\n"); break; case 0x01: printf("Analog VESA NAVI-V (15HD)\n"); break; case 0x02: printf("Analog VESA NAVI-D\n"); break; default: printf("Reserved\n"); break; } break; case 0x01: printf("LVDS\n"); break; case 0x02: printf("TMDS\n"); break; case 0x03: printf("RSDS\n"); break; case 0x04: printf("DVI-D\n"); break; case 0x05: printf("DVI-I, analog\n"); break; case 0x06: printf("DVI-I, digital\n"); break; case 0x07: printf("HDMI-A\n"); break; case 0x08: printf("HDMI-B\n"); break; case 0x09: printf("MDDI\n"); break; case 0x0a: printf("DisplayPort\n"); break; case 0x0b: printf("Proprietary Digital Interface\n"); break; default: printf("Reserved\n"); break; } if (x[3] >> 4) printf(" Number of Links: %u\n", x[3] & 0xf); printf(" Interface Standard Version: %u.%u\n", x[4] >> 4, x[4] & 0xf); print_flags(" Supported bpc for RGB encoding", x[5], bpc444); print_flags(" Supported bpc for YCbCr 4:4:4 encoding", x[6], bpc444); print_flags(" Supported bpc for YCbCr 4:2:2 encoding", x[7], bpc4xx); printf(" Supported Content Protection: "); switch (x[8] & 0xf) { case 0x00: printf("None\n"); break; case 0x01: printf("HDCP "); break; case 0x02: printf("DTCP "); break; case 0x03: printf("DPCP "); break; default: printf("Reserved "); break; } if (x[8] & 0xf) printf("%u.%u\n", x[9] >> 4, x[9] & 0xf); unsigned char v = x[0x0a] & 0xf; printf(" Spread Spectrum: "); switch (x[0x0a] >> 6) { case 0x00: printf("None\n"); break; case 0x01: printf("Down Spread %.1f%%\n", v / 10.0); break; case 0x02: printf("Center Spread %.1f%%\n", v / 10.0); break; case 0x03: printf("Reserved\n"); break; } switch (x[3] >> 4) { case 0x01: printf(" LVDS Color Mapping: %s mode\n", (x[0x0b] & 0x10) ? "6 bit compatible" : "normal"); if (x[0x0b] & 0x08) printf(" LVDS supports 2.8V\n"); if (x[0x0b] & 0x04) printf(" LVDS supports 12V\n"); if (x[0x0b] & 0x02) printf(" LVDS supports 5V\n"); if (x[0x0b] & 0x01) printf(" LVDS supports 3.3V\n"); printf(" LVDS %s Mode\n", (x[0x0c] & 0x04) ? "Fixed" : "DE"); if (x[0x0c] & 0x04) printf(" LVDS %s Signal Level\n", (x[0x0c] & 0x02) ? "Low" : "High"); else printf(" LVDS DE Polarity Active %s\n", (x[0x0c] & 0x02) ? "Low" : "High"); printf(" LVDS Shift Clock Data Strobe at %s Edge\n", (x[0x0c] & 0x01) ? "Rising" : "Falling"); break; case 0x0b: printf(" PDI %s Mode\n", (x[0x0b] & 0x04) ? "Fixed" : "DE"); if (x[0x0b] & 0x04) printf(" PDI %s Signal Level\n", (x[0x0b] & 0x02) ? "Low" : "High"); else printf(" PDI DE Polarity Active %s\n", (x[0x0b] & 0x02) ? "Low" : "High"); printf(" PDI Shift Clock Data Strobe at %s Edge\n", (x[0x0b] & 0x01) ? "Rising" : "Falling"); break; } } // tag 0x10 and 0x27 void edid_state::parse_displayid_stereo_display_intf(const unsigned char *x) { check_displayid_datablock_revision(x[1], 0xc0, 1); switch (x[1] >> 6) { case 0x00: printf(" Timings that explicitly report 3D capability\n"); break; case 0x01: printf(" Timings that explicitly report 3D capability & Timing Codes listed here\n"); break; case 0x02: printf(" All listed timings\n"); break; case 0x03: printf(" Only Timings Codes listed here\n"); break; } unsigned len = x[2]; switch (x[4]) { case 0x00: printf(" Field Sequential Stereo (L/R Polarity: %s)\n", (x[5] & 1) ? "0/1" : "1/0"); break; case 0x01: printf(" Side-by-side Stereo (Left Half = %s Eye View)\n", (x[5] & 1) ? "Right" : "Left"); break; case 0x02: printf(" Pixel Interleaved Stereo:\n"); for (unsigned y = 0; y < 8; y++) { unsigned char v = x[5 + y]; printf(" "); for (int x = 7; x >= 0; x--) printf("%c", (v & (1 << x)) ? 'L' : 'R'); printf("\n"); } break; case 0x03: printf(" Dual Interface, Left and Right Separate\n"); printf(" Carries the %s-eye view\n", (x[5] & 1) ? "Right" : "Left"); printf(" "); switch ((x[5] >> 1) & 3) { case 0x00: printf("No mirroring\n"); break; case 0x01: printf("Left/Right mirroring\n"); break; case 0x02: printf("Top/Bottom mirroring\n"); break; case 0x03: printf("Reserved\n"); break; } break; case 0x04: printf(" Multi-View: %u views, Interleaving Method Code: %u\n", x[5], x[6]); break; case 0x05: printf(" Stacked Frame Stereo (Top Half = %s Eye View)\n", (x[5] & 1) ? "Right" : "Left"); break; case 0xff: printf(" Proprietary\n"); break; default: printf(" Reserved\n"); break; } if (!(x[1] & 0x40)) // Has No Timing Codes return; len -= 1 + x[3]; x += 4 + x[3]; while (1U + (x[0] & 0x1f) <= len) { unsigned num_codes = x[0] & 0x1f; unsigned type = x[0] >> 6; char type_name[16]; for (unsigned i = 1; i <= num_codes; i++) { switch (type) { case 0x00: sprintf(type_name, "DMT 0x%02x", x[i]); print_timings(" ", find_dmt_id(x[i]), type_name); break; case 0x01: sprintf(type_name, "VIC %3u", x[i]); print_timings(" ", find_vic_id(x[i]), type_name); break; case 0x02: sprintf(type_name, "HDMI VIC %u", x[i]); print_timings(" ", find_hdmi_vic_id(x[i]), type_name); break; } } len -= 1 + num_codes; x += 1 + num_codes; } } // tag 0x11 void edid_state::parse_displayid_type_5_timing(const unsigned char *x) { struct timings t = {}; std::string s("aspect "); t.hact = 1 + (x[2] | (x[3] << 8)); t.vact = 1 + (x[4] | (x[5] << 8)); calc_ratio(&t); s += std::to_string(t.hratio) + ":" + std::to_string(t.vratio); switch ((x[0] >> 5) & 0x3) { case 0: s += ", no 3D stereo"; break; case 1: s += ", 3D stereo"; break; case 2: s += ", 3D stereo depends on user action"; break; case 3: s += ", reserved"; fail("Reserved stereo 0x03.\n"); break; } if (x[0] & 0x10) s += ", refresh rate * (1000/1001) supported"; t.rb = RB_CVT_V2; if ((x[0] & 0x03) == 1) warn("Unexpected use of 'custom reduced blanking'.\n"); else if ((x[0] & 0x03) > 1) fail("Invalid Timing Formula.\n"); edid_cvt_mode(1 + x[6], t); if (x[0] & 0x80) { s += ", preferred"; dispid.preferred_timings.push_back(timings_ext(t, "CVT", s)); } print_timings(" ", &t, "CVT", s.c_str()); } // tag 0x12 and 0x28 void edid_state::parse_displayid_tiled_display_topology(const unsigned char *x, bool is_v2) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 22, 22)) return; unsigned caps = x[3]; unsigned num_v_tile = (x[4] & 0xf) | (x[6] & 0x30); unsigned num_h_tile = (x[4] >> 4) | ((x[6] >> 2) & 0x30); unsigned tile_v_location = (x[5] & 0xf) | ((x[6] & 0x3) << 4); unsigned tile_h_location = (x[5] >> 4) | (((x[6] >> 2) & 0x3) << 4); unsigned tile_width = x[7] | (x[8] << 8); unsigned tile_height = x[9] | (x[10] << 8); unsigned pix_mult = x[11]; printf(" Capabilities:\n"); printf(" Behavior if it is the only tile: "); switch (caps & 0x07) { case 0x00: printf("Undefined\n"); break; case 0x01: printf("Image is displayed at the Tile Location\n"); break; case 0x02: printf("Image is scaled to fit the entire tiled display\n"); break; case 0x03: printf("Image is cloned to all other tiles\n"); break; default: printf("Reserved\n"); break; } printf(" Behavior if more than one tile and fewer than total number of tiles: "); switch ((caps >> 3) & 0x03) { case 0x00: printf("Undefined\n"); break; case 0x01: printf("Image is displayed at the Tile Location\n"); break; default: printf("Reserved\n"); break; } if (caps & 0x80) printf(" Tiled display consists of a single physical display enclosure\n"); else printf(" Tiled display consists of multiple physical display enclosures\n"); printf(" Num horizontal tiles: %u Num vertical tiles: %u\n", num_h_tile + 1, num_v_tile + 1); printf(" Tile location: %u, %u\n", tile_h_location, tile_v_location); printf(" Tile resolution: %ux%u\n", tile_width + 1, tile_height + 1); if (caps & 0x40) { if (pix_mult) { printf(" Top bevel size: %.1f pixels\n", pix_mult * x[12] / 10.0); printf(" Bottom bevel size: %.1f pixels\n", pix_mult * x[13] / 10.0); printf(" Right bevel size: %.1f pixels\n", pix_mult * x[14] / 10.0); printf(" Left bevel size: %.1f pixels\n", pix_mult * x[15] / 10.0); } else { fail("No bevel information, but the pixel multiplier is non-zero.\n"); } printf(" Tile resolution: %ux%u\n", tile_width + 1, tile_height + 1); } else if (pix_mult) { fail("No bevel information, but the pixel multiplier is non-zero.\n"); } if (is_v2) printf(" Tiled Display Manufacturer/Vendor ID: %02X-%02X-%02X\n", x[0x10], x[0x11], x[0x12]); else printf(" Tiled Display Manufacturer/Vendor ID: %c%c%c\n", x[0x10], x[0x11], x[0x12]); printf(" Tiled Display Product ID Code: %u\n", x[0x13] | (x[0x14] << 8)); if (hide_serial_numbers) printf(" Tiled Display Serial Number: ...\n"); else printf(" Tiled Display Serial Number: %u\n", x[0x15] | (x[0x16] << 8) | (x[0x17] << 16)| (x[0x18] << 24)); } // tag 0x13 void edid_state::parse_displayid_type_6_timing(const unsigned char *x) { struct timings t = {}; std::string s("aspect "); t.pixclk_khz = 1 + (x[0] + (x[1] << 8) + ((x[2] & 0x3f) << 16)); t.hact = 1 + (x[3] | ((x[4] & 0x3f) << 8)); if ((x[4] >> 7) & 0x1) t.pos_pol_hsync = true; unsigned hbl = 1 + (x[7] | ((x[9] & 0xf) << 8)); t.hfp = 1 + (x[8] | ((x[9] & 0xf0) << 4)); t.hsync = 1 + x[10]; t.hbp = hbl - t.hfp - t.hsync; t.vact = 1 + (x[5] | ((x[6] & 0x3f) << 8)); if ((x[6] >> 7) & 0x1) t.pos_pol_vsync = true; unsigned vbl = 1 + x[11]; t.vfp = 1 + x[12]; t.vsync = 1 + (x[13] & 0x0f); t.vbp = vbl - t.vfp - t.vsync; if (x[13] & 0x80) { t.interlaced = true; t.vfp /= 2; t.vsync /= 2; t.vbp /= 2; } calc_ratio(&t); s += std::to_string(t.hratio) + ":" + std::to_string(t.vratio); if (x[2] & 0x40) { double aspect_mult = x[14] * 3.0 / 256.0; unsigned size_mult = 1 + (x[16] >> 4); t.vsize_mm = size_mult * (1 + (x[15] | ((x[16] & 0xf) << 8))); t.hsize_mm = t.vsize_mm * aspect_mult; } switch ((x[13] >> 5) & 0x3) { case 0: s += ", no 3D stereo"; break; case 1: s += ", 3D stereo"; break; case 2: s += ", 3D stereo depends on user action"; break; case 3: s += ", reserved"; fail("Reserved stereo 0x03.\n"); break; } if (x[2] & 0x80) { s += ", preferred"; dispid.preferred_timings.push_back(timings_ext(t, "DTD", s)); } print_timings(" ", &t, "DTD", s.c_str(), true); } static std::string ieee7542d(unsigned short fp) { int exp = ((fp & 0x7c00) >> 10) - 15; unsigned fract = (fp & 0x3ff) | 0x400; if (fp == 0x8000) return "do not use"; if (fp & 0x8000) return "reserved"; return std::to_string(pow(2, exp) * fract / 1024.0) + " cd/m^2"; } // tag 0x21 void edid_state::parse_displayid_parameters_v2(const unsigned char *x, unsigned block_rev) { if (!check_displayid_datablock_length(x, 29, 29)) return; unsigned hor_size = (x[4] << 8) + x[3]; unsigned vert_size = (x[6] << 8) + x[5]; dispid.has_display_parameters = true; if (x[1] & 0x80) printf(" Image size: %u mm x %u mm\n", hor_size, vert_size); else printf(" Image size: %.1f mm x %.1f mm\n", hor_size / 10.0, vert_size / 10.0); unsigned w = (x[8] << 8) + x[7]; unsigned h = (x[10] << 8) + x[9]; if (w && h) { printf(" Native Format: %ux%u\n", w, h); dispid.native_width = w; dispid.native_height = h; } else if (w || h) { fail("Invalid Native Format %ux%u.\n", w, h); } unsigned char v = x[11]; printf(" Scan Orientation: "); switch (v & 0x07) { case 0x00: printf("Left to Right, Top to Bottom\n"); break; case 0x01: printf("Right to Left, Top to Bottom\n"); break; case 0x02: printf("Top to Bottom, Right to Left\n"); break; case 0x03: printf("Bottom to Top, Right to Left\n"); break; case 0x04: printf("Right to Left, Bottom to Top\n"); break; case 0x05: printf("Left to Right, Bottom to Top\n"); break; case 0x06: printf("Bottom to Top, Left to Right\n"); break; case 0x07: printf("Top to Bottom, Left to Right\n"); break; } printf(" Luminance Information: "); switch ((v >> 3) & 0x03) { case 0x00: printf("Minimum guaranteed value\n"); break; case 0x01: printf("Guidance for the Source device\n"); break; default: printf("Reserved\n"); break; } printf(" Color Information: CIE %u\n", (v & 0x40) ? 1976 : 1931); printf(" Audio Speaker Information: %sintegrated\n", (v & 0x80) ? "not " : ""); printf(" Native Color Chromaticity:\n"); printf(" Primary #1: (%.6f, %.6f)\n", fp2d(x[0x0c] | ((x[0x0d] & 0x0f) << 8)), fp2d(((x[0x0d] & 0xf0) >> 4) | (x[0x0e] << 4))); printf(" Primary #2: (%.6f, %.6f)\n", fp2d(x[0x0f] | ((x[0x10] & 0x0f) << 8)), fp2d(((x[0x10] & 0xf0) >> 4) | (x[0x11] << 4))); printf(" Primary #3: (%.6f, %.6f)\n", fp2d(x[0x12] | ((x[0x13] & 0x0f) << 8)), fp2d(((x[0x13] & 0xf0) >> 4) | (x[0x14] << 4))); printf(" White Point: (%.6f, %.6f)\n", fp2d(x[0x15] | ((x[0x16] & 0x0f) << 8)), fp2d(((x[0x16] & 0xf0) >> 4) | (x[0x17] << 4))); printf(" Native Maximum Luminance (Full Coverage): %s\n", ieee7542d(x[0x18] | (x[0x19] << 8)).c_str()); printf(" Native Maximum Luminance (10%% Rectangular Coverage): %s\n", ieee7542d(x[0x1a] | (x[0x1b] << 8)).c_str()); printf(" Native Minimum Luminance: %s\n", ieee7542d(x[0x1c] | (x[0x1d] << 8)).c_str()); printf(" Native Color Depth: "); if (!(x[0x1e] & 0x07)) printf("Not defined\n"); else if (bpc444[x[0x1e] & 0x07]) printf("%s bpc\n", bpc444[x[0x1e] & 0x07]); else printf("Reserved\n"); printf(" Display Device Technology: "); switch ((x[0x1e] >> 4) & 0x07) { case 0x00: printf("Not Specified\n"); break; case 0x01: printf("Active Matrix LCD\n"); break; case 0x02: printf("Organic LED\n"); break; default: printf("Reserved\n"); break; } if (block_rev) printf(" Display Device Theme Preference: %s\n", (x[0x1e] & 0x80) ? "Dark Theme Preferred" : "No Preference"); if (x[0x1f] != 0xff) printf(" Native Gamma EOTF: %.2f\n", (100 + x[0x1f]) / 100.0); } // tag 0x24 void edid_state::parse_displayid_type_9_timing(const unsigned char *x) { struct timings t = {}; std::string s("aspect "); t.hact = 1 + (x[1] | (x[2] << 8)); t.vact = 1 + (x[3] | (x[4] << 8)); calc_ratio(&t); s += std::to_string(t.hratio) + ":" + std::to_string(t.vratio); switch ((x[0] >> 5) & 0x3) { case 0: s += ", no 3D stereo"; break; case 1: s += ", 3D stereo"; break; case 2: s += ", 3D stereo depends on user action"; break; case 3: s += ", reserved"; fail("Reserved stereo 0x03.\n"); break; } if (x[0] & 0x10) s += ", refresh rate * (1000/1001) supported"; switch (x[0] & 0x07) { case 1: t.rb = RB_CVT_V1; break; case 2: t.rb = RB_CVT_V2; break; default: break; } edid_cvt_mode(1 + x[5], t); print_timings(" ", &t, "CVT", s.c_str()); } // tag 0x25 void edid_state::parse_displayid_dynamic_video_timings_range_limits(const unsigned char *x) { check_displayid_datablock_revision(x[1], 0, (x[1] & 7) == 1); if (!check_displayid_datablock_length(x, 9, 9)) return; printf(" Minimum Pixel Clock: %u kHz\n", 1 + (x[3] | (x[4] << 8) | (x[5] << 16))); printf(" Maximum Pixel Clock: %u kHz\n", 1 + (x[6] | (x[7] << 8) | (x[8] << 16))); printf(" Minimum Vertical Refresh Rate: %u Hz\n", x[9]); if (x[1] & 7) printf(" Maximum Vertical Refresh Rate: %u Hz\n", x[10] + ((x[11] & 3) << 8)); else printf(" Maximum Vertical Refresh Rate: %u Hz\n", x[10]); printf(" Seamless Dynamic Video Timing Support: %s\n", (x[11] & 0x80) ? "Yes" : "No"); } // tag 0x26 static const char *colorspace_eotf_combinations[] = { "sRGB", "BT.601", "BT.709/BT.1886", "Adobe RGB", "DCI-P3", "BT.2020", "BT.2020/SMPTE ST 2084" }; static const char *colorspace_eotf_reserved[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static const char *colorspaces[] = { "Undefined", "sRGB", "BT.601", "BT.709", "Adobe RGB", "DCI-P3", "BT.2020", "Custom" }; static const char *eotfs[] = { "Undefined", "sRGB", "BT.601", "BT.1886", "Adobe RGB", "DCI-P3", "BT.2020", "Gamma function", "SMPTE ST 2084", "Hybrid Log", "Custom" }; void edid_state::parse_displayid_interface_features(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 9)) return; dispid.has_display_interface_features = true; unsigned len = x[2]; if (len > 0) print_flags(" Supported bpc for RGB encoding", x[3], bpc444); if (len > 1) print_flags(" Supported bpc for YCbCr 4:4:4 encoding", x[4], bpc444); if (len > 2) print_flags(" Supported bpc for YCbCr 4:2:2 encoding", x[5], bpc4xx); if (len > 3) print_flags(" Supported bpc for YCbCr 4:2:0 encoding", x[6], bpc4xx); if (len > 4 && x[7]) printf(" Minimum pixel rate at which YCbCr 4:2:0 encoding is supported: %.3f MHz\n", 74.25 * x[7]); if (len > 5) print_flags(" Supported audio capability and features (kHz)", x[8], audiorates, true); if (len > 6) print_flags(" Supported color space and EOTF standard combination 1", x[9], colorspace_eotf_combinations); if (len > 7) print_flags(" Supported color space and EOTF standard combination 2",x[10], colorspace_eotf_reserved); unsigned i = 0; if (len > 8 && x[11]) { printf(" Supported color space and EOTF additional combinations:"); for (i = 0; i < x[11]; i++) { if (i > 6) { printf("\n Number of additional color space and EOTF combinations (%d) is greater than allowed (7).", x[11]); break; } else if (i + 10 > len) { printf("\n Number of additional color space and EOTF combinations (%d) is too many to fit in block (%d).", x[11], len - 9); break; } const char *colorspace = "Out of range"; const char *eotf = "Out of range"; unsigned colorspace_index = (x[12 + i] >> 4) & 0xf; unsigned eotf_index = x[12 + i] & 0xf; if (colorspace_index < sizeof(colorspaces) / sizeof(colorspaces[0])) colorspace = colorspaces[colorspace_index]; if (eotf_index < sizeof(eotfs) / sizeof(eotfs[0])) eotf = eotfs[eotf_index]; if (i > 0) printf(", "); if (!strcmp(colorspace, eotf)) printf("%s", colorspace); else printf("%s/%s", colorspace, eotf); } printf("\n"); } check_displayid_datablock_length(x, 9 + i, 9 + i, 9 + i); } // tag 0x29 void edid_state::parse_displayid_ContainerID(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (check_displayid_datablock_length(x, 16, 16)) { x += 3; printf(" Container ID: %s\n", containerid2s(x).c_str()); } } // tag 0x32 void edid_state::parse_displayid_type_10_timing(const unsigned char *x, bool is_cta) { struct timings t = {}; std::string s("aspect "); t.hact = 1 + (x[1] | (x[2] << 8)); t.vact = 1 + (x[3] | (x[4] << 8)); calc_ratio(&t); s += std::to_string(t.hratio) + ":" + std::to_string(t.vratio); switch ((x[0] >> 5) & 0x3) { case 0: s += ", no 3D stereo"; break; case 1: s += ", 3D stereo"; break; case 2: s += ", 3D stereo depends on user action"; break; case 3: s += ", reserved"; fail("Reserved stereo 0x03.\n"); break; } switch (x[0] & 0x07) { case 1: t.rb = RB_CVT_V1; break; case 2: t.rb = RB_CVT_V2; break; case 3: t.rb = RB_CVT_V3; break; default: break; } if (x[0] & 0x10) { if (t.rb == RB_CVT_V2) { s += ", refresh rate * (1000/1001) supported"; t.rb |= RB_ALT; } else if (t.rb == RB_CVT_V3) { s += ", hblank is 160 pixels"; t.rb |= RB_ALT; } else { fail("VR_HB must be 0.\n"); } } if (x[0] & 0x80) s += ", YCbCr 4:2:0"; edid_cvt_mode(1 + x[5], t); print_timings(" ", &t, "CVT", s.c_str()); if (is_cta) { timings_ext te(t, "CVT", s); cta.vec_vtdbs.push_back(te); } } // tag 0x7e, OUI 3A-02-92 (VESA) void edid_state::parse_displayid_vesa(const unsigned char *x) { check_displayid_datablock_revision(x[1]); if (!check_displayid_datablock_length(x, 5, 7)) return; unsigned len = x[2]; x += 6; printf(" Data Structure Type: "); switch (x[0] & 0x07) { case 0x00: printf("eDP\n"); break; case 0x01: printf("DP\n"); break; default: printf("Reserved\n"); break; } printf(" Default Colorspace and EOTF Handling: %s\n", (x[0] & 0x80) ? "Native as specified in the Display Parameters DB" : "sRGB"); printf(" Number of Pixels in Hor Pix Cnt Overlapping an Adjacent Panel: %u\n", x[1] & 0xf); printf(" Multi-SST Operation: "); switch ((x[1] >> 5) & 0x03) { case 0x00: printf("Not Supported\n"); break; case 0x01: printf("Two Streams (number of links shall be 2 or 4)\n"); break; case 0x02: printf("Four Streams (number of links shall be 4)\n"); break; case 0x03: printf("Reserved\n"); break; } if (len >= 7) { double bpp = (x[2] & 0x3f) + (x[3] & 0x0f) / 16.0; printf(" Pass through timing's target DSC bits per pixel: %.4f\n", bpp); } } // tag 0x81 void edid_state::parse_displayid_cta_data_block(const unsigned char *x) { check_displayid_datablock_revision(x[1]); unsigned len = x[2]; unsigned i; if (len > 248) { fail("Length is > 248.\n"); len = 248; } x += 3; for (i = 0; i < len; i += (x[i] & 0x1f) + 1) { unsigned tag = (x[i] & 0xe0) << 3; if (tag == 0x700) tag |= x[i + 1]; bool duplicate = dispid.found_tags.find(tag) != dispid.found_tags.end(); cta_block(x + i, duplicate); if (!duplicate) dispid.found_tags.insert(tag); } if (i != len) fail("Length is %u instead of %u.\n", len, i); } // DisplayID main std::string edid_state::product_type(unsigned char x, bool heading) { std::string headingstr; if (dispid.version < 0x20) { headingstr = "Display Product Type"; if (heading) return headingstr; dispid.is_display = x == 2 || x == 3 || x == 4 || x == 6; switch (x) { case 0: return "Extension Section"; case 1: return "Test Structure; test equipment only"; case 2: return "Display panel or other transducer, LCD or PDP module, etc."; case 3: return "Standalone display device"; case 4: return "Television receiver"; case 5: return "Repeater/translator"; case 6: return "DIRECT DRIVE monitor"; default: break; } } else { headingstr = "Display Product Primary Use Case"; if (heading) return headingstr; dispid.is_display = x >= 2 && x <= 8; switch (x) { case 0: return "Same primary use case as the base section"; case 1: return "Test Structure; test equipment only"; case 2: return "None of the listed primary use cases; generic display"; case 3: return "Television (TV) display"; case 4: return "Desktop productivity display"; case 5: return "Desktop gaming display"; case 6: return "Presentation display"; case 7: return "Head-mounted Virtual Reality (VR) display"; case 8: return "Head-mounted Augmented Reality (AR) display"; default: break; } } fail("Unknown %s 0x%02x.\n", headingstr.c_str(), x); return std::string("Unknown " + headingstr + " (") + utohex(x) + ")"; } void edid_state::preparse_displayid_block(const unsigned char *x) { unsigned length = x[2]; if (length > 121) length = 121; unsigned offset = 5; dispid.preparsed_displayid_blocks++; while (length > 0) { unsigned tag = x[offset]; unsigned len = x[offset + 2]; switch (tag) { case 0x02: dispid.preparsed_color_ids |= 1 << ((x[offset + 1] >> 3) & 0x0f); break; case 0x0e: dispid.preparsed_xfer_ids |= 1 << ((x[offset + 1] >> 4) & 0x0f); break; default: break; } if (length < 3) break; if (length < len + 3) break; if (!tag && !len) break; length -= len + 3; offset += len + 3; } } void edid_state::parse_displayid_block(const unsigned char *x) { unsigned version = x[1]; unsigned length = x[2]; unsigned prod_type = x[3]; // future check: based on type, check for required data blocks unsigned ext_count = x[4]; unsigned i; printf(" Version: %u.%u\n Extension Count: %u\n", version >> 4, version & 0xf, ext_count); if (dispid.is_base_block) { dispid.version = version; printf(" %s: %s\n", product_type(prod_type, true).c_str(), product_type(prod_type, false).c_str()); if (!prod_type) fail("DisplayID Base Block has no product type.\n"); if (ext_count != dispid.preparsed_displayid_blocks - 1) fail("Expected %u DisplayID Extension Block%s, but got %u.\n", ext_count, ext_count > 1 ? "s" : "", dispid.preparsed_displayid_blocks - 1); } else { if (prod_type) fail("Product Type should be 0 in extension block.\n"); if (ext_count) fail("Extension Count should be 0 in extension block.\n"); if (version != dispid.version) fail("Got version %u.%u, expected %u.%u.\n", version >> 4, version & 0xf, dispid.version >> 4, dispid.version & 0xf); } if (length > 121) { fail("DisplayID length %d is greater than 121.\n", length); length = 121; } unsigned offset = 5; bool first_data_block = true; while (length > 0) { unsigned tag = x[offset]; unsigned oui = 0; switch (tag) { // DisplayID 1.3: case 0x00: data_block = "Product Identification Data Block (" + utohex(tag) + ")"; break; case 0x01: data_block = "Display Parameters Data Block (" + utohex(tag) + ")"; break; case 0x02: data_block = "Color Characteristics Data Block"; break; case 0x03: data_block = "Video Timing Modes Type 1 - Detailed Timings Data Block"; break; case 0x04: data_block = "Video Timing Modes Type 2 - Detailed Timings Data Block"; break; case 0x05: data_block = "Video Timing Modes Type 3 - Short Timings Data Block"; break; case 0x06: data_block = "Video Timing Modes Type 4 - DMT Timings Data Block"; break; case 0x07: data_block = "Supported Timing Modes Type 1 - VESA DMT Timings Data Block"; break; case 0x08: data_block = "Supported Timing Modes Type 2 - CTA-861 Timings Data Block"; break; case 0x09: data_block = "Video Timing Range Data Block"; break; case 0x0a: data_block = "Product Serial Number Data Block"; break; case 0x0b: data_block = "GP ASCII String Data Block"; break; case 0x0c: data_block = "Display Device Data Data Block"; break; case 0x0d: data_block = "Interface Power Sequencing Data Block"; break; case 0x0e: data_block = "Transfer Characteristics Data Block"; break; case 0x0f: data_block = "Display Interface Data Block"; break; case 0x10: data_block = "Stereo Display Interface Data Block (" + utohex(tag) + ")"; break; case 0x11: data_block = "Video Timing Modes Type 5 - Short Timings Data Block"; break; case 0x12: data_block = "Tiled Display Topology Data Block (" + utohex(tag) + ")"; break; case 0x13: data_block = "Video Timing Modes Type 6 - Detailed Timings Data Block"; break; // 0x14 .. 0x7e RESERVED for Additional VESA-defined Data Blocks // DisplayID 2.0 case 0x20: data_block = "Product Identification Data Block (" + utohex(tag) + ")"; break; case 0x21: data_block = "Display Parameters Data Block (" + utohex(tag) + ")"; break; case 0x22: data_block = "Video Timing Modes Type 7 - Detailed Timings Data Block"; break; case 0x23: data_block = "Video Timing Modes Type 8 - Enumerated Timing Codes Data Block"; break; case 0x24: data_block = "Video Timing Modes Type 9 - Formula-based Timings Data Block"; break; case 0x25: data_block = "Dynamic Video Timing Range Limits Data Block"; break; case 0x26: data_block = "Display Interface Features Data Block"; break; case 0x27: data_block = "Stereo Display Interface Data Block (" + utohex(tag) + ")"; break; case 0x28: data_block = "Tiled Display Topology Data Block (" + utohex(tag) + ")"; break; case 0x29: data_block = "ContainerID Data Block"; break; case 0x32: data_block = "Video Timing Modes Type 10 - Formula-based Timings Data Block"; break; // 0x2a .. 0x7d RESERVED for Additional VESA-defined Data Blocks case 0x7e: // DisplayID 2.0 case 0x7f: // DisplayID 1.3 if ((tag == 0x7e && version >= 0x20) || (tag == 0x7f && version < 0x20)) { oui = (x[offset + 3] << 16) + (x[offset + 4] << 8) + x[offset + 5]; const char *name = oui_name(oui); bool reversed = false; if (!name) { name = oui_name(oui, true); if (name) reversed = true; } if (name) data_block = std::string("Vendor-Specific Data Block (") + name + ")"; else data_block = "Vendor-Specific Data Block, OUI " + ouitohex(oui); if (reversed) fail((std::string("OUI ") + ouitohex(oui) + " is in the wrong byte order.\n").c_str()); } else { data_block = "Unknown DisplayID Data Block (" + utohex(tag) + ")"; } break; // 0x80 RESERVED case 0x81: data_block = "CTA-861 DisplayID Data Block (" + utohex(tag) + ")"; break; // 0x82 .. 0xff RESERVED default: data_block = "Unknown DisplayID Data Block (" + utohex(tag) + ")"; break; } if (version >= 0x20 && (tag < 0x20 || tag == 0x7f)) fail("Use of DisplayID v1.x tag for DisplayID v%u.%u.\n", version >> 4, version & 0xf); if (version < 0x20 && tag >= 0x20 && tag <= 0x7e) fail("Use of DisplayID v2.0 tag for DisplayID v%u.%u.\n", version >> 4, version & 0xf); if (length < 3) { // report a problem when the remaining bytes are not 0. if (tag || x[offset + 1]) { fail("Not enough bytes remain (%d) for a DisplayID data block or the DisplayID filler is non-0.\n", length); } break; } unsigned block_rev = x[offset + 1] & 0x07; unsigned len = x[offset + 2]; if (length < len + 3) { fail("The length of this DisplayID data block (%d) exceeds the number of bytes remaining (%d).\n", len + 3, length); break; } if (!tag && !len) { // A Product Identification Data Block with no payload bytes is not valid - assume this is the end. if (!memchk(x + offset, length)) { fail("Non-0 filler bytes in the DisplayID block.\n"); } break; } printf(" %s:\n", data_block.c_str()); switch (tag) { case 0x00: parse_displayid_product_id(x + offset); break; case 0x01: parse_displayid_parameters(x + offset); break; case 0x02: parse_displayid_color_characteristics(x + offset); break; case 0x03: check_displayid_datablock_revision(x[offset + 1], 0, block_rev & 1); for (i = 0; i < len / 20; i++) parse_displayid_type_1_7_timing(&x[offset + 3 + (i * 20)], false, block_rev); break; case 0x04: check_displayid_datablock_revision(x[offset + 1]); for (i = 0; i < len / 11; i++) parse_displayid_type_2_timing(&x[offset + 3 + (i * 11)]); break; case 0x05: check_displayid_datablock_revision(x[offset + 1], 0, block_rev & 1); for (i = 0; i < len / 3; i++) parse_displayid_type_3_timing(&x[offset + 3 + (i * 3)]); break; case 0x06: check_displayid_datablock_revision(x[offset + 1], 0xc0, 1); for (i = 0; i < len; i++) parse_displayid_type_4_8_timing((x[offset + 1] & 0xc0) >> 6, x[offset + 3 + i]); break; case 0x07: check_displayid_datablock_revision(x[offset + 1]); for (i = 0; i < min(len, 10) * 8; i++) if (x[offset + 3 + i / 8] & (1 << (i % 8))) { char type[16]; sprintf(type, "DMT 0x%02x", i + 1); print_timings(" ", find_dmt_id(i + 1), type); } break; case 0x08: check_displayid_datablock_revision(x[offset + 1]); for (i = 0; i < min(len, 8) * 8; i++) if (x[offset + 3 + i / 8] & (1 << (i % 8))) { char type[16]; sprintf(type, "VIC %3u", i + 1); print_timings(" ", find_vic_id(i + 1), type); } break; case 0x09: parse_displayid_video_timing_range_limits(x + offset); break; case 0x0a: case 0x0b: parse_displayid_string(x + offset); break; case 0x0c: parse_displayid_display_device(x + offset); break; case 0x0d: parse_displayid_intf_power_sequencing(x + offset); break; case 0x0e: parse_displayid_transfer_characteristics(x + offset); break; case 0x0f: parse_displayid_display_intf(x + offset); break; case 0x10: parse_displayid_stereo_display_intf(x + offset); break; case 0x11: check_displayid_datablock_revision(x[offset + 1]); for (i = 0; i < len / 7; i++) parse_displayid_type_5_timing(&x[offset + 3 + (i * 7)]); break; case 0x12: parse_displayid_tiled_display_topology(x + offset, false); break; case 0x13: check_displayid_datablock_revision(x[offset + 1]); for (i = 0; i < len; i += (x[offset + 3 + i + 2] & 0x40) ? 17 : 14) parse_displayid_type_6_timing(&x[offset + 3 + i]); break; case 0x20: parse_displayid_product_id(x + offset); break; case 0x21: if (block_rev >= 1) check_displayid_datablock_revision(x[offset + 1], 0x80, 1); else check_displayid_datablock_revision(x[offset + 1], 0x80, 0); parse_displayid_parameters_v2(x + offset, block_rev); break; case 0x22: { unsigned sz = 20; if (block_rev >= 2) check_displayid_datablock_revision(x[offset + 1], 0x08, 2); else if (block_rev == 1) check_displayid_datablock_revision(x[offset + 1], 0x08, 1); else check_displayid_datablock_revision(x[offset + 1]); sz += (x[offset + 1] & 0x70) >> 4; if (block_rev >= 1 && (x[offset + 1] & 0x08)) printf(" These timings support DSC pass-through\n"); for (i = 0; i < len / sz; i++) parse_displayid_type_1_7_timing(&x[offset + 3 + i * sz], true, block_rev); break; } case 0x23: if (block_rev) check_displayid_datablock_revision(x[offset + 1], 0xe8, 1); else check_displayid_datablock_revision(x[offset + 1], 0xc8); if (x[offset + 1] & 0x08) { for (i = 0; i < len / 2; i++) parse_displayid_type_4_8_timing((x[offset + 1] & 0xc0) >> 6, x[offset + 3 + i * 2] | (x[offset + 4 + i * 2] << 8)); } else { for (i = 0; i < len; i++) parse_displayid_type_4_8_timing((x[offset + 1] & 0xc0) >> 6, x[offset + 3 + i]); } break; case 0x24: check_displayid_datablock_revision(x[offset + 1]); for (i = 0; i < len / 6; i++) parse_displayid_type_9_timing(&x[offset + 3 + i * 6]); break; case 0x25: parse_displayid_dynamic_video_timings_range_limits(x + offset); break; case 0x26: parse_displayid_interface_features(x + offset); break; case 0x27: parse_displayid_stereo_display_intf(x + offset); break; case 0x28: parse_displayid_tiled_display_topology(x + offset, true); break; case 0x29: parse_displayid_ContainerID(x + offset); break; case 0x32: { unsigned sz = 6 + ((x[offset + 1] & 0x70) >> 4); check_displayid_datablock_revision(x[offset + 1], 0x10); for (i = 0; i < len / sz; i++) parse_displayid_type_10_timing(&x[offset + 3 + i * sz]); break; } case 0x81: parse_displayid_cta_data_block(x + offset); break; case 0x7e: if (oui == 0x3a0292) { parse_displayid_vesa(x + offset); break; } // fall-through default: hex_block(" ", x + offset + 3, len); break; } if ((tag == 0x00 || tag == 0x20) && (!dispid.is_base_block || !first_data_block)) fail("%s is required to be the first DisplayID Data Block.\n", data_block.c_str()); length -= len + 3; offset += len + 3; first_data_block = false; } /* * DisplayID length field is number of following bytes * but checksum is calculated over the entire structure * (excluding DisplayID-in-EDID magic byte) */ data_block.clear(); do_checksum(" ", x + 1, x[2] + 5); if (!memchk(x + 1 + x[2] + 5, 0x7f - (1 + x[2] + 5))) { data_block = "Padding"; fail("DisplayID padding contains non-zero bytes.\n"); } dispid.is_base_block = false; } void edid_state::check_displayid_blocks() { data_block = "DisplayID"; if (!dispid.has_product_identification) fail("Missing DisplayID Product Identification Data Block.\n"); if (dispid.is_display && !dispid.has_display_parameters) fail("Missing DisplayID Display Parameters Data Block.\n"); if (dispid.is_display && !dispid.has_display_interface_features) fail("Missing DisplayID Display Interface Features Data Block.\n"); if (dispid.is_display && !dispid.has_type_1_7) fail("Missing DisplayID Type %s Detailed Timing Data Block.\n", dispid.version >= 0x20 ? "VII" : "I"); if (dispid.preferred_timings.empty()) fail("DisplayID expects at least one preferred timing.\n"); }