code: plan9front

ref: 5622b0bbd878dbc34045cc6fd37cffa64461eabe
dir: /sys/src/cmd/aux/vga/edid.c/

View raw version
#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" : "");
	}
}