edid: add function to manually specify mode

This patch will let you to choose a favourite mode to
display, while not just taking the edid detail timing.
But not all modes are able to set, only modes that
are in established or standard timing, and we only
support a few common common resolutions for now.

BUG=chrome-os-partner:42946
BRANCH=firmware-veyron
TEST=tested dev mode on Mickey at 640x480@60Hz

Change-Id: I8a9dedfe08057d42d85b8ca129935a258cb26762
Signed-off-by: Patrick Georgi <patrick@georgi-clan.de>
Original-Commit-Id: 090583f90ff720d88e5cfe69fcb2d541c716f0e6
Original-Change-Id: Iaa8c9a6fad106ee792f7cd1a0ac77e3dcbadf481
Original-Signed-off-by: Yakir Yang <ykk@rock-chips.com>
Original-Signed-off-by: David Hendricks <dhendrix@chromium.org>
Original-Reviewed-on: https://chromium-review.googlesource.com/289671
Original-Reviewed-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: http://review.coreboot.org/11390
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
diff --git a/src/lib/edid.c b/src/lib/edid.c
index ed3565c..be3d906 100644
--- a/src/lib/edid.c
+++ b/src/lib/edid.c
@@ -977,6 +977,52 @@
 }
 
 /*
+ * Lookup table of some well-known modes that can be useful in case the
+ * auto-detected mode is unsuitable.
+ * ha = hdisplay;			va = vdisplay;
+ * hbl = htotal - hdisplay;		vbl = vtotal - vdisplay;
+ * hso = hsync_start - hdsiplay;	vso = vsync_start - vdisplay;
+ * hspw = hsync_end - hsync_start;	vspw = vsync_end - vsync_start;
+ */
+static struct edid_mode known_modes[NUM_KNOWN_MODES] = {
+	[EDID_MODE_640x480_60Hz] = {
+		.name = "640x480@60Hz", .pixel_clock = 25175, .refresh = 60,
+		.ha = 640, .hbl = 160, .hso = 16, .hborder = 96,
+		.va = 480, .vbl = 45, .vso = 10, .vspw = 2,
+		.phsync = '-', .pvsync = '-' },
+	[EDID_MODE_720x480_60Hz] = {
+		.name = "720x480@60Hz", .pixel_clock = 27000, .refresh = 60,
+		.ha = 720, .hbl = 78, .hso = 16, .hborder = 62,
+		.va = 480, .vbl = 45, .vso = 9, .vspw = 6,
+		.phsync = '-', .pvsync = '-' },
+	[EDID_MODE_1280x720_60Hz] = {
+		.name = "1280x720@60Hz", .pixel_clock = 74250, .refresh = 60,
+		.ha = 1280, .hbl = 370, .hso = 110, .hborder = 40,
+		.va = 720, .vbl = 30, .vso = 5, .vspw = 20,
+		.phsync = '+', .pvsync = '+' },
+	[EDID_MODE_1920x1080_60Hz] = {
+		.name = "1920x1080@60Hz", .pixel_clock = 148500, .refresh = 60,
+		.ha = 1920, .hbl = 280, .hso = 88, .hborder = 44,
+		.va = 1080, .vbl = 45, .vso = 4, .vspw = 5,
+		.phsync = '+', .pvsync = '+' },
+};
+
+int set_display_mode(struct edid *edid, enum edid_modes mode)
+{
+	if (mode == EDID_MODE_AUTO)
+		return 0;
+
+	if (edid->mode_is_supported[mode]) {
+		printk(BIOS_DEBUG, "Forcing mode %s\n", known_modes[mode].name);
+		edid->mode = known_modes[mode];
+		return 0;
+	}
+
+	printk(BIOS_ERR, "Requested display mode not supported.\n");
+	return -1;
+}
+
+/*
  * Given a raw edid bloc, decode it into a form
  * that other parts of coreboot can use -- mainly
  * graphics bringup functions. The raw block is
@@ -986,7 +1032,7 @@
  */
 int decode_edid(unsigned char *edid, int size, struct edid *out)
 {
-	int analog, i;
+	int analog, i, j;
 	struct edid_context c = {
 	    .has_valid_cvt = 1,
 	    .has_valid_dummy_block = 1,
@@ -1210,6 +1256,14 @@
 			printk(BIOS_SPEW, "  %dx%d@%dHz\n", established_timings[i].x,
 			       established_timings[i].y, established_timings[i].refresh);
 		}
+
+		for (j = 0; j < NUM_KNOWN_MODES; j++) {
+			if (known_modes[j].ha == established_timings[i].x &&
+				known_modes[j].va == established_timings[i].y &&
+				known_modes[j].refresh ==  established_timings[i].refresh)
+					out->mode_is_supported[j] = 1;
+		}
+
 	}
 
 	printk(BIOS_SPEW, "Standard timings supported:\n");
@@ -1245,6 +1299,11 @@
 		refresh = 60 + (b2 & 0x3f);
 
 		printk(BIOS_SPEW, "  %dx%d@%dHz\n", x, y, refresh);
+		for (j = 0; j < NUM_KNOWN_MODES; j++) {
+			if (known_modes[j].ha == x && known_modes[j].va == y &&
+					known_modes[j].refresh == refresh)
+				out->mode_is_supported[j] = 1;
+		}
 	}
 
 	/* detailed timings */