Skip to content

Commit

Permalink
vga_switcheroo: initial implementation (v15)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Dave Airlie authored and Dave Airlie committed Mar 1, 2010
1 parent 9fd1de5 commit 6a9ee8a
Show file tree
Hide file tree
Showing 24 changed files with 1,104 additions and 70 deletions.
35 changes: 35 additions & 0 deletions drivers/gpu/drm/i915/i915_dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "i915_drv.h"
#include "i915_trace.h"
#include <linux/vgaarb.h>
#include <linux/vga_switcheroo.h>

/* Really want an OS-independent resettable timer. Would like to have
* this loop run for (eg) 3 sec, but have the timer reset every time
Expand Down Expand Up @@ -1199,6 +1200,32 @@ 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);
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,
unsigned long prealloc_start,
unsigned long prealloc_size,
Expand Down Expand Up @@ -1260,6 +1287,12 @@ 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,
i915_switcheroo_can_switch);
if (ret)
goto destroy_ringbuffer;

intel_modeset_init(dev);

ret = drm_irq_install(dev);
Expand Down Expand Up @@ -1544,6 +1577,7 @@ int i915_driver_unload(struct drm_device *dev)
dev_priv->child_dev_num = 0;
}
drm_irq_uninstall(dev);
vga_switcheroo_unregister_client(dev->pdev);
vga_client_register(dev->pdev, NULL, NULL, NULL);
}

Expand Down Expand Up @@ -1611,6 +1645,7 @@ void i915_driver_lastclose(struct drm_device * dev)

if (!dev_priv || drm_core_check_feature(dev, DRIVER_MODESET)) {
drm_fb_helper_restore();
vga_switcheroo_process_delayed_switch();
return;
}

Expand Down
4 changes: 2 additions & 2 deletions drivers/gpu/drm/i915/i915_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ static int i915_drm_freeze(struct drm_device *dev)
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;

Expand Down Expand Up @@ -255,7 +255,7 @@ static int i915_drm_thaw(struct drm_device *dev)
return error;
}

static int i915_resume(struct drm_device *dev)
int i915_resume(struct drm_device *dev)
{
if (pci_enable_device(dev->pdev))
return -EIO;
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/i915/i915_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,8 @@ extern unsigned int i915_fbpercrtc;
extern unsigned int i915_powersave;
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_restore_display(struct drm_device *dev);
extern int i915_master_create(struct drm_device *dev, struct drm_master *master);
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/i915/intel_fb.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/vga_switcheroo.h>

#include "drmP.h"
#include "drm.h"
Expand Down Expand Up @@ -235,6 +236,7 @@ static int intelfb_create(struct drm_device *dev, uint32_t fb_width,
obj_priv->gtt_offset, fbo);

mutex_unlock(&dev->struct_mutex);
vga_switcheroo_client_fb_set(dev->pdev, info);
return 0;

out_unpin:
Expand Down
160 changes: 120 additions & 40 deletions drivers/gpu/drm/nouveau/nouveau_acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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;
Expand All @@ -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;
}

Expand All @@ -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();
}
9 changes: 6 additions & 3 deletions drivers/gpu/drm/nouveau/nouveau_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -233,7 +233,7 @@ nouveau_pci_suspend(struct pci_dev *pdev, pm_message_t pm_state)
return ret;
}

static int
int
nouveau_pci_resume(struct pci_dev *pdev)
{
struct drm_device *dev = pci_get_drvdata(pdev);
Expand Down Expand Up @@ -402,15 +402,18 @@ 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);
}

static void __exit nouveau_exit(void)
{
drm_exit(&driver);
nouveau_unregister_dsm_handler();
}

module_init(nouveau_init);
Expand Down
19 changes: 5 additions & 14 deletions drivers/gpu/drm/nouveau/nouveau_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,6 @@ struct drm_nouveau_private {
} susres;

struct backlight_device *backlight;
bool acpi_dsm;

struct nouveau_channel *evo;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 6a9ee8a

Please sign in to comment.