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

Commit 269dc2d9 authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge remote branch 'korg/drm-nvidia-switch-fixes' into drm-core-next

* korg/drm-nvidia-switch-fixes:
  mxm/wmi: add MXMX interface entry point.
  nouveau: add optimus detection to DSM code.
  vgaarb: use bridges to control VGA routing where possible.
  nouveau/acpi: hook up to the MXM method for mux switching.
  platform/x86: add MXM WMI driver.
parents 776f2b7c 000703f4
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ config DRM_NOUVEAU
	select FRAMEBUFFER_CONSOLE if !EXPERT
	select FB_BACKLIGHT if DRM_NOUVEAU_BACKLIGHT
	select ACPI_VIDEO if ACPI && X86 && BACKLIGHT_CLASS_DEVICE && VIDEO_OUTPUT_CONTROL && INPUT
	select MXM_WMI if ACPI
	help
	  Choose this option for open-source nVidia support.

+97 −11
Original line number Diff line number Diff line
@@ -4,6 +4,8 @@
#include <acpi/acpi_drivers.h>
#include <acpi/acpi_bus.h>
#include <acpi/video.h>
#include <acpi/acpi.h>
#include <linux/mxm-wmi.h>

#include "drmP.h"
#include "drm.h"
@@ -35,15 +37,71 @@

static struct nouveau_dsm_priv {
	bool dsm_detected;
	bool optimus_detected;
	acpi_handle dhandle;
	acpi_handle rom_handle;
} nouveau_dsm_priv;

#define NOUVEAU_DSM_HAS_MUX 0x1
#define NOUVEAU_DSM_HAS_OPT 0x2

static const char nouveau_dsm_muid[] = {
	0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D,
	0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4,
};

static const char nouveau_op_dsm_muid[] = {
	0xF8, 0xD8, 0x86, 0xA4, 0xDA, 0x0B, 0x1B, 0x47,
	0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0,
};

static int nouveau_optimus_dsm(acpi_handle handle, int func, int arg, uint32_t *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;

	input.count = 4;
	input.pointer = params;
	params[0].type = ACPI_TYPE_BUFFER;
	params[0].buffer.length = sizeof(nouveau_op_dsm_muid);
	params[0].buffer.pointer = (char *)nouveau_op_dsm_muid;
	params[1].type = ACPI_TYPE_INTEGER;
	params[1].integer.value = 0x00000100;
	params[2].type = ACPI_TYPE_INTEGER;
	params[2].integer.value = func;
	params[3].type = ACPI_TYPE_BUFFER;
	params[3].buffer.length = 0;

	err = acpi_evaluate_object(handle, "_DSM", &input, &output);
	if (err) {
		printk(KERN_INFO "failed to evaluate _DSM: %d\n", err);
		return err;
	}

	obj = (union acpi_object *)output.pointer;

	if (obj->type == ACPI_TYPE_INTEGER)
		if (obj->integer.value == 0x80000002) {
			return -ENODEV;
		}

	if (obj->type == ACPI_TYPE_BUFFER) {
		if (obj->buffer.length == 4 && result) {
			*result = 0;
			*result |= obj->buffer.pointer[0];
			*result |= (obj->buffer.pointer[1] << 8);
			*result |= (obj->buffer.pointer[2] << 16);
			*result |= (obj->buffer.pointer[3] << 24);
		}
	}

	kfree(output.pointer);
	return 0;
}

static int nouveau_dsm(acpi_handle handle, int func, int arg, uint32_t *result)
{
	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
@@ -92,6 +150,8 @@ static int nouveau_dsm(acpi_handle handle, int func, int arg, uint32_t *result)

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, NULL);
}

@@ -148,11 +208,11 @@ static struct vga_switcheroo_handler nouveau_dsm_handler = {
	.get_client_id = nouveau_dsm_get_client_id,
};

static bool nouveau_dsm_pci_probe(struct pci_dev *pdev)
static int nouveau_dsm_pci_probe(struct pci_dev *pdev)
{
	acpi_handle dhandle, nvidia_handle;
	acpi_status status;
	int ret;
	int ret, retval = 0;
	uint32_t result;

	dhandle = DEVICE_ACPI_HANDLE(&pdev->dev);
@@ -166,11 +226,17 @@ static bool nouveau_dsm_pci_probe(struct pci_dev *pdev)

	ret = nouveau_dsm(dhandle, NOUVEAU_DSM_SUPPORTED,
			  NOUVEAU_DSM_SUPPORTED_FUNCTIONS, &result);
	if (ret < 0)
		return false;
	if (ret == 0)
		retval |= NOUVEAU_DSM_HAS_MUX;

	ret = nouveau_optimus_dsm(dhandle, 0, 0, &result);
	if (ret == 0)
		retval |= NOUVEAU_DSM_HAS_OPT;

	if (retval)
		nouveau_dsm_priv.dhandle = dhandle;
	return true;

	return retval;
}

static bool nouveau_dsm_detect(void)
@@ -179,22 +245,42 @@ static bool nouveau_dsm_detect(void)
	struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name};
	struct pci_dev *pdev = NULL;
	int has_dsm = 0;
	int has_optimus;
	int vga_count = 0;
	bool guid_valid;
	int retval;
	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_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) {
		vga_count++;

		has_dsm |= (nouveau_dsm_pci_probe(pdev) == true);
		retval = nouveau_dsm_pci_probe(pdev);
		printk("ret val is %d\n", retval);
		if (retval & NOUVEAU_DSM_HAS_MUX)
			has_dsm |= 1;
		if (retval & NOUVEAU_DSM_HAS_OPT)
			has_optimus = 1;
	}

	if (vga_count == 2 && has_dsm) {
	if (vga_count == 2 && has_dsm && guid_valid) {
		acpi_get_name(nouveau_dsm_priv.dhandle, 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;
		ret = true;
	}
	return false;

	if (has_optimus == 1)
		nouveau_dsm_priv.optimus_detected = true;

	return ret;
}

void nouveau_register_dsm_handler(void)
@@ -247,7 +333,7 @@ bool nouveau_acpi_rom_supported(struct pci_dev *pdev)
	acpi_status status;
	acpi_handle dhandle, rom_handle;

	if (!nouveau_dsm_priv.dsm_detected)
	if (!nouveau_dsm_priv.dsm_detected && !nouveau_dsm_priv.optimus_detected)
		return false;

	dhandle = DEVICE_ACPI_HANDLE(&pdev->dev);
+99 −14
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ struct vga_device {
	unsigned int mem_lock_cnt;	/* legacy MEM lock count */
	unsigned int io_norm_cnt;	/* normal IO count */
	unsigned int mem_norm_cnt;	/* normal MEM count */

	bool bridge_has_one_vga;
	/* allow IRQ enable/disable hook */
	void *cookie;
	void (*irq_set_state)(void *cookie, bool enable);
@@ -165,6 +165,8 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev,
	unsigned int wants, legacy_wants, match;
	struct vga_device *conflict;
	unsigned int pci_bits;
	u32 flags = 0;

	/* Account for "normal" resources to lock. If we decode the legacy,
	 * counterpart, we need to request it as well
	 */
@@ -237,16 +239,23 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev,
		/* looks like he doesn't have a lock, we can steal
		 * them from him
		 */
		vga_irq_set_state(conflict, false);

		flags = 0;
		pci_bits = 0;

		if (!conflict->bridge_has_one_vga) {
			vga_irq_set_state(conflict, false);
			flags |= PCI_VGA_STATE_CHANGE_DECODES;
			if (lwants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM))
				pci_bits |= PCI_COMMAND_MEMORY;
			if (lwants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO))
				pci_bits |= PCI_COMMAND_IO;
		}

		if (change_bridge)
			flags |= PCI_VGA_STATE_CHANGE_BRIDGE;

		pci_set_vga_state(conflict->pdev, false, pci_bits,
				  change_bridge);
		pci_set_vga_state(conflict->pdev, false, pci_bits, flags);
		conflict->owns &= ~lwants;
		/* If he also owned non-legacy, that is no longer the case */
		if (lwants & VGA_RSRC_LEGACY_MEM)
@@ -261,14 +270,24 @@ enable_them:
	 * also have in "decodes". We can lock resources we don't decode but
	 * not own them.
	 */
	flags = 0;
	pci_bits = 0;

	if (!vgadev->bridge_has_one_vga) {
		flags |= PCI_VGA_STATE_CHANGE_DECODES;
		if (wants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM))
			pci_bits |= PCI_COMMAND_MEMORY;
		if (wants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO))
			pci_bits |= PCI_COMMAND_IO;
	pci_set_vga_state(vgadev->pdev, true, pci_bits, !!(wants & VGA_RSRC_LEGACY_MASK));
	}
	if (!!(wants & VGA_RSRC_LEGACY_MASK))
		flags |= PCI_VGA_STATE_CHANGE_BRIDGE;

	pci_set_vga_state(vgadev->pdev, true, pci_bits, flags);

	if (!vgadev->bridge_has_one_vga) {
		vga_irq_set_state(vgadev, true);
	}
	vgadev->owns |= (wants & vgadev->decodes);
lock_them:
	vgadev->locks |= (rsrc & VGA_RSRC_LEGACY_MASK);
@@ -421,6 +440,62 @@ bail:
}
EXPORT_SYMBOL(vga_put);

/* Rules for using a bridge to control a VGA descendant decoding:
   if a bridge has only one VGA descendant then it can be used
   to control the VGA routing for that device.
   It should always use the bridge closest to the device to control it.
   If a bridge has a direct VGA descendant, but also have a sub-bridge
   VGA descendant then we cannot use that bridge to control the direct VGA descendant.
   So for every device we register, we need to iterate all its parent bridges
   so we can invalidate any devices using them properly.
*/
static void vga_arbiter_check_bridge_sharing(struct vga_device *vgadev)
{
	struct vga_device *same_bridge_vgadev;
	struct pci_bus *new_bus, *bus;
	struct pci_dev *new_bridge, *bridge;

	vgadev->bridge_has_one_vga = true;

	if (list_empty(&vga_list))
		return;

	/* okay iterate the new devices bridge hierarachy */
	new_bus = vgadev->pdev->bus;
	while (new_bus) {
		new_bridge = new_bus->self;

		if (new_bridge) {
			/* go through list of devices already registered */
			list_for_each_entry(same_bridge_vgadev, &vga_list, list) {
				bus = same_bridge_vgadev->pdev->bus;
				bridge = bus->self;

				/* see if the share a bridge with this device */
				if (new_bridge == bridge) {
					/* if their direct parent bridge is the same
					   as any bridge of this device then it can't be used
					   for that device */
					same_bridge_vgadev->bridge_has_one_vga = false;
				}

				/* now iterate the previous devices bridge hierarchy */
				/* if the new devices parent bridge is in the other devices
				   hierarchy then we can't use it to control this device */
				while (bus) {
					bridge = bus->self;
					if (bridge) {
						if (bridge == vgadev->pdev->bus->self)
							vgadev->bridge_has_one_vga = false;
					}
					bus = bus->parent;
				}
			}
		}
		new_bus = new_bus->parent;
	}
}

/*
 * Currently, we assume that the "initial" setup of the system is
 * not sane, that is we come up with conflicting devices and let
@@ -500,6 +575,8 @@ static bool vga_arbiter_add_pci_device(struct pci_dev *pdev)
		vga_default = pci_dev_get(pdev);
#endif

	vga_arbiter_check_bridge_sharing(vgadev);

	/* Add to the list */
	list_add(&vgadev->list, &vga_list);
	vga_count++;
@@ -1222,6 +1299,7 @@ static int __init vga_arb_device_init(void)
{
	int rc;
	struct pci_dev *pdev;
	struct vga_device *vgadev;

	rc = misc_register(&vga_arb_device);
	if (rc < 0)
@@ -1238,6 +1316,13 @@ static int __init vga_arb_device_init(void)
		vga_arbiter_add_pci_device(pdev);

	pr_info("vgaarb: loaded\n");

	list_for_each_entry(vgadev, &vga_list, list) {
		if (vgadev->bridge_has_one_vga)
			pr_info("vgaarb: bridge control possible %s\n", pci_name(vgadev->pdev));
		else
			pr_info("vgaarb: no bridge control possible %s\n", pci_name(vgadev->pdev));
	}
	return rc;
}
subsys_initcall(vga_arb_device_init);
+14 −11
Original line number Diff line number Diff line
@@ -2875,31 +2875,34 @@ static int pci_set_vga_state_arch(struct pci_dev *dev, bool decode,
 * @dev: the PCI device
 * @decode: true = enable decoding, false = disable decoding
 * @command_bits: PCI_COMMAND_IO and/or PCI_COMMAND_MEMORY
 * @change_bridge: traverse ancestors and change bridges
 * @change_bridge_flags: traverse ancestors and change bridges
 * CHANGE_BRIDGE_ONLY / CHANGE_BRIDGE
 */
int pci_set_vga_state(struct pci_dev *dev, bool decode,
		      unsigned int command_bits, bool change_bridge)
		      unsigned int command_bits, u32 flags)
{
	struct pci_bus *bus;
	struct pci_dev *bridge;
	u16 cmd;
	int rc;

	WARN_ON(command_bits & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY));
	WARN_ON((flags & PCI_VGA_STATE_CHANGE_DECODES) & (command_bits & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY)));

	/* ARCH specific VGA enables */
	rc = pci_set_vga_state_arch(dev, decode, command_bits, change_bridge);
	rc = pci_set_vga_state_arch(dev, decode, command_bits, flags);
	if (rc)
		return rc;

	if (flags & PCI_VGA_STATE_CHANGE_DECODES) {
		pci_read_config_word(dev, PCI_COMMAND, &cmd);
		if (decode == true)
			cmd |= command_bits;
		else
			cmd &= ~command_bits;
		pci_write_config_word(dev, PCI_COMMAND, cmd);
	}

	if (change_bridge == false)
	if (!(flags & PCI_VGA_STATE_CHANGE_BRIDGE))
		return 0;

	bus = dev->bus;
+7 −0
Original line number Diff line number Diff line
@@ -753,4 +753,11 @@ config SAMSUNG_LAPTOP
	  To compile this driver as a module, choose M here: the module
	  will be called samsung-laptop.

config MXM_WMI
       tristate "WMI support for MXM Laptop Graphics"
       depends on WMI
       ---help---
          MXM is a standard for laptop graphics cards, the WMI interface
	  is required for switchable nvidia graphics machines

endif # X86_PLATFORM_DEVICES
Loading