diff mbox series

[09/21] drm/nouveau/nvkm: move switcheroo init from drm

Message ID 20240613170046.88687-10-bskeggs@nvidia.com (mailing list archive)
State New, archived
Headers show
Series drm/nouveau: insert auxiliary device between nvkm and drm | expand

Commit Message

Ben Skeggs June 13, 2024, 5 p.m. UTC
Signed-off-by: Ben Skeggs <bskeggs@nvidia.com>
---
 drivers/gpu/drm/nouveau/nouveau_acpi.c     | 344 ---------------------
 drivers/gpu/drm/nouveau/nouveau_acpi.h     |   7 +-
 drivers/gpu/drm/nouveau/nouveau_drm.c      |   2 -
 drivers/gpu/drm/nouveau/nvkm/device/acpi.c | 339 +++++++++++++++++++-
 drivers/gpu/drm/nouveau/nvkm/device/acpi.h |  19 ++
 drivers/gpu/drm/nouveau/nvkm/module.c      |   3 +
 6 files changed, 362 insertions(+), 352 deletions(-)
diff mbox series

Patch

diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c
index 8f0c69aad248..b9c43fe552c0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.c
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c
@@ -2,55 +2,12 @@ 
 #include <linux/pci.h>
 #include <linux/acpi.h>
 #include <linux/slab.h>
-#include <linux/mxm-wmi.h>
-#include <linux/vga_switcheroo.h>
 #include <drm/drm_edid.h>
 #include <acpi/video.h>
 
 #include "nouveau_drv.h"
 #include "nouveau_acpi.h"
 
-#define NOUVEAU_DSM_LED 0x02
-#define NOUVEAU_DSM_LED_STATE 0x00
-#define NOUVEAU_DSM_LED_OFF 0x10
-#define NOUVEAU_DSM_LED_STAMINA 0x11
-#define NOUVEAU_DSM_LED_SPEED 0x12
-
-#define NOUVEAU_DSM_POWER 0x03
-#define NOUVEAU_DSM_POWER_STATE 0x00
-#define NOUVEAU_DSM_POWER_SPEED 0x01
-#define NOUVEAU_DSM_POWER_STAMINA 0x02
-
-#define NOUVEAU_DSM_OPTIMUS_CAPS 0x1A
-#define NOUVEAU_DSM_OPTIMUS_FLAGS 0x1B
-
-#define NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 (3 << 24)
-#define NOUVEAU_DSM_OPTIMUS_NO_POWERDOWN_PS3 (2 << 24)
-#define NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED (1)
-
-#define NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN (NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 | NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED)
-
-/* result of the optimus caps function */
-#define OPTIMUS_ENABLED (1 << 0)
-#define OPTIMUS_STATUS_MASK (3 << 3)
-#define OPTIMUS_STATUS_OFF  (0 << 3)
-#define OPTIMUS_STATUS_ON_ENABLED  (1 << 3)
-#define OPTIMUS_STATUS_PWR_STABLE  (3 << 3)
-#define OPTIMUS_DISPLAY_HOTPLUG (1 << 6)
-#define OPTIMUS_CAPS_MASK (7 << 24)
-#define OPTIMUS_DYNAMIC_PWR_CAP (1 << 24)
-
-#define OPTIMUS_AUDIO_CAPS_MASK (3 << 27)
-#define OPTIMUS_HDA_CODEC_MASK (2 << 27) /* hda bios control */
-
-static struct nouveau_dsm_priv {
-	bool dsm_detected;
-	bool optimus_detected;
-	bool optimus_flags_detected;
-	bool optimus_skip_dsm;
-	acpi_handle dhandle;
-} nouveau_dsm_priv;
-
 bool nouveau_is_optimus(void) {
 	return nouveau_dsm_priv.optimus_detected;
 }
@@ -59,307 +16,6 @@  bool nouveau_is_v1_dsm(void) {
 	return nouveau_dsm_priv.dsm_detected;
 }
 
-#ifdef CONFIG_VGA_SWITCHEROO
-static const guid_t nouveau_dsm_muid =
-	GUID_INIT(0x9D95A0A0, 0x0060, 0x4D48,
-		  0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4);
-
-static const guid_t nouveau_op_dsm_muid =
-	GUID_INIT(0xA486D8F8, 0x0BDA, 0x471B,
-		  0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0);
-
-static int nouveau_optimus_dsm(acpi_handle handle, int func, int arg, uint32_t *result)
-{
-	int i;
-	union acpi_object *obj;
-	char args_buff[4];
-	union acpi_object argv4 = {
-		.buffer.type = ACPI_TYPE_BUFFER,
-		.buffer.length = 4,
-		.buffer.pointer = args_buff
-	};
-
-	/* ACPI is little endian, AABBCCDD becomes {DD,CC,BB,AA} */
-	for (i = 0; i < 4; i++)
-		args_buff[i] = (arg >> i * 8) & 0xFF;
-
-	*result = 0;
-	obj = acpi_evaluate_dsm_typed(handle, &nouveau_op_dsm_muid, 0x00000100,
-				      func, &argv4, ACPI_TYPE_BUFFER);
-	if (!obj) {
-		acpi_handle_info(handle, "failed to evaluate _DSM\n");
-		return AE_ERROR;
-	} else {
-		if (obj->buffer.length == 4) {
-			*result |= obj->buffer.pointer[0];
-			*result |= (obj->buffer.pointer[1] << 8);
-			*result |= (obj->buffer.pointer[2] << 16);
-			*result |= (obj->buffer.pointer[3] << 24);
-		}
-		ACPI_FREE(obj);
-	}
-
-	return 0;
-}
-
-/*
- * On some platforms, _DSM(nouveau_op_dsm_muid, func0) has special
- * requirements on the fourth parameter, so a private implementation
- * instead of using acpi_check_dsm().
- */
-static int nouveau_dsm_get_optimus_functions(acpi_handle handle)
-{
-	int result;
-
-	/*
-	 * Function 0 returns a Buffer containing available functions.
-	 * The args parameter is ignored for function 0, so just put 0 in it
-	 */
-	if (nouveau_optimus_dsm(handle, 0, 0, &result))
-		return 0;
-
-	/*
-	 * ACPI Spec v4 9.14.1: if bit 0 is zero, no function is supported.
-	 * If the n-th bit is enabled, function n is supported
-	 */
-	if (result & 1 && result & (1 << NOUVEAU_DSM_OPTIMUS_CAPS))
-		return result;
-	return 0;
-}
-
-static int nouveau_dsm(acpi_handle handle, int func, int arg)
-{
-	int ret = 0;
-	union acpi_object *obj;
-	union acpi_object argv4 = {
-		.integer.type = ACPI_TYPE_INTEGER,
-		.integer.value = arg,
-	};
-
-	obj = acpi_evaluate_dsm_typed(handle, &nouveau_dsm_muid, 0x00000102,
-				      func, &argv4, ACPI_TYPE_INTEGER);
-	if (!obj) {
-		acpi_handle_info(handle, "failed to evaluate _DSM\n");
-		return AE_ERROR;
-	} else {
-		if (obj->integer.value == 0x80000002)
-			ret = -ENODEV;
-		ACPI_FREE(obj);
-	}
-
-	return ret;
-}
-
-static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id)
-{
-	mxm_wmi_call_mxmx(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0);
-	mxm_wmi_call_mxds(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0);
-	return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id);
-}
-
-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);
-	return 0;
-}
-
-static int nouveau_dsm_switchto(enum vga_switcheroo_client_id id)
-{
-	if (!nouveau_dsm_priv.dsm_detected)
-		return 0;
-	if (id == VGA_SWITCHEROO_IGD)
-		return nouveau_dsm_switch_mux(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_LED_STAMINA);
-	else
-		return nouveau_dsm_switch_mux(nouveau_dsm_priv.dhandle, 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;
-
-	/* Optimus laptops have the card already disabled in
-	 * nouveau_switcheroo_set_state */
-	if (!nouveau_dsm_priv.dsm_detected)
-		return 0;
-
-	return nouveau_dsm_set_discrete_state(nouveau_dsm_priv.dhandle, state);
-}
-
-static enum vga_switcheroo_client_id nouveau_dsm_get_client_id(struct pci_dev *pdev)
-{
-	/* easy option one - intel vendor ID means Integrated */
-	if (pdev->vendor == PCI_VENDOR_ID_INTEL)
-		return VGA_SWITCHEROO_IGD;
-
-	/* is this device on Bus 0? - this may need improving */
-	if (pdev->bus->number == 0)
-		return VGA_SWITCHEROO_IGD;
-
-	return VGA_SWITCHEROO_DIS;
-}
-
-static const struct vga_switcheroo_handler nouveau_dsm_handler = {
-	.switchto = nouveau_dsm_switchto,
-	.power_state = nouveau_dsm_power_state,
-	.get_client_id = nouveau_dsm_get_client_id,
-};
-
-static void nouveau_dsm_pci_probe(struct pci_dev *pdev, acpi_handle *dhandle_out,
-				  bool *has_mux, bool *has_opt,
-				  bool *has_opt_flags, bool *has_pr3)
-{
-	acpi_handle dhandle;
-	bool supports_mux;
-	int optimus_funcs;
-	struct pci_dev *parent_pdev;
-
-	if (pdev->vendor != PCI_VENDOR_ID_NVIDIA)
-		return;
-
-	*has_pr3 = false;
-	parent_pdev = pci_upstream_bridge(pdev);
-	if (parent_pdev) {
-		if (parent_pdev->bridge_d3)
-			*has_pr3 = pci_pr3_present(parent_pdev);
-		else
-			pci_d3cold_disable(pdev);
-	}
-
-	dhandle = ACPI_HANDLE(&pdev->dev);
-	if (!dhandle)
-		return;
-
-	if (!acpi_has_method(dhandle, "_DSM"))
-		return;
-
-	supports_mux = acpi_check_dsm(dhandle, &nouveau_dsm_muid, 0x00000102,
-				      1 << NOUVEAU_DSM_POWER);
-	optimus_funcs = nouveau_dsm_get_optimus_functions(dhandle);
-
-	/* Does not look like a Nvidia device. */
-	if (!supports_mux && !optimus_funcs)
-		return;
-
-	*dhandle_out = dhandle;
-	*has_mux = supports_mux;
-	*has_opt = !!optimus_funcs;
-	*has_opt_flags = optimus_funcs & (1 << NOUVEAU_DSM_OPTIMUS_FLAGS);
-
-	if (optimus_funcs) {
-		uint32_t result;
-		nouveau_optimus_dsm(dhandle, NOUVEAU_DSM_OPTIMUS_CAPS, 0,
-				    &result);
-		dev_info(&pdev->dev, "optimus capabilities: %s, status %s%s\n",
-			 (result & OPTIMUS_ENABLED) ? "enabled" : "disabled",
-			 (result & OPTIMUS_DYNAMIC_PWR_CAP) ? "dynamic power, " : "",
-			 (result & OPTIMUS_HDA_CODEC_MASK) ? "hda bios codec supported" : "");
-	}
-}
-
-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;
-	acpi_handle dhandle = NULL;
-	bool has_mux = false;
-	bool has_optimus = false;
-	bool has_optimus_flags = false;
-	bool has_power_resources = false;
-	int vga_count = 0;
-	bool guid_valid;
-	bool ret = false;
-
-	/* lookup the MXM GUID */
-	guid_valid = mxm_wmi_supported();
-
-	if (guid_valid)
-		printk("MXM: GUID detected in BIOS\n");
-
-	/* now do DSM detection */
-	while ((pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, pdev))) {
-		if ((pdev->class != PCI_CLASS_DISPLAY_VGA << 8) &&
-		    (pdev->class != PCI_CLASS_DISPLAY_3D << 8))
-			continue;
-
-		vga_count++;
-
-		nouveau_dsm_pci_probe(pdev, &dhandle, &has_mux, &has_optimus,
-				      &has_optimus_flags, &has_power_resources);
-	}
-
-	/* find the optimus DSM or the old v1 DSM */
-	if (has_optimus) {
-		nouveau_dsm_priv.dhandle = dhandle;
-		acpi_get_name(nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME,
-			&buffer);
-		pr_info("VGA switcheroo: detected Optimus DSM method %s handle\n",
-			acpi_method_name);
-		if (has_power_resources)
-			pr_info("nouveau: detected PR support, will not use DSM\n");
-		nouveau_dsm_priv.optimus_detected = true;
-		nouveau_dsm_priv.optimus_flags_detected = has_optimus_flags;
-		nouveau_dsm_priv.optimus_skip_dsm = has_power_resources;
-		ret = true;
-	} else if (vga_count == 2 && has_mux && guid_valid) {
-		nouveau_dsm_priv.dhandle = dhandle;
-		acpi_get_name(nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME,
-			&buffer);
-		pr_info("VGA switcheroo: detected DSM switching method %s handle\n",
-			acpi_method_name);
-		nouveau_dsm_priv.dsm_detected = true;
-		ret = true;
-	}
-
-
-	return ret;
-}
-
-void nouveau_register_dsm_handler(void)
-{
-	bool r;
-
-	r = nouveau_dsm_detect();
-	if (!r)
-		return;
-
-	vga_switcheroo_register_handler(&nouveau_dsm_handler, 0);
-}
-
-/* Must be called for Optimus models before the card can be turned off */
-void nouveau_switcheroo_optimus_dsm(void)
-{
-	u32 result = 0;
-	if (!nouveau_dsm_priv.optimus_detected || nouveau_dsm_priv.optimus_skip_dsm)
-		return;
-
-	if (nouveau_dsm_priv.optimus_flags_detected)
-		nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FLAGS,
-				    0x3, &result);
-
-	nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_CAPS,
-		NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN, &result);
-
-}
-
-void nouveau_unregister_dsm_handler(void)
-{
-	if (nouveau_dsm_priv.optimus_detected || nouveau_dsm_priv.dsm_detected)
-		vga_switcheroo_unregister_handler();
-}
-#else
-void nouveau_register_dsm_handler(void) {}
-void nouveau_unregister_dsm_handler(void) {}
-void nouveau_switcheroo_optimus_dsm(void) {}
-#endif
-
 void *
 nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector)
 {
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.h b/drivers/gpu/drm/nouveau/nouveau_acpi.h
index e39dd8b94b8b..a7fac1f7a73d 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.h
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.h
@@ -7,17 +7,14 @@ 
 #if defined(CONFIG_ACPI) && defined(CONFIG_X86)
 bool nouveau_is_optimus(void);
 bool nouveau_is_v1_dsm(void);
-void nouveau_register_dsm_handler(void);
-void nouveau_unregister_dsm_handler(void);
-void nouveau_switcheroo_optimus_dsm(void);
+#include <device/acpi.h>
+static inline void nouveau_switcheroo_optimus_dsm(void) { nvkm_acpi_switcheroo_set_powerdown(); }
 void *nouveau_acpi_edid(struct drm_device *, struct drm_connector *);
 bool nouveau_acpi_video_backlight_use_native(void);
 void nouveau_acpi_video_register_backlight(void);
 #else
 static inline bool nouveau_is_optimus(void) { return false; };
 static inline bool nouveau_is_v1_dsm(void) { return false; };
-static inline void nouveau_register_dsm_handler(void) {}
-static inline void nouveau_unregister_dsm_handler(void) {}
 static inline void nouveau_switcheroo_optimus_dsm(void) {}
 static inline void *nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector) { return NULL; }
 static inline bool nouveau_acpi_video_backlight_use_native(void) { return true; }
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 6c1cfc38d8fa..0ac17da04819 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -1399,7 +1399,6 @@  nouveau_drm_init(void)
 	platform_driver_register(&nouveau_platform_driver);
 #endif
 
-	nouveau_register_dsm_handler();
 	nouveau_backlight_ctor();
 
 #ifdef CONFIG_PCI
@@ -1419,7 +1418,6 @@  nouveau_drm_exit(void)
 	pci_unregister_driver(&nouveau_drm_pci_driver);
 #endif
 	nouveau_backlight_dtor();
-	nouveau_unregister_dsm_handler();
 
 #ifdef CONFIG_NOUVEAU_PLATFORM_DRIVER
 	platform_driver_unregister(&nouveau_platform_driver);
diff --git a/drivers/gpu/drm/nouveau/nvkm/device/acpi.c b/drivers/gpu/drm/nouveau/nvkm/device/acpi.c
index c948a0dc9e62..2bead7e879a5 100644
--- a/drivers/gpu/drm/nouveau/nvkm/device/acpi.c
+++ b/drivers/gpu/drm/nouveau/nvkm/device/acpi.c
@@ -26,6 +26,343 @@ 
 #include <core/device.h>
 #include <subdev/clk.h>
 
+#include <linux/mxm-wmi.h>
+#include <linux/vga_switcheroo.h>
+
+#ifdef CONFIG_VGA_SWITCHEROO
+struct nouveau_dsm_priv nouveau_dsm_priv = {};
+
+static const guid_t nouveau_op_dsm_muid =
+	GUID_INIT(0xA486D8F8, 0x0BDA, 0x471B,
+		  0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0);
+
+#define NOUVEAU_DSM_OPTIMUS_CAPS 0x1A
+#define NOUVEAU_DSM_OPTIMUS_FLAGS 0x1B
+
+#define NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 (3 << 24)
+#define NOUVEAU_DSM_OPTIMUS_NO_POWERDOWN_PS3 (2 << 24)
+#define NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED (1)
+
+#define NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN (NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 | NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED)
+
+/* result of the optimus caps function */
+#define OPTIMUS_ENABLED (1 << 0)
+#define OPTIMUS_STATUS_MASK (3 << 3)
+#define OPTIMUS_STATUS_OFF  (0 << 3)
+#define OPTIMUS_STATUS_ON_ENABLED  (1 << 3)
+#define OPTIMUS_STATUS_PWR_STABLE  (3 << 3)
+#define OPTIMUS_DISPLAY_HOTPLUG (1 << 6)
+#define OPTIMUS_CAPS_MASK (7 << 24)
+#define OPTIMUS_DYNAMIC_PWR_CAP (1 << 24)
+
+#define OPTIMUS_AUDIO_CAPS_MASK (3 << 27)
+#define OPTIMUS_HDA_CODEC_MASK (2 << 27) /* hda bios control */
+
+static int nouveau_optimus_dsm(acpi_handle handle, int func, int arg, uint32_t *result)
+{
+	int i;
+	union acpi_object *obj;
+	char args_buff[4];
+	union acpi_object argv4 = {
+		.buffer.type = ACPI_TYPE_BUFFER,
+		.buffer.length = 4,
+		.buffer.pointer = args_buff
+	};
+
+	/* ACPI is little endian, AABBCCDD becomes {DD,CC,BB,AA} */
+	for (i = 0; i < 4; i++)
+		args_buff[i] = (arg >> i * 8) & 0xFF;
+
+	*result = 0;
+	obj = acpi_evaluate_dsm_typed(handle, &nouveau_op_dsm_muid, 0x00000100,
+				      func, &argv4, ACPI_TYPE_BUFFER);
+	if (!obj) {
+		acpi_handle_info(handle, "failed to evaluate _DSM\n");
+		return AE_ERROR;
+	} else {
+		if (obj->buffer.length == 4) {
+			*result |= obj->buffer.pointer[0];
+			*result |= (obj->buffer.pointer[1] << 8);
+			*result |= (obj->buffer.pointer[2] << 16);
+			*result |= (obj->buffer.pointer[3] << 24);
+		}
+		ACPI_FREE(obj);
+	}
+
+	return 0;
+}
+
+/* Must be called for Optimus models before the card can be turned off */
+void nvkm_acpi_switcheroo_set_powerdown(void)
+{
+	u32 result = 0;
+	if (!nouveau_dsm_priv.optimus_detected || nouveau_dsm_priv.optimus_skip_dsm)
+		return;
+
+	if (nouveau_dsm_priv.optimus_flags_detected)
+		nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FLAGS,
+				    0x3, &result);
+
+	nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_CAPS,
+		NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN, &result);
+
+}
+
+/*
+ * On some platforms, _DSM(nouveau_op_dsm_muid, func0) has special
+ * requirements on the fourth parameter, so a private implementation
+ * instead of using acpi_check_dsm().
+ */
+static int nouveau_dsm_get_optimus_functions(acpi_handle handle)
+{
+	int result;
+
+	/*
+	 * Function 0 returns a Buffer containing available functions.
+	 * The args parameter is ignored for function 0, so just put 0 in it
+	 */
+	if (nouveau_optimus_dsm(handle, 0, 0, &result))
+		return 0;
+
+	/*
+	 * ACPI Spec v4 9.14.1: if bit 0 is zero, no function is supported.
+	 * If the n-th bit is enabled, function n is supported
+	 */
+	if (result & 1 && result & (1 << NOUVEAU_DSM_OPTIMUS_CAPS))
+		return result;
+	return 0;
+}
+
+static const guid_t nouveau_dsm_muid =
+	GUID_INIT(0x9D95A0A0, 0x0060, 0x4D48,
+		  0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4);
+
+#define NOUVEAU_DSM_LED 0x02
+#define NOUVEAU_DSM_LED_STATE 0x00
+#define NOUVEAU_DSM_LED_OFF 0x10
+#define NOUVEAU_DSM_LED_STAMINA 0x11
+#define NOUVEAU_DSM_LED_SPEED 0x12
+
+#define NOUVEAU_DSM_POWER 0x03
+#define NOUVEAU_DSM_POWER_STATE 0x00
+#define NOUVEAU_DSM_POWER_SPEED 0x01
+#define NOUVEAU_DSM_POWER_STAMINA 0x02
+
+static int nouveau_dsm(acpi_handle handle, int func, int arg)
+{
+	int ret = 0;
+	union acpi_object *obj;
+	union acpi_object argv4 = {
+		.integer.type = ACPI_TYPE_INTEGER,
+		.integer.value = arg,
+	};
+
+	obj = acpi_evaluate_dsm_typed(handle, &nouveau_dsm_muid, 0x00000102,
+				      func, &argv4, ACPI_TYPE_INTEGER);
+	if (!obj) {
+		acpi_handle_info(handle, "failed to evaluate _DSM\n");
+		return AE_ERROR;
+	} else {
+		if (obj->integer.value == 0x80000002)
+			ret = -ENODEV;
+		ACPI_FREE(obj);
+	}
+
+	return ret;
+}
+
+static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id)
+{
+	mxm_wmi_call_mxmx(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0);
+	mxm_wmi_call_mxds(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0);
+	return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id);
+}
+
+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);
+	return 0;
+}
+
+static int nouveau_dsm_switchto(enum vga_switcheroo_client_id id)
+{
+	if (!nouveau_dsm_priv.dsm_detected)
+		return 0;
+	if (id == VGA_SWITCHEROO_IGD)
+		return nouveau_dsm_switch_mux(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_LED_STAMINA);
+	else
+		return nouveau_dsm_switch_mux(nouveau_dsm_priv.dhandle, 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;
+
+	/* Optimus laptops have the card already disabled in
+	 * nouveau_switcheroo_set_state */
+	if (!nouveau_dsm_priv.dsm_detected)
+		return 0;
+
+	return nouveau_dsm_set_discrete_state(nouveau_dsm_priv.dhandle, state);
+}
+
+static enum vga_switcheroo_client_id nouveau_dsm_get_client_id(struct pci_dev *pdev)
+{
+	/* easy option one - intel vendor ID means Integrated */
+	if (pdev->vendor == PCI_VENDOR_ID_INTEL)
+		return VGA_SWITCHEROO_IGD;
+
+	/* is this device on Bus 0? - this may need improving */
+	if (pdev->bus->number == 0)
+		return VGA_SWITCHEROO_IGD;
+
+	return VGA_SWITCHEROO_DIS;
+}
+
+static const struct vga_switcheroo_handler nouveau_dsm_handler = {
+	.switchto = nouveau_dsm_switchto,
+	.power_state = nouveau_dsm_power_state,
+	.get_client_id = nouveau_dsm_get_client_id,
+};
+
+static void nouveau_dsm_pci_probe(struct pci_dev *pdev, acpi_handle *dhandle_out,
+				  bool *has_mux, bool *has_opt,
+				  bool *has_opt_flags, bool *has_pr3)
+{
+	acpi_handle dhandle;
+	bool supports_mux;
+	int optimus_funcs;
+	struct pci_dev *parent_pdev;
+
+	if (pdev->vendor != PCI_VENDOR_ID_NVIDIA)
+		return;
+
+	*has_pr3 = false;
+	parent_pdev = pci_upstream_bridge(pdev);
+	if (parent_pdev) {
+		if (parent_pdev->bridge_d3)
+			*has_pr3 = pci_pr3_present(parent_pdev);
+		else
+			pci_d3cold_disable(pdev);
+	}
+
+	dhandle = ACPI_HANDLE(&pdev->dev);
+	if (!dhandle)
+		return;
+
+	if (!acpi_has_method(dhandle, "_DSM"))
+		return;
+
+	supports_mux = acpi_check_dsm(dhandle, &nouveau_dsm_muid, 0x00000102,
+				      1 << NOUVEAU_DSM_POWER);
+	optimus_funcs = nouveau_dsm_get_optimus_functions(dhandle);
+
+	/* Does not look like a Nvidia device. */
+	if (!supports_mux && !optimus_funcs)
+		return;
+
+	*dhandle_out = dhandle;
+	*has_mux = supports_mux;
+	*has_opt = !!optimus_funcs;
+	*has_opt_flags = optimus_funcs & (1 << NOUVEAU_DSM_OPTIMUS_FLAGS);
+
+	if (optimus_funcs) {
+		uint32_t result;
+		nouveau_optimus_dsm(dhandle, NOUVEAU_DSM_OPTIMUS_CAPS, 0,
+				    &result);
+		dev_info(&pdev->dev, "optimus capabilities: %s, status %s%s\n",
+			 (result & OPTIMUS_ENABLED) ? "enabled" : "disabled",
+			 (result & OPTIMUS_DYNAMIC_PWR_CAP) ? "dynamic power, " : "",
+			 (result & OPTIMUS_HDA_CODEC_MASK) ? "hda bios codec supported" : "");
+	}
+}
+
+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;
+	acpi_handle dhandle = NULL;
+	bool has_mux = false;
+	bool has_optimus = false;
+	bool has_optimus_flags = false;
+	bool has_power_resources = false;
+	int vga_count = 0;
+	bool guid_valid;
+	bool ret = false;
+
+	/* lookup the MXM GUID */
+	guid_valid = mxm_wmi_supported();
+
+	if (guid_valid)
+		printk("MXM: GUID detected in BIOS\n");
+
+	/* now do DSM detection */
+	while ((pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, pdev))) {
+		if ((pdev->class != PCI_CLASS_DISPLAY_VGA << 8) &&
+		    (pdev->class != PCI_CLASS_DISPLAY_3D << 8))
+			continue;
+
+		vga_count++;
+
+		nouveau_dsm_pci_probe(pdev, &dhandle, &has_mux, &has_optimus,
+				      &has_optimus_flags, &has_power_resources);
+	}
+
+	/* find the optimus DSM or the old v1 DSM */
+	if (has_optimus) {
+		nouveau_dsm_priv.dhandle = dhandle;
+		acpi_get_name(nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME,
+			&buffer);
+		pr_info("VGA switcheroo: detected Optimus DSM method %s handle\n",
+			acpi_method_name);
+		if (has_power_resources)
+			pr_info("nouveau: detected PR support, will not use DSM\n");
+		nouveau_dsm_priv.optimus_detected = true;
+		nouveau_dsm_priv.optimus_flags_detected = has_optimus_flags;
+		nouveau_dsm_priv.optimus_skip_dsm = has_power_resources;
+		ret = true;
+	} else if (vga_count == 2 && has_mux && guid_valid) {
+		nouveau_dsm_priv.dhandle = dhandle;
+		acpi_get_name(nouveau_dsm_priv.dhandle, ACPI_FULL_PATHNAME,
+			&buffer);
+		pr_info("VGA switcheroo: detected DSM switching method %s handle\n",
+			acpi_method_name);
+		nouveau_dsm_priv.dsm_detected = true;
+		ret = true;
+	}
+
+
+	return ret;
+}
+
+void
+nvkm_acpi_switcheroo_fini(void)
+{
+	if (nouveau_dsm_priv.optimus_detected || nouveau_dsm_priv.dsm_detected)
+		vga_switcheroo_unregister_handler();
+}
+
+void
+nvkm_acpi_switcheroo_init(void)
+{
+	bool r;
+
+	r = nouveau_dsm_detect();
+	if (!r)
+		return;
+
+	vga_switcheroo_register_handler(&nouveau_dsm_handler, 0);
+}
+#endif
+
 #ifdef CONFIG_ACPI
 static int
 nvkm_acpi_ntfy(struct notifier_block *nb, unsigned long val, void *data)
@@ -38,7 +375,7 @@  nvkm_acpi_ntfy(struct notifier_block *nb, unsigned long val, void *data)
 
 	return NOTIFY_DONE;
 }
-#endif
+#endif /* CONFIG_ACPI */
 
 void
 nvkm_acpi_fini(struct nvkm_device *device)
diff --git a/drivers/gpu/drm/nouveau/nvkm/device/acpi.h b/drivers/gpu/drm/nouveau/nvkm/device/acpi.h
index 1d3c5cf7c3b4..34854d10026d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/device/acpi.h
+++ b/drivers/gpu/drm/nouveau/nvkm/device/acpi.h
@@ -6,4 +6,23 @@  struct nvkm_device;
 
 void nvkm_acpi_init(struct nvkm_device *);
 void nvkm_acpi_fini(struct nvkm_device *);
+
+#ifdef CONFIG_VGA_SWITCHEROO
+extern struct nouveau_dsm_priv {
+	bool dsm_detected;
+	bool optimus_detected;
+	bool optimus_flags_detected;
+	bool optimus_skip_dsm;
+	acpi_handle dhandle;
+} nouveau_dsm_priv;
+
+void nvkm_acpi_switcheroo_init(void);
+void nvkm_acpi_switcheroo_fini(void);
+void nvkm_acpi_switcheroo_set_powerdown(void);
+#else
+static inline void nvkm_acpi_switcheroo_init(void) {};
+static inline void nvkm_acpi_switcheroo_fini(void) {};
+static inline void nvkm_acpi_switcheroo_set_powerdown(void) {};
+#endif
+
 #endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/module.c b/drivers/gpu/drm/nouveau/nvkm/module.c
index 66d28465514b..cf71c68af89a 100644
--- a/drivers/gpu/drm/nouveau/nvkm/module.c
+++ b/drivers/gpu/drm/nouveau/nvkm/module.c
@@ -20,14 +20,17 @@ 
  * DEALINGS IN THE SOFTWARE.
  */
 #include <core/module.h>
+#include <device/acpi.h>
 
 void __exit
 nvkm_exit(void)
 {
+	nvkm_acpi_switcheroo_fini();
 }
 
 int __init
 nvkm_init(void)
 {
+	nvkm_acpi_switcheroo_init();
 	return 0;
 }