summaryrefslogtreecommitdiffstats
path: root/drivers/gpu
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2011-05-09 13:23:18 +1000
committerDave Airlie <airlied@redhat.com>2011-05-09 13:23:18 +1000
commit269dc2d9194f74b920647e0f2ed0d822dc431770 (patch)
tree6b6be1a7b9c152e27f12fba9d9d75cced9551b21 /drivers/gpu
parent776f2b7cb473baa52cdeee9804073342dee7d7cb (diff)
parent000703f44c77b152cd966eaf06f4ab043274ff46 (diff)
downloadlinux-269dc2d9194f74b920647e0f2ed0d822dc431770.tar.gz
linux-269dc2d9194f74b920647e0f2ed0d822dc431770.tar.bz2
linux-269dc2d9194f74b920647e0f2ed0d822dc431770.zip
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.
Diffstat (limited to 'drivers/gpu')
-rw-r--r--drivers/gpu/drm/nouveau/Kconfig1
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_acpi.c108
-rw-r--r--drivers/gpu/vga/vgaarb.c113
3 files changed, 197 insertions, 25 deletions
diff --git a/drivers/gpu/drm/nouveau/Kconfig b/drivers/gpu/drm/nouveau/Kconfig
index de70959b9ed5..e2f8a38d5f24 100644
--- a/drivers/gpu/drm/nouveau/Kconfig
+++ b/drivers/gpu/drm/nouveau/Kconfig
@@ -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.
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c
index a54238058dc5..f0d459bb46e4 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.c
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c
@@ -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;
- nouveau_dsm_priv.dhandle = dhandle;
- return true;
+ ret = nouveau_optimus_dsm(dhandle, 0, 0, &result);
+ if (ret == 0)
+ retval |= NOUVEAU_DSM_HAS_OPT;
+
+ if (retval)
+ nouveau_dsm_priv.dhandle = dhandle;
+
+ 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);
diff --git a/drivers/gpu/vga/vgaarb.c b/drivers/gpu/vga/vgaarb.c
index be8d4cb5861c..8a1021f2e319 100644
--- a/drivers/gpu/vga/vgaarb.c
+++ b/drivers/gpu/vga/vgaarb.c
@@ -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 (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;
- pci_set_vga_state(conflict->pdev, false, pci_bits,
- change_bridge);
+ 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, 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 (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));
- vga_irq_set_state(vgadev, true);
+ 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;
+ }
+ 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);