Commit 6a9ee8af authored by Dave Airlie's avatar Dave Airlie Committed by Dave Airlie

vga_switcheroo: initial implementation (v15)

Many new laptops now come with 2 gpus, one to be used for low power
modes and one for gaming/on-ac applications. These GPUs are typically
wired to the laptop panel and VGA ports via a multiplexer unit which
is controlled via ACPI methods.

4 combinations of systems typically exist - with 2 ACPI methods.
Intel/ATI - Lenovo W500/T500 - use ATPX ACPI method
ATI/ATI - some ASUS - use ATPX ACPI Method
Intel/Nvidia - - use _DSM ACPI method
Nvidia/Nvidia -  - use _DSM ACPI method.

TODO:
This patch adds support for the ATPX method and initial bits
for the _DSM methods that need to written by someone with
access to the hardware.
Add a proper non-debugfs interface - need to get some proper
testing first.

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

v5: rearchitect after more mjg59 discussion - move ATPX handling to
    radeon driver.

v6: add file headers + initial nouveau bits (to be filled out).

v7: merge delayed switcher code.

v8: avoid suspend/resume of gpu that is off

v9: rearchitect - mjg59 is always right. - move all ATPX code to
radeon, should allow simpler DSM also proper ATRM handling

v10: add ATRM support for radeon BIOS, add mutex to lock vgasr_priv

v11: fix bug in resuming Intel for 2nd time.

v12: start fixing up nvidia code blindly.

v13: blindly guess at finishing nvidia code

v14: remove radeon audio hacks - fix up intel resume more like upstream

v15: clean up printks + remove unnecessary igd/dis pointers

mount debugfs

/sys/kernel/debug/vgaswitcheroo/switch - should exist if ATPX detected
 + 2 cards.

DIS - immediate change to discrete
IGD - immediate change to IGD
DDIS - delayed change to discrete
DIGD - delayed change to IGD
ON - turn on not in use
OFF - turn off not in use

Tested on W500 (Intel/ATI) and T500 (Intel/ATI)
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 9fd1de52
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "i915_drv.h" #include "i915_drv.h"
#include "i915_trace.h" #include "i915_trace.h"
#include <linux/vgaarb.h> #include <linux/vgaarb.h>
#include <linux/vga_switcheroo.h>
/* Really want an OS-independent resettable timer. Would like to have /* 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 * this loop run for (eg) 3 sec, but have the timer reset every time
...@@ -1199,6 +1200,32 @@ static unsigned int i915_vga_set_decode(void *cookie, bool state) ...@@ -1199,6 +1200,32 @@ static unsigned int i915_vga_set_decode(void *cookie, bool state)
return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; 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);
pm_message_t pmm = { .event = PM_EVENT_SUSPEND };
if (state == VGA_SWITCHEROO_ON) {
printk(KERN_INFO "i915: switched off\n");
/* i915 resume handler doesn't set to D0 */
pci_set_power_state(dev->pdev, PCI_D0);
i915_resume(dev);
} else {
printk(KERN_ERR "i915: switched off\n");
i915_suspend(dev, pmm);
}
}
static bool i915_switcheroo_can_switch(struct pci_dev *pdev)
{
struct drm_device *dev = pci_get_drvdata(pdev);
bool can_switch;
spin_lock(&dev->count_lock);
can_switch = (dev->open_count == 0);
spin_unlock(&dev->count_lock);
return can_switch;
}
static int i915_load_modeset_init(struct drm_device *dev, static int i915_load_modeset_init(struct drm_device *dev,
unsigned long prealloc_start, unsigned long prealloc_start,
unsigned long prealloc_size, unsigned long prealloc_size,
...@@ -1260,6 +1287,12 @@ static int i915_load_modeset_init(struct drm_device *dev, ...@@ -1260,6 +1287,12 @@ static int i915_load_modeset_init(struct drm_device *dev,
if (ret) if (ret)
goto destroy_ringbuffer; goto destroy_ringbuffer;
ret = vga_switcheroo_register_client(dev->pdev,
i915_switcheroo_set_state,
i915_switcheroo_can_switch);
if (ret)
goto destroy_ringbuffer;
intel_modeset_init(dev); intel_modeset_init(dev);
ret = drm_irq_install(dev); ret = drm_irq_install(dev);
...@@ -1544,6 +1577,7 @@ int i915_driver_unload(struct drm_device *dev) ...@@ -1544,6 +1577,7 @@ int i915_driver_unload(struct drm_device *dev)
dev_priv->child_dev_num = 0; dev_priv->child_dev_num = 0;
} }
drm_irq_uninstall(dev); drm_irq_uninstall(dev);
vga_switcheroo_unregister_client(dev->pdev);
vga_client_register(dev->pdev, NULL, NULL, NULL); vga_client_register(dev->pdev, NULL, NULL, NULL);
} }
...@@ -1611,6 +1645,7 @@ void i915_driver_lastclose(struct drm_device * dev) ...@@ -1611,6 +1645,7 @@ void i915_driver_lastclose(struct drm_device * dev)
if (!dev_priv || drm_core_check_feature(dev, DRIVER_MODESET)) { if (!dev_priv || drm_core_check_feature(dev, DRIVER_MODESET)) {
drm_fb_helper_restore(); drm_fb_helper_restore();
vga_switcheroo_process_delayed_switch();
return; return;
} }
......
...@@ -201,7 +201,7 @@ static int i915_drm_freeze(struct drm_device *dev) ...@@ -201,7 +201,7 @@ static int i915_drm_freeze(struct drm_device *dev)
return 0; return 0;
} }
static int i915_suspend(struct drm_device *dev, pm_message_t state) int i915_suspend(struct drm_device *dev, pm_message_t state)
{ {
int error; int error;
...@@ -255,7 +255,7 @@ static int i915_drm_thaw(struct drm_device *dev) ...@@ -255,7 +255,7 @@ static int i915_drm_thaw(struct drm_device *dev)
return error; return error;
} }
static int i915_resume(struct drm_device *dev) int i915_resume(struct drm_device *dev)
{ {
if (pci_enable_device(dev->pdev)) if (pci_enable_device(dev->pdev))
return -EIO; return -EIO;
......
...@@ -736,6 +736,8 @@ extern unsigned int i915_fbpercrtc; ...@@ -736,6 +736,8 @@ extern unsigned int i915_fbpercrtc;
extern unsigned int i915_powersave; extern unsigned int i915_powersave;
extern unsigned int i915_lvds_downclock; extern unsigned int i915_lvds_downclock;
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_save_display(struct drm_device *dev);
extern void i915_restore_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); extern int i915_master_create(struct drm_device *dev, struct drm_master *master);
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/fb.h> #include <linux/fb.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/vga_switcheroo.h>
#include "drmP.h" #include "drmP.h"
#include "drm.h" #include "drm.h"
...@@ -235,6 +236,7 @@ static int intelfb_create(struct drm_device *dev, uint32_t fb_width, ...@@ -235,6 +236,7 @@ static int intelfb_create(struct drm_device *dev, uint32_t fb_width,
obj_priv->gtt_offset, fbo); obj_priv->gtt_offset, fbo);
mutex_unlock(&dev->struct_mutex); mutex_unlock(&dev->struct_mutex);
vga_switcheroo_client_fb_set(dev->pdev, info);
return 0; return 0;
out_unpin: out_unpin:
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
#include "nouveau_drm.h" #include "nouveau_drm.h"
#include "nv50_display.h" #include "nv50_display.h"
#include <linux/vga_switcheroo.h>
#define NOUVEAU_DSM_SUPPORTED 0x00 #define NOUVEAU_DSM_SUPPORTED 0x00
#define NOUVEAU_DSM_SUPPORTED_FUNCTIONS 0x00 #define NOUVEAU_DSM_SUPPORTED_FUNCTIONS 0x00
...@@ -28,31 +30,30 @@ ...@@ -28,31 +30,30 @@
#define NOUVEAU_DSM_POWER_SPEED 0x01 #define NOUVEAU_DSM_POWER_SPEED 0x01
#define NOUVEAU_DSM_POWER_STAMINA 0x02 #define NOUVEAU_DSM_POWER_STAMINA 0x02
static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) static struct nouveau_dsm_priv {
{ bool dsm_detected;
static char muid[] = { acpi_handle dhandle;
0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, acpi_handle dsm_handle;
0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4, } nouveau_dsm_priv;
};
static const char nouveau_dsm_muid[] = {
0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D,
0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4,
};
struct pci_dev *pdev = dev->pdev; static int nouveau_dsm(acpi_handle handle, int func, int arg, int *result)
struct acpi_handle *handle; {
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_object_list input; struct acpi_object_list input;
union acpi_object params[4]; union acpi_object params[4];
union acpi_object *obj; union acpi_object *obj;
int err; int err;
handle = DEVICE_ACPI_HANDLE(&pdev->dev);
if (!handle)
return -ENODEV;
input.count = 4; input.count = 4;
input.pointer = params; input.pointer = params;
params[0].type = ACPI_TYPE_BUFFER; params[0].type = ACPI_TYPE_BUFFER;
params[0].buffer.length = sizeof(muid); params[0].buffer.length = sizeof(nouveau_dsm_muid);
params[0].buffer.pointer = (char *)muid; params[0].buffer.pointer = (char *)nouveau_dsm_muid;
params[1].type = ACPI_TYPE_INTEGER; params[1].type = ACPI_TYPE_INTEGER;
params[1].integer.value = 0x00000102; params[1].integer.value = 0x00000102;
params[2].type = ACPI_TYPE_INTEGER; params[2].type = ACPI_TYPE_INTEGER;
...@@ -62,7 +63,7 @@ static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) ...@@ -62,7 +63,7 @@ static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result)
err = acpi_evaluate_object(handle, "_DSM", &input, &output); err = acpi_evaluate_object(handle, "_DSM", &input, &output);
if (err) { if (err) {
NV_INFO(dev, "failed to evaluate _DSM: %d\n", err); printk(KERN_INFO "failed to evaluate _DSM: %d\n", err);
return err; return err;
} }
...@@ -86,40 +87,119 @@ static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) ...@@ -86,40 +87,119 @@ static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result)
return 0; return 0;
} }
int nouveau_hybrid_setup(struct drm_device *dev) static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id)
{ {
int result; return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id, NULL);
}
if (nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STATE,
&result)) static int nouveau_dsm_set_discrete_state(acpi_handle handle, enum vga_switcheroo_state state)
return -ENODEV; {
int arg;
NV_INFO(dev, "_DSM hardware status gave 0x%x\n", result); if (state == VGA_SWITCHEROO_ON)
arg = NOUVEAU_DSM_POWER_SPEED;
if (result) { /* Ensure that the external GPU is enabled */ else
nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_SPEED, NULL); arg = NOUVEAU_DSM_POWER_STAMINA;
nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_SPEED, nouveau_dsm(handle, NOUVEAU_DSM_POWER, arg, NULL);
NULL); return 0;
} else { /* Stamina mode - disable the external GPU */ }
nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_STAMINA,
NULL); static int nouveau_dsm_switchto(enum vga_switcheroo_client_id id)
nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STAMINA, {
NULL); if (id == VGA_SWITCHEROO_IGD)
} return nouveau_dsm_switch_mux(nouveau_dsm_priv.dsm_handle, NOUVEAU_DSM_LED_STAMINA);
else
return nouveau_dsm_switch_mux(nouveau_dsm_priv.dsm_handle, NOUVEAU_DSM_LED_SPEED);
}
static int nouveau_dsm_power_state(enum vga_switcheroo_client_id id,
enum vga_switcheroo_state state)
{
if (id == VGA_SWITCHEROO_IGD)
return 0;
return nouveau_dsm_set_discrete_state(nouveau_dsm_priv.dsm_handle, state);
}
static int nouveau_dsm_init(void)
{
return 0; return 0;
} }
bool nouveau_dsm_probe(struct drm_device *dev) static int nouveau_dsm_get_client_id(struct pci_dev *pdev)
{ {
int support = 0; if (nouveau_dsm_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev))
return VGA_SWITCHEROO_IGD;
else
return VGA_SWITCHEROO_DIS;
}
static struct vga_switcheroo_handler nouveau_dsm_handler = {
.switchto = nouveau_dsm_switchto,
.power_state = nouveau_dsm_power_state,
.init = nouveau_dsm_init,
.get_client_id = nouveau_dsm_get_client_id,
};
if (nouveau_dsm(dev, NOUVEAU_DSM_SUPPORTED, static bool nouveau_dsm_pci_probe(struct pci_dev *pdev)
NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &support)) {
acpi_handle dhandle, nvidia_handle;
acpi_status status;
int ret;
uint32_t result;
dhandle = DEVICE_ACPI_HANDLE(&pdev->dev);
if (!dhandle)
return false;
status = acpi_get_handle(dhandle, "_DSM", &nvidia_handle);
if (ACPI_FAILURE(status)) {
return false; return false;
}
if (!support) ret= nouveau_dsm(nvidia_handle, NOUVEAU_DSM_SUPPORTED,
NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &result);
if (ret < 0)
return false; return false;
nouveau_dsm_priv.dhandle = dhandle;
nouveau_dsm_priv.dsm_handle = nvidia_handle;
return true; return true;
} }
static bool nouveau_dsm_detect(void)
{
char acpi_method_name[255] = { 0 };
struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name};
struct pci_dev *pdev = NULL;
int has_dsm = 0;
int vga_count = 0;
while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) {
vga_count++;
has_dsm |= (nouveau_dsm_pci_probe(pdev) == true);
}
if (vga_count == 2 && has_dsm) {
acpi_get_name(nouveau_dsm_priv.dsm_handle, ACPI_FULL_PATHNAME, &buffer);
printk(KERN_INFO "VGA switcheroo: detected DSM switching method %s handle\n",
acpi_method_name);
nouveau_dsm_priv.dsm_detected = true;
return true;
}
return false;
}
void nouveau_register_dsm_handler(void)
{
bool r;
r = nouveau_dsm_detect();
if (!r)
return;
vga_switcheroo_register_handler(&nouveau_dsm_handler);
}
void nouveau_unregister_dsm_handler(void)
{
vga_switcheroo_unregister_handler();
}
...@@ -135,7 +135,7 @@ nouveau_pci_remove(struct pci_dev *pdev) ...@@ -135,7 +135,7 @@ nouveau_pci_remove(struct pci_dev *pdev)
drm_put_dev(dev); drm_put_dev(dev);
} }
static int int
nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state) nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state)
{ {
struct drm_device *dev = pci_get_drvdata(pdev); struct drm_device *dev = pci_get_drvdata(pdev);
...@@ -233,7 +233,7 @@ nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state) ...@@ -233,7 +233,7 @@ nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state)
return ret; return ret;
} }
static int int
nouveau_pci_resume(struct pci_dev *pdev) nouveau_pci_resume(struct pci_dev *pdev)
{ {
struct drm_device *dev = pci_get_drvdata(pdev); struct drm_device *dev = pci_get_drvdata(pdev);
...@@ -402,8 +402,10 @@ static int __init nouveau_init(void) ...@@ -402,8 +402,10 @@ static int __init nouveau_init(void)
nouveau_modeset = 1; nouveau_modeset = 1;
} }
if (nouveau_modeset == 1) if (nouveau_modeset == 1) {
driver.driver_features |= DRIVER_MODESET; driver.driver_features |= DRIVER_MODESET;
nouveau_register_dsm_handler();
}
return drm_init(&driver); return drm_init(&driver);
} }
...@@ -411,6 +413,7 @@ static int __init nouveau_init(void) ...@@ -411,6 +413,7 @@ static int __init nouveau_init(void)
static void __exit nouveau_exit(void) static void __exit nouveau_exit(void)
{ {
drm_exit(&driver); drm_exit(&driver);
nouveau_unregister_dsm_handler();
} }
module_init(nouveau_init); module_init(nouveau_init);
......
...@@ -614,7 +614,6 @@ struct drm_nouveau_private { ...@@ -614,7 +614,6 @@ struct drm_nouveau_private {
} susres; } susres;
struct backlight_device *backlight; struct backlight_device *backlight;
bool acpi_dsm;
struct nouveau_channel *evo; struct nouveau_channel *evo;
...@@ -682,6 +681,9 @@ extern int nouveau_ignorelid; ...@@ -682,6 +681,9 @@ extern int nouveau_ignorelid;
extern int nouveau_nofbaccel; extern int nouveau_nofbaccel;
extern int nouveau_noaccel; extern int nouveau_noaccel;
extern int nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state);
extern int nouveau_pci_resume(struct pci_dev *pdev);
/* nouveau_state.c */ /* nouveau_state.c */
extern void nouveau_preclose(struct drm_device *dev, struct drm_file *); extern void nouveau_preclose(struct drm_device *dev, struct drm_file *);
extern int nouveau_load(struct drm_device *, unsigned long flags); extern int nouveau_load(struct drm_device *, unsigned long flags);
...@@ -848,19 +850,8 @@ extern int nouveau_dma_init(struct nouveau_channel *); ...@@ -848,19 +850,8 @@ extern int nouveau_dma_init(struct nouveau_channel *);
extern int nouveau_dma_wait(struct nouveau_channel *, int size); extern int nouveau_dma_wait(struct nouveau_channel *, int size);
/* nouveau_acpi.c */ /* nouveau_acpi.c */
#ifdef CONFIG_ACPI void nouveau_register_dsm_handler(void);
extern int nouveau_hybrid_setup(struct drm_device *dev); void nouveau_unregister_dsm_handler(void);
extern bool nouveau_dsm_probe(struct drm_device *dev);
#else
static inline int nouveau_hybrid_setup(struct drm_device *dev)
{
return 0;
}
static inline bool nouveau_dsm_probe(struct drm_device *dev)
{
return false;
}
#endif
/* nouveau_backlight.c */ /* nouveau_backlight.c */
#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include <linux/fb.h> #include <linux/fb.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/screen_info.h> #include <linux/screen_info.h>
#include <linux/vga_switcheroo.h>
#include "drmP.h" #include "drmP.h"
#include "drm.h" #include "drm.h"
...@@ -370,6 +371,7 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width, ...@@ -370,6 +371,7 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
nvbo->bo.offset, nvbo); nvbo->bo.offset, nvbo);
mutex_unlock(&dev->struct_mutex); mutex_unlock(&dev->struct_mutex);
vga_switcheroo_client_fb_set(dev->pdev, info);
return 0; return 0;
out_unref: out_unref:
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "drm_sarea.h" #include "drm_sarea.h"
#include "drm_crtc_helper.h" #include "drm_crtc_helper.h"
#include <linux/vgaarb.h> #include <linux/vgaarb.h>
#include <linux/vga_switcheroo.h>
#include "nouveau_drv.h" #include "nouveau_drv.h"
#include "nouveau_drm.h" #include "nouveau_drm.h"
...@@ -371,6 +372,30 @@ nouveau_card_init_channel(struct drm_device *dev) ...@@ -371,6 +372,30 @@ nouveau_card_init_channel(struct drm_device *dev)
return ret; return ret;
} }
static void nouveau_switcheroo_set_state(struct pci_dev *pdev,
enum vga_switcheroo_state state)
{
pm_message_t pmm = { .event = PM_EVENT_SUSPEND };
if (state == VGA_SWITCHEROO_ON) {
printk(KERN_ERR "VGA switcheroo: switched nouveau on\n");
nouveau_pci_resume(pdev);
} else {
printk(KERN_ERR "VGA switcheroo: switched nouveau off\n");
nouveau_pci_suspend(pdev, pmm);
}
}
static bool nouveau_switcheroo_can_switch(struct pci_dev *pdev)
{
struct drm_device *dev = pci_get_drvdata(pdev);
bool can_switch;
spin_lock(&dev->count_lock);
can_switch = (dev->open_count == 0);
spin_unlock(&dev->count_lock);
return can_switch;
}
int int
nouveau_card_init(struct drm_device *dev) nouveau_card_init(struct drm_device *dev)
{ {
...@@ -384,6 +409,8 @@ nouveau_card_init(struct drm_device *dev) ...@@ -384,6 +409,8 @@ nouveau_card_init(struct drm_device *dev)
return 0; return 0;
vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode);
vga_switcheroo_register_client(dev->pdev, nouveau_switcheroo_set_state,
nouveau_switcheroo_can_switch);
/* Initialise internal driver API hooks */ /* Initialise internal driver API hooks */
ret = nouveau_init_engine_ptrs(dev); ret = nouveau_init_engine_ptrs(dev);
...@@ -617,11 +644,6 @@ int nouveau_load(struct drm_device *dev, unsigned long flags) ...@@ -617,11 +644,6 @@ int nouveau_load(struct drm_device *dev, unsigned long flags)
NV_DEBUG(dev, "vendor: 0x%X device: 0x%X class: 0x%X\n", NV_DEBUG(dev, "vendor: 0x%X device: 0x%X class: 0x%X\n",
dev->pci_vendor, dev->pci_device, dev->pdev->class); dev->pci_vendor, dev->pci_device, dev->pdev->class);
dev_priv->acpi_dsm = nouveau_dsm_probe(dev);
if (dev_priv->acpi_dsm)
nouveau_hybrid_setup(dev);
dev_priv->wq = create_workqueue("nouveau"); dev_priv->wq = create_workqueue("nouveau");
if (!dev_priv->wq) if (!dev_priv->wq)
return -EINVAL; return -EINVAL;
......
...@@ -54,7 +54,8 @@ radeon-y += radeon_device.o radeon_kms.o \ ...@@ -54,7 +54,8 @@ radeon-y += radeon_device.o radeon_kms.o \
radeon_cs.o radeon_bios.o radeon_benchmark.o r100.o r300.o r420.o \ radeon_cs.o radeon_bios.o radeon_benchmark.o r100.o r300.o r420.o \
rs400.o rs600.o rs690.o rv515.o r520.o r600.o rv770.o radeon_test.o \ rs400.o rs600.o rs690.o rv515.o r520.o r600.o rv770.o radeon_test.o \
r200.o radeon_legacy_tv.o r600_cs.o r600_blit.o r600_blit_shaders.o \ r200.o radeon_legacy_tv.o r600_cs.o r600_blit.o r600_blit_shaders.o \
r600_blit_kms.o radeon_pm.o atombios_dp.o r600_audio.o r600_hdmi.o r600_blit_kms.o radeon_pm.o atombios_dp.o r600_audio.o r600_hdmi.o \
radeon_atpx_handler.o
radeon-$(CONFIG_COMPAT) += radeon_ioc32.o radeon-$(CONFIG_COMPAT) += radeon_ioc32.o
......
...@@ -118,6 +118,10 @@ struct radeon_device; ...@@ -118,6 +118,10 @@ struct radeon_device;
/* /*
* BIOS. * BIOS.
*/ */
#define ATRM_BIOS_PAGE 4096
bool radeon_atrm_supported(struct pci_dev *pdev);
int radeon_atrm_get_bios_chunk(uint8_t *bios, int offset, int len);
bool radeon_get_bios(struct radeon_device *rdev); bool radeon_get_bios(struct radeon_device *rdev);
...@@ -838,6 +842,8 @@ struct radeon_device { ...@@ -838,6 +842,8 @@ struct radeon_device {
int audio_bits_per_sample; int audio_bits_per_sample;
uint8_t audio_status_bits; uint8_t audio_status_bits;
uint8_t audio_category_code; uint8_t audio_category_code;
bool powered_down;
}; };
int radeon_device_init(struct radeon_device *rdev, int radeon_device_init(struct radeon_device *rdev,
...@@ -1042,6 +1048,8 @@ extern void radeon_legacy_set_clock_gating(struct radeon_device *rdev, int enabl ...@@ -1042,6 +1048,8 @@ extern void radeon_legacy_set_clock_gating(struct radeon_device *rdev, int enabl
extern void radeon_atom_set_clock_gating(struct radeon_device *rdev, int enable); extern void radeon_atom_set_clock_gating(struct radeon_device *rdev, int enable);
extern void radeon_ttm_placement_from_domain(struct radeon_bo *rbo, u32 domain); extern void radeon_ttm_placement_from_domain(struct radeon_bo *rbo, u32 domain);
extern bool radeon_ttm_bo_is_radeon_bo(struct ttm_buffer_object *bo); extern bool radeon_ttm_bo_is_radeon_bo(struct ttm_buffer_object *bo);
extern int radeon_resume_kms(struct drm_device *dev);
extern int radeon_suspend_kms(struct drm_device *dev, pm_message_t state);
/* r100,rv100,rs100,rv200,rs200,r200,rv250,rs300,rv280 */ /* r100,rv100,rs100,rv200,rs200,r200,rv250,rs300,rv280 */
struct r100_mc_save { struct r100_mc_save {
......
/*
* Copyright (c) 2010 Red Hat Inc.
* Author : Dave Airlie <airlied@redhat.com>
*
* Licensed under GPLv2
*
* ATPX support for both Intel/ATI
*/
#include <linux/vga_switcheroo.h>
#include <acpi/acpi.h>
#include <acpi/acpi_bus.h>
#include <linux/pci.h>
#define ATPX_VERSION 0
#define ATPX_GPU_PWR 2
#define ATPX_MUX_SELECT 3
#define ATPX_INTEGRATED 0
#define ATPX_DISCRETE 1
#define ATPX_MUX_IGD 0
#define ATPX_MUX_DISCRETE 1
static struct radeon_atpx_priv {
bool atpx_detected;
/* handle for device - and atpx */
acpi_handle dhandle;
acpi_handle atpx_handle;
acpi_handle atrm_handle;
} radeon_atpx_priv;
/* retrieve the ROM in 4k blocks */
static int radeon_atrm_call(acpi_handle atrm_handle, uint8_t *bios,
int offset, int len)
{
acpi_status status;
union acpi_object atrm_arg_elements[2], *obj;
struct acpi_object_list atrm_arg;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL};
atrm_arg.count = 2;
atrm_arg.pointer = &atrm_arg_elements[0];
atrm_arg_elements[0].type = ACPI_TYPE_INTEGER;
atrm_arg_elements[0].integer.value = offset;
atrm_arg_elements[1].type = ACPI_TYPE_INTEGER;
atrm_arg_elements[1].integer.value = len;
status = acpi_evaluate_object(atrm_handle, NULL, &atrm_arg, &buffer);
if (ACPI_FAILURE(status)) {
printk("failed to evaluate ATRM 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 radeon_atrm_supported(struct pci_dev *pdev)
{
/* get the discrete ROM only via ATRM */
if (!radeon_atpx_priv.atpx_detected)
return false;
if (radeon_atpx_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev))
return false;
return true;
}
int radeon_atrm_get_bios_chunk(uint8_t *bios, int offset, int len)
{
return radeon_atrm_call(radeon_atpx_priv.atrm_handle, bios, offset, len);
}
static int radeon_atpx_get_version(acpi_handle handle)
{
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(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(KERN_INFO "radeon atpx: version is %d\n", *((u8 *)(obj->buffer.pointer) + 2));
kfree(buffer.pointer);
return 0;
}
static int radeon_atpx_execute(acpi_handle handle, 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 (!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(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 radeon_atpx_set_discrete_state(acpi_handle handle, int state)
{
return radeon_atpx_execute(handle, ATPX_GPU_PWR, state);
}
static int radeon_atpx_switch_mux(acpi_handle handle, int mux_id)
{
return radeon_atpx_execute(handle, ATPX_MUX_SELECT, mux_id);
}
static int radeon_atpx_switchto(enum vga_switcheroo_client_id id)
{
if (id == VGA_SWITCHEROO_IGD)
radeon_atpx_switch_mux(radeon_atpx_priv.atpx_handle, 0);
else
radeon_atpx_switch_mux(radeon_atpx_priv.atpx_handle, 1);
return 0;
}
static int radeon_atpx_power_state(enum vga_switcheroo_client_id id,
enum vga_switcheroo_state state)
{
/* on w500 ACPI can't change intel gpu state */
if (id == VGA_SWITCHEROO_IGD)
return 0;
radeon_atpx_set_discrete_state(radeon_atpx_priv.atpx_handle, state);
return 0;
}
static bool radeon_atpx_pci_probe_handle(struct pci_dev *pdev)
{
acpi_handle dhandle, atpx_handle, atrm_handle;
acpi_status status;
dhandle = DEVICE_ACPI_HANDLE(&pdev->dev);
if (!dhandle)
return false;
status = acpi_get_handle(dhandle, "ATPX", &atpx_handle);
if (ACPI_FAILURE(status))
return false;
status = acpi_get_handle(dhandle, "ATRM", &atrm_handle);
if (ACPI_FAILURE(status))
return false;
radeon_atpx_priv.dhandle = dhandle;
radeon_atpx_priv.atpx_handle = atpx_handle;
radeon_atpx_priv.atrm_handle = atrm_handle;
return true;
}
static int radeon_atpx_init(void)
{
/* set up the ATPX handle */
radeon_atpx_get_version(radeon_atpx_priv.atpx_handle);
return 0;
}
static int radeon_atpx_get_client_id(struct pci_dev *pdev)
{
if (radeon_atpx_priv.dhandle == DEVICE_ACPI_HANDLE(&pdev->dev))
return VGA_SWITCHEROO_IGD;
else
return VGA_SWITCHEROO_DIS;
}
static struct vga_switcheroo_handler radeon_atpx_handler = {
.switchto = radeon_atpx_switchto,
.power_state = radeon_atpx_power_state,
.init = radeon_atpx_init,
.get_client_id = radeon_atpx_get_client_id,
};
static bool radeon_atpx_detect(void)
{
char acpi_method_name[255] = { 0 };
struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name};
struct pci_dev *pdev = NULL;
bool has_atpx = false;
int vga_count = 0;
while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) {
vga_count++;
has_atpx |= (radeon_atpx_pci_probe_handle(pdev) == true);
}
if (has_atpx && vga_count == 2) {
acpi_get_name(radeon_atpx_priv.atpx_handle, ACPI_FULL_PATHNAME, &buffer);
printk(KERN_INFO "VGA switcheroo: detected switching method %s handle\n",
acpi_method_name);
radeon_atpx_priv.atpx_detected = true;
return true;
}
return false;
}
void radeon_register_atpx_handler(void)
{
bool r;
/* detect if we have any ATPX + 2 VGA in the system */
r = radeon_atpx_detect();
if (!r)
return;
vga_switcheroo_register_handler(&radeon_atpx_handler);
}
void radeon_unregister_atpx_handler(void)
{
vga_switcheroo_unregister_handler();
}
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "radeon.h" #include "radeon.h"
#include "atom.h" #include "atom.h"
#include <linux/vga_switcheroo.h>
/* /*
* BIOS. * BIOS.
*/ */
...@@ -62,7 +63,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) ...@@ -62,7 +63,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev)
iounmap(bios); iounmap(bios);
return false; return false;
} }
memcpy(rdev->bios, bios, size); memcpy_fromio(rdev->bios, bios, size);
iounmap(bios); iounmap(bios);
return true; return true;
} }
...@@ -93,6 +94,38 @@ static bool radeon_read_bios(struct radeon_device *rdev) ...@@ -93,6 +94,38 @@ static bool radeon_read_bios(struct radeon_device *rdev)
return true; return true;
} }
/* ATRM is used to get the BIOS on the discrete cards in
* dual-gpu systems.
*/
static bool radeon_atrm_get_bios(struct radeon_device *rdev)
{
int ret;
int size = 64 * 1024;
int i;
if (!radeon_atrm_supported(rdev->pdev))
return false;
rdev->bios = kmalloc(size, GFP_KERNEL);
if (!rdev->bios) {
DRM_ERROR("Unable to allocate bios\n");
return false;
}
for (i = 0; i < size / ATRM_BIOS_PAGE; i++) {
ret = radeon_atrm_get_bios_chunk(rdev->bios,
(i * ATRM_BIOS_PAGE),
ATRM_BIOS_PAGE);
if (ret <= 0)
break;
}
if (i == 0 || rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) {
kfree(rdev->bios);
return false;
}
return true;
}
static bool r700_read_disabled_bios(struct radeon_device *rdev) static bool r700_read_disabled_bios(struct radeon_device *rdev)
{ {
uint32_t viph_control; uint32_t viph_control;
...@@ -388,16 +421,16 @@ static bool radeon_read_disabled_bios(struct radeon_device *rdev) ...@@ -388,16 +421,16 @@ static bool radeon_read_disabled_bios(struct radeon_device *rdev)
return legacy_read_disabled_bios(rdev); return legacy_read_disabled_bios(rdev);
} }
bool radeon_get_bios(struct radeon_device *rdev) bool radeon_get_bios(struct radeon_device *rdev)
{ {
bool r; bool r;
uint16_t tmp; uint16_t tmp;
if (rdev->flags & RADEON_IS_IGP) { r = radeon_atrm_get_bios(rdev);
if (r == false)
r = igp_read_bios_from_vram(rdev); r = igp_read_bios_from_vram(rdev);
if (r == false) if (r == false)
r = radeon_read_bios(rdev);
} else
r = radeon_read_bios(rdev); r = radeon_read_bios(rdev);
if (r == false) { if (r == false) {
r = radeon_read_disabled_bios(rdev); r = radeon_read_disabled_bios(rdev);
...@@ -408,6 +441,7 @@ bool radeon_get_bios(struct radeon_device *rdev) ...@@ -408,6 +441,7 @@ bool radeon_get_bios(struct radeon_device *rdev)
return false; return false;
} }
if (rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) { if (rdev->bios[0] != 0x55 || rdev->bios[1] != 0xaa) {
printk("BIOS signature incorrect %x %x\n", rdev->bios[0], rdev->bios[1]);
goto free_bios; goto free_bios;
} }
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
#include <drm/radeon_drm.h> #include <drm/radeon_drm.h>
#include <linux/vgaarb.h> #include <linux/vgaarb.h>
#include <linux/vga_switcheroo.h>
#include "radeon_reg.h" #include "radeon_reg.h"
#include "radeon.h" #include "radeon.h"
#include "radeon_asic.h" #include "radeon_asic.h"
...@@ -613,6 +614,36 @@ void radeon_check_arguments(struct radeon_device *rdev) ...@@ -613,6 +614,36 @@ 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_INFO "radeon: switched on\n");
/* don't suspend or resume card normally */
rdev->powered_down = false;
radeon_resume_kms(dev);
} else {
printk(KERN_INFO "radeon: switched off\n");
radeon_suspend_kms(dev, pmm);
/* don't suspend or resume card normally */
rdev->powered_down = true;
}
}
static bool radeon_switcheroo_can_switch(struct pci_dev *pdev)
{
struct drm_device *dev = pci_get_drvdata(pdev);
bool can_switch;
spin_lock(&dev->count_lock);
can_switch = (dev->open_count == 0);
spin_unlock(&dev->count_lock);
return can_switch;
}
int radeon_device_init(struct radeon_device *rdev, int radeon_device_init(struct radeon_device *rdev,
struct drm_device *ddev, struct drm_device *ddev,
struct pci_dev *pdev, struct pci_dev *pdev,
...@@ -692,6 +723,9 @@ int radeon_device_init(struct radeon_device *rdev, ...@@ -692,6 +723,9 @@ int radeon_device_init(struct radeon_device *rdev,
/* this will fail for cards that aren't VGA class devices, just /* this will fail for cards that aren't VGA class devices, just
* ignore it */ * ignore it */
vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode); vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode);
vga_switcheroo_register_client(rdev->pdev,
radeon_switcheroo_set_state,
radeon_switcheroo_can_switch);
r = radeon_init(rdev); r = radeon_init(rdev);
if (r) if (r)
...@@ -723,6 +757,7 @@ void radeon_device_fini(struct radeon_device *rdev) ...@@ -723,6 +757,7 @@ void radeon_device_fini(struct radeon_device *rdev)
rdev->shutdown = true; rdev->shutdown = true;
radeon_fini(rdev); radeon_fini(rdev);
destroy_workqueue(rdev->wq); destroy_workqueue(rdev->wq);
vga_switcheroo_unregister_client(rdev->pdev);
vga_client_register(rdev->pdev, NULL, NULL, NULL); vga_client_register(rdev->pdev, NULL, NULL, NULL);
iounmap(rdev->rmmio); iounmap(rdev->rmmio);
rdev->rmmio = NULL; rdev->rmmio = NULL;
...@@ -746,6 +781,8 @@ int radeon_suspend_kms(struct drm_device *dev, pm_message_t state) ...@@ -746,6 +781,8 @@ int radeon_suspend_kms(struct drm_device *dev, pm_message_t state)
} }
rdev = dev->dev_private; rdev = dev->dev_private;
if (rdev->powered_down)
return 0;
/* unpin the front buffers */ /* unpin the front buffers */
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
struct radeon_framebuffer *rfb = to_radeon_framebuffer(crtc->fb); struct radeon_framebuffer *rfb = to_radeon_framebuffer(crtc->fb);
...@@ -791,6 +828,9 @@ int radeon_resume_kms(struct drm_device *dev) ...@@ -791,6 +828,9 @@ int radeon_resume_kms(struct drm_device *dev)
{ {
struct radeon_device *rdev = dev->dev_private; struct radeon_device *rdev = dev->dev_private;
if (rdev->powered_down)
return 0;
acquire_console_sem(); acquire_console_sem();
pci_set_power_state(dev->pdev, PCI_D0); pci_set_power_state(dev->pdev, PCI_D0);
pci_restore_state(dev->pdev); pci_restore_state(dev->pdev);
......
...@@ -339,6 +339,7 @@ static int __init radeon_init(void) ...@@ -339,6 +339,7 @@ static int __init radeon_init(void)
driver = &kms_driver; driver = &kms_driver;
driver->driver_features |= DRIVER_MODESET; driver->driver_features |= DRIVER_MODESET;
driver->num_ioctls = radeon_max_kms_ioctl; driver->num_ioctls = radeon_max_kms_ioctl;
radeon_register_atpx_handler();
} }
/* if the vga console setting is enabled still /* if the vga console setting is enabled still
* let modprobe override it */ * let modprobe override it */
...@@ -348,6 +349,7 @@ static int __init radeon_init(void) ...@@ -348,6 +349,7 @@ static int __init radeon_init(void)
static void __exit radeon_exit(void) static void __exit radeon_exit(void)
{ {
drm_exit(driver); drm_exit(driver);
radeon_unregister_atpx_handler();
} }
module_init(radeon_init); module_init(radeon_init);
......
...@@ -455,6 +455,9 @@ extern void r600_blit_swap(struct drm_device *dev, ...@@ -455,6 +455,9 @@ extern void r600_blit_swap(struct drm_device *dev,
int sx, int sy, int dx, int dy, int sx, int sy, int dx, int dy,
int w, int h, int src_pitch, int dst_pitch, int cpp); int w, int h, int src_pitch, int dst_pitch, int cpp);
/* atpx handler */
void radeon_register_atpx_handler(void);
void radeon_unregister_atpx_handler(void);
/* Flags for stats.boxes /* Flags for stats.boxes
*/ */
#define RADEON_BOX_DMA_IDLE 0x1 #define RADEON_BOX_DMA_IDLE 0x1
......
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
#include "drm_fb_helper.h" #include "drm_fb_helper.h"
#include <linux/vga_switcheroo.h>
struct radeon_fb_device { struct radeon_fb_device {
struct drm_fb_helper helper; struct drm_fb_helper helper;
struct radeon_framebuffer *rfb; struct radeon_framebuffer *rfb;
...@@ -291,6 +293,7 @@ int radeonfb_create(struct drm_device *dev, ...@@ -291,6 +293,7 @@ int radeonfb_create(struct drm_device *dev,
rfbdev->rdev = rdev; rfbdev->rdev = rdev;
mutex_unlock(&rdev->ddev->struct_mutex); mutex_unlock(&rdev->ddev->struct_mutex);
vga_switcheroo_client_fb_set(rdev->ddev->pdev, info);
return 0; return 0;
out_unref: out_unref:
......
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
#include "radeon.h" #include "radeon.h"
#include "radeon_drm.h" #include "radeon_drm.h"
#include <linux/vga_switcheroo.h>
int radeon_driver_unload_kms(struct drm_device *dev) int radeon_driver_unload_kms(struct drm_device *dev)
{ {
struct radeon_device *rdev = dev->dev_private; struct radeon_device *rdev = dev->dev_private;
...@@ -136,6 +138,7 @@ int radeon_driver_firstopen_kms(struct drm_device *dev) ...@@ -136,6 +138,7 @@ int radeon_driver_firstopen_kms(struct drm_device *dev)
void radeon_driver_lastclose_kms(struct drm_device *dev) void radeon_driver_lastclose_kms(struct drm_device *dev)
{ {
vga_switcheroo_process_delayed_switch();
} }
int radeon_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) int radeon_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv)
......
...@@ -8,3 +8,16 @@ config VGA_ARB ...@@ -8,3 +8,16 @@ config VGA_ARB
are accessed at same time they need some kind of coordination. Please are accessed at same time they need some kind of coordination. Please
see Documentation/vgaarbiter.txt for more details. Select this to see Documentation/vgaarbiter.txt for more details. Select this to
enable VGA arbiter. enable VGA arbiter.
config VGA_SWITCHEROO
bool "Laptop Hybrid Grapics - GPU switching support"
default y
depends on X86
depends on ACPI
help
Many laptops released in 2008/9/10 have two gpus with a multiplxer
to switch between them. This adds support for dynamic switching when
X isn't running and delayed switching until the next logoff. This
features is called hybrid graphics, ATI PowerXpress, and Nvidia
HybridPower.
obj-$(CONFIG_VGA_ARB) += vgaarb.o obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_VGA_SWITCHEROO) += vga_switcheroo.o
/*
* Copyright (c) 2010 Red Hat Inc.
* Author : Dave Airlie <airlied@redhat.com>
*
*
* Licensed under GPLv2
*
* vga_switcheroo.c - Support for laptop with dual GPU using one set of outputs
Switcher interface - methods require for ATPX and DCM
- switchto - this throws the output MUX switch
- discrete_set_power - sets the power state for the discrete card
GPU driver interface
- set_gpu_state - this should do the equiv of s/r for the card
- this should *not* set the discrete power state
- switch_check - check if the device is in a position to switch now
*/
#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 <acpi/acpi_bus.h>
#include <linux/pci.h>
#include <linux/vga_switcheroo.h>
struct vga_switcheroo_client {
struct pci_dev *pdev;
struct fb_info *fb_info;
int pwr_state;
void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state);
bool (*can_switch)(struct pci_dev *pdev);
int id;
bool active;
};
static DEFINE_MUTEX(vgasr_mutex);
struct vgasr_priv {
bool active;
bool delayed_switch_active;
enum vga_switcheroo_client_id delayed_client_id;
struct dentry *debugfs_root;
struct dentry *switch_file;
int registered_clients;
struct vga_switcheroo_client clients[VGA_SWITCHEROO_MAX_CLIENTS];
struct vga_switcheroo_handler *handler;
};
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;
int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler)
{
mutex_lock(&vgasr_mutex);
if (vgasr_priv.handler) {
mutex_unlock(&vgasr_mutex);
return -EINVAL;
}
vgasr_priv.handler = handler;
mutex_unlock(&vgasr_mutex);
return 0;
}
EXPORT_SYMBOL(vga_switcheroo_register_handler);
void vga_switcheroo_unregister_handler(void)
{
mutex_lock(&vgasr_mutex);
vgasr_priv.handler = NULL;
mutex_unlock(&vgasr_mutex);
}
EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
static void vga_switcheroo_enable(void)
{
int i;
int ret;
/* call the handler to init */
vgasr_priv.handler->init();
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
ret = vgasr_priv.handler->get_client_id(vgasr_priv.clients[i].pdev);
if (ret < 0)
return;
vgasr_priv.clients[i].id = ret;
}
vga_switcheroo_debugfs_init(&vgasr_priv);
vgasr_priv.active = true;
}
int vga_switcheroo_register_client(struct pci_dev *pdev,
void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state),
bool (*can_switch)(struct pci_dev *pdev))
{
int index;
mutex_lock(&vgasr_mutex);
/* don't do IGD vs DIS here */
if (vgasr_priv.registered_clients & 1)
index = 1;
else
index = 0;
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;
vgasr_priv.clients[index].can_switch = can_switch;
vgasr_priv.clients[index].id = -1;
if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW)
vgasr_priv.clients[index].active = true;
vgasr_priv.registered_clients |= (1 << index);
/* if we get two clients + handler */
if (vgasr_priv.registered_clients == 0x3 && vgasr_priv.handler) {
printk(KERN_INFO "vga_switcheroo: enabled\n");
vga_switcheroo_enable();
}
mutex_unlock(&vgasr_mutex);
return 0;
}
EXPORT_SYMBOL(vga_switcheroo_register_client);
void vga_switcheroo_unregister_client(struct pci_dev *pdev)
{
int i;
mutex_lock(&vgasr_mutex);
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].pdev == pdev) {
vgasr_priv.registered_clients &= ~(1 << i);
break;
}
}
printk(KERN_INFO "vga_switcheroo: disabled\n");
vga_switcheroo_debugfs_fini(&vgasr_priv);
vgasr_priv.active = false;
mutex_unlock(&vgasr_mutex);
}
EXPORT_SYMBOL(vga_switcheroo_unregister_client);
void vga_switcheroo_client_fb_set(struct pci_dev *pdev,
struct fb_info *info)
{
int i;
mutex_lock(&vgasr_mutex);
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].pdev == pdev) {
vgasr_priv.clients[i].fb_info = info;
break;
}
}
mutex_unlock(&vgasr_mutex);
}
EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
static int vga_switcheroo_show(struct seq_file *m, void *v)
{
int i;
mutex_lock(&vgasr_mutex);
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
seq_printf(m, "%d:%c:%s:%s\n", i,
vgasr_priv.clients[i].active ? '+' : ' ',
vgasr_priv.clients[i].pwr_state ? "Pwr" : "Off",
pci_name(vgasr_priv.clients[i].pdev));
}
mutex_unlock(&vgasr_mutex);
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(struct vga_switcheroo_client *client)
{
int ret;
ret = vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON);
/* call the driver callback to turn on device */
client->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON);
client->pwr_state = VGA_SWITCHEROO_ON;
return 0;
}
static int vga_switchoff(struct vga_switcheroo_client *client)
{
/* call the driver callback to turn off device */
client->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF);
vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF);
client->pwr_state = VGA_SWITCHEROO_OFF;
return 0;
}
static int vga_switchto(struct vga_switcheroo_client *new_client)
{
int ret;
int i;
struct vga_switcheroo_client *active = NULL;
if (new_client->active == true)
return 0;
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].active == true) {
active = &vgasr_priv.clients[i];
break;
}
}
if (!active)
return 0;
/* power up the first device */
ret = pci_enable_device(new_client->pdev);
if (ret)
return ret;
if (new_client->pwr_state == VGA_SWITCHEROO_OFF)
vga_switchon(new_client);
/* swap shadow resource to denote boot VGA device has changed so X starts on new device */
active->active = false;
active->pdev->resource[PCI_ROM_RESOURCE].flags &= ~IORESOURCE_ROM_SHADOW;
new_client->pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW;
if (new_client->fb_info) {
struct fb_event event;
event.info = new_client->fb_info;
fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event);
}
ret = vgasr_priv.handler->switchto(new_client->id);
if (ret)
return ret;
if (active->pwr_state == VGA_SWITCHEROO_ON)
vga_switchoff(active);
new_client->active = true;
return 0;
}
static ssize_t
vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
size_t cnt, loff_t *ppos)
{
char usercmd[64];
const char *pdev_name;
int i, ret;
bool delay = false, can_switch;
int client_id = -1;
struct vga_switcheroo_client *client = NULL;
if (cnt > 63)
cnt = 63;
if (copy_from_user(usercmd, ubuf, cnt))
return -EFAULT;
mutex_lock(&vgasr_mutex);
if (!vgasr_priv.active)
return -EINVAL;
/* pwr off the device not in use */
if (strncmp(usercmd, "OFF", 3) == 0) {
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].active)
continue;
if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_ON)
vga_switchoff(&vgasr_priv.clients[i]);
}
goto out;
}
/* pwr on the device not in use */
if (strncmp(usercmd, "ON", 2) == 0) {
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].active)
continue;
if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_OFF)
vga_switchon(&vgasr_priv.clients[i]);
}
goto out;
}
/* request a delayed switch - test can we switch now */
if (strncmp(usercmd, "DIGD", 4) == 0) {
client_id = VGA_SWITCHEROO_IGD;
delay = true;
}
if (strncmp(usercmd, "DDIS", 4) == 0) {
client_id = VGA_SWITCHEROO_DIS;
delay = true;
}
if (strncmp(usercmd, "IGD", 3) == 0)
client_id = VGA_SWITCHEROO_IGD;
if (strncmp(usercmd, "DIS", 3) == 0)
client_id = VGA_SWITCHEROO_DIS;
if (client_id == -1)
goto out;
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].id == client_id) {
client = &vgasr_priv.clients[i];
break;
}
}
vgasr_priv.delayed_switch_active = false;
/* okay we want a switch - test if devices are willing to switch */
can_switch = true;
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev);
if (can_switch == false) {
printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i);
break;
}
}
if (can_switch == false && delay == false)
goto out;
if (can_switch == true) {
pdev_name = pci_name(client->pdev);
ret = vga_switchto(client);
if (ret)
printk(KERN_ERR "vga_switcheroo: switching failed %d\n", ret);
} else {
printk(KERN_INFO "vga_switcheroo: setting delayed switch to client %d\n", client->id);
vgasr_priv.delayed_switch_active = true;
vgasr_priv.delayed_client_id = client_id;
/* we should at least power up the card to
make the switch faster */
if (client->pwr_state == VGA_SWITCHEROO_OFF)
vga_switchon(client);
}
out:
mutex_unlock(&vgasr_mutex);
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 "vga_switcheroo: 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 "vga_switcheroo: cannot create /sys/kernel/debug/vgaswitcheroo/switch\n");
goto fail;
}
return 0;
fail:
vga_switcheroo_debugfs_fini(priv);
return -1;
}
int vga_switcheroo_process_delayed_switch(void)
{
struct vga_switcheroo_client *client = NULL;
const char *pdev_name;
bool can_switch = true;
int i;
int ret;
int err = -EINVAL;
mutex_lock(&vgasr_mutex);
if (!vgasr_priv.delayed_switch_active)
goto err;
printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id);
for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) {
if (vgasr_priv.clients[i].id == vgasr_priv.delayed_client_id)
client = &vgasr_priv.clients[i];
can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev);
if (can_switch == false) {
printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i);
break;
}
}
if (can_switch == false || client == NULL)
goto err;
pdev_name = pci_name(client->pdev);
ret = vga_switchto(client);
if (ret)
printk(KERN_ERR "vga_switcheroo: delayed switching failed %d\n", ret);
vgasr_priv.delayed_switch_active = false;
err = 0;
err:
mutex_unlock(&vgasr_mutex);
return err;
}
EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch);
...@@ -3025,6 +3025,20 @@ static int fbcon_fb_unregistered(struct fb_info *info) ...@@ -3025,6 +3025,20 @@ static int fbcon_fb_unregistered(struct fb_info *info)
return 0; 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 #ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
static void fbcon_select_primary(struct fb_info *info) static void fbcon_select_primary(struct fb_info *info)
{ {
...@@ -3225,6 +3239,10 @@ static int fbcon_event_notify(struct notifier_block *self, ...@@ -3225,6 +3239,10 @@ static int fbcon_event_notify(struct notifier_block *self,
caps = event->data; caps = event->data;
fbcon_get_requirement(info, caps); fbcon_get_requirement(info, caps);
break; break;
case FB_EVENT_REMAP_ALL_CONSOLE:
idx = info->node;
fbcon_remap_all(idx);
break;
} }
done: done:
return ret; return ret;
......
...@@ -543,6 +543,8 @@ struct fb_cursor_user { ...@@ -543,6 +543,8 @@ struct fb_cursor_user {
#define FB_EVENT_GET_REQ 0x0D #define FB_EVENT_GET_REQ 0x0D
/* Unbind from the console if possible */ /* Unbind from the console if possible */
#define FB_EVENT_FB_UNBIND 0x0E #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_event {
struct fb_info *info; struct fb_info *info;
......
/*
* Copyright (c) 2010 Red Hat Inc.
* Author : Dave Airlie <airlied@redhat.com>
*
* Licensed under GPLv2
*
* vga_switcheroo.h - Support for laptop with dual GPU using one set of outputs
*/
#include <acpi/acpi.h>
#include <linux/fb.h>
enum vga_switcheroo_state {
VGA_SWITCHEROO_OFF,
VGA_SWITCHEROO_ON,
};
enum vga_switcheroo_client_id {
VGA_SWITCHEROO_IGD,
VGA_SWITCHEROO_DIS,
VGA_SWITCHEROO_MAX_CLIENTS,
};
struct vga_switcheroo_handler {
int (*switchto)(enum vga_switcheroo_client_id id);
int (*power_state)(enum vga_switcheroo_client_id id,
enum vga_switcheroo_state state);
int (*init)(void);
int (*get_client_id)(struct pci_dev *pdev);
};
#if defined(CONFIG_VGA_SWITCHEROO)
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),
bool (*can_switch)(struct pci_dev *dev));
void vga_switcheroo_client_fb_set(struct pci_dev *dev,
struct fb_info *info);
int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler);
void vga_switcheroo_unregister_handler(void);
int vga_switcheroo_process_delayed_switch(void);
#else
static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {}
static inline int vga_switcheroo_register_client(struct pci_dev *dev,
void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state),
bool (*can_switch)(struct pci_dev *dev)) { return 0; }
static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {}
static inline int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { return 0; }
static inline void vga_switcheroo_unregister_handler(void) {}
static inline int vga_switcheroo_process_delayed_switch(void) { return 0; }
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment