| // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #include "edid_utils.h" |
| |
| /* Dump out an EDID block in a simple format */ |
| void show_edid_data(FILE* outfile, unsigned char* edid_data, |
| int items, int base) |
| { |
| int item = 0; |
| |
| while (item < items) { |
| int i; |
| fprintf(outfile, " 0x%04x: ", item + base); |
| for(i=0; i < 16; i++) { |
| fprintf(outfile, "%02x ", edid_data[item++]); |
| if (item >= items) break; |
| } |
| fprintf(outfile, "\n"); |
| } |
| } |
| |
| |
| unsigned char test_edid1[128] = { |
| 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00, |
| 0x06,0xaf,0x5c,0x20,0x00,0x00,0x00,0x00, |
| 0x01,0x12,0x01,0x03,0x80,0x1a,0x0e,0x78, |
| 0x0a,0x99,0x85,0x95,0x55,0x56,0x92,0x28, |
| 0x22,0x50,0x54,0x00,0x00,0x00,0x01,0x01, |
| 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, |
| 0x01,0x01,0x01,0x01,0x01,0x01,0x96,0x19, |
| 0x56,0x28,0x50,0x00,0x08,0x30,0x18,0x10, |
| 0x24,0x00,0x00,0x90,0x10,0x00,0x00,0x18, |
| 0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00, |
| 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
| 0x00,0x20,0x00,0x00,0x00,0xfe,0x00,0x41, |
| 0x55,0x4f,0x0a,0x20,0x20,0x20,0x20,0x20, |
| 0x20,0x20,0x20,0x20,0x00,0x00,0x00,0xfe, |
| 0x00,0x42,0x31,0x31,0x36,0x58,0x57,0x30, |
| 0x32,0x20,0x56,0x30,0x20,0x0a,0x00,0xf8 |
| }; |
| |
| unsigned char test_edid2[128] = { |
| 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00, |
| 0x30,0xe4,0x00,0x00,0x00,0x00,0x00,0x00, |
| 0x00,0x14,0x01,0x03,0x80,0x1a,0x0e,0x78, |
| 0x0a,0xbf,0x45,0x95,0x58,0x52,0x8a,0x28, |
| 0x25,0x50,0x54,0x00,0x00,0x00,0x01,0x01, |
| 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, |
| 0x01,0x01,0x01,0x01,0x01,0x01,0x84,0x1c, |
| 0x56,0xa8,0x50,0x00,0x19,0x30,0x30,0x20, |
| 0x35,0x00,0x00,0x90,0x10,0x00,0x00,0x1b, |
| 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
| 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
| 0x00,0x00,0x00,0x00,0x00,0xfe,0x00,0x4c, |
| 0x47,0x20,0x44,0x69,0x73,0x70,0x6c,0x61, |
| 0x79,0x0a,0x20,0x20,0x00,0x00,0x00,0xfc, |
| 0x00,0x4c,0x50,0x31,0x31,0x36,0x57,0x48, |
| 0x31,0x2d,0x54,0x4c,0x4e,0x31,0x00,0x4e |
| }; |
| |
| |
| /* Print an edid descriptor block (standard case is at 54 + 18*i) */ |
| void show_edid_dtd(FILE* outfile, unsigned char* base) |
| { |
| int pelclk = base[0] + (base[1]<<8); |
| |
| if (pelclk != 0) { |
| int hres = base[2] + ((base[4] & 0xf0)<<4); |
| int hbl = base[3] + ((base[4] & 0x0f)<<8); |
| int vres = base[5] + ((base[7] & 0xf0)<<4); |
| int vbl = base[6] + ((base[7] & 0x0f)<<8); |
| int hso = base[8] + ((base[11] & 0xc0)<<2); |
| int hsw = base[9] + ((base[11] & 0x30)<<4); |
| int vso = (base[10] >> 4) + |
| ((base[11] & 0x0c)<<2); |
| int vsw = (base[10] & 0xf) + |
| ((base[11] & 0x03)<<4); |
| int hsiz = base[12] + ((base[14] & 0xf0)<<4); |
| int vsiz = base[13] + ((base[14] & 0x0f)<<8); |
| int hbdr = base[15]; |
| int vbdr = base[16]; |
| int mdflg = base[17]; |
| |
| int refr = (pelclk * 10000)/((hres+hbl)*(vres+vbl)); |
| int refm = (pelclk * 10000)%((hres+hbl)*(vres+vbl)); |
| int refd = (refm*100)/((hres+hbl)*(vres+vbl)); |
| |
| fprintf(outfile, "%dx%d%c@%d.%02d, dot clock %d %cHsync %cVsync\n", |
| hres, vres, (mdflg & 0x80) ? 'i':'p', |
| refr, refd, |
| pelclk * 10000, |
| (mdflg & 0x2) ? '+':'-', |
| (mdflg & 0x4) ? '+':'-'); |
| fprintf(outfile, "H: start %d, end %d, total %d\n", |
| hres+hso, hres+hso+hsw, hres+hbl); |
| fprintf(outfile, "V: start %d, end %d, total %d\n", |
| vres+vso, vres+vso+vsw, vres+vbl); |
| fprintf(outfile, "Size %dx%dmm, Border %dx%d pixels\n", |
| hsiz, vsiz, hbdr, vbdr); |
| } else { |
| char monstr[18]; |
| int stp; |
| switch (base[3]) { |
| |
| case 0xff: |
| case 0xfe: |
| case 0xfc: |
| strncpy(monstr, (char *)base+5, 13); |
| for(stp = 0; stp < 13; stp++) if (monstr[stp] == 0x0a) monstr[stp] = 0; |
| monstr[stp] = 0; |
| if (base[3] != 0xfe) |
| fprintf(outfile, "%s: %s\n", |
| (base[3] == 0xfc) ? "Name" : "Serial", monstr); |
| else |
| fprintf(outfile, "%s\n", monstr); |
| break; |
| |
| case 0xfd: |
| fprintf(outfile, "Range V %d - %d Hz, H %d - %d kHz, Pel <= %d MHz\n", |
| base[5], base[6], base[7], base[8], base[9]*10); |
| break; |
| |
| default: |
| fprintf(outfile, "Undecoded descriptor block type 0x%x\n", base[3]); |
| break; |
| } |
| } |
| } |
| |
| |
| char *sad_audio_type[16] = { |
| "Reserved", "LPCM", "AC-3", "MPEG1 (Layer 1 and 2)", |
| "MP3", "MPEG2", "AAC", "DTS", |
| "ATRAC", "SACD", "DD+", "DTS-HD", |
| "MLP/Dolby TrueHD", "DST Audio", "WMA Pro", "Reserved"}; |
| |
| void show_cea_timing(FILE* outfile, unsigned char* edid_ext) |
| { |
| int i, dbc; |
| int off_dtd = edid_ext[2]; |
| int n_dtd = edid_ext[3] & 0xf; |
| fprintf(outfile, "Found CEA EDID Timing Extension rev 3\n"); |
| |
| if (off_dtd < 4) { |
| fprintf(outfile, "Block is empty (off_dtd = %d, n_dtd = %d\n", |
| off_dtd, n_dtd); |
| return; |
| } |
| fprintf(outfile, |
| "Block has %d DTDs starting at offset %d (thus %d bytes of DBCs)\n", |
| n_dtd, off_dtd, off_dtd - 4); |
| fprintf(outfile, |
| "Support: %sunderscan, %sbasic audio, %sYCrCb 4:4:4, %sYCrCb 4:2:2\n", |
| (edid_ext[3] & 0x80) ? "" : "no ", |
| (edid_ext[3] & 0x40) ? "" : "no ", |
| (edid_ext[3] & 0x20) ? "" : "no ", |
| (edid_ext[3] & 0x10) ? "" : "no "); |
| |
| /* Between offset 4 and off_dtd is the Data Block Collection */ |
| /* There may be none, in which case off_dtd == 4 */ |
| dbc = 4; |
| while (dbc < off_dtd) { |
| int db_len = edid_ext[dbc] & 0x1f; |
| int dbp = dbc + 1; |
| |
| switch (edid_ext[dbc] >> 5) { |
| |
| case 1: |
| /* Audio Data Block */ |
| while (dbp < (dbc + db_len + 1)) { |
| int atype =(edid_ext[dbp]>>3) & 0xf; |
| fprintf(outfile, "Audio: %d channel %s: ", edid_ext[dbp] & 0x7, |
| sad_audio_type[atype]); |
| dbp++; |
| if (edid_ext[dbp] & 0x40) fprintf(outfile, "192k "); |
| if (edid_ext[dbp] & 0x20) fprintf(outfile, "176k "); |
| if (edid_ext[dbp] & 0x10) fprintf(outfile, "96k "); |
| if (edid_ext[dbp] & 0x08) fprintf(outfile, "88k "); |
| if (edid_ext[dbp] & 0x04) fprintf(outfile, "48k "); |
| if (edid_ext[dbp] & 0x02) fprintf(outfile, "44k "); |
| if (edid_ext[dbp] & 0x01) fprintf(outfile, "32k "); |
| dbp++; |
| if (atype == 1) |
| fprintf(outfile, "%s%s%s\n", |
| (edid_ext[dbp] & 0x4) ? "24-bit ":"", |
| (edid_ext[dbp] & 0x2) ? "20-bit ":"", |
| (edid_ext[dbp] & 0x1) ? "16-bit":""); |
| else |
| fprintf(outfile, "Max %dkHz\n", edid_ext[dbp] * 8); |
| dbp++; |
| } |
| break; |
| |
| case 2: |
| /* Vidio Data Block */ |
| while (dbp < (dbc + db_len + 1)) { |
| int vtype = edid_ext[dbp] & 0x7f; |
| fprintf(outfile, "Video: Code %d %s\n", vtype, |
| (edid_ext[dbp] & 0x80) ? "(native)":""); |
| dbp++; |
| } |
| break; |
| |
| case 3: |
| /* Vendor Data Block */ |
| if ((edid_ext[dbp+0] == 0x03) && |
| (edid_ext[dbp+1] == 0x0C) && |
| (edid_ext[dbp+2] == 0x00)) { |
| fprintf(outfile, |
| "HDMI Vendor block (CEC @0x%04x):\nSupport: %s%s%s%s%s%s\n", |
| edid_ext[dbp+3] + (edid_ext[dbp+4]<<8), |
| (edid_ext[dbp+5] & 0x80) ? "AI ":"", |
| (edid_ext[dbp+5] & 0x40) ? "DC_48bit ":"", |
| (edid_ext[dbp+5] & 0x20) ? "DC_36bit ":"", |
| (edid_ext[dbp+5] & 0x10) ? "DC_30bit ":"", |
| (edid_ext[dbp+5] & 0x08) ? "DC_Y444 ":"", |
| (edid_ext[dbp+5] & 0x01) ? "DVI_Dual":""); |
| if (edid_ext[dbp+6] > 0) |
| fprintf(outfile, "Max TMDS Frequency %dMHz\n", edid_ext[dbp+6]*5); |
| if (edid_ext[dbp+7] & 0x80) |
| fprintf(outfile, "Video latency %dms, audio latency %dms\n", |
| 2 * (edid_ext[dbp+8] - 1), 2 * (edid_ext[dbp+9] - 1)); |
| if (edid_ext[dbp+7] & 0x40) |
| fprintf(outfile, |
| "Interlaced Video latency %dms, audio latency %dms\n", |
| 2 * (edid_ext[dbp+10] - 1), 2 * (edid_ext[dbp+11] - 1)); |
| } |
| else |
| fprintf(outfile, "Vendor block for %02x-%02x-%02x", |
| edid_ext[dbp+0], edid_ext[dbp+1], edid_ext[dbp+2]); |
| break; |
| |
| case 4: |
| /* Speaker allocation Block */ |
| fprintf(outfile, "Speakers: %s%s%s%s%s%s%s\n", |
| (edid_ext[dbp+0] & 0x40) ? "RearCenter L/R ":"", |
| (edid_ext[dbp+0] & 0x20) ? "FrontCenter L/R ":"", |
| (edid_ext[dbp+0] & 0x10) ? "Rear Center":"", |
| (edid_ext[dbp+0] & 0x08) ? "Rear L/R ":"", |
| (edid_ext[dbp+0] & 0x04) ? "Front Center ":"", |
| (edid_ext[dbp+0] & 0x02) ? "LFE ":"", |
| (edid_ext[dbp+0] & 0x01) ? "Front L/R ":""); |
| break; |
| |
| default: |
| fprintf(outfile, "Unknown Data Block with type tag 0x%x, length 0x%x\n", |
| edid_ext[dbc] >> 5, db_len); |
| break; |
| } |
| dbc += db_len + 1; |
| } |
| for(i=0; i < n_dtd; i++) |
| show_edid_dtd(outfile, edid_ext + (off_dtd + 18*i)); |
| } |
| |
| |
| int edid_valid(unsigned char *edid_data) |
| { |
| return ((edid_data[0]==0x00) && (edid_data[1]==0xff) && |
| (edid_data[2]==0xff) && (edid_data[3]==0xff) && |
| (edid_data[4]==0xff) && (edid_data[5]==0xff) && |
| (edid_data[6]==0xff) && (edid_data[7]==0x00)); |
| } |
| |
| int edid_lpcm_support(unsigned char *edid_data, int ext) |
| { |
| unsigned char* edid_ext = edid_data + 128; |
| int dbc; |
| int off_dtd = edid_ext[2]; |
| |
| /* No if no extension, which can happen for two reasons */ |
| /* a) ext < 1 indicates no data was read into the extension area */ |
| /* b) edid_data[126] < 1 indicates EDID does not use extension area */ |
| if ((ext < 1) || (edid_data[126] < 1)) |
| return 0; |
| |
| /* No if extension is not CEA rev 3 */ |
| if (!((edid_ext[0] == 0x02) && (edid_ext[1] == 0x03))) |
| return 0; |
| |
| /* No if block is empty */ |
| if (off_dtd < 4) |
| return 0; |
| |
| /* Between offset 4 and off_dtd is the Data Block Collection */ |
| /* There may be none, in which case off_dtd == 4 */ |
| dbc = 4; |
| while (dbc < off_dtd) { |
| int db_len = edid_ext[dbc] & 0x1f; |
| int dbp = dbc + 1; |
| |
| if (((edid_ext[dbc] >> 5) == 1) && |
| (((edid_ext[dbp]>>3) & 0xF) == 1)) { |
| /* Audio Data Block, type LPCM, return bitmap of frequencies */ |
| return edid_ext[dbp+1]; |
| } |
| dbc += db_len + 1; |
| } |
| return 0; |
| } |
| |
| |
| int edid_has_hdmi_info(unsigned char *edid_data, int ext) |
| { |
| unsigned char* edid_ext = edid_data + 128; |
| int dbc; |
| int off_dtd = edid_ext[2]; |
| |
| /* No if no extension, which can happen for two reasons */ |
| /* a) ext < 1 indicates no data was read into the extension area */ |
| /* b) edid_data[126] < 1 indicates EDID does not use extension area */ |
| if ((ext < 1) || (edid_data[126] < 1)) |
| return 0; |
| |
| /* No if extension is not CEA rev 3 */ |
| if (!((edid_ext[0] == 0x02) && (edid_ext[1] == 0x03))) |
| return 0; |
| |
| /* No if block is empty */ |
| if (off_dtd < 4) |
| return 0; |
| |
| /* Between offset 4 and off_dtd is the Data Block Collection */ |
| /* There may be none, in which case off_dtd == 4 */ |
| dbc = 4; |
| while (dbc < off_dtd) { |
| int db_len = edid_ext[dbc] & 0x1f; |
| int dbp = dbc + 1; |
| |
| if ((edid_ext[dbc] >> 5) == 3) { |
| /* Vendor Data Block */ |
| if ((edid_ext[dbp+0] == 0x03) && |
| (edid_ext[dbp+1] == 0x0C) && |
| (edid_ext[dbp+2] == 0x00)) |
| return 1; |
| } |
| dbc += db_len + 1; |
| } |
| return 0; |
| } |
| |
| /* Print out an EDID */ |
| void show_edid(FILE *outfile, unsigned char *edid_data, int ext) |
| { |
| int i; |
| int edidver = edid_data[18]; |
| int edidrev = edid_data[19]; |
| unsigned char* edid_ext; |
| |
| if (!edid_valid(edid_data)) { |
| fprintf(outfile, "Block does not contain EDID header\n"); |
| return; |
| } |
| /* unsigned edid_data so the right shifts pull in zeros */ |
| fprintf(outfile, "Manufacturer ID %c%c%c, product ID 0x%x\n", |
| '@' + (edid_data[8]>>2), |
| '@' + ((edid_data[8] & 3)<<3) + (edid_data[9]>>5), |
| '@' + (edid_data[9] & 0x1f), |
| edid_data[10] + (edid_data[11]<<8)); |
| fprintf(outfile, "Manufactured wk %d of %d. Edid version %d.%d\n", |
| edid_data[16], 1990+edid_data[17], edidver, edidrev); |
| fprintf(outfile, |
| "Input: %s, vid level %d, %s, %s %s %s %s sync, %dx%dcm, Gamma %f\n", |
| (edid_data[20] & 0x80) ? "digital" : "analog", |
| (edid_data[20]>>5) & 3, |
| (edid_data[20] * 0x10) ? "Blank to black" : "", |
| (edid_data[20] * 0x08) ? "Separate" : "", |
| (edid_data[20] * 0x04) ? "Composite" : "", |
| (edid_data[20] * 0x02) ? "On-green" : "", |
| (edid_data[20] * 0x01) ? "Serration V" : "", |
| edid_data[21], edid_data[22], 1.0+((float)edid_data[23]/100.0)); |
| fprintf(outfile, "Features: %s %s %s %s %s %s %s\n", |
| (edid_data[24] & 0x80) ? "standby" : "", |
| (edid_data[24] & 0x40) ? "suspend" : "", |
| (edid_data[24] & 0x20) ? "active-off" : "", |
| (edid_data[24] & 0x18) ? "colour" : "monochrome", |
| (edid_data[24] & 0x04) ? "std-cspace" : "non-std-cspace", |
| (edid_data[24] & 0x02) ? "preferred-timing" : "", |
| (edid_data[24] & 0x02) ? "default-GTF" : ""); |
| |
| fprintf(outfile, "Established Timing:\n"); |
| if (edid_data[35] & 0x80) fprintf(outfile, "720x400@70\n"); |
| if (edid_data[35] & 0x40) fprintf(outfile, "720x400@88\n"); |
| if (edid_data[35] & 0x20) fprintf(outfile, "640x480@60\n"); |
| if (edid_data[35] & 0x10) fprintf(outfile, "640x480@67\n"); |
| if (edid_data[35] & 0x08) fprintf(outfile, "640x480@72\n"); |
| if (edid_data[35] & 0x04) fprintf(outfile, "640x480@75\n"); |
| if (edid_data[35] & 0x02) fprintf(outfile, "800x600@56\n"); |
| if (edid_data[35] & 0x01) fprintf(outfile, "800x600@60\n"); |
| if (edid_data[36] & 0x80) fprintf(outfile, "800x600@72\n"); |
| if (edid_data[36] & 0x40) fprintf(outfile, "800x600@75\n"); |
| if (edid_data[36] & 0x20) fprintf(outfile, "832x624@75\n"); |
| if (edid_data[36] & 0x10) fprintf(outfile, "1024x768i@87\n"); |
| if (edid_data[36] & 0x08) fprintf(outfile, "1024x768@60\n"); |
| if (edid_data[36] & 0x04) fprintf(outfile, "1024x768@70\n"); |
| if (edid_data[36] & 0x02) fprintf(outfile, "1024x768@75\n"); |
| if (edid_data[36] & 0x01) fprintf(outfile, "1280x1024@75\n"); |
| if (edid_data[36] & 0x80) fprintf(outfile, "1152x870@75\n"); |
| |
| fprintf(outfile, "Standard timing:\n"); |
| for(i=0; i < 8; i++) { |
| int hinfo = edid_data[38+2*i]; |
| int vinfo = edid_data[39+2*i]; |
| int hres, vres; |
| |
| /* 01 01 is pad by spec, but 00 00 and 20 20 are see in wild */ |
| if (((hinfo == 0x01) && (vinfo == 0x01)) || |
| ((hinfo == 0x00) && (vinfo == 0x00)) || |
| ((hinfo == 0x20) && (vinfo == 0x20))) |
| continue; |
| hres = (hinfo * 8) + 248; |
| switch (vinfo >> 6) { |
| case ASPECT_16_10: |
| vres = (hres * 10)/16; |
| break; |
| case ASPECT_4_3: |
| vres = (hres * 3)/4; |
| break; |
| case ASPECT_5_4: |
| vres = (hres * 4)/5; |
| break; |
| case ASPECT_16_9: |
| vres = (hres * 9)/16; |
| break; |
| /* Default will only be hit if compiler broken */ |
| default: |
| vres = 0; |
| } |
| fprintf(outfile, "%d: %dx%d@%d\n", i, hres, vres, 60 + (vinfo & 0x3f)); |
| } |
| |
| fprintf(outfile, "Descriptor blocks:\n"); |
| for(i = 0; i < 4; i++) { |
| show_edid_dtd(outfile, edid_data + (54 + i * 18)); |
| } |
| fprintf(outfile, "EDID contains %d extensions\n", edid_data[126]); |
| |
| edid_ext = edid_data + 128; |
| |
| if ((ext >= 1) && (edid_data[126] >= 1)) { |
| if ((edid_ext[0] == 0x02) && (edid_ext[1] == 0x03)) { |
| show_cea_timing(outfile, edid_ext); |
| |
| } else |
| fprintf(outfile, "EDID extension tag 0x%02x rev 0x%02x not known\n", |
| edid_data[128], edid_data[129]); |
| |
| } |
| } |
| |
| |
| /* Pixel counts normally round to 8 */ |
| #define CLOSE_ENOUGH(a, b) (abs((a)-(b)) < 16) |
| |
| char *aspect_to_str[]={"16:10","4:3","5:4","16:9"}; |
| |
| int find_aspect(int h, int v) |
| { |
| if (CLOSE_ENOUGH((h * 3), (v * 4))) |
| return ASPECT_4_3; |
| if (CLOSE_ENOUGH((h * 4), (v * 5))) |
| return ASPECT_5_4; |
| if (CLOSE_ENOUGH((h * 9), (v * 16))) |
| return ASPECT_16_9; |
| if (CLOSE_ENOUGH((h * 10), (v * 16))) |
| return ASPECT_16_10; |
| |
| return -1; |
| } |
| |
| int find_aspect_fromisize(unsigned char *edid_data) |
| { |
| int hsiz = edid_data[21]; |
| int vsiz = edid_data[22]; |
| int res; |
| |
| /* Zero size for projector */ |
| /* Only use this code if there was no preferred resolution */ |
| /* So assume it is an older 4:3 projector not a video one */ |
| if ((hsiz == 0) && (vsiz == 0)) |
| return ASPECT_4_3; |
| |
| res = find_aspect(hsiz, vsiz); |
| |
| /* If things didn't work out, assume the old 4:3 case */ |
| if (res < 0) |
| return ASPECT_4_3; |
| else |
| return res; |
| } |
| |