From e16cdf561b9d7adbc23cd93f4e620e31bec14d94 Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Tue, 13 Dec 2011 21:08:43 +0100 Subject: [PATCH] bbswitch: Support for legacy _DSM function Newer Optimus laptops seems to use a _DSM method which is quite standard. This is referred to as "Optimus DSM" in some DSDT files and must be called before the discrete nVidia graphics card is turned off. Other (older?) laptops use a different UUID, revision ID and arguments which actually enables or disables a device. The right _DSM arguments is detected during the module initialization. --- bbswitch.c | 110 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 19 deletions(-) diff --git a/bbswitch.c b/bbswitch.c index 255049c..e975e27 100644 --- a/bbswitch.c +++ b/bbswitch.c @@ -23,6 +23,15 @@ static const char acpi_optimus_dsm_muid[] = { 0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0, }; +static const char acpi_nvidia_dsm_muid[] = { + 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, + 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4 +}; + +#define DSM_TYPE_OPTIMUS 1 +#define DSM_TYPE_NVIDIA 2 +static int dsm_type = DSM_TYPE_UNSUPPORTED; + static struct pci_dev *dis_dev; static acpi_handle dis_handle; @@ -31,22 +40,36 @@ static struct notifier_block nb; /* whether the card was off before suspend or not; on: 0, off: 1 */ int dis_before_suspend_disabled; -/* shamelessly taken from nouveau_acpi.c */ -static int acpi_optimus_dsm(acpi_handle handle, int func, char *args, - uint32_t *result) { +static char *buffer_to_string(const char buffer[], char *target) { + int i; + for (i=0; itype == ACPI_TYPE_INTEGER) - if (result) + if (obj->type == ACPI_TYPE_INTEGER && result) *result = obj->integer.value; - // REVS (revision number) not found, possibly not an Optimus? - if (obj->integer.value == 0x80000002) { - printk(KERN_INFO "bbswitch: Optimus function not found\n"); - return -ENODEV; - } - if (obj->type == ACPI_TYPE_BUFFER) { if (obj->buffer.length == 4 && result) { *result = 0; @@ -86,18 +106,59 @@ static int acpi_optimus_dsm(acpi_handle handle, int func, char *args, return 0; } -static int bbswitch_acpi_off(void) { +// Returns 1 if a _DSM function and its function index exists and 0 otherwise +static int has_dsm_func(const char muid[], int revid, int sfnc) { + u32 result = 0; + + // fail if the _DSM call failed + if (!acpi_call_dsm(dis_handle, muid, revid, 0, 0, &result)) + return 1; + + // 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 + return result & 1 && result & (1 << sfnc); +} + +static int bbswitch_optimus_dsm(void) { char args[] = {1, 0, 0, 3}; u32 result = 0; - if (!acpi_optimus_dsm(dis_handle, 0x1A, args, &result)) { + if (!acpi_call_dsm(dis_handle, acpi_optimus_dsm_muid, 0x100, 0x1A, args, + &result)) { printk(KERN_INFO "bbswitch: Result of _DSM call: %08X\n", result); return 0; } // failure return 1; } + +static int bbswitch_acpi_off(void) { + if (dsm_type == DSM_TYPE_NVIDIA) { + char args[] = {2, 0, 0, 0}; + u32 result = 0; + + if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args, + &result)) { + // failure + return 1; + } + printk(KERN_INFO "bbswitch: Result of _DSM call for OFF: %08X\n", result); + } + return 0; +} + static int bbswitch_acpi_on(void) { + if (dsm_type == DSM_TYPE_NVIDIA) { + char args[] = {1, 0, 0, 0}; + u32 result = 0; + + if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args, + &result)) { + // failure + return 1; + } + printk(KERN_INFO "bbswitch: Result of _DSM call for ON: %08X\n", result); + } return 0; } @@ -126,7 +187,7 @@ static void bbswitch_off(void) { printk(KERN_INFO "bbswitch: disabling discrete graphics\n"); - if (bbswitch_acpi_off()) { + if (dsm_type == DSM_TYPE_OPTIMUS && bbswitch_optimus_dsm()) { printk(KERN_WARNING "bbswitch: ACPI call failed, the device is not" " disabled\n"); return; @@ -136,6 +197,8 @@ static void bbswitch_off(void) { pci_clear_master(dis_dev); pci_disable_device(dis_dev); pci_set_power_state(dis_dev, PCI_D3hot); + + bbswitch_acpi_off(); } static void bbswitch_on(void) { @@ -238,6 +301,15 @@ static int __init bbswitch_init(void) { return -ENODEV; } + if (has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) { + dsm_type = DSM_TYPE_OPTIMUS; + } else if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) { + dsm_type = DSM_TYPE_NVIDIA; + } else { + printk(KERN_ERR "bbswitch: No suitable _DSM call found.\n"); + return -ENODEV; + } + acpi_entry = create_proc_entry("bbswitch", 0660, acpi_root_dir); if (acpi_entry == NULL) { printk(KERN_ERR "bbswitch: Couldn't create proc entry\n");