From d475be956cf014a643668cd1ec2f9e8925cb887a Mon Sep 17 00:00:00 2001
From: Dave Airlie <airlied@linux.ie>
Date: Mon, 1 Feb 2010 15:38:10 +1000
Subject: [PATCH] vga_switcheroo: initial implementation (v2)

v2: add power up/down support for both devices
on W500 puts i915/radeon into D3 and cuts power to radeon.
---
 drivers/gpu/drm/i915/i915_dma.c        |   19 ++
 drivers/gpu/drm/i915/i915_drv.c        |    4 +-
 drivers/gpu/drm/i915/i915_drv.h        |    2 +
 drivers/gpu/drm/i915/intel_fb.c        |    3 +
 drivers/gpu/drm/radeon/radeon_bios.c   |   11 +-
 drivers/gpu/drm/radeon/radeon_device.c |   19 ++
 drivers/gpu/drm/radeon/radeon_fb.c     |    3 +
 drivers/gpu/vga/Makefile               |    2 +-
 drivers/gpu/vga/vga_switcheroo.c       |  389 ++++++++++++++++++++++++++++++++
 drivers/video/console/fbcon.c          |   18 ++
 include/linux/fb.h                     |    2 +
 include/linux/vga_switcheroo.h         |   17 ++
 12 files changed, 480 insertions(+), 9 deletions(-)
 create mode 100644 drivers/gpu/vga/vga_switcheroo.c
 create mode 100644 include/linux/vga_switcheroo.h

diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
index 02607ed..b35d8c8 100644
--- a/drivers/gpu/drm/i915/i915_dma.c
+++ b/drivers/gpu/drm/i915/i915_dma.c
@@ -35,6 +35,7 @@
 #include "i915_drv.h"
 #include "i915_trace.h"
 #include <linux/vgaarb.h>
+#include <linux/vga_switcheroo.h>
 
 /* Really want an OS-independent resettable timer.  Would like to have
  * this loop run for (eg) 3 sec, but have the timer reset every time
@@ -1188,6 +1189,20 @@ static unsigned int i915_vga_set_decode(void *cookie, bool state)
 		return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM;
 }
 
+static void i915_switcheroo_set_state(struct pci_dev *pdev, enum vga_switcheroo_state state)
+{
+	struct drm_device *dev = pci_get_drvdata(pdev);
+	struct radeon_device *rdev = dev->dev_private;
+	pm_message_t pmm = { .event = PM_EVENT_SUSPEND };
+	if (state == VGA_SWITCHEROO_ON) {
+		printk(KERN_ERR "VGA switched i915 on\n");
+		i915_resume(dev);
+	} else {
+		printk(KERN_ERR "VGA switched i915 off\n");
+		i915_suspend(dev, pmm);
+	}
+}
+
 static int i915_load_modeset_init(struct drm_device *dev,
 				  unsigned long prealloc_start,
 				  unsigned long prealloc_size,
@@ -1257,6 +1272,10 @@ static int i915_load_modeset_init(struct drm_device *dev,
 	if (ret)
 		goto destroy_ringbuffer;
 
+	ret = vga_switcheroo_register_client(dev->pdev, i915_switcheroo_set_state);
+	if (ret)
+		goto destroy_ringbuffer;
+
 	ret = drm_irq_install(dev);
 	if (ret)
 		goto destroy_ringbuffer;
diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c
index 24286ca..2011ecd 100644
--- a/drivers/gpu/drm/i915/i915_drv.c
+++ b/drivers/gpu/drm/i915/i915_drv.c
@@ -56,7 +56,7 @@ static struct pci_device_id pciidlist[] = {
 MODULE_DEVICE_TABLE(pci, pciidlist);
 #endif
 
-static int i915_suspend(struct drm_device *dev, pm_message_t state)
+int i915_suspend(struct drm_device *dev, pm_message_t state)
 {
 	struct drm_i915_private *dev_priv = dev->dev_private;
 
@@ -95,7 +95,7 @@ static int i915_suspend(struct drm_device *dev, pm_message_t state)
 	return 0;
 }
 
-static int i915_resume(struct drm_device *dev)
+int i915_resume(struct drm_device *dev)
 {
 	struct drm_i915_private *dev_priv = dev->dev_private;
 	int ret = 0;
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index fbecac7..ddfaffd 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -704,6 +704,8 @@ extern int i915_max_ioctl;
 extern unsigned int i915_fbpercrtc;
 extern unsigned int i915_powersave;
 
+extern int i915_suspend(struct drm_device *dev, pm_message_t state);
+extern int i915_resume(struct drm_device *dev);
 extern void i915_save_display(struct drm_device *dev);
 extern void i915_restore_display(struct drm_device *dev);
 extern int i915_master_create(struct drm_device *dev, struct drm_master *master);
diff --git a/drivers/gpu/drm/i915/intel_fb.c b/drivers/gpu/drm/i915/intel_fb.c
index d4823cc..a24bf75 100644
--- a/drivers/gpu/drm/i915/intel_fb.c
+++ b/drivers/gpu/drm/i915/intel_fb.c
@@ -35,6 +35,7 @@
 #include <linux/delay.h>
 #include <linux/fb.h>
 #include <linux/init.h>
+#include <linux/vga_switcheroo.h>
 
 #include "drmP.h"
 #include "drm.h"
@@ -234,6 +235,8 @@ static int intelfb_create(struct drm_device *dev, uint32_t fb_width,
 			intel_fb->base.width, intel_fb->base.height,
 			obj_priv->gtt_offset, fbo);
 
+	vga_switcheroo_client_fb_set(dev->pdev, info);
+
 	mutex_unlock(&dev->struct_mutex);
 	return 0;
 
diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c
index 9069217..a406af0 100644
--- a/drivers/gpu/drm/radeon/radeon_bios.c
+++ b/drivers/gpu/drm/radeon/radeon_bios.c
@@ -50,8 +50,10 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev)
 	vram_base = drm_get_resource_start(rdev->ddev, 0);
 	bios = ioremap(vram_base, size);
 	if (!bios) {
+		DRM_ERROR("unable to ioremap %x %d\n", (unsigned int)vram_base, (unsigned int)size);
 		return false;
 	}
+	DRM_ERROR("ioremapped %d %d \n", bios[0], bios[1]);
 
 	if (size == 0 || bios[0] != 0x55 || bios[1] != 0xaa) {
 		iounmap(bios);
@@ -62,7 +64,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev)
 		iounmap(bios);
 		return false;
 	}
-	memcpy(rdev->bios, bios, size);
+	memcpy_fromio(rdev->bios, bios, size);
 	iounmap(bios);
 	return true;
 }
@@ -393,11 +395,8 @@ bool radeon_get_bios(struct radeon_device *rdev)
 	bool r;
 	uint16_t tmp;
 
-	if (rdev->flags & RADEON_IS_IGP) {
-		r = igp_read_bios_from_vram(rdev);
-		if (r == false)
-			r = radeon_read_bios(rdev);
-	} else
+	r = igp_read_bios_from_vram(rdev);
+	if (r == false)
 		r = radeon_read_bios(rdev);
 	if (r == false) {
 		r = radeon_read_disabled_bios(rdev);
diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c
index 768b150..d5ddd07 100644
--- a/drivers/gpu/drm/radeon/radeon_device.c
+++ b/drivers/gpu/drm/radeon/radeon_device.c
@@ -30,11 +30,15 @@
 #include <drm/drm_crtc_helper.h>
 #include <drm/radeon_drm.h>
 #include <linux/vgaarb.h>
+#include <linux/vga_switcheroo.h>
 #include "radeon_reg.h"
 #include "radeon.h"
 #include "radeon_asic.h"
 #include "atom.h"
 
+int radeon_suspend_kms(struct drm_device *dev, pm_message_t state);
+int radeon_resume_kms(struct drm_device *dev);
+
 /*
  * Clear GPU surface registers.
  */
@@ -613,6 +617,20 @@ void radeon_check_arguments(struct radeon_device *rdev)
 	}
 }
 
+static void radeon_switcheroo_set_state(struct pci_dev *pdev, enum vga_switcheroo_state state)
+{
+	struct drm_device *dev = pci_get_drvdata(pdev);
+	struct radeon_device *rdev = dev->dev_private;
+	pm_message_t pmm = { .event = PM_EVENT_SUSPEND };
+	if (state == VGA_SWITCHEROO_ON) {
+		printk(KERN_ERR "VGA switched radeon on\n");
+		radeon_resume_kms(dev);
+	} else {
+		printk(KERN_ERR "VGA switched radeon off\n");
+		radeon_suspend_kms(dev, pmm);
+	}
+}
+
 int radeon_device_init(struct radeon_device *rdev,
 		       struct drm_device *ddev,
 		       struct pci_dev *pdev,
@@ -692,6 +710,7 @@ int radeon_device_init(struct radeon_device *rdev,
 	/* this will fail for cards that aren't VGA class devices, just
 	 * ignore it */
 	vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode);
+	vga_switcheroo_register_client(rdev->pdev, radeon_switcheroo_set_state);
 
 	r = radeon_init(rdev);
 	if (r)
diff --git a/drivers/gpu/drm/radeon/radeon_fb.c b/drivers/gpu/drm/radeon/radeon_fb.c
index 66055b3..cbf4dbb 100644
--- a/drivers/gpu/drm/radeon/radeon_fb.c
+++ b/drivers/gpu/drm/radeon/radeon_fb.c
@@ -39,6 +39,8 @@
 
 #include "drm_fb_helper.h"
 
+#include <linux/vga_switcheroo.h>
+
 struct radeon_fb_device {
 	struct drm_fb_helper helper;
 	struct radeon_framebuffer	*rfb;
@@ -290,6 +292,7 @@ int radeonfb_create(struct drm_device *dev,
 	rfbdev->rfb = rfb;
 	rfbdev->rdev = rdev;
 
+	vga_switcheroo_client_fb_set(rdev->ddev->pdev, info);
 	mutex_unlock(&rdev->ddev->struct_mutex);
 	return 0;
 
diff --git a/drivers/gpu/vga/Makefile b/drivers/gpu/vga/Makefile
index 7cc8c1e..6592546 100644
--- a/drivers/gpu/vga/Makefile
+++ b/drivers/gpu/vga/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_VGA_ARB)  += vgaarb.o
+obj-$(CONFIG_VGA_ARB)  += vgaarb.o vga_switcheroo.o
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c
new file mode 100644
index 0000000..60766d4
--- /dev/null
+++ b/drivers/gpu/vga/vga_switcheroo.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2010 Red Hat Inc.
+ * Author : Dave Airlie <airlied@redhat.com>
+ * 
+ * Parts:
+ * Copyright (c) 2009: Sylvain Joyeux <sylvain.joyeux@m4x.org>
+ * 
+ * Licensed under GPLv2
+ */
+
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/fb.h>
+
+#include <acpi/acpi.h>
+
+#include <linux/pci.h>
+#include <linux/vga_switcheroo.h>
+
+/* VGA switcheroo */
+struct dmi_match_data {
+	int acpi_method;
+	const char *acpi_object;
+	int (*switchto)(int index);
+        int (*power_state)(int index, enum vga_switcheroo_state state);
+};
+
+
+#define VGA_SWITCHEROO_MAX_CLIENTS 2
+
+struct vga_switcheroo_clients {
+	struct pci_dev *pdev;
+	struct fb_info *fb_info;
+	int pwr_state;
+	/* TODO callbacks */
+	void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state);
+};
+	
+struct vgasr_priv {
+
+	bool vga_switcheroo_active;
+
+	struct dentry *debugfs_root;
+	struct dentry *switch_file;
+
+	int current_client;
+	int num_clients;
+	struct vga_switcheroo_clients clients[VGA_SWITCHEROO_MAX_CLIENTS];
+
+	struct dmi_match_data *machine;
+};
+
+/* only one switcheroo per system */
+static struct vgasr_priv vgasr_priv;
+
+static acpi_handle root_handle;
+
+#define ATPX_GPU_PWR 2
+#define ATPX_MUX_SELECT 3
+
+#define ATPX_MUX_IGD 0
+#define ATPX_MUX_DISCRETE 0xffffffff
+
+#define W500_OBJECT "\\_SB_.PCI0.VID.ATPX"
+
+/* from lenovo_acpi.c some objects seem to accept an array of 
+ * parameters for ATPX not a single integer like the W500
+ * we need alternate functions for those */
+static int execute_atpx_int(char *object, int cmd_id, u32 value)
+{
+	acpi_status status;
+	acpi_handle handle;
+	union acpi_object atpx_arg_elements[2];
+	struct acpi_object_list atpx_arg;
+	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	status = acpi_get_handle(root_handle, object, &handle);
+	if (ACPI_FAILURE(status)) {
+		printk(KERN_ERR "Cannot get ACPI handle: %s\n", acpi_format_exception(status));
+		return -ENOSYS;
+	}
+
+	atpx_arg.count = 2;
+	atpx_arg.pointer = &atpx_arg_elements[0];
+
+	atpx_arg_elements[0].type = ACPI_TYPE_INTEGER;
+	atpx_arg_elements[0].integer.value = cmd_id;
+
+	atpx_arg_elements[1].type = ACPI_TYPE_INTEGER;
+	atpx_arg_elements[1].integer.value = value;
+
+	status = acpi_evaluate_object(handle, NULL, &atpx_arg, &buffer);
+	if (ACPI_FAILURE(status)) {
+		printk("%s: failed to call ATPX: %s\n", __func__, acpi_format_exception(status));
+		return -ENOSYS;
+	}
+	kfree(buffer.pointer);
+
+	return 0;
+}
+
+static int w500_set_discrete_state(int state)
+{
+	return execute_atpx_int(W500_OBJECT, ATPX_GPU_PWR, state ? (u32)-1 : 0);
+}
+
+static int w500_switch_mux(int mux_id)
+{
+	return execute_atpx_int(W500_OBJECT, ATPX_MUX_SELECT, mux_id);
+}
+
+#define ACPI_METHOD_W500 1
+
+static int w500_switchto(int index)
+{
+	if (vgasr_priv.clients[index].pdev->vendor == 0x8086)
+		w500_switch_mux(0);
+	else
+		w500_switch_mux(0xffffffff);
+	return 0;
+}
+
+static int w500_power_state(int index, enum vga_switcheroo_state state)
+{
+	/* on w500 ACPI can't change intel gpu state */
+	if (vgasr_priv.clients[index].pdev->vendor == 0x8086)
+		return 0;
+
+	w500_set_discrete_state(state);
+	return 0;
+}
+
+static struct dmi_match_data switchables[] = {
+	{ ACPI_METHOD_W500, W500_OBJECT, w500_switchto, w500_power_state },
+};
+
+
+static int vga_switcheroo_dmi_match(const struct dmi_system_id *id)
+{
+	struct dmi_match_data *data = id->driver_data;
+	printk("multi-gpu laptop detected\n");
+	vgasr_priv.machine = data;
+	return 1;
+}
+
+static struct dmi_system_id __initdata vga_switcheroo_table[] = {
+	{
+		.callback = vga_switcheroo_dmi_match,
+		.ident = "Lenovo W500",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "406334M"),
+		},
+		.driver_data = &switchables[0],
+	},
+};
+
+int vga_switcheroo_register_client(struct pci_dev *pdev,
+				   void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state))
+{
+	int index;
+
+	if (vgasr_priv.vga_switcheroo_active == false)
+		return 0;
+
+	if (vgasr_priv.num_clients > VGA_SWITCHEROO_MAX_CLIENTS)
+		return -EINVAL;
+
+	index = vgasr_priv.num_clients;
+
+	vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_ON;
+	vgasr_priv.clients[index].pdev = pdev;
+	vgasr_priv.clients[index].set_gpu_state = set_gpu_state;
+	if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW)
+		vgasr_priv.current_client = index;
+	vgasr_priv.num_clients++;
+
+	return 0;
+}
+EXPORT_SYMBOL(vga_switcheroo_register_client);
+
+void vga_switcheroo_client_fb_set(struct pci_dev *pdev,
+				 struct fb_info *info)
+{
+	int i;
+
+	for (i = 0; i < vgasr_priv.num_clients; i++) {
+		if (vgasr_priv.clients[i].pdev == pdev) {
+			vgasr_priv.clients[i].fb_info = info;
+			break;
+		}
+	}
+}
+EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
+
+static int vga_switcheroo_show(struct seq_file *m, void *v)
+{
+	int i;
+	seq_printf(m, "%d\n", vgasr_priv.num_clients);
+	for (i = 0; i < vgasr_priv.num_clients; i++) {
+		seq_printf(m, "%d:%c:%s:%s\n", i, (i == vgasr_priv.current_client ? '+': ' '),	
+			vgasr_priv.clients[i].pwr_state ? "Pwr" : "Off",
+			pci_name(vgasr_priv.clients[i].pdev));
+	}
+	return 0;
+}
+
+static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, vga_switcheroo_show, NULL);
+}
+
+static int vga_switchon(int index)
+{
+	int ret;
+
+	ret = vgasr_priv.machine->power_state(index, VGA_SWITCHEROO_ON);
+	vgasr_priv.clients[index].set_gpu_state(vgasr_priv.clients[index].pdev, VGA_SWITCHEROO_ON);
+	/* call the driver callback to turn on device */
+
+	vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_ON;
+	return 0;
+} 
+
+static int vga_switchoff(int index)
+{
+	vgasr_priv.clients[index].set_gpu_state(vgasr_priv.clients[index].pdev, VGA_SWITCHEROO_OFF);
+	/* call the driver callback to turn on device */
+	vgasr_priv.machine->power_state(index, VGA_SWITCHEROO_OFF);
+	vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_OFF;
+	return 0;
+} 
+
+static int vga_switchto(int index)
+{
+	int cur = vgasr_priv.current_client;
+	int ret;
+
+	if (cur == index)
+		return 0;
+
+	/* power up the first device */
+	if (vgasr_priv.clients[index].pwr_state == VGA_SWITCHEROO_OFF) {
+		vga_switchon(index);
+	}
+	
+	
+	ret = pci_enable_device(vgasr_priv.clients[index].pdev);
+	if (ret)
+		return ret;
+
+	ret = vgasr_priv.machine->switchto(index);
+	if (ret)
+		return ret;
+
+	/* swap shadow resource to denote boot VGA device has changed so X starts on new device */
+	vgasr_priv.clients[cur].pdev->resource[PCI_ROM_RESOURCE].flags &= ~IORESOURCE_ROM_SHADOW;
+	vgasr_priv.clients[index].pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW;
+
+	if (vgasr_priv.clients[index].fb_info) {
+		struct fb_event event;
+		event.info = vgasr_priv.clients[index].fb_info;
+		fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
+	}
+
+	if (vgasr_priv.clients[cur].pwr_state == VGA_SWITCHEROO_ON) {
+		vga_switchoff(cur);
+	}
+	
+	vgasr_priv.current_client = index;
+	return 0;
+}
+
+static ssize_t
+vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
+			     size_t cnt, loff_t *ppos)
+{
+	char pci_id[64];
+	char *pdev_name;
+	int i, ret;
+
+	if (cnt > 63)
+		cnt = 63;
+
+	if (copy_from_user(pci_id, ubuf, cnt))
+		return -EFAULT;
+
+	/* pwr off the device not in use */
+	if (strncmp(pci_id, "OFF", 3) == 0) {
+		for (i = 0; i < vgasr_priv.num_clients; i++) {
+			if (vgasr_priv.current_client == i)
+				continue;
+			if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_ON) {
+				vga_switchoff(i);
+			}
+		}
+		goto out;
+	}
+	/* pwr on the device not in use */
+	if (strncmp(pci_id, "ON", 2) == 0) {
+		for (i = 0; i < vgasr_priv.num_clients; i++) {
+			if (vgasr_priv.current_client == i)
+				continue;
+			if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_OFF) {
+				vga_switchon(i);
+			}
+		}
+		goto out;
+	}
+	/* switch devices */
+	for (i = 0; i < vgasr_priv.num_clients; i++) {
+		pdev_name = pci_name(vgasr_priv.clients[i].pdev);
+		if (strncmp(pci_id, pdev_name, strlen(pdev_name)) == 0) {
+			printk("switching to %d %s\n", i, pdev_name);
+			ret = vga_switchto(i);
+			if (ret)
+				printk("switching failed %d\n", ret);
+			break;
+		}
+	}
+out:
+	return cnt;
+}
+
+static const struct file_operations vga_switcheroo_debugfs_fops = {
+	.owner = THIS_MODULE,
+	.open = vga_switcheroo_debugfs_open,
+	.write = vga_switcheroo_debugfs_write,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv)
+{
+	if (priv->switch_file) {
+		debugfs_remove(priv->switch_file);
+		priv->switch_file = NULL;
+	}
+	if (priv->debugfs_root) {
+		debugfs_remove(priv->debugfs_root);
+		priv->debugfs_root = NULL;
+	}
+}
+
+static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv)
+{
+	priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL);
+
+	if (!priv->debugfs_root) {
+		printk(KERN_ERR "Cannot create /sys/kernel/debug/vgaswitcheroo\n");
+		goto fail;
+	}
+
+	priv->switch_file = debugfs_create_file("switch", 0644,
+						priv->debugfs_root, NULL, &vga_switcheroo_debugfs_fops);
+	if (!priv->switch_file) {
+		printk(KERN_ERR "cannot create /sys/kernel/debug/vgaswitcheroo/switch\n");
+		goto fail;
+	}
+	return 0;
+fail:
+	vga_switcheroo_debugfs_fini(priv);
+	return -1;
+}
+
+int vga_switcheroo_init(void)
+{
+  /* use dmi to detect we have a switchable graphics machine */
+	if (!dmi_check_system(vga_switcheroo_table))
+		return -EINVAL;
+	
+	printk("Detected a switchable graphics machine\n");
+
+	vgasr_priv.vga_switcheroo_active = true;
+	vga_switcheroo_debugfs_init(&vgasr_priv);
+	return 0;
+}
+
+void vga_switcheroo_fini(void)
+{
+	vga_switcheroo_debugfs_fini(&vgasr_priv);
+}
+
+subsys_initcall(vga_switcheroo_init);
diff --git a/drivers/video/console/fbcon.c b/drivers/video/console/fbcon.c
index 3681c6a..4fadd99 100644
--- a/drivers/video/console/fbcon.c
+++ b/drivers/video/console/fbcon.c
@@ -3025,6 +3025,20 @@ static int fbcon_fb_unregistered(struct fb_info *info)
 	return 0;
 }
 
+static void fbcon_remap_all(int idx)
+{
+	int i;
+	for (i = first_fb_vc; i <= last_fb_vc; i++) {
+		set_con2fb_map(i, idx, 0);
+	}
+	if (con_is_bound(&fb_con)) {
+		printk(KERN_INFO "fbcon: Remapping primary device, "
+		       "fb%i, to tty %i-%i\n", idx,
+		       first_fb_vc + 1, last_fb_vc + 1);
+		info_idx = idx;
+	}
+}
+
 #ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
 static void fbcon_select_primary(struct fb_info *info)
 {
@@ -3225,6 +3239,10 @@ static int fbcon_event_notify(struct notifier_block *self,
 		caps = event->data;
 		fbcon_get_requirement(info, caps);
 		break;
+	case FB_EVENT_REMAP_ALL_CONSOLE:
+		idx = info->node;
+		fbcon_remap_all(idx);
+		break;
 	}
 done:
 	return ret;
diff --git a/include/linux/fb.h b/include/linux/fb.h
index de9c722..cb3ff5c 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -543,6 +543,8 @@ struct fb_cursor_user {
 #define FB_EVENT_GET_REQ                0x0D
 /*      Unbind from the console if possible */
 #define FB_EVENT_FB_UNBIND              0x0E
+/*      CONSOLE-SPECIFIC: remap all consoles to new fb - for vga switcheroo */
+#define FB_EVENT_REMAP_ALL_CONSOLE   0x0F
 
 struct fb_event {
 	struct fb_info *info;
diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h
new file mode 100644
index 0000000..53dbbaf
--- /dev/null
+++ b/include/linux/vga_switcheroo.h
@@ -0,0 +1,17 @@
+
+enum vga_switcheroo_state {
+	VGA_SWITCHEROO_OFF,
+	VGA_SWITCHEROO_ON,
+};
+
+struct vga_switcheroo {
+	void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state);
+};
+
+
+int vga_switcheroo_register_client(struct pci_dev *dev,
+				   void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state));
+
+void vga_switcheroo_client_fb_set(struct pci_dev *dev,
+				  struct fb_info *info);
+
-- 
1.6.5.2

