From efe824100b80c4ca9dfd56bda362bdcbf8d9a98e Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Mon, 1 Feb 2010 15:38:10 +1000 Subject: [PATCH] vga_switcheroo: initial implementation (v4) v2: add power up/down support for both devices on W500 puts i915/radeon into D3 and cuts power to radeon. v3: redo probing methods, no DMI list, drm devices call to register with switcheroo, it tries to find an ATPX method on any device and once there is two devices + ATPX it inits the switcher. v4: ATPX msg handling using buffers - should work on more machines mount debugfs /sys/kernel/debug/vgaswitcheroo/switch - should exist if ATPX detected + 2 cards. echo OFF > switch - turn Off whatever card isn't in use echo ON > switch - turn On whatever card isn't in use echo 0000:01:00.0 > switch - switch to that PCI card (ATI) + D3 Intel echo 0000:00:02.0 > switch - switch to that PCI card (Intel) + D3/PWR ATI Tested on W500 (Intel/ATI) and T500 (Intel/ATI) --- 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 | 441 ++++++++++++++++++++++++++++++++ drivers/video/console/fbcon.c | 18 ++ include/linux/fb.h | 2 + include/linux/vga_switcheroo.h | 17 ++ 12 files changed, 532 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 +#include /* 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 #include #include +#include #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 #include #include +#include #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 + 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..ee5dae0 --- /dev/null +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2010 Red Hat Inc. + * Author : Dave Airlie + * + * Parts: + * Copyright (c) 2009: Sylvain Joyeux + * + * Licensed under GPLv2 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +struct vga_switcheroo_methods { + int acpi_method; + int (*switchto)(int index); + int (*power_state)(int index, enum vga_switcheroo_state state); +}; + + +#define VGA_SWITCHEROO_MAX_CLIENTS 2 + +struct vga_switcheroo_client { + 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 active; + + struct dentry *debugfs_root; + struct dentry *switch_file; + + int current_client; + int num_clients; + struct vga_switcheroo_client clients[VGA_SWITCHEROO_MAX_CLIENTS]; + + struct vga_switcheroo_methods *method; + acpi_handle atpx_handle; +}; + +static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); +static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv); + +/* only one switcheroo per system */ +static struct vgasr_priv vgasr_priv; + + +#define ATPX_MUX_IGD 0 +#define ATPX_MUX_DISCRETE 0xffffffff + + +static bool vgaswitcheroo_detect_atpx(struct pci_dev *pdev, acpi_handle *handle) +{ + acpi_handle dhandle, atpx_handle; + acpi_status status; + + dhandle = DEVICE_ACPI_HANDLE(&pdev->dev); + if (dhandle) { + status = acpi_get_handle(dhandle, "ATPX", &atpx_handle); + if (ACPI_SUCCESS(status)) { + *handle = atpx_handle; + return true; + } + } + return false; +} + +/* 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 */ + +#define ATPX_VERSION 0 +#define ATPX_GPU_PWR 2 +#define ATPX_MUX_SELECT 3 + +#define ATPX_INTEGRATED 0 +#define ATPX_DISCRETE 1 + +static int atpx_get_version(void) +{ + acpi_status status; + union acpi_object atpx_arg_elements[2], *obj; + struct acpi_object_list atpx_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + + 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 = ATPX_VERSION; + + atpx_arg_elements[1].type = ACPI_TYPE_INTEGER; + atpx_arg_elements[1].integer.value = ATPX_VERSION; + + status = acpi_evaluate_object(vgasr_priv.atpx_handle, NULL, &atpx_arg, &buffer); + if (ACPI_FAILURE(status)) { + printk("%s: failed to call ATPX: %s\n", __func__, acpi_format_exception(status)); + return -ENOSYS; + } + obj = (union acpi_object *)buffer.pointer; + if (obj && (obj->type == ACPI_TYPE_BUFFER)) { + printk("VERN is %d\n", *((u8 *)(obj->buffer.pointer) + 2)); + } + kfree(buffer.pointer); + return 0; +} + +/* ATPX cmd 2 is PWST - byte 2 contains the 0 (integrated) or 1 (discrete) + ATPX cmd 3 is DMUX - word @ 2 contains 0 (integrated) or 1 (discrete) +*/ +static int execute_atpx_int(int cmd_id, u16 value) +{ + acpi_status status; + union acpi_object atpx_arg_elements[2]; + struct acpi_object_list atpx_arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + uint8_t buf[4] = {0}; + + if (!vgasr_priv.atpx_handle) + return -EINVAL; + + 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; + + buf[2] = value & 0xff; + buf[3] = (value >> 8) & 0xff; + + atpx_arg_elements[1].type = ACPI_TYPE_BUFFER; + atpx_arg_elements[1].buffer.length = 4; + atpx_arg_elements[1].buffer.pointer = buf; + + status = acpi_evaluate_object(vgasr_priv.atpx_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 atpx_set_discrete_state(int state) +{ + return execute_atpx_int(ATPX_GPU_PWR, state); +} + +static int atpx_switch_mux(int mux_id) +{ + return execute_atpx_int(ATPX_MUX_SELECT, mux_id); +} + +#define ACPI_METHOD_ATPX 1 + +static int atpx_switchto(int index) +{ + if (vgasr_priv.clients[index].pdev->vendor == 0x8086) + atpx_switch_mux(0); + else + atpx_switch_mux(1); + return 0; +} + +static int atpx_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; + + atpx_set_discrete_state(state); + return 0; +} + +static struct vga_switcheroo_methods methods[] = { + { VGA_SWITCHEROO_NONE, NULL, NULL }, + { VGA_SWITCHEROO_ATPX, atpx_switchto, atpx_power_state }, +}; + + +int vga_switcheroo_register_client(struct pci_dev *pdev, + void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state)) +{ + int index; + char atpx_name[255] = { 0 }; + struct acpi_buffer buffer = {sizeof(atpx_name), atpx_name}; + bool ret; + + 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; + + ret = vgaswitcheroo_detect_atpx(pdev, &vgasr_priv.atpx_handle); + if (ret == true) { + acpi_get_name(vgasr_priv.atpx_handle, ACPI_FULL_PATHNAME, &buffer); + vgasr_priv.method = &methods[VGA_SWITCHEROO_ATPX]; + printk(KERN_INFO "VGA switcheroo: detect ATPX method on %s device %s handle\n", pci_name(pdev), + atpx_name); + atpx_get_version(); + } + vgasr_priv.num_clients++; + printk("registered %d client %s %p\n", vgasr_priv.num_clients, pci_name(pdev), vgasr_priv.atpx_handle); + + if (vgasr_priv.num_clients >= 2 && vgasr_priv.atpx_handle) { + printk(KERN_INFO "VGA switcheroo enabled\n"); + vga_switcheroo_debugfs_init(&vgasr_priv); + vgasr_priv.active = true; + } + return 0; +} +EXPORT_SYMBOL(vga_switcheroo_register_client); + +void vga_switcheroo_unregister_client(struct pci_dev *pdev) +{ + int i, j; + for (i = 0; i < vgasr_priv.num_clients; i++) { + if (vgasr_priv.clients[i].pdev == pdev) { + for (j = i + 1; j < vgasr_priv.num_clients; j++) + memcpy(&vgasr_priv.clients[j-1], &vgasr_priv.clients[j], sizeof(struct vga_switcheroo_client)); + break; + } + } + vgasr_priv.num_clients--; + if (vgasr_priv.num_clients < 2) { + printk(KERN_INFO "VGA switcheroo disabled\n"); + vga_switcheroo_debugfs_fini(&vgasr_priv); + vgasr_priv.active = false; + } +} +EXPORT_SYMBOL(vga_switcheroo_unregister_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.method->power_state(index, VGA_SWITCHEROO_ON); + /* call the driver callback to turn on device */ + vgasr_priv.clients[index].set_gpu_state(vgasr_priv.clients[index].pdev, VGA_SWITCHEROO_ON); + vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_ON; + return 0; +} + +static int vga_switchoff(int index) +{ + /* call the driver callback to turn off device */ + vgasr_priv.clients[index].set_gpu_state(vgasr_priv.clients[index].pdev, VGA_SWITCHEROO_OFF); + vgasr_priv.method->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.method->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) +{ + /* already initialised */ + if (priv->debugfs_root) + return 0; + 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; +} 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..c1d62b2 --- /dev/null +++ b/include/linux/vga_switcheroo.h @@ -0,0 +1,17 @@ + +enum vga_switcheroo_state { + VGA_SWITCHEROO_OFF, + VGA_SWITCHEROO_ON, +}; + +#define VGA_SWITCHEROO_NONE 0 +#define VGA_SWITCHEROO_ATPX 1 +#define VGA_SWITCHEROO_DSM 2 + +void vga_switcheroo_unregister_client(struct pci_dev *dev); +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