// 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[256] = {
	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[256] = {
	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
};

unsigned char test_edid3[256] = {
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
	0x4d, 0xd9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x11, 0x01, 0x03, 0x80, 0x00, 0x00, 0x78,
	0x0a, 0x0d, 0xc9, 0xa0, 0x57, 0x47, 0x98, 0x27,
	0x12, 0x48, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1d,
	0x80, 0xd0, 0x72, 0x1c, 0x16, 0x20, 0x10, 0x2c,
	0x25, 0x80, 0xc4, 0x8e, 0x21, 0x00, 0x00, 0x9e,
	0x01, 0x1d, 0x80, 0x18, 0x71, 0x1c, 0x16, 0x20,
	0x58, 0x2c, 0x25, 0x00, 0xc4, 0x8e, 0x21, 0x00,
	0x00, 0x9e, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x48,
	0x44, 0x4d, 0x49, 0x20, 0x4c, 0x4c, 0x43, 0x0a,
	0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd,
	0x00, 0x3b, 0x3d, 0x0f, 0x2d, 0x08, 0x00, 0x0a,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0xc0,
	0x02, 0x03, 0x1e, 0x47, 0x4f, 0x94, 0x13, 0x05,
	0x03, 0x04, 0x02, 0x01, 0x16, 0x15, 0x07, 0x06,
	0x11, 0x10, 0x12, 0x1f, 0x23, 0x09, 0x07, 0x01,
	0x65, 0x03, 0x0c, 0x00, 0x10, 0x00, 0x8c, 0x0a,
	0xd0, 0x90, 0x20, 0x40, 0x31, 0x20, 0x0c, 0x40,
	0x55, 0x00, 0x13, 0x8e, 0x21, 0x00, 0x00, 0x18,
	0x01, 0x1d, 0x00, 0xbc, 0x52, 0xd0, 0x1e, 0x20,
	0xb8, 0x28, 0x55, 0x40, 0xc4, 0x8e, 0x21, 0x00,
	0x00, 0x1e, 0x8c, 0x0a, 0xd0, 0x8a, 0x20, 0xe0,
	0x2d, 0x10, 0x10, 0x3e, 0x96, 0x00, 0xc4, 0x8e,
	0x21, 0x00, 0x00, 0x18, 0x01, 0x1d, 0x00, 0x72,
	0x51, 0xd0, 0x1e, 0x20, 0x6e, 0x28, 0x55, 0x00,
	0xc4, 0x8e, 0x21, 0x00, 0x00, 0x1e, 0x8c, 0x0a,
	0xd0, 0x8a, 0x20, 0xe0, 0x2d, 0x10, 0x10, 0x3e,
	0x96, 0x00, 0x13, 0x8e, 0x21, 0x00, 0x00, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb
};

unsigned char test_edid4[256] = {
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
	0x4c, 0x2d, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00,
	0x31, 0x0f, 0x01, 0x03, 0x80, 0x10, 0x09, 0x8c,
	0x0a, 0xe2, 0xbd, 0xa1, 0x5b, 0x4a, 0x98, 0x24,
	0x15, 0x47, 0x4a, 0x20, 0x00, 0x00, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1d,
	0x00, 0x72, 0x51, 0xd0, 0x1e, 0x20, 0x6e, 0x28,
	0x55, 0x00, 0xa0, 0x5a, 0x00, 0x00, 0x00, 0x1e,
	0x01, 0x1d, 0x80, 0x18, 0x71, 0x1c, 0x16, 0x20,
	0x58, 0x2c, 0x25, 0x00, 0xa0, 0x5a, 0x00, 0x00,
	0x00, 0x9e, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x3b,
	0x3d, 0x1e, 0x2e, 0x08, 0x00, 0x0a, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc,
	0x00, 0x53, 0x41, 0x4d, 0x53, 0x55, 0x4e, 0x47,
	0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x8d,
	0x02, 0x03, 0x16, 0x71, 0x43, 0x84, 0x05, 0x03,
	0x23, 0x09, 0x07, 0x07, 0x83, 0x01, 0x00, 0x00,
	0x65, 0x03, 0x0c, 0x00, 0x20, 0x00, 0x8c, 0x0a,
	0xd0, 0x8a, 0x20, 0xe0, 0x2d, 0x10, 0x10, 0x3e,
	0x96, 0x00, 0xa0, 0x5a, 0x00, 0x00, 0x00, 0x18,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30
};

unsigned char test_edid5[256] = {
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
	0x3d, 0xcb, 0x61, 0x07, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x11, 0x01, 0x03, 0x80, 0x00, 0x00, 0x78,
	0x0a, 0x0d, 0xc9, 0xa0, 0x57, 0x47, 0x98, 0x27,
	0x12, 0x48, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1d,
	0x80, 0x18, 0x71, 0x1c, 0x16, 0x20, 0x58, 0x2c,
	0x25, 0x00, 0xc4, 0x8e, 0x21, 0x00, 0x00, 0x9e,
	0x01, 0x1d, 0x80, 0xd0, 0x72, 0x1c, 0x16, 0x20,
	0x10, 0x2c, 0x25, 0x80, 0xc4, 0x8e, 0x21, 0x00,
	0x00, 0x9e, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x54,
	0x58, 0x2d, 0x53, 0x52, 0x36, 0x30, 0x35, 0x0a,
	0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd,
	0x00, 0x17, 0xf0, 0x0f, 0x7e, 0x11, 0x00, 0x0a,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x93,
	0x02, 0x03, 0x3b, 0x72, 0x55, 0x85, 0x04, 0x03,
	0x02, 0x0e, 0x0f, 0x07, 0x23, 0x24, 0x10, 0x94,
	0x13, 0x12, 0x11, 0x1d, 0x1e, 0x16, 0x25, 0x26,
	0x01, 0x1f, 0x35, 0x09, 0x7f, 0x07, 0x0f, 0x7f,
	0x07, 0x17, 0x07, 0x50, 0x3f, 0x06, 0xc0, 0x57,
	0x06, 0x00, 0x5f, 0x7e, 0x01, 0x67, 0x5e, 0x00,
	0x83, 0x4f, 0x00, 0x00, 0x66, 0x03, 0x0c, 0x00,
	0x20, 0x00, 0x80, 0x8c, 0x0a, 0xd0, 0x8a, 0x20,
	0xe0, 0x2d, 0x10, 0x10, 0x3e, 0x96, 0x00, 0xc4,
	0x8e, 0x21, 0x00, 0x00, 0x18, 0x8c, 0x0a, 0xd0,
	0x90, 0x20, 0x40, 0x31, 0x20, 0x0c, 0x40, 0x55,
	0x00, 0xc4, 0x8e, 0x21, 0x00, 0x00, 0x18, 0x01,
	0x1d, 0x00, 0x72, 0x51, 0xd0, 0x1e, 0x20, 0x6e,
	0x28, 0x55, 0x00, 0xc4, 0x8e, 0x21, 0x00, 0x00,
	0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd
};

/* Has DTD that is too wide */
unsigned char test_edid6[256] = {
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
	0x10, 0xac, 0x63, 0x40, 0x4c, 0x35, 0x31, 0x33,
	0x0c, 0x15, 0x01, 0x03, 0x80, 0x40, 0x28, 0x78,
	0xea, 0x8d, 0x85, 0xad, 0x4f, 0x35, 0xb1, 0x25,
	0x0e, 0x50, 0x54, 0xa5, 0x4b, 0x00, 0x71, 0x4f,
	0x81, 0x00, 0x81, 0x80, 0xa9, 0x40, 0xd1, 0x00,
	0xd1, 0x40, 0x01, 0x01, 0x01, 0x01, 0xe2, 0x68,
	0x00, 0xa0, 0xa0, 0x40, 0x2e, 0x60, 0x30, 0x20,
	0x36, 0x00, 0x81, 0x91, 0x21, 0x00, 0x00, 0x1a,
	0x00, 0x00, 0x00, 0xff, 0x00, 0x50, 0x48, 0x35,
	0x4e, 0x59, 0x31, 0x33, 0x4d, 0x33, 0x31, 0x35,
	0x4c, 0x0a, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x44,
	0x45, 0x4c, 0x4c, 0x20, 0x55, 0x33, 0x30, 0x31,
	0x31, 0x0a, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd,
	0x00, 0x31, 0x56, 0x1d, 0x71, 0x1c, 0x00, 0x0a,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0xb0
};

static unsigned char *test_edids[N_TEST_EDIDS] = {
	test_edid1, test_edid2, test_edid3, test_edid4, test_edid5,
	test_edid6
};

int get_test_edid(int n, unsigned char *dst)
{
	if ((n < 1) || (n > N_TEST_EDIDS))
		return -1;
	memcpy(dst, test_edids[n-1], 256);
	return 0;
}

int show_test_edid(FILE *outfile, int n)
{
	if ((n < 1) || (n > N_TEST_EDIDS))
		return -1;
	fprintf(outfile, "Test EDID %d\n", n);
	show_edid(outfile, test_edids[n-1], 1);
	return 0;
}

static void get_dtd_string(const char *str, char *buf, int buf_size)
{
	int stp;
	int len = buf_size < 14 ? buf_size : 14;

	strncpy(buf, str, len - 1);
	for (stp = 0; stp < len - 1; stp++)
		if (buf[stp] == 0x0a)
			buf[stp] = 0;
	buf[stp] = 0;
}

/* Print an edid descriptor block (standard case is at 54 + 18 * i) */
void show_edid_dtd(FILE *outfile, unsigned char *base)
{
	int pelclk = base[DTD_PCLK_LO] + (base[DTD_PCLK_HI]<<8);
	char monstr[DTD_SIZE];

	if (pelclk != 0) {
		int hres = base[DTD_HA_LO] + ((base[DTD_HABL_HI] & 0xf0)<<4);
		int hbl = base[DTD_HBL_LO] + ((base[DTD_HABL_HI] & 0x0f)<<8);
		int vres = base[DTD_VA_LO] + ((base[DTD_VABL_HI] & 0xf0)<<4);
		int vbl = base[DTD_VBL_LO] + ((base[DTD_VABL_HI] & 0x0f)<<8);
		int hso = base[DTD_HSO_LO] + ((base[DTD_HVSX_HI] & 0xc0)<<2);
		int hsw = base[DTD_HSW_LO] + ((base[DTD_HVSX_HI] & 0x30)<<4);
		int vso = (base[DTD_VSX_LO] >> 4) +
			   ((base[DTD_HVSX_HI] & 0x0c) << 2);
		int vsw = (base[DTD_VSX_LO] & 0xf) +
			   ((base[DTD_HVSX_HI] & 0x03) << 4);
		int hsiz = base[DTD_HSIZE_LO] +
			   ((base[DTD_HVSIZE_HI] & 0xf0) << 4);
		int vsiz = base[DTD_VSIZE_LO] +
			   ((base[DTD_HVSIZE_HI] & 0x0f) << 8);
		int hbdr = base[DTD_HBORDER];
		int vbdr = base[DTD_VBORDER];
		int mdflg = base[DTD_FLAGS];

		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);
		return;
	}

	switch (base[DTD_TYPETAG]) {
	case DTDTYPE_SERIAL:
	case DTDTYPE_STRING:
	case DTDTYPE_NAME:
		get_dtd_string((const char *)base + DTD_STRING,
			       monstr, DTD_SIZE);

		if (base[3] != DTDTYPE_STRING)
			fprintf(outfile, "%s: %s\n",
				(base[3] == DTDTYPE_NAME) ?
				"Name" : "Serial",
				monstr);
		else
			fprintf(outfile, "%s\n", monstr);
		break;

	case DTDTYPE_LIMITS:
		fprintf(outfile,
			"V %d - %d Hz, H %d - %d kHz, Pel <= %d MHz\n",
			base[DTD_MINV_HZ], base[DTD_MAXV_HZ],
			base[DTD_MINH_kHZ], base[DTD_MAXH_kHZ],
			base[DTD_MAXCLK_100kHZ]*10);
		break;

	default:
		fprintf(outfile,
			"Undecoded descriptor block type 0x%x\n",
			base[DTD_TYPETAG]);
		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",
};

char *uscanstr[4] = {
	"not supported",
	"always overscan",
	"always underscan",
	"supports both over- and underscan",
};

static inline void show_audio_dbc(FILE *outfile,
				  const unsigned char *edid_ext,
				  int dbc)
{
	int dbp = dbc + 1;
	int db_len = edid_ext[dbc+DBC_TAG_LENGTH] & DBC_LEN_MASK;

	while (dbp < (dbc + db_len + 1)) {
		int atype =
			(edid_ext[dbp + DBCA_FORMAT]>>3) & 0xf;
		unsigned char dbca_rate = edid_ext[dbp + DBCA_RATE];

		fprintf(outfile, "Audio: %d channels %s: ",
			(edid_ext[dbp + DBCA_FORMAT] & 0x7) + 1,
			sad_audio_type[atype]);

		if (dbca_rate & 0x40)
			fprintf(outfile, "192k ");
		if (dbca_rate & 0x20)
			fprintf(outfile, "176k ");
		if (dbca_rate & 0x10)
			fprintf(outfile, "96k ");
		if (dbca_rate & 0x08)
			fprintf(outfile, "88k ");
		if (dbca_rate & 0x04)
			fprintf(outfile, "48k ");
		if (dbca_rate & 0x02)
			fprintf(outfile, "44k ");
		if (dbca_rate & 0x01)
			fprintf(outfile, "32k ");

		if (atype == 1) {
			unsigned char dbca_info = edid_ext[dbp + DBCA_INFO];
			fprintf(outfile, "%s%s%s\n",
				(dbca_info & 0x4) ? "24-bit " : "",
				(dbca_info & 0x2) ? "20-bit " : "",
				(dbca_info & 0x1) ? "16-bit" : "");
		} else if ((atype >= 2) && (atype <= 8)) {
			fprintf(outfile, "Max %dkHz\n",
				edid_ext[dbp + DBCA_INFO] * 8);
		} else {
			fprintf(outfile, "Codec vendor flags 0x%02x\n",
				edid_ext[dbp + DBCA_INFO]);
		}

		dbp += DBCA_SIZE;
	}
}

static inline void show_vendor_dbc(FILE *outfile,
				   const unsigned char *edid_ext,
				   int dbp)
{
	if ((edid_ext[dbp + DBCVND_IEEE_LO] != 0x03) ||
	    (edid_ext[dbp + DBCVND_IEEE_MID] != 0x0C) ||
	    (edid_ext[dbp + DBCVND_IEEE_HI] != 0x00)) {
		fprintf(outfile, "Vendor block for %02x-%02x-%02x",
			edid_ext[dbp + DBCVND_IEEE_LO],
			edid_ext[dbp + DBCVND_IEEE_MID],
			edid_ext[dbp + DBCVND_IEEE_HI]);
		return;
	}

	fprintf(outfile,
		"HDMI Vendor block (CEC @0x%04x):\n"
		"Support: %s%s%s%s%s%s\n",
		edid_ext[dbp + DBCVHDMI_CEC_LO] +
		(edid_ext[dbp + DBCVHDMI_CEC_HI] << 8),
		(edid_ext[dbp + DBCVHDMI_SUPPORT] & 0x80) ? "AI " : "",
		(edid_ext[dbp + DBCVHDMI_SUPPORT] & 0x40) ? "DC_48bit " : "",
		(edid_ext[dbp + DBCVHDMI_SUPPORT] & 0x20) ? "DC_36bit " : "",
		(edid_ext[dbp + DBCVHDMI_SUPPORT] & 0x10) ? "DC_30bit " : "",
		(edid_ext[dbp + DBCVHDMI_SUPPORT] & 0x08) ? "DC_Y444 " : "",
		(edid_ext[dbp + DBCVHDMI_SUPPORT] & 0x01) ? "DVI_Dual" : "");

	if (edid_ext[dbp + DBCVHDMI_MAXTMDS_5MHz] > 0)
		fprintf(outfile, "Max TMDS Frequency %dMHz\n",
			edid_ext[dbp + DBCVHDMI_MAXTMDS_5MHz]*5);

	if (edid_ext[dbp + DBCVHDMI_LATFLAGS] & 0x80)
		fprintf(outfile, "Video latency %dms, audio latency %dms\n",
			2 * (edid_ext[dbp + DBCVHDMI_VLAT] - 1),
			2 * (edid_ext[dbp + DBCVHDMI_ALAT] - 1));

	if (edid_ext[dbp + 7] & 0x40)
		fprintf(outfile,
			"Interlaced Video latency %dms, audio latency %dms\n",
			2 * (edid_ext[dbp + DBCVHDMI_IVLAT] - 1),
			2 * (edid_ext[dbp + DBCVHDMI_IALAT] - 1));
}

static void show_extended_dbc(FILE *outfile,
			      const unsigned char *edid_ext,
			      int dbc)
{
	int dbp = dbc + 1;
	int db_len = edid_ext[dbc + DBC_TAG_LENGTH] & DBC_LEN_MASK;

	switch (edid_ext[dbp + DBC_ETAG]) {
	case DBC_ETAG_VCDB:
	{
		unsigned char vcdb_flags;

		fprintf(outfile, "Video Capabilities:\n");
		fprintf(outfile,
			"  Quantization range selectable: %s\n",
			(edid_ext[dbp + VCDB_FLAGS] & 0x40) ?
				"unknown" : "via AVI Q");

		/* PT field zero implies no data, just use IT
		 * and CE fields
		 */
		vcdb_flags = edid_ext[dbp + VCDB_FLAGS];
		if (VCDB_S_PT(vcdb_flags))
			fprintf(outfile,
				"  Preferred mode %s\n",
				uscanstr[VCDB_S_PT(vcdb_flags)]);
		fprintf(outfile, "  IT modes %s\n",
			uscanstr[VCDB_S_IT(vcdb_flags)]);
		fprintf(outfile, "  CE modes %s\n",
			uscanstr[VCDB_S_CE(vcdb_flags)]);
		break;
	}

	case DBC_ETAG_COL:
		fprintf(outfile,
			"Colorimetry supports %s%s metadata 0x%x\n",
			(edid_ext[dbp + COL_FLAGS] & 0x02) ? "HD(YCC709) " : "",
			(edid_ext[dbp + COL_FLAGS] & 0x01) ? "SD(YCC601) " : "",
			(edid_ext[dbp + COL_META] & 0x07));
		break;

	default:
		fprintf(outfile,
			"Unknown extended tag data block 0x%x,  length 0x%x\n",
			edid_ext[dbc + DBC_ETAG], db_len);
	}
}

void show_cea_timing(FILE *outfile, unsigned char *edid_ext)
{
	int i, dbc;
	int off_dtd = edid_ext[CEA_DTD_OFFSET];
	int n_dtd;
	fprintf(outfile, "Found CEA EDID Timing Extension rev 3\n");

	if (off_dtd < CEA_DBC_START) {
		fprintf(outfile, "Block is empty (off_dtd = %d)\n", off_dtd);
		return;
	}
	/* Ends with 0 and a checksum, have at least one pad byte */
	n_dtd = (CEA_LAST_PAD - off_dtd)/DTD_SIZE;
	fprintf(outfile,
		"Block has DTDs starting at offset %d (%d bytes of DBCs)\n",
		off_dtd, off_dtd - CEA_DBC_START);
	fprintf(outfile, "There is space for %d DTDs in extension\n", n_dtd);
	fprintf(outfile,
		"There are %d native DTDs (between regular and extensions)\n",
		edid_ext[CEA_NATIVE_DTDS] & 0xf);
	fprintf(outfile, "IT formats %sdefault to underscan\n",
		(edid_ext[CEA_SUPPORT] & 0x80) ? "" : "do not ");
	fprintf(outfile,
		"Support: %sbasic audio, %sYCrCb 4:4:4, %sYCrCb 4:2:2\n",
		(edid_ext[CEA_SUPPORT] & 0x40) ? "" : "no ",
		(edid_ext[CEA_SUPPORT] & 0x20) ? "" : "no ",
		(edid_ext[CEA_SUPPORT] & 0x10) ? "" : "no ");

	/* Between offset 4 and off_dtd is the Data Block Collection */
	/* There may be none, in which case off_dtd == 4             */
	dbc = CEA_DBC_START;
	while (dbc < off_dtd) {
		int db_len = edid_ext[dbc + DBC_TAG_LENGTH] & DBC_LEN_MASK;
		int dbp = dbc + 1;

		switch (edid_ext[dbc+DBC_TAG_LENGTH] >> DBC_TAG_SHIFT) {
		case DBC_TAG_AUDIO:
			/* Audio Data Block */
			show_audio_dbc(outfile, edid_ext, dbc);
			break;

		case DBC_TAG_VIDEO:
			/* Vidio Data Block */
			while (dbp < (dbc + db_len + 1)) {
				int vtype = edid_ext[dbp + DBCV_CODE] & 0x7f;
				fprintf(outfile, "Video: Code %d %s\n", vtype,
					(edid_ext[dbp + DBCV_CODE] & 0x80) ?
						"(native)" : "");
				dbp += DBCV_SIZE;
			}
			break;

		case DBC_TAG_VENDOR:
			/* Vendor Data Block */
			show_vendor_dbc(outfile, edid_ext, dbc + 1);
			break;

		case DBC_TAG_SPEAKER:
		{
			/* Speaker allocation Block */
			unsigned char dbcsp_alloc = edid_ext[dbp + DBCSP_ALLOC];

			fprintf(outfile, "Speakers: %s%s%s%s%s%s%s\n",
				(dbcsp_alloc & 0x40) ? "RearCenter L/R " : "",
				(dbcsp_alloc & 0x20) ? "FrontCenter L/R " : "",
				(dbcsp_alloc & 0x10) ? "Rear Center" : "",
				(dbcsp_alloc & 0x08) ? "Rear L/R " : "",
				(dbcsp_alloc & 0x04) ? "Front Center " : "",
				(dbcsp_alloc & 0x02) ? "LFE " : "",
				(dbcsp_alloc & 0x01) ? "Front L/R " : "");
			break;
		}

		case DBC_TAG_EXTENDED:
			show_extended_dbc(outfile, edid_ext, dbc);
			break;

		default:
			fprintf(outfile,
				"Unknown Data Block type tag 0x%x, len 0x%x\n",
				edid_ext[dbc+DBC_TAG_LENGTH] >> DBC_TAG_SHIFT,
				db_len);
			break;
		}

		dbc += db_len + 1;
	}
	for (i = 0; i < n_dtd; i++) {
		/* Find 0,0 when we hit padding */
		if ((edid_ext[off_dtd + DTD_SIZE * i + DTD_PCLK_LO] == 0) &&
		    (edid_ext[off_dtd + DTD_SIZE * i + DTD_PCLK_HI] == 0)) {
			fprintf(outfile,
				"End of DTD padding after %d DTDs\n", i);
			break;
		}
		show_edid_dtd(outfile, edid_ext + (off_dtd + DTD_SIZE * i));
	}
}


int edid_valid(const unsigned char *edid_data)
{
	return ((edid_data[EDID_HDR + 0] == 0x00) &&
		(edid_data[EDID_HDR + 1] == 0xff) &&
		(edid_data[EDID_HDR + 2] == 0xff) &&
		(edid_data[EDID_HDR + 3] == 0xff) &&
		(edid_data[EDID_HDR + 4] == 0xff) &&
		(edid_data[EDID_HDR + 5] == 0xff) &&
		(edid_data[EDID_HDR + 6] == 0xff) &&
		(edid_data[EDID_HDR + 7] == 0x00));
}

int edid_lpcm_support(const unsigned char *edid_data, int ext)
{
	const unsigned char *edid_ext = edid_data + EDID_SIZE;
	int dbc;
	int off_dtd = edid_ext[CEA_DTD_OFFSET];

	/* 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[EDID_EXT_FLAG] < 1))
		return 0;

	/* No if extension is not CEA rev 3 */
	if (!((edid_ext[EEXT_TAG] == 0x02) && (edid_ext[EEXT_REV] == 0x03)))
		return 0;

	/* If DBC block is not empty look for audio info */
	if (off_dtd <= CEA_DBC_START)
		goto done_dtd;

	/* Between offset 4 and off_dtd is the Data Block Collection */
	/* There may be none, in which case off_dtd == 4             */
	dbc = CEA_DBC_START;
	while (dbc < off_dtd) {
		int db_len = edid_ext[dbc + DBC_TAG_LENGTH] & DBC_LEN_MASK;
		int dbp = dbc + 1;
		unsigned char dbc_type;

		/* Audio Data Block, type LPCM, return bitmap of frequencies */
		dbc_type = edid_ext[dbc + DBC_TAG_LENGTH] >> DBC_TAG_SHIFT;
		if ((dbc_type == DBC_TAG_AUDIO) &&
		    (((edid_ext[dbp + DBCA_FORMAT]>>3) & 0xF) == DBCA_FMT_LPCM))
			return edid_ext[dbp + DBCA_RATE];

		dbc += db_len + 1;
	}
	/* Get here if failed to find LPCM info in DBC block */

done_dtd:
	/* Last chance is to look for Basic Audio support. Return bitmap for 32,
	 * 44.1, 48 */
	if (edid_ext[CEA_SUPPORT] & 0x40)
		return 0x7;

	return 0;
}


int edid_has_hdmi_info(const unsigned char *edid_data, int ext)
{
	const unsigned char *edid_ext = edid_data + EDID_SIZE;
	int dbc;
	int off_dtd = edid_ext[CEA_DTD_OFFSET];

	/* 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[EDID_EXT_FLAG] < 1))
		return 0;

	/* No if extension is not CEA rev 3 */
	if (!((edid_ext[EEXT_TAG] == 0x02) && (edid_ext[EEXT_REV] == 0x03)))
		return 0;

	/* No if block is empty */
	if (off_dtd < CEA_DBC_START)
		return 0;

	/* Between offset 4 and off_dtd is the Data Block Collection */
	/* There may be none, in which case off_dtd == 4             */
	dbc = CEA_DBC_START;
	while (dbc < off_dtd) {
		int db_len = edid_ext[dbc + DBC_TAG_LENGTH] & DBC_LEN_MASK;
		int dbp = dbc + 1;
		unsigned char dbc_type;

		dbc_type = edid_ext[dbc + DBC_TAG_LENGTH] >> DBC_TAG_SHIFT;
		if (dbc_type == DBC_TAG_VENDOR) {
			/* Vendor Data Block */
			if ((edid_ext[dbp + DBCVND_IEEE_LO] == 0x03) &&
			    (edid_ext[dbp + DBCVND_IEEE_MID] == 0x0C) &&
			    (edid_ext[dbp + DBCVND_IEEE_HI] == 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[EDID_VERSION];
	int edidrev = edid_data[EDID_REVISION];
	unsigned char *edid_ext;
	unsigned char edid_features;

	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[EDID_MFG_EID]>>2),
		'@' + (((edid_data[EDID_MFG_EID] & 3)<<3) +
			(edid_data[EDID_MFG_EID+1]>>5)),
		'@' + (edid_data[EDID_MFG_EID+1] & 0x1f),
		edid_data[EDID_MFG_PROD_LO] + (edid_data[EDID_MFG_PROD_HI]<<8));
	fprintf(outfile,
		"Manufactured wk %d of %d. Edid version %d.%d\n",
		edid_data[EDID_MFG_WEEK], 1990+edid_data[EDID_MFG_YEAR],
		edidver, edidrev);
	fprintf(outfile,
		"Input: %s, vid level %d, %s, %s %s %s %s sync, %dx%dcm, Gamma %f\n",
		(edid_data[EDID_VIDEO_IN] & 0x80) ? "digital" : "analog",
		(edid_data[EDID_VIDEO_IN]>>5) & 3,
		(edid_data[EDID_VIDEO_IN] * 0x10) ? "Blank to black" : "",
		(edid_data[EDID_VIDEO_IN] * 0x08) ? "Separate" : "",
		(edid_data[EDID_VIDEO_IN] * 0x04) ? "Composite" : "",
		(edid_data[EDID_VIDEO_IN] * 0x02) ? "On-green" : "",
		(edid_data[EDID_VIDEO_IN] * 0x01) ? "Serration V" : "",
		edid_data[EDID_MAX_HSIZE], edid_data[EDID_MAX_VSIZE],
		1.0+((float)edid_data[EDID_GAMMA]/100.0));

	edid_features = edid_data[EDID_FEATURES];
	fprintf(outfile, "Features: %s %s %s %s %s %s %s\n",
		(edid_features & 0x80) ? "standby" : "",
		(edid_features & 0x40) ? "suspend" : "",
		(edid_features & 0x20) ? "active-off" : "",
		(edid_features & 0x18) ? "colour" : "monochrome",
		(edid_features & 0x04) ? "std-cspace" : "non-std-cspace",
		(edid_features & 0x02) ? "preferred-timing" : "",
		(edid_features & 0x01) ? "default-GTF" : "");

	fprintf(outfile, "Established Timing:\n");
	if (edid_data[EDID_ESTTIME1] & 0x80)
		fprintf(outfile, "720x400@70\n");
	if (edid_data[EDID_ESTTIME1] & 0x40)
		fprintf(outfile, "720x400@88\n");
	if (edid_data[EDID_ESTTIME1] & 0x20)
		fprintf(outfile, "640x480@60\n");
	if (edid_data[EDID_ESTTIME1] & 0x10)
		fprintf(outfile, "640x480@67\n");
	if (edid_data[EDID_ESTTIME1] & 0x08)
		fprintf(outfile, "640x480@72\n");
	if (edid_data[EDID_ESTTIME1] & 0x04)
		fprintf(outfile, "640x480@75\n");
	if (edid_data[EDID_ESTTIME1] & 0x02)
		fprintf(outfile, "800x600@56\n");
	if (edid_data[EDID_ESTTIME1] & 0x01)
		fprintf(outfile, "800x600@60\n");
	if (edid_data[EDID_ESTTIME2] & 0x80)
		fprintf(outfile, "800x600@72\n");
	if (edid_data[EDID_ESTTIME2] & 0x40)
		fprintf(outfile, "800x600@75\n");
	if (edid_data[EDID_ESTTIME2] & 0x20)
		fprintf(outfile, "832x624@75\n");
	if (edid_data[EDID_ESTTIME2] & 0x10)
		fprintf(outfile, "1024x768i@87\n");
	if (edid_data[EDID_ESTTIME2] & 0x08)
		fprintf(outfile, "1024x768@60\n");
	if (edid_data[EDID_ESTTIME2] & 0x04)
		fprintf(outfile, "1024x768@70\n");
	if (edid_data[EDID_ESTTIME2] & 0x02)
		fprintf(outfile, "1024x768@75\n");
	if (edid_data[EDID_ESTTIME2] & 0x01)
		fprintf(outfile, "1280x1024@75\n");
	if (edid_data[EDID_MFGTIME]  & 0x80)
		fprintf(outfile, "1152x870@75\n");

	fprintf(outfile, "Standard timing:\n");
	for (i = 0; i < EDID_N_STDTIME; i++) {
		int hinfo = edid_data[EDID_STDTIMEH + 2 * i];
		int vinfo = edid_data[EDID_STDTIMEV + 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 only 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 < EDID_N_DTDS; i++)
		show_edid_dtd(outfile,
			      edid_data + (EDID_DTD_BASE + i * DTD_SIZE));
	fprintf(outfile,
		"EDID contains %d extensions\n",
		edid_data[EDID_EXT_FLAG]);

	edid_ext = edid_data + EDID_SIZE;

	if ((ext >= 1) && (edid_data[EDID_EXT_FLAG] >= 1)) {
		unsigned char eext_tag = edid_ext[EEXT_TAG];
		if ((eext_tag == 0x02) && (edid_ext[EEXT_REV] == 0x03)) {
			show_cea_timing(outfile, edid_ext);
		} else {
			char *tagtype;
			switch (eext_tag) {
			case 0x01:
				tagtype = "LCD Timings";
				break;
			case 0x02:
				tagtype = "CEA";
				break;
			case 0x20:
				tagtype = "EDID 2.0";
				break;
			case 0x30:
				tagtype = "Color Information";
				break;
			case 0x40:
				tagtype = "DVI Feature";
				break;
			case 0x50:
				tagtype = "Touch Screen Map";
				break;
			case 0xF0:
				tagtype = "Block Map";
				break;
			case 0xFF:
				tagtype = "Manufacturer";
				break;
			default:
				tagtype = "Unknown";
			}
			fprintf(outfile,
				"EDID %s ext tag 0x%02x rev 0x%02x skipped\n",
				tagtype,
				edid_ext[EEXT_TAG],
				edid_ext[EEXT_REV]);
		}
	}
}


/* Pixel counts normally round to 8 */
#define CLOSE_ENOUGH(a, b) (abs((a)-(b)) < 16)

/* These match order of defines ASPECT_x_y in edid_utils.h */
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[EDID_MAX_HSIZE];
	int vsiz = edid_data[EDID_MAX_VSIZE];
	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;
}

int edid_get_monitor_name(const unsigned char *edid_data,
			  char *buf,
			  unsigned int buf_size)
{
	int i;
	const unsigned char *dtd;

	for (i = 0; i < EDID_N_DTDS; i++) {
		dtd = edid_data + (EDID_DTD_BASE + i * DTD_SIZE);
		if (dtd[DTD_PCLK_LO] == 0x00 && dtd[DTD_PCLK_HI] == 0x00 &&
		    dtd[DTD_TYPETAG] == DTDTYPE_NAME) {
			get_dtd_string((const char *)dtd + DTD_STRING,
				       buf, buf_size);
			return 0;
		}
	}

	return -1;
}