Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -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
@@ -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,
@@ -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);
@@ -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);
	}

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

+2 −2
Original line number Diff line number Diff line
@@ -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;

@@ -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;
+2 −0
Original line number Diff line number Diff line
@@ -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);
+2 −0
Original line number Diff line number Diff line
@@ -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"
@@ -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:
+120 −40
Original line number Diff line number Diff line
@@ -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[] = {
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;
	return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id, NULL);
}

	if (nouveau_dsm(dev, NOUVEAU_DSM_POWER, NOUVEAU_DSM_POWER_STATE,
								&result))
		return -ENODEV;
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;
}

	NV_INFO(dev, "_DSM hardware status gave 0x%x\n", result);
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;

	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_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();
}
Loading