diff options
author | Dave Airlie <airlied@linux.ie> | 2010-02-01 15:38:10 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2010-03-01 16:20:37 +1000 |
commit | 6a9ee8af344e3bd7dbd61e67037096cdf7f83289 (patch) | |
tree | 07cdb493a790cf45bc473f2fc2ea1b9a166d5191 /drivers/gpu/drm/nouveau | |
parent | 9fd1de52945e06ed88a440c99ca92dab74b9b33c (diff) |
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: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/nouveau')
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_acpi.c | 160 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_drv.c | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_drv.h | 19 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_fbcon.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_state.c | 32 |
5 files changed, 160 insertions, 62 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c index 48227e744753..0e0730a53137 100644 --- a/drivers/gpu/drm/nouveau/nouveau_acpi.c +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c @@ -11,6 +11,8 @@ #include "nouveau_drm.h" #include "nv50_display.h" +#include <linux/vga_switcheroo.h> + #define NOUVEAU_DSM_SUPPORTED 0x00 #define NOUVEAU_DSM_SUPPORTED_FUNCTIONS 0x00 @@ -28,31 +30,30 @@ #define NOUVEAU_DSM_POWER_SPEED 0x01 #define NOUVEAU_DSM_POWER_STAMINA 0x02 -static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) -{ - static char muid[] = { - 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, - 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4, - }; +static struct nouveau_dsm_priv { + bool dsm_detected; + acpi_handle dhandle; + acpi_handle dsm_handle; +} 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; - struct acpi_handle *handle; +static int nouveau_dsm(acpi_handle handle, int func, int arg, int *result) +{ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_object_list input; union acpi_object params[4]; union acpi_object *obj; int err; - handle = DEVICE_ACPI_HANDLE(&pdev->dev); - - if (!handle) - return -ENODEV; - input.count = 4; input.pointer = params; params[0].type = ACPI_TYPE_BUFFER; - params[0].buffer.length = sizeof(muid); - params[0].buffer.pointer = (char *)muid; + params[0].buffer.length = sizeof(nouveau_dsm_muid); + params[0].buffer.pointer = (char *)nouveau_dsm_muid; params[1].type = ACPI_TYPE_INTEGER; params[1].integer.value = 0x00000102; params[2].type = ACPI_TYPE_INTEGER; @@ -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); if (err) { - NV_INFO(dev, "failed to evaluate _DSM: %d\n", err); + printk(KERN_INFO "failed to evaluate _DSM: %d\n", err); return err; } @@ -86,40 +87,119 @@ static int nouveau_dsm(struct drm_device *dev, int func, int arg, int *result) return 0; } -int nouveau_hybrid_setup(struct drm_device *dev) +static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id) { - int result; - - if (nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STATE, - &result)) - return -ENODEV; - - NV_INFO(dev, "_DSM hardware status gave 0x%x\n", result); - - if (result) { /* Ensure that the external GPU is enabled */ - nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_SPEED, NULL); - nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_SPEED, - NULL); - } else { /* Stamina mode - disable the external GPU */ - nouveau_dsm(dev, NOUVEAU_DSM_LED, NOUVEAU_DSM_LED_STAMINA, - NULL); - nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STAMINA, - NULL); - } + return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id, NULL); +} + +static int nouveau_dsm_set_discrete_state(acpi_handle handle, enum vga_switcheroo_state state) +{ + int arg; + if (state == VGA_SWITCHEROO_ON) + arg = NOUVEAU_DSM_POWER_SPEED; + else + arg = NOUVEAU_DSM_POWER_STAMINA; + nouveau_dsm(handle, NOUVEAU_DSM_POWER, arg, NULL); + return 0; +} + +static int nouveau_dsm_switchto(enum vga_switcheroo_client_id id) +{ + 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; } -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, - NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &support)) +static bool nouveau_dsm_pci_probe(struct pci_dev *pdev) +{ + 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; + } - if (!support) + ret= nouveau_dsm(nvidia_handle, NOUVEAU_DSM_SUPPORTED, + NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &result); + if (ret < 0) return false; + nouveau_dsm_priv.dhandle = dhandle; + nouveau_dsm_priv.dsm_handle = nvidia_handle; 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(); +} diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.c b/drivers/gpu/drm/nouveau/nouveau_drv.c index da3b93b84502..f83ec65addba 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.c +++ b/drivers/gpu/drm/nouveau/nouveau_drv.c @@ -135,7 +135,7 @@ nouveau_pci_remove(struct pci_dev *pdev) drm_put_dev(dev); } -static int +int nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state) { struct drm_device *dev = pci_get_drvdata(pdev); @@ -233,7 +233,7 @@ out_abort: return ret; } -static int +int nouveau_pci_resume(struct pci_dev *pdev) { struct drm_device *dev = pci_get_drvdata(pdev); @@ -402,8 +402,10 @@ static int __init nouveau_init(void) nouveau_modeset = 1; } - if (nouveau_modeset == 1) + if (nouveau_modeset == 1) { driver.driver_features |= DRIVER_MODESET; + nouveau_register_dsm_handler(); + } return drm_init(&driver); } @@ -411,6 +413,7 @@ static int __init nouveau_init(void) static void __exit nouveau_exit(void) { drm_exit(&driver); + nouveau_unregister_dsm_handler(); } module_init(nouveau_init); diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h index 1c15ef37b71c..85c05feab4f0 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.h +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h @@ -614,7 +614,6 @@ struct drm_nouveau_private { } susres; struct backlight_device *backlight; - bool acpi_dsm; struct nouveau_channel *evo; @@ -682,6 +681,9 @@ extern int nouveau_ignorelid; extern int nouveau_nofbaccel; 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 */ extern void nouveau_preclose(struct drm_device *dev, struct drm_file *); extern int nouveau_load(struct drm_device *, unsigned long flags); @@ -848,19 +850,8 @@ extern int nouveau_dma_init(struct nouveau_channel *); extern int nouveau_dma_wait(struct nouveau_channel *, int size); /* nouveau_acpi.c */ -#ifdef CONFIG_ACPI -extern int nouveau_hybrid_setup(struct drm_device *dev); -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 +void nouveau_register_dsm_handler(void); +void nouveau_unregister_dsm_handler(void); /* nouveau_backlight.c */ #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c index ea879a2efef3..1ebf22b664dd 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fbcon.c +++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c @@ -36,6 +36,7 @@ #include <linux/fb.h> #include <linux/init.h> #include <linux/screen_info.h> +#include <linux/vga_switcheroo.h> #include "drmP.h" #include "drm.h" @@ -370,6 +371,7 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width, nvbo->bo.offset, nvbo); mutex_unlock(&dev->struct_mutex); + vga_switcheroo_client_fb_set(dev->pdev, info); return 0; out_unref: diff --git a/drivers/gpu/drm/nouveau/nouveau_state.c b/drivers/gpu/drm/nouveau/nouveau_state.c index a4851af5b05e..85d65b91389c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_state.c +++ b/drivers/gpu/drm/nouveau/nouveau_state.c @@ -29,6 +29,7 @@ #include "drm_sarea.h" #include "drm_crtc_helper.h" #include <linux/vgaarb.h> +#include <linux/vga_switcheroo.h> #include "nouveau_drv.h" #include "nouveau_drm.h" @@ -371,6 +372,30 @@ out_err: 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 nouveau_card_init(struct drm_device *dev) { @@ -384,6 +409,8 @@ nouveau_card_init(struct drm_device *dev) return 0; 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 */ ret = nouveau_init_engine_ptrs(dev); @@ -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", 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"); if (!dev_priv->wq) return -EINVAL; |