| // 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 <[email protected]> |
| * Maintainer: Hans Verkuil <[email protected]> |
| */ |
| |
| #include <math.h> |
| |
| #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"); |
| } |