pci: Add general support for execution of video ROMs

Some platforms don't have native code for dealing with their video
hardware. In some cases they use a binary blob to set it up and perform
required actions like setting the video mode. This approach is a hangover
from the old PC days where a ROM was provided and executed during startup.

Even now, these ROMs are supplied as a way to set up video. It avoids the
code for every video chip needing to be provided in the boot loader. But
it makes the video much less flexible - e.g. it is not possible to do
anything else while the video init is happening (including waiting hundreds
of milliseconds for display panels to start up).

In any case, to deal with this sad state of affairs, provide an API for
execution of x86 video ROMs, either natively or through emulation.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index e73a4986..55d6a9b 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -6,7 +6,7 @@
 #
 
 obj-$(CONFIG_FSL_PCI_INIT) += fsl_pci_init.o
-obj-$(CONFIG_PCI) += pci.o pci_auto.o
+obj-$(CONFIG_PCI) += pci.o pci_auto.o pci_rom.o
 obj-$(CONFIG_PCI_INDIRECT_BRIDGE) += pci_indirect.o
 obj-$(CONFIG_PCI_GT64120) += pci_gt64120.o
 obj-$(CONFIG_PCI_MSC01) += pci_msc01.o
diff --git a/drivers/pci/pci_rom.c b/drivers/pci/pci_rom.c
new file mode 100644
index 0000000..af6a3ae
--- /dev/null
+++ b/drivers/pci/pci_rom.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2014 Google, Inc
+ *
+ * From coreboot, originally based on the Linux kernel (drivers/pci/pci.c).
+ *
+ * Modifications are:
+ * Copyright (C) 2003-2004 Linux Networx
+ * (Written by Eric Biederman <ebiederman@lnxi.com> for Linux Networx)
+ * Copyright (C) 2003-2006 Ronald G. Minnich <rminnich@gmail.com>
+ * Copyright (C) 2004-2005 Li-Ta Lo <ollie@lanl.gov>
+ * Copyright (C) 2005-2006 Tyan
+ * (Written by Yinghai Lu <yhlu@tyan.com> for Tyan)
+ * Copyright (C) 2005-2009 coresystems GmbH
+ * (Written by Stefan Reinauer <stepan@coresystems.de> for coresystems GmbH)
+ *
+ * PCI Bus Services, see include/linux/pci.h for further explanation.
+ *
+ * Copyright 1993 -- 1997 Drew Eckhardt, Frederic Potter,
+ * David Mosberger-Tang
+ *
+ * Copyright 1997 -- 1999 Martin Mares <mj@atrey.karlin.mff.cuni.cz>
+
+ * SPDX-License-Identifier:	GPL-2.0
+ */
+
+#include <common.h>
+#include <bios_emul.h>
+#include <errno.h>
+#include <malloc.h>
+#include <pci.h>
+#include <pci_rom.h>
+#include <vbe.h>
+#include <video_fb.h>
+
+#ifdef CONFIG_HAVE_ACPI_RESUME
+#include <asm/acpi.h>
+#endif
+
+__weak bool board_should_run_oprom(pci_dev_t dev)
+{
+	return true;
+}
+
+static bool should_load_oprom(pci_dev_t dev)
+{
+#ifdef CONFIG_HAVE_ACPI_RESUME
+	if (acpi_get_slp_type() == 3)
+		return false;
+#endif
+	if (IS_ENABLED(CONFIG_ALWAYS_LOAD_OPROM))
+		return 1;
+	if (board_should_run_oprom(dev))
+		return 1;
+
+	return 0;
+}
+
+__weak uint32_t board_map_oprom_vendev(uint32_t vendev)
+{
+	return vendev;
+}
+
+static int pci_rom_probe(pci_dev_t dev, uint class,
+			 struct pci_rom_header **hdrp)
+{
+	struct pci_rom_header *rom_header;
+	struct pci_rom_data *rom_data;
+	u16 vendor, device;
+	u32 vendev;
+	u32 mapped_vendev;
+	u32 rom_address;
+
+	pci_read_config_word(dev, PCI_VENDOR_ID, &vendor);
+	pci_read_config_word(dev, PCI_DEVICE_ID, &device);
+	vendev = vendor << 16 | device;
+	mapped_vendev = board_map_oprom_vendev(vendev);
+	if (vendev != mapped_vendev)
+		debug("Device ID mapped to %#08x\n", mapped_vendev);
+
+#ifdef CONFIG_X86_OPTION_ROM_ADDR
+	rom_address = CONFIG_X86_OPTION_ROM_ADDR;
+#else
+	pci_write_config_dword(dev, PCI_ROM_ADDRESS, (u32)PCI_ROM_ADDRESS_MASK);
+	pci_read_config_dword(dev, PCI_ROM_ADDRESS, &rom_address);
+	if (rom_address == 0x00000000 || rom_address == 0xffffffff) {
+		debug("%s: rom_address=%x\n", __func__, rom_address);
+		return -ENOENT;
+	}
+
+	/* Enable expansion ROM address decoding. */
+	pci_write_config_dword(dev, PCI_ROM_ADDRESS,
+			       rom_address | PCI_ROM_ADDRESS_ENABLE);
+#endif
+	debug("Option ROM address %x\n", rom_address);
+	rom_header = (struct pci_rom_header *)rom_address;
+
+	debug("PCI expansion ROM, signature %#04x, INIT size %#04x, data ptr %#04x\n",
+	      le32_to_cpu(rom_header->signature),
+	      rom_header->size * 512, le32_to_cpu(rom_header->data));
+
+	if (le32_to_cpu(rom_header->signature) != PCI_ROM_HDR) {
+		printf("Incorrect expansion ROM header signature %04x\n",
+		       le32_to_cpu(rom_header->signature));
+		return -EINVAL;
+	}
+
+	rom_data = (((void *)rom_header) + le32_to_cpu(rom_header->data));
+
+	debug("PCI ROM image, vendor ID %04x, device ID %04x,\n",
+	      rom_data->vendor, rom_data->device);
+
+	/* If the device id is mapped, a mismatch is expected */
+	if ((vendor != rom_data->vendor || device != rom_data->device) &&
+	    (vendev == mapped_vendev)) {
+		printf("ID mismatch: vendor ID %04x, device ID %04x\n",
+		       rom_data->vendor, rom_data->device);
+		return -EPERM;
+	}
+
+	debug("PCI ROM image, Class Code %04x%02x, Code Type %02x\n",
+	      rom_data->class_hi, rom_data->class_lo, rom_data->type);
+
+	if (class != ((rom_data->class_hi << 8) | rom_data->class_lo)) {
+		debug("Class Code mismatch ROM %08x, dev %08x\n",
+		      (rom_data->class_hi << 8) | rom_data->class_lo,
+		      class);
+	}
+	*hdrp = rom_header;
+
+	return 0;
+}
+
+int pci_rom_load(uint16_t class, struct pci_rom_header *rom_header,
+		 struct pci_rom_header **ram_headerp)
+{
+	struct pci_rom_data *rom_data;
+	unsigned int rom_size;
+	unsigned int image_size = 0;
+	void *target;
+
+	do {
+		/* Get next image, until we see an x86 version */
+		rom_header = (struct pci_rom_header *)((void *)rom_header +
+							    image_size);
+
+		rom_data = (struct pci_rom_data *)((void *)rom_header +
+				le32_to_cpu(rom_header->data));
+
+		image_size = le32_to_cpu(rom_data->ilen) * 512;
+	} while ((rom_data->type != 0) && (rom_data->indicator != 0));
+
+	if (rom_data->type != 0)
+		return -EACCES;
+
+	rom_size = rom_header->size * 512;
+
+	target = (void *)PCI_VGA_RAM_IMAGE_START;
+	if (target != rom_header) {
+		debug("Copying VGA ROM Image from %p to %p, 0x%x bytes\n",
+		      rom_header, target, rom_size);
+		memcpy(target, rom_header, rom_size);
+		if (memcmp(target, rom_header, rom_size)) {
+			printf("VGA ROM copy failed\n");
+			return -EFAULT;
+		}
+	}
+	*ram_headerp = target;
+
+	return 0;
+}
+
+static struct vbe_mode_info mode_info;
+
+int vbe_get_video_info(struct graphic_device *gdev)
+{
+#ifdef CONFIG_FRAMEBUFFER_SET_VESA_MODE
+	struct vesa_mode_info *vesa = &mode_info.vesa;
+
+	gdev->winSizeX = vesa->x_resolution;
+	gdev->winSizeY = vesa->y_resolution;
+
+	gdev->plnSizeX = vesa->x_resolution;
+	gdev->plnSizeY = vesa->y_resolution;
+
+	gdev->gdfBytesPP = vesa->bits_per_pixel / 8;
+
+	switch (vesa->bits_per_pixel) {
+	case 24:
+		gdev->gdfIndex = GDF_32BIT_X888RGB;
+		break;
+	case 16:
+		gdev->gdfIndex = GDF_16BIT_565RGB;
+		break;
+	default:
+		gdev->gdfIndex = GDF__8BIT_INDEX;
+		break;
+	}
+
+	gdev->isaBase = CONFIG_SYS_ISA_IO_BASE_ADDRESS;
+	gdev->pciBase = vesa->phys_base_ptr;
+
+	gdev->frameAdrs = vesa->phys_base_ptr;
+	gdev->memSize = vesa->bytes_per_scanline * vesa->y_resolution;
+
+	gdev->vprBase = vesa->phys_base_ptr;
+	gdev->cprBase = vesa->phys_base_ptr;
+
+	return 0;
+#else
+	return -ENOSYS;
+#endif
+}
+
+int pci_run_vga_bios(pci_dev_t dev, int (*int15_handler)(void), bool emulate)
+{
+	struct pci_rom_header *rom, *ram;
+	int vesa_mode = -1;
+	uint16_t class;
+	int ret;
+
+	/* Only execute VGA ROMs */
+	pci_read_config_word(dev, PCI_CLASS_DEVICE, &class);
+	if ((class ^ PCI_CLASS_DISPLAY_VGA) & 0xff00) {
+		debug("%s: Class %#x, should be %#x\n", __func__, class,
+		      PCI_CLASS_DISPLAY_VGA);
+		return -ENODEV;
+	}
+
+	if (!should_load_oprom(dev))
+		return -ENXIO;
+
+	ret = pci_rom_probe(dev, class, &rom);
+	if (ret)
+		return ret;
+
+	ret = pci_rom_load(class, rom, &ram);
+	if (ret)
+		return ret;
+
+	if (!board_should_run_oprom(dev))
+		return -ENXIO;
+
+#if defined(CONFIG_FRAMEBUFFER_SET_VESA_MODE) && \
+		defined(CONFIG_FRAMEBUFFER_VESA_MODE)
+	vesa_mode = CONFIG_FRAMEBUFFER_VESA_MODE;
+#endif
+	debug("Selected vesa mode %d\b", vesa_mode);
+	if (emulate) {
+#ifdef CONFIG_BIOSEMU
+		BE_VGAInfo *info;
+
+		ret = biosemu_setup(dev, &info);
+		if (ret)
+			return ret;
+		biosemu_set_interrupt_handler(0x15, int15_handler);
+		ret = biosemu_run(dev, (uchar *)ram, 1 << 16, info, true,
+				  vesa_mode, &mode_info);
+		if (ret)
+			return ret;
+#else
+		printf("BIOS emulation not available - see CONFIG_BIOSEMU\n");
+		return -ENOSYS;
+#endif
+	} else {
+#ifdef CONFIG_X86
+		bios_set_interrupt_handler(0x15, int15_handler);
+
+		bios_run_on_x86(dev, (unsigned long)ram, vesa_mode,
+				&mode_info);
+#else
+		printf("BIOS native execution is only available on x86\n");
+		return -ENOSYS;
+#endif
+	}
+	debug("Final vesa mode %d\n", mode_info.video_mode);
+
+	return 0;
+}