ref: 9ced712a2ad70557a8ceb89ff3b969b92c85f68c
dir: /sys/src/cmd/aux/vga/edid.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <ndb.h> #include "pci.h" #include "vga.h" static uchar magic[8] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; static void addmode(Modelist **l, Mode m) { Modelist *ll; int rr; rr = (m.frequency+m.ht*m.vt/2)/(m.ht*m.vt); snprint(m.name, sizeof m.name, "%dx%d@%dHz", m.x, m.y, rr); for(ll = *l; ll != nil; ll = *l){ if(strcmp(ll->name, m.name) == 0) return; l = &ll->next; } ll = alloc(sizeof(Modelist)); ll->Mode = m; *l = ll; } /* * Parse VESA EDID information. Based on the VESA * Extended Display Identification Data standard, Version 3, * November 13, 1997. See /public/doc/vesa/edidv3.pdf. * * This only handles 128-byte EDID blocks. Until I find * a monitor that produces 256-byte blocks, I'm not going * to try to decode them. */ /* * Established timings block. There is a bitmap * that says whether each mode is supported. Most * of these have VESA definitions. Those that don't are marked * as such, and we ignore them (the lookup fails). */ static char *estabtime[] = { "720x400@70Hz", /* non-VESA: IBM, VGA */ "720x400@88Hz", /* non-VESA: IBM, XGA2 */ "640x480@60Hz", "640x480@67Hz", /* non-VESA: Apple, Mac II */ "640x480@72Hz", "640x480@75Hz", "800x600@56Hz", "800x600@60Hz", "800x600@72Hz", "800x600@75Hz", "832x624@75Hz", /* non-VESA: Apple, Mac II */ "1024x768i@87Hz", /* non-VESA: IBM */ "1024x768@60Hz", "1024x768@70Hz", "1024x768@75Hz", "1280x1024@75Hz", "1152x870@75Hz", /* non-VESA: Apple, Mac II */ }; /* * Decode the EDID detailed timing block. See pp. 20-21 of the standard. */ static int decodedtb(Mode *m, uchar *p) { int ha, hb, hso, hspw, rr, va, vb, vso, vspw; /* int hbord, vbord, dxmm, dymm, hbord, vbord; */ memset(m, 0, sizeof *m); m->frequency = ((p[1]<<8) | p[0]) * 10000; ha = ((p[4] & 0xF0)<<4) | p[2]; /* horizontal active */ hb = ((p[4] & 0x0F)<<8) | p[3]; /* horizontal blanking */ va = ((p[7] & 0xF0)<<4) | p[5]; /* vertical active */ vb = ((p[7] & 0x0F)<<8) | p[6]; /* vertical blanking */ hso = ((p[11] & 0xC0)<<2) | p[8]; /* horizontal sync offset */ hspw = ((p[11] & 0x30)<<4) | p[9]; /* horizontal sync pulse width */ vso = ((p[11] & 0x0C)<<2) | ((p[10] & 0xF0)>>4); /* vertical sync offset */ vspw = ((p[11] & 0x03)<<4) | (p[10] & 0x0F); /* vertical sync pulse width */ /* dxmm = (p[14] & 0xF0)<<4) | p[12]; /* horizontal image size (mm) */ /* dymm = (p[14] & 0x0F)<<8) | p[13]; /* vertical image size (mm) */ /* hbord = p[15]; /* horizontal border (pixels) */ /* vbord = p[16]; /* vertical border (pixels) */ m->x = ha; m->y = va; m->ht = ha+hb; m->shb = ha+hso; m->ehb = ha+hso+hspw; m->vt = va+vb; m->vrs = va+vso; m->vre = va+vso+vspw; if(p[17] & 0x80) /* interlaced */ m->interlace = 'v'; if(p[17] & 0x60) /* some form of stereo monitor mode; no support */ return -1; /* * Sync signal description. I have no idea how to properly handle the * first three cases, which I think are aimed at things other than * canonical SVGA monitors. */ switch((p[17] & 0x18)>>3) { case 0: /* analog composite sync signal*/ case 1: /* bipolar analog composite sync signal */ /* p[17] & 0x04 means serration: hsync during vsync */ /* p[17] & 0x02 means sync pulse appears on RGB not just G */ break; case 2: /* digital composite sync signal */ /* p[17] & 0x04 means serration: hsync during vsync */ /* p[17] & 0x02 means hsync positive outside vsync */ break; case 3: /* digital separate sync signal; the norm */ m->vsync = (p[17] & 0x04) ? '+' : '-'; m->hsync = (p[17] & 0x02) ? '+' : '-'; break; } /* p[17] & 0x01 is another stereo bit, only referenced if p[17] & 0x60 != 0 */ rr = (m->frequency+m->ht*m->vt/2) / (m->ht*m->vt); snprint(m->name, sizeof m->name, "%dx%d@%dHz", m->x, m->y, rr); return 0; } static int vesalookup(Mode *m, char *name) { Mode **p; for(p=vesamodes; *p; p++) if(strcmp((*p)->name, name) == 0) { *m = **p; return 0; } return -1; } static int decodesti(Mode *m, uchar *p) { int x, y, rr; char str[20]; x = (p[0]+31)*8; switch((p[1]>>6) & 3){ default: case 0: y = x; break; case 1: y = (x*4)/3; break; case 2: y = (x*5)/4; break; case 3: y = (x*16)/9; break; } rr = (p[1] & 0x1F) + 60; sprint(str, "%dx%d@%dHz", x, y, rr); return vesalookup(m, str); } uchar* edidshift(uchar buf[256]) { uchar tmp[263]; int i = 256; if(memcmp(buf, magic, 8) == 0) return buf; /* * Some devices (e.g., igfx) access address space which has * wrapped values, so shift if needed. Comparing and copying is * easier by extending temp buffer slightly. */ memcpy(tmp, buf, 256); memcpy(tmp+256, buf, 7); while(--i > 0) if(memcmp(tmp+i, magic, 8) == 0){ trace("magic begins at index %d, shifting\n", i); memcpy(buf, tmp+i, 256-i); memcpy(buf+(256-i), tmp, i); break; } trace("edid:\n"); for(i=0; i<256; i++){ trace("\t%x", buf[i]); if ( (i+1) % 16 == 0) trace("\n"); } return buf; } Edid* parseedid128(void *v) { uchar *p, *q, sum; int dpms, estab, i, m, vid; Mode mode; Edid *e; e = alloc(sizeof(Edid)); p = (uchar*)v; if(memcmp(p, magic, 8) != 0) { free(e); werrstr("bad edid header"); return nil; } sum = 0; for(i=0; i<128; i++) sum += p[i]; if(sum != 0) { free(e); werrstr("bad edid checksum"); return nil; } p += 8; assert(p == (uchar*)v+8); /* assertion offsets from pp. 12-13 of the standard */ /* * Manufacturer name is three 5-bit ascii letters, packed * into a big endian [sic] short in big endian order. The high bit is unused. */ i = (p[0]<<8) | p[1]; p += 2; e->mfr[0] = 'A'-1 + ((i>>10) & 0x1F); e->mfr[1] = 'A'-1 + ((i>>5) & 0x1F); e->mfr[2] = 'A'-1 + (i & 0x1F); e->mfr[3] = '\0'; /* * Product code is a little endian short. */ e->product = (p[1]<<8) | p[0]; p += 2; /* * Serial number is a little endian long, 0x01010101 = unused. */ e->serial = (p[3]<<24) | (p[2]<<16) | (p[1]<<8) | p[0]; p += 4; if(e->serial == 0x01010101) e->serial = 0; e->mfrweek = *p++; e->mfryear = 1990 + *p++; assert(p == (uchar*)v+8+10); /* * Structure version is next two bytes: major.minor. */ e->version = *p++; e->revision = *p++; assert(p == (uchar*)v+8+10+2); /* * Basic display parameters / features. */ /* * Video input definition byte: 0x80 tells whether it is * an analog or digital screen; we ignore the other bits. * See p. 15 of the standard. */ vid = *p++; if(vid & 0x80) e->flags |= Fdigital; e->dxcm = *p++; e->dycm = *p++; e->gamma = 100 + *p++; dpms = *p++; if(dpms & 0x80) e->flags |= Fdpmsstandby; if(dpms & 0x40) e->flags |= Fdpmssuspend; if(dpms & 0x20) e->flags |= Fdpmsactiveoff; if((dpms & 0x18) == 0x00) e->flags |= Fmonochrome; if(dpms & 0x01) e->flags |= Fgtf; assert(p == (uchar*)v+8+10+2+5); /* * Color characteristics currently ignored. */ p += 10; assert(p == (uchar*)v+8+10+2+5+10); /* * Timing information priority order (EDID 1.3 section 5) * 1. Preferred Timing Mode (first detailed timing block) * 2. Other Detailed Timing Mode, in order listed * 3. Standard Timings, in order listed * 4. Established Timings */ /* * Detailed Timings */ p = (uchar*)v+8+10+2+5+10+3+16; for(i=0; i<4; i++, p+=18) if(p[0] || p[1]) /* detailed timing block: p[0] or p[1] != 0 */ if(decodedtb(&mode, p) == 0) addmode(&e->modelist, mode); assert(p == (uchar*)v+8+10+2+5+10+3+16+72); /* * Standard Timing Identifications: eight 2-byte selectors * of more standard timings. */ p = (uchar*)v+8+10+2+5+10+3; for(i=0; i<8; i++, p+=2) if(decodesti(&mode, p+2*i) == 0) addmode(&e->modelist, mode); assert(p == (uchar*)v+8+10+2+5+10+3+16); p = (uchar*)v+8+10+2+5+10+3+16; for(i=0; i<4; i++, p+=18){ if(p[0] || p[1]) continue; /* monitor descriptor block */ switch(p[3]) { case 0xFF: /* monitor serial number (13-byte ascii, 0A terminated) */ if(q = memchr(p+5, 0x0A, 13)) *q = '\0'; memset(e->serialstr, 0, sizeof(e->serialstr)); strncpy(e->serialstr, (char*)p+5, 13); break; case 0xFE: /* ascii string (13-byte ascii, 0A terminated) */ break; case 0xFD: /* monitor range limits */ e->rrmin = p[5]; e->rrmax = p[6]; e->hrmin = p[7]*1000; e->hrmax = p[8]*1000; if(p[9] != 0xFF) e->pclkmax = p[9]*10*1000000; break; case 0xFC: /* monitor name (13-byte ascii, 0A terminated) */ if(q = memchr(p+5, 0x0A, 13)) *q = '\0'; memset(e->name, 0, sizeof(e->name)); strncpy(e->name, (char*)p+5, 13); break; case 0xFB: /* extra color point data */ break; case 0xFA: /* extra standard timing identifications */ for(i=0; i<6; i++) if(decodesti(&mode, p+5+2*i) == 0) addmode(&e->modelist, mode); break; } } assert(p == (uchar*)v+8+10+2+5+10+3+16+72); /* * Established timings: a bitmask of 19 preset timings. */ p = (uchar*)v+8+10+2+5+10; estab = (p[0]<<16) | (p[1]<<8) | p[2]; p += 3; for(i=0, m=1<<23; i<nelem(estabtime); i++, m>>=1) if(estab & m) if(vesalookup(&mode, estabtime[i]) == 0) addmode(&e->modelist, mode); assert(p == (uchar*)v+8+10+2+5+10+3); return e; } Flag edidflags[] = { Fdigital, "digital", Fdpmsstandby, "standby", Fdpmssuspend, "suspend", Fdpmsactiveoff, "activeoff", Fmonochrome, "monochrome", Fgtf, "gtf", 0 }; void printflags(Flag *f, int b) { int i; for(i=0; f[i].bit; i++) if(f[i].bit & b) Bprint(&stdout, " %s", f[i].desc); Bprint(&stdout, "\n"); } void printedid(Edid *e) { Modelist *l; printitem("edid", "mfr"); Bprint(&stdout, "%s\n", e->mfr); printitem("edid", "serialstr"); Bprint(&stdout, "%s\n", e->serialstr); printitem("edid", "name"); Bprint(&stdout, "%s\n", e->name); printitem("edid", "product"); Bprint(&stdout, "%d\n", e->product); printitem("edid", "serial"); Bprint(&stdout, "%lud\n", e->serial); printitem("edid", "version"); Bprint(&stdout, "%d.%d\n", e->version, e->revision); printitem("edid", "mfrdate"); Bprint(&stdout, "%d.%d\n", e->mfryear, e->mfrweek); printitem("edid", "size (cm)"); Bprint(&stdout, "%dx%d\n", e->dxcm, e->dycm); printitem("edid", "gamma"); Bprint(&stdout, "%.2f\n", e->gamma/100.); printitem("edid", "vert (Hz)"); Bprint(&stdout, "%d-%d\n", e->rrmin, e->rrmax); printitem("edid", "horz (Hz)"); Bprint(&stdout, "%d-%d\n", e->hrmin, e->hrmax); printitem("edid", "pclkmax"); Bprint(&stdout, "%lud\n", e->pclkmax); printitem("edid", "flags"); printflags(edidflags, e->flags); for(l=e->modelist; l; l=l->next){ printitem("edid", l->name); Bprint(&stdout, "\n\t\tclock=%g\n", l->frequency/1.e6); Bprint(&stdout, "\t\tshb=%d ehb=%d ht=%d\n", l->shb, l->ehb, l->ht); Bprint(&stdout, "\t\tvrs=%d vre=%d vt=%d\n", l->vrs, l->vre, l->vt); Bprint(&stdout, "\t\thsync=%c vsync=%c %s\n", l->hsync?l->hsync:'?', l->vsync?l->vsync:'?', l->interlace?"interlace=v" : ""); } }