From 6a9ee8af344e3bd7dbd61e67037096cdf7f83289 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Mon, 1 Feb 2010 15:38:10 +1000 Subject: 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 --- drivers/gpu/drm/nouveau/nouveau_acpi.c | 160 ++++++++++++++++++++++++--------- 1 file changed, 120 insertions(+), 40 deletions(-) (limited to 'drivers/gpu/drm/nouveau/nouveau_acpi.c') diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c index 48227e74475..0e0730a5313 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 + #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(); +} -- cgit v1.2.3