From fe0efc8956a0fceaa9eda4300c1db7d6083539f8 Mon Sep 17 00:00:00 2001
From: Dave Airlie <airlied@redhat.com>
Date: Fri, 5 Mar 2010 15:08:51 +1000
Subject: [PATCH] nouveau: attempt to get bios rom in switcheroo

---
 drivers/gpu/drm/nouveau/nouveau_acpi.c |   53 +++++++++++++++++++++++++++++++-
 drivers/gpu/drm/nouveau/nouveau_bios.c |   19 +++++++++++
 drivers/gpu/drm/nouveau/nouveau_drv.h  |    5 +++
 3 files changed, 76 insertions(+), 1 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c
index 0e0730a..66262be 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.c
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c
@@ -34,6 +34,7 @@ static struct nouveau_dsm_priv {
 	bool dsm_detected;
 	acpi_handle dhandle;
 	acpi_handle dsm_handle;
+	acpi_handle rom_handle;
 } nouveau_dsm_priv;
 
 static const char nouveau_dsm_muid[] = {
@@ -142,7 +143,7 @@ static struct vga_switcheroo_handler nouveau_dsm_handler = {
 
 static bool nouveau_dsm_pci_probe(struct pci_dev *pdev)
 {
-	acpi_handle dhandle, nvidia_handle;
+	acpi_handle dhandle, nvidia_handle, rom_handle;
 	acpi_status status;
 	int ret;
 	uint32_t result;
@@ -155,6 +156,10 @@ static bool nouveau_dsm_pci_probe(struct pci_dev *pdev)
 		return false;
 	}
 
+	status = acpi_get_handle(dhandle, "_ROM", &rom_handle);
+	if (ACPI_FAILURE(status))
+		return false;
+
 	ret= nouveau_dsm(nvidia_handle, NOUVEAU_DSM_SUPPORTED,
 			 NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &result);
 	if (ret < 0)
@@ -162,6 +167,7 @@ static bool nouveau_dsm_pci_probe(struct pci_dev *pdev)
 
 	nouveau_dsm_priv.dhandle = dhandle;
 	nouveau_dsm_priv.dsm_handle = nvidia_handle;
+	nouveau_dsm_priv.rom_handle = rom_handle;
 	return true;
 }
 
@@ -203,3 +209,48 @@ void nouveau_unregister_dsm_handler(void)
 {
 	vga_switcheroo_unregister_handler();
 }
+
+/* retrieve the ROM in 4k blocks */
+static int nouveau_rom_call(acpi_handle rom_handle, uint8_t *bios,
+                            int offset, int len)
+{
+        acpi_status status;
+        union acpi_object rom_arg_elements[2], *obj;
+        struct acpi_object_list rom_arg;
+        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL};
+
+        rom_arg.count = 2;
+        rom_arg.pointer = &rom_arg_elements[0];
+
+        rom_arg_elements[0].type = ACPI_TYPE_INTEGER;
+        rom_arg_elements[0].integer.value = offset;
+
+        rom_arg_elements[1].type = ACPI_TYPE_INTEGER;
+        rom_arg_elements[1].integer.value = len;
+
+        status = acpi_evaluate_object(rom_handle, NULL, &rom_arg, &buffer);
+        if (ACPI_FAILURE(status)) {
+                printk("failed to evaluate ROM got %s\n", acpi_format_exception(status));
+                return -ENODEV;
+	}
+	obj = (union acpi_object *)buffer.pointer;
+	memcpy(bios+offset, obj->buffer.pointer, len);
+	kfree(buffer.pointer);
+	return len;
+}
+
+bool nouveau_acpi_rom_supported(struct pci_dev *pdev)
+{
+	if (!nouveau_dsm_priv.dsm_detected)
+		return false;
+
+	if (nouveau_dsm_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev))
+		return false;
+
+	return true;
+}
+
+int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len)
+{
+	return nouveau_rom_call(nouveau_dsm_priv.rom_handle, bios, offset, len);
+}
diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c b/drivers/gpu/drm/nouveau/nouveau_bios.c
index 71247da..0618ef3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bios.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bios.c
@@ -177,6 +177,24 @@ out:
 	pci_disable_rom(dev->pdev);
 }
 
+static void load_vbios_acpi(struct drm_device *dev, uint8_t *data)
+{
+	int i;
+	int ret;
+	int size = 64 * 1024;
+	if (!nouveau_acpi_rom_supported(dev->pdev))
+		return;
+
+	for (i = 0; i < size / ROM_BIOS_PAGE; i++) {
+		ret = nouveau_acpi_get_bios_chunk(data,
+						  (i * ROM_BIOS_PAGE),
+						  ROM_BIOS_PAGE);
+		if (ret <= 0)
+			break;
+	}
+	return;
+}
+
 struct methods {
 	const char desc[8];
 	void (*loadbios)(struct drm_device *, uint8_t *);
@@ -190,6 +208,7 @@ static struct methods nv04_methods[] = {
 };
 
 static struct methods nv50_methods[] = {
+	{ "ACPI", load_vbios_acpi, true },
 	{ "PRAMIN", load_vbios_pramin, true },
 	{ "PROM", load_vbios_prom, false },
 	{ "PCIROM", load_vbios_pci, true },
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 5f8d987..363e886 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -852,12 +852,17 @@ extern int  nouveau_dma_init(struct nouveau_channel *);
 extern int  nouveau_dma_wait(struct nouveau_channel *, int slots, int size);
 
 /* nouveau_acpi.c */
+#define ROM_BIOS_PAGE 4096
 #if defined(CONFIG_ACPI)
 void nouveau_register_dsm_handler(void);
 void nouveau_unregister_dsm_handler(void);
+int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len);
+bool nouveau_acpi_rom_supported(struct pci_dev *pdev);
 #else
 static inline void nouveau_register_dsm_handler(void) {}
 static inline void nouveau_unregister_dsm_handler(void) {}
+static inline bool nouveau_acpi_rom_supported(struct pci_dev *pdev) { return false; }
+static inline int nouveau_acpi_get_bios_chunk(uint8_t *bios, int offset, int len) { return -EINVAL; }
 #endif
 
 /* nouveau_backlight.c */
-- 
1.6.5.2

