/*
* Parse VESA Extended Display Identification Data
*
- * (c) 2011 Martin Mares <mj@ucw.cz>
+ * (c) 2011--2015 Martin Mares <mj@ucw.cz>
*
* This program can be distributed and used under the terms
* of the GNU General Public License version 2 or later.
static byte edid[128];
static int version;
+static uns block;
+static uns num_ext_blocks;
static void
#ifdef __GNUC__
return x ? '+' : '-';
}
+/*** EDID Basics ***/
+
static void
show_model(byte *p)
{
('A' - 1 + (p[1] & 0x1f)),
u16_le(p+2),
u32_le(p+4));
- printf("Manufactured: %d week %d\n", p[9] + 1990, p[8]);
+
+ uns y = p[9] + 1990;
+ if (p[8] == 0xff)
+ printf("Model year: %d\n", y);
+ else if (p[8])
+ printf("Manufactured: %d week %d\n", y, p[8]);
+ else
+ printf("Manufactured: %d, week unspecified\n", y);
}
static void
{
uns input = p[0];
if (input & 0x80)
- printf("Input: Digital DFP%c\n", B(input & 1));
+ {
+ printf("Input: Digital,");
+ if (version < 0x104)
+ printf(" DFP%c\n", B(input & 1));
+ else
+ {
+ uns depth = (input >> 4) & 7;
+ static const char * const depths[] = { "undefined", "6", "8", "10", "12", "14", "16", "RFU7" };
+
+ uns iface = input & 0x0f;
+ static const char * const ifaces[] = {
+ "undefined", "DVI", "HDMI-a", "HDMI-b", "MDDI", "DisplayPort", "RFU6", "RFU7",
+ "RFU8", "RFU9", "RFU10", "RFU11", "RFU12", "RFU13", "RFU14", "RFU16"
+ };
+
+ printf(" Depth=%s Iface=%s\n", depths[depth], ifaces[iface]);
+ }
+ }
else
- printf("Input: Analog SigLev=%d Setup%c SepSync%c CompSync%c GreenSync%c SerrSync%c\n",
+ printf("Input: Analog, SigLev=%d Setup%c SepSync%c CompSync%c GreenSync%c SerrSync%c\n",
((input >> 5) & 3),
B(input & 0x10),
B(input & 0x08),
B(input & 0x02),
B(input & 0x01));
- printf("Image size: %dx%dcm\n", p[1], p[2]);
+ if (!p[1] && !p[2])
+ printf("Image size: undefined\n");
+ else if (!p[2])
+ printf("Image aspect ratio: %u.%02u:1\n", 1 + p[1]/100, p[1] % 100);
+ else if (!p[1])
+ printf("Image aspect ratio: 1:%u.%02u\n", 1 + p[2]/100, p[2] % 100);
+ else
+ printf("Image size: %dx%dcm\n", p[1], p[2]);
+
if (p[3] == 0xff)
printf("Gamma: not given\n");
else
- printf("Gamma: %d.%02d\n", (p[3] / 100) + 1, p[3] % 100);
+ printf("Gamma: %d.%02d\n", 1 + p[3]/100, p[3] % 100);
uns flags = p[4];
- static const char * const types[4] = { "Mono/Gray", "RGB", "NonRGB", "Unknown" };
- printf("Flags: Suspend%c Standby%c ActiveOff%c Colors=%s HasSRGB%c PrefTiming%c GTF%c\n",
+ static const char * const color_types[4] = { "Mono/Gray", "RGB", "NonRGB", "Unknown" };
+ static const char * const color_encodings[4] = {
+ "RGB444",
+ "RGB444/YCrCb444",
+ "RGB444/YCrCb422",
+ "RGB444/YCrCb444/YCrCb422",
+ };
+ printf("Flags: Suspend%c Standby%c ActiveOff%c",
B(flags & 0x80),
B(flags & 0x40),
- B(flags & 0x20),
- types[(flags >> 3) & 3],
- B(flags & 0x04),
- B(flags & 0x02),
- B(flags & 0x01));
+ B(flags & 0x20));
+ if (version >= 0x104 && (input & 0x80))
+ printf(" Colors=%s", color_encodings[(flags >> 3) & 3]);
+ else
+ printf(" Colors=%s", color_types[(flags >> 3) & 3]);
+ printf(" HasSRGB%c", B(flags & 0x04));
+ printf(" %s%c", (version >= 0x104 ? "PreferredIsNative" : "PrefTiming"), B(flags & 0x02));
+ printf(" %s%c\n", (version >= 0x104 ? "ContinuousFreq" : "GTF"), B(flags & 0x01));
}
static void
"1024x768@70",
"1024x768@75",
"1280x1024@75",
- "MFG7",
+ "1152x870@75",
"MFG6",
"MFG5",
"MFG4",
printf(" %s", estab_timings[i]);
printf("\n");
- printf("Extended timings:");
+ printf("Standard timings:");
for (int i=0; i<8; i++)
{
byte *q = p + 3 + 2*i;
printf("\n");
}
+/*** EDID Descriptors ***/
+
static void
show_detailed_timing(byte *p)
{
uns flags = p[17];
uns stereo = ((flags >> 4) & 6) | (flags & 1);
- uns input = (flags >> 3) & 3;
+ static const char * const stereo_types[8] = {
+ "none",
+ "none",
+ "FieldSeq/RightOn1",
+ "2-wayIL/RightEven",
+ "FieldSeq/LeftOn1",
+ "2-wayIL/LeftEven",
+ "4-wayIL",
+ "SideBySide",
+ };
+ printf("\tFlags: Interlace%c Stereo=%s\n", B(flags & 0x80), stereo_types[stereo]);
if (stereo >= 2)
printf("\tStereo mode: #%d\n", stereo);
- static const char * const input_types[4] = { "AnalogComposite", "BipolarAnalogComposite", "DigitalComposite", "DigitalSeparate" };
- printf("\tInput: %s", input_types[input]);
- if (input < 3)
+ uns sync = (flags >> 3) & 3;
+
+ static const char * const sync_types[4] = { "AnalogComposite", "BipolarAnalogComposite", "DigitalComposite", "DigitalSeparate" };
+ printf("\tSync: %s", sync_types[sync]);
+ if (sync < 3)
printf(" Serrate%c", B(flags & 4));
else
printf(" VPolarity%c", B(flags & 4));
- if (input < 2)
+ if (sync < 2)
printf(" OnRGB%c", B(flags & 2));
- else if (input == 2)
- printf(" CompPolarity%c", B(flags & 2));
+ else if (sync == 2)
+ printf(" Polarity%c", B(flags & 2));
else
printf(" HPolarity%c", B(flags & 2));
printf("\n");
putchar('\n');
}
+static void
+show_limits(byte *p)
+{
+ uns flags = p[4];
+ uns voffset = flags & 3;
+ uns hoffset = (flags >> 2) & 3;
+
+ printf("Limits: Vert=%d-%dHz Horiz=%d-%dkHz PixClk=%dMHz",
+ p[5] + (voffset == 3 ? 255 : 0),
+ p[6] + (voffset & 2 ? 255 : 0),
+ p[7] + (hoffset == 3 ? 255 : 0),
+ p[8] + (hoffset & 2 ? 255 : 0),
+ p[9]*10);
+
+ switch (p[10])
+ {
+ case 0: // No secondary timing formula
+ printf(" (no formula)\n");
+ break;
+ case 1: // Range limits only
+ printf(" (range only)\n");
+ break;
+ case 2: // Secondary GTF
+ printf(" (2ndary GTF)\n");
+ printf("\tSecondary GTF parameters: fStart=%dkHz C=%.1f M=%d K=%d J=%.1f\n",
+ p[12]*2, p[13]/2., u16_le(p+14), p[16], p[17]/2.);
+ break;
+ case 4:
+ printf(" (CVT v%d.%d)\n", p[11] >> 4, p[11] & 0x0f);
+ printf("\t[...]\n");
+ break;
+ default:
+ printf(" (unknown formula #%02x)\n", p[10]);
+ }
+}
+
static void
show_descriptor(byte *p)
{
show_ascii("Comment", p+5, 13);
break;
case 0xfd: // Range limits
- printf("Limits: Vert=%d-%dHz Horiz=%d-%dkHz PixClk=%dMHz\n",
- p[5], p[6], p[7], p[8], p[9]*10);
- switch (p[10])
- {
- case 0: // No secondary timing formula
- break;
- case 2: // Secondary GTF
- printf("Secondary GTF parameters: fStart=%dkHz C=%.1f M=%d K=%d J=%.1f\n",
- p[12]*2, p[13]/2., u16_le(p+14), p[16], p[17]/2.);
- break;
- default:
- printf("Unidentified secondary timing formula #%02x\n", p[10]);
- }
+ show_limits(p);
break;
- case 0xfc: // Monitor name
- show_ascii("Monitor name", p+5, 13);
+ case 0xfc: // Product name
+ show_ascii("Product name", p+5, 13);
break;
case 0xfb: // Color point
printf("Color point: [...]\n");
case 0xfa: // More standard timings
printf("More standard timings: [...]\n");
break;
+ case 0xf9:
+ printf("Display color management: [...]\n");
+ break;
+ case 0xf8:
+ printf("CVT-3: [...]\n");
+ break;
+ case 0xf7:
+ printf("Established timings 3: [...]\n");
+ break;
case 0x10: // Dummy descriptor -- entry unused
return;
default:
- printf("Unknown descriptor type %02x\n", p[3]);
+ printf("%s descriptor type %02x\n", (p[3] <= 0x0f ? "Vendor-specific" : "Unknown"), p[3]);
}
}
+static void
+check_sum(void)
+{
+ byte sum = 0;
+ for (int i=0; i<128; i++)
+ sum += edid[i];
+ if (sum)
+ printf("Invalid checksum: off by %02x\n", sum);
+}
+
static void
show_edid(void)
{
printf("EDID %d.%d\n", ver, rev);
version = (ver << 8) | rev;
- byte sum = 0;
- for (int i=0; i<128; i++)
- sum += edid[i];
- if (sum)
- printf("Invalid checksum: off by %02x\n", sum);
+ if (version >= 0x200)
+ die("Unsupported version");
+ check_sum();
show_model(edid + 8);
show_basic(edid + 0x14);
show_descriptor(p);
}
- if (edid[0x7e])
- printf("### %d extension blocks follow, but they are not supported yet ###\n", edid[0x7e]);
+ num_ext_blocks = edid[0x7e];
+}
+
+/*** Extensions ***/
+
+static void ext_block_map(void)
+{
+ for (uns i=1; i<=0x7e; i++)
+ if (edid[i])
+ printf("Block %u: extension %u\n", i, edid[i]);
+}
+
+static void ext_hdmi(byte *p, uns len)
+{
+ if (len < 6)
+ {
+ printf("\t!!! Truncated\n");
+ return;
+ }
+ printf("\tPhysical address: %u.%u.%u.%u\n", p[4], p[5], p[6], p[7]);
+
+ if (len < 7)
+ return;
+ uns f = p[6];
+ printf("\tFlags: AI%c 48bpp%c 36bpp%c 30bpp%c DeepColor-YCbCr444%c DVIDual%c\n",
+ B(f & 0x80), B(f & 0x40), B(f & 0x20), B(f & 0x10),
+ B(f & 0x08), B(f & 0x01));
+
+ if (len < 8)
+ return;
+ printf("\tMax TMDS Clock [MHz]: %u\n", 5 * p[7]);
+
+ if (len < 9)
+ return;
+ uns f2 = p[8];
+
+ uns i = 9;
+ if (f2 & 0x80) // Latency fields present
+ {
+ if (i+2 > len)
+ {
+ printf("\t!!! Truncated latency fields\n");
+ return;
+ }
+ printf("\tLatency [ms]:");
+ if (p[i] && p[i] != 0xff)
+ printf(" Video=%u", (p[i]-1)*2);
+ if (p[i+1] && p[i+1] != 0xff)
+ printf(" Audio=%u", (p[i+1]-1)*2);
+ i += 2;
+
+ if (f2 & 0x40) // Interlaced latency present
+ {
+ if (i+2 > len)
+ {
+ printf("\t!!! Truncated latency fields\n");
+ return;
+ }
+ printf("\tInterlaced latency [ms]:");
+ if (p[i] && p[i] != 0xff)
+ printf(" Video=%u", (p[i]-1)*2);
+ if (p[i+1] && p[i+1] != 0xff)
+ printf(" Audio=%u", (p[i+1]-1)*2);
+ i += 2;
+ }
+ }
}
+// A table of CEA video modes taken from https://en.wikipedia.org/wiki/Extended_Display_Identification_Data
+static const char * const cea_modes[] = {
+ [1] = "640x480p@60 4:3",
+ [2] = "720x480p@60 4:3",
+ [3] = "720x480p@60 16:9",
+ [4] = "1280x720p@60 16:9",
+ [5] = "1920x1080i@60 16:9",
+ [6] = "720(1440)x480i@60 4:3",
+ [7] = "720(1440)x480i@60 16:9",
+ [8] = "720(1440)x240p@60 4:3",
+ [9] = "720(1440)x240p@60 16:9",
+ [10] = "(2880)x480i@60 4:3",
+ [11] = "(2880)x480i@60 16:9",
+ [12] = "(2880)x240p@60 4:3",
+ [13] = "(2880)x240p@60 16:9",
+ [14] = "1440x480p@60 4:3",
+ [15] = "1440x480p@60 16:9",
+ [16] = "1920x1080p@60 16:9",
+ [17] = "720x576p@50 4:3",
+ [18] = "720x576p@50 16:9",
+ [19] = "1280x720p@50 16:9",
+ [20] = "1920x1080i@50 16:9",
+ [21] = "720(1440)x576i@50 4:3",
+ [22] = "720(1440)x576i@50 16:9",
+ [23] = "720(1440)x288p@50 4:3",
+ [24] = "720(1440)x288p@50 16:9",
+ [25] = "(2880)x576i@50 4:3",
+ [26] = "(2880)x576i@50 16:9",
+ [27] = "(2880)x288p@50 4:3",
+ [28] = "(2880)x288p@50 16:9",
+ [29] = "1440x576p@50 4:3",
+ [30] = "1440x576p@50 16:9",
+ [31] = "1920x1080p@50 16:9",
+ [32] = "1920x1080p@24 16:9",
+ [33] = "1920x1080p@25 16:9",
+ [34] = "1920x1080p@30 16:9",
+ [35] = "(2880)x480p@60 4:3",
+ [36] = "(2880)x480p@60 16:9",
+ [37] = "(2880)x576p@50 4:3",
+ [38] = "(2880)x576p@50 16:9",
+ [39] = "1920x1080i@50 16:9",
+ [40] = "1920x1080i@100 16:9",
+ [41] = "1280x720p@100 16:9",
+ [42] = "720x576p@100 4:3",
+ [43] = "720x576p@100 16:9",
+ [44] = "720(1440)x576i@100 4:3",
+ [45] = "720(1440)x576i@100 16:9",
+ [46] = "1920x1080i@120 16:9",
+ [47] = "1280x720p@120 16:9",
+ [48] = "720x480p@120 4:3",
+ [49] = "720x480p@120 16:9",
+ [50] = "720(1440)x480i@120 4:3",
+ [51] = "720(1440)x480i@120 16:9",
+ [52] = "720x576p@200 4:3",
+ [53] = "720x576p@200 16:9",
+ [54] = "720(1440)x576i@200 4:3",
+ [55] = "720(1440)x576i@200 16:9",
+ [56] = "720x480p@240 4:3",
+ [57] = "720x480p@240 16:9",
+ [58] = "720(1440)x480i@240 4:3",
+ [59] = "720(1440)x480i@240 16:9",
+ [60] = "1280x720p@24 16:9",
+ [61] = "1280x720p@25 16:9",
+ [62] = "1280x720p@30 16:9",
+ [63] = "1920x1080p@120 16:9",
+ [64] = "1920x1080p@100 16:9",
+ [65] = "1280x720p@24 64:27",
+ [66] = "1280x720p@25 64:27",
+ [67] = "1280x720p@30 64:27",
+ [68] = "1280x720p@50 64:27",
+ [69] = "1280x720p@60 64:27",
+ [70] = "1280x720p@100 64:27",
+ [71] = "1280x720p@120 64:27",
+ [72] = "1920x1080p@24 64:27",
+ [73] = "1920x1080p@25 64:27",
+ [74] = "1920x1080p@30 64:27",
+ [75] = "1920x1080p@50 64:27",
+ [76] = "1920x1080p@60 64:27",
+ [77] = "1920x1080p@100 64:27",
+ [78] = "1920x1080p@120 64:27",
+ [79] = "1680x720p@24 64:27",
+ [80] = "1680x720p@25 64:27",
+ [81] = "1680x720p@30 64:27",
+ [82] = "1680x720p@50 64:27",
+ [83] = "1680x720p@60 64:27",
+ [84] = "1680x720p@100 64:27",
+ [85] = "1680x720p@120 64:27",
+ [86] = "2560x1080p@24 64:27",
+ [87] = "2560x1080p@25 64:27",
+ [88] = "2560x1080p@30 64:27",
+ [89] = "2560x1080p@50 64:27",
+ [90] = "2560x1080p@60 64:27",
+ [91] = "2560x1080p@100 64:27",
+ [92] = "2560x1080p@120 64:27",
+ [93] = "3840x2160p@24 16:9",
+ [94] = "3840x2160p@25 16:9",
+ [95] = "3840x2160p@30 16:9",
+ [96] = "3840x2160p@50 16:9",
+ [97] = "3840x2160p@60 16:9",
+ [98] = "4096x2160p@24 256:135",
+ [99] = "4096x2160p@25 256:135",
+ [100] = "4096x2160p@30 256:135",
+ [101] = "4096x2160p@50 256:135",
+ [102] = "4096x2160p@60 256:135",
+ [103] = "3840x2160p@24 64:27",
+ [104] = "3840x2160p@25 64:27",
+ [105] = "3840x2160p@30 64:27",
+ [106] = "3840x2160p@50 64:27",
+ [107] = "3840x2160p@60 64:27",
+};
+
+static void ext_cea_data(uns pos, uns max_pos)
+{
+ while (pos < max_pos)
+ {
+ byte *p = edid + pos;
+ uns type = p[0] >> 5;
+ uns len = (p[0] & 31) + 1;
+ if (pos + len > max_pos)
+ {
+ printf("!!! Truncated data block (pos=%u, type=%02x, len=%u)\n", pos, type, len);
+ break;
+ }
+
+ switch (type)
+ {
+ case 1: // Audio
+ printf("Audio formats:\n");
+ for (uns j=1; j < len; j += 3)
+ {
+ byte *q = p + j;
+ uns fmt = (q[0] >> 3) & 0x0f;
+ static const char * const fmt_names[16] = {
+ "RFU0", "LPCM", "AC-3", "MPEG1",
+ "MP3", "MPEG2", "AAC", "DTS",
+ "ATRAC", "SACD", "DD+", "DTS+HD",
+ "MLP", "DST", "WMApro", "RFU15",
+ };
+ uns chans = (q[0] & 7) + 1;
+ printf("\t%s, Channels=%u\n", fmt_names[fmt], chans);
+
+ uns rates = q[1];
+ printf("\t\tSample rates [kHz]: 192%c 176%c 96%c 88%c 48%c 44%c 32%c\n",
+ B(rates & 0x40), B(rates & 0x20), B(rates & 0x10),
+ B(rates & 0x08), B(rates & 0x04), B(rates & 0x02), B(rates & 0x01));
+
+ uns br = q[2];
+ if (fmt == 1)
+ printf("\t\tBit depths: 24%c 20%c 16%c\n", B(br & 4), B(br & 2), B(br & 1));
+ else
+ printf("\t\tMax bit rate [kHz]: %u\n", br*8);
+ }
+ break;
+ case 2: // Video
+ printf("Video formats:\n");
+ for (uns j=1; j < len; j++)
+ {
+ uns vf = p[j] & 0x7f;
+ if (vf && vf < sizeof(cea_modes) / sizeof(cea_modes[0]))
+ printf("\t%s", cea_modes[vf]);
+ else
+ printf("\t#%02x", vf);
+ if (p[j] & 0x80)
+ printf(" (native)");
+ printf("\n");
+ }
+ break;
+ case 3: // Vendor specific
+ {
+ printf("Vendor-specific: ");
+ if (len < 4)
+ {
+ printf("\t!!! Truncated\n");
+ break;
+ }
+ uns oid = p[1] | (p[2] << 8) | (p[3] << 16);
+ if (oid == 0x000c03)
+ {
+ printf("HDMI\n");
+ ext_hdmi(p, len);
+ }
+ else
+ printf("Vendor %06x\n", oid);
+ break;
+ }
+ case 4: // Speaker allocation
+ printf("Speakers present:\n");
+ if (len < 4)
+ {
+ printf("\t!!! Truncated\n");
+ break;
+ }
+ static const char * const spk_types[8] = {
+ "Front Left+Right", "LFE", "Front Center", "Rear Left+Right",
+ "Rear Center", "Front Left+Right Center", "Rear Left+Right Center", "RFU7",
+ };
+ for (uns j=0; j<8; j++)
+ if (p[1] & (1 << j))
+ printf("\t%s\n", spk_types[j]);
+ break;
+ default:
+ printf("Unknown data block #%02x (pos=%u, len=%u)\n", type, pos, len);
+ }
+
+ pos += len;
+ }
+}
+
+static void ext_cea_dtd(uns pos, uns max_pos)
+{
+ // Detailed Timing Descriptors follow the same syntax as in basic EDID
+ while (pos + 18 <= max_pos && edid[pos] && edid[pos+1])
+ {
+ show_detailed_timing(edid + pos);
+ pos += 18;
+ }
+}
+
+static void ext_cea_861(void)
+{
+ uns version = edid[1];
+ uns dtd_pos = edid[2];
+ uns flags = edid[3];
+
+ if (version >= 2)
+ printf("Flags: Underscan%c BasicAudio%c YCbCr444%c YCbCr422%c\n",
+ B(flags & 0x80),
+ B(flags & 0x40),
+ B(flags & 0x20),
+ B(flags & 0x10));
+
+ ext_cea_data(4, (dtd_pos < 127 ? dtd_pos : 127));
+ ext_cea_dtd(dtd_pos, 127);
+}
+
+struct extension {
+ const char *name;
+ void (*parser)(void);
+};
+
+static const struct extension ext_table[0x100] = {
+ [0x02] = { "CEA 861", ext_cea_861 },
+ [0x10] = { "Video Timing Block", NULL },
+ [0x40] = { "Display Information", NULL },
+ [0x50] = { "Localized Strings", NULL },
+ [0x60] = { "Digital Packet Video Link", NULL },
+ [0xf0] = { "Block map", ext_block_map },
+ [0xff] = { "Vendor-specific", NULL },
+};
+
+static void show_ext(void)
+{
+ byte ext = edid[0];
+ const struct extension *e = &ext_table[ext];
+
+ printf("\n>> Extension block %u: ", block);
+ if (e->name)
+ printf("%s", e->name);
+ else
+ printf("Extension #%02x", ext);
+ if (ext != 0xf0)
+ printf(" (version %02x)", edid[1]);
+ printf("\n\n");
+
+ check_sum();
+
+ if (e->parser)
+ e->parser();
+ else
+ printf("!!! Not decoded yet\n");
+}
+
+/*** Main loop ***/
+
static void read_binary(void)
{
int c = read(0, edid, 128);
read_xorg_log(void)
{
char line[1024];
- int mode = 0;
+ static int mode = 0;
int cnt = 0;
while (fgets(line, sizeof(line), stdin))
if (optind < argc)
usage();
- if (log_mode)
- read_xorg_log();
- else
- read_binary();
+ while (block <= num_ext_blocks)
+ {
+ if (log_mode)
+ read_xorg_log();
+ else
+ read_binary();
- show_edid();
+ if (!block)
+ show_edid();
+ else
+ show_ext();
+
+ if (hex_mode)
+ show_hex();
+ block++;
+ }
- if (hex_mode)
- show_hex();
return 0;
}