blob: 582661086c0b911ac28ccf275eec04ffffd35846 [file] [log] [blame]
// 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;
}