Lenovo W541, windows 10 and ACPI PM

From: Dave Airlie
Date: Tue Feb 16 2016 - 20:57:57 EST


So we got a bug filed in Fedora 23 that the nvidia GPU wasn't stalling
on resume from runtime poweroff. When we looked into it, we noticed
the GPU wasn't powering off and the delay was from things going to
hell after that.

I dug out the DSDT and noticed it has special Windows 10 handling code
for the SB.PCI0.PEG.VID device. Adding acpi_osi="!Windows 2013" made
things work again and everyone rejoiced and had battery life again.

Now what to do about this? I doubt this is restricted to the W541
laptop, I assume all Optimus laptops that support windows 10 will have
this problem, so I'm not sure blacklisting on a case by case basis is
going to help.

More information from investigation:
On Win7/8 paths, the way to poweroff the GPU is to call an ACPI _DSM
on the VID device that sets a magic bit, then the next time you go
into D3 the _PS3 method gets called and powers the device down. This
is to make up for the lack of D3cold in Windows at the time I suspect.

Now with Win10, the PCI0.PEG device has power resource methods,
specifically PR3 which enables D3cold. However nouveau is bound to the
VID device not the PEG device, so never attempts to power it down
using those methods. I've hacked up the attached patch, and it does
indeed power the device down, but I've no idea how this is meant to
work in real world.

Dave.
From 753bbb202b8e464cb425372266042cc7d60d3513 Mon Sep 17 00:00:00 2001
From: Dave Airlie <airlied@xxxxxxxxxx>
Date: Wed, 17 Feb 2016 23:21:13 +1000
Subject: [PATCH] nouveau: hack the parent power down so the GPU powers down.

---
drivers/acpi/device_pm.c | 3 +++
drivers/gpu/drm/nouveau/nouveau_drm.c | 16 ++++++++++++++++
2 files changed, 19 insertions(+)

diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c
index cd2c3d6..8696889 100644
--- a/drivers/acpi/device_pm.c
+++ b/drivers/acpi/device_pm.c
@@ -117,6 +117,9 @@ int acpi_device_get_power(struct acpi_device *device, int *state)
&& result == ACPI_STATE_D0)
device->parent->power.state = ACPI_STATE_D0;

+ if (result == ACPI_STATE_UNKNOWN)
+ result = device->parent ?
+ device->parent->power.state : ACPI_STATE_D0;
*state = result;

out:
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 2f2f252..d9b2bd6 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -715,6 +715,14 @@ nouveau_pmops_runtime_suspend(struct device *dev)
pci_disable_device(pdev);
pci_ignore_hotplug(pdev);
pci_set_power_state(pdev, PCI_D3cold);
+ {
+ struct acpi_device *adev;
+ int r;
+
+ r = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev);
+ if (!r)
+ acpi_device_set_power(adev->parent, ACPI_STATE_D3_COLD);
+ }
drm_dev->switch_power_state = DRM_SWITCH_POWER_DYNAMIC_OFF;
return ret;
}
@@ -730,6 +738,14 @@ nouveau_pmops_runtime_resume(struct device *dev)
if (nouveau_runtime_pm == 0)
return -EINVAL;

+ {
+ struct acpi_device *adev;
+ int r;
+
+ r = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev);
+ if (!r)
+ acpi_device_set_power(adev->parent, ACPI_STATE_D0);
+ }
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
ret = pci_enable_device(pdev);
--
2.5.0