All of lore.kernel.org
 help / color / mirror / Atom feed
* optimus dynamic power patches
@ 2013-08-05  2:56 Dave Airlie
  2013-08-05  2:56 ` [PATCH 1/4] gpu/vga_switcheroo: add driver control power feature. (v3) Dave Airlie
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Dave Airlie @ 2013-08-05  2:56 UTC (permalink / raw)
  To: dri-devel; +Cc: tiwai

This is a repost of the series with some bugs fixed, I've really only
tested this on my one optimus laptop so far. I've also ported radeon
to this patch series, but it doesn't work on my only radeon test box
since we always end up with a crtc configured, but I may just push
the patches as they might help others with muxless hw.

Dave.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 1/4] gpu/vga_switcheroo: add driver control power feature. (v3)
  2013-08-05  2:56 optimus dynamic power patches Dave Airlie
@ 2013-08-05  2:56 ` Dave Airlie
  2013-08-05  2:56 ` [PATCH 2/4] drm: allow open of dynamic off devices Dave Airlie
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: Dave Airlie @ 2013-08-05  2:56 UTC (permalink / raw)
  To: dri-devel; +Cc: tiwai

From: Dave Airlie <airlied@dhcp-40-90.bne.redhat.com>

For optimus and powerxpress muxless we really want the GPU
driver deciding when to power up/down the GPU, not userspace.

This adds the ability for a driver to dynamically power up/down
the GPU and remove the switcheroo from controlling it, the
switcheroo reports the dynamic state to userspace also.

It also adds 2 power domains, one for machine where the power
switch is controlled outside the GPU D3 state, so the powerdown
ordering is done correctly, and the second for the hdmi audio
device to make sure it can resume for PCI config space accesses.

v1.1: fix build with switcheroo off

v2: add power domain support for radeon and v1 nvidia dsms
v2.1: fix typo in off case

v3: add audio power domain for hdmi audio + misc audio fixes

v4: use PCI_SLOT macro, drop power reference on hdmi audio resume
failure also.

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/i915/i915_dma.c        |   2 +-
 drivers/gpu/drm/nouveau/nouveau_vga.c  |   2 +-
 drivers/gpu/drm/radeon/radeon_device.c |   2 +-
 drivers/gpu/vga/vga_switcheroo.c       | 147 +++++++++++++++++++++++++++++++--
 include/linux/vga_switcheroo.h         |  13 ++-
 5 files changed, 156 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c
index b152068..5f930a1 100644
--- a/drivers/gpu/drm/i915/i915_dma.c
+++ b/drivers/gpu/drm/i915/i915_dma.c
@@ -1293,7 +1293,7 @@ static int i915_load_modeset_init(struct drm_device *dev)
 
 	intel_register_dsm_handler();
 
-	ret = vga_switcheroo_register_client(dev->pdev, &i915_switcheroo_ops);
+	ret = vga_switcheroo_register_client(dev->pdev, &i915_switcheroo_ops, false);
 	if (ret)
 		goto cleanup_vga_client;
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_vga.c b/drivers/gpu/drm/nouveau/nouveau_vga.c
index 25d3495..40a09f1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_vga.c
+++ b/drivers/gpu/drm/nouveau/nouveau_vga.c
@@ -79,7 +79,7 @@ nouveau_vga_init(struct nouveau_drm *drm)
 {
 	struct drm_device *dev = drm->dev;
 	vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode);
-	vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops);
+	vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, false);
 }
 
 void
diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c
index 82335e3..0610ca4 100644
--- a/drivers/gpu/drm/radeon/radeon_device.c
+++ b/drivers/gpu/drm/radeon/radeon_device.c
@@ -1269,7 +1269,7 @@ int radeon_device_init(struct radeon_device *rdev,
 	/* this will fail for cards that aren't VGA class devices, just
 	 * ignore it */
 	vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode);
-	vga_switcheroo_register_client(rdev->pdev, &radeon_switcheroo_ops);
+	vga_switcheroo_register_client(rdev->pdev, &radeon_switcheroo_ops, false);
 
 	r = radeon_init(rdev);
 	if (r)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c
index cf787e1..ec0ae2d 100644
--- a/drivers/gpu/vga/vga_switcheroo.c
+++ b/drivers/gpu/vga/vga_switcheroo.c
@@ -27,6 +27,7 @@
 #include <linux/pci.h>
 #include <linux/console.h>
 #include <linux/vga_switcheroo.h>
+#include <linux/pm_runtime.h>
 
 #include <linux/vgaarb.h>
 
@@ -37,6 +38,7 @@ struct vga_switcheroo_client {
 	const struct vga_switcheroo_client_ops *ops;
 	int id;
 	bool active;
+	bool driver_power_control;
 	struct list_head list;
 };
 
@@ -132,7 +134,7 @@ EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
 
 static int register_client(struct pci_dev *pdev,
 			   const struct vga_switcheroo_client_ops *ops,
-			   int id, bool active)
+			   int id, bool active, bool driver_power_control)
 {
 	struct vga_switcheroo_client *client;
 
@@ -145,6 +147,7 @@ static int register_client(struct pci_dev *pdev,
 	client->ops = ops;
 	client->id = id;
 	client->active = active;
+	client->driver_power_control = driver_power_control;
 
 	mutex_lock(&vgasr_mutex);
 	list_add_tail(&client->list, &vgasr_priv.clients);
@@ -160,10 +163,11 @@ static int register_client(struct pci_dev *pdev,
 }
 
 int vga_switcheroo_register_client(struct pci_dev *pdev,
-				   const struct vga_switcheroo_client_ops *ops)
+				   const struct vga_switcheroo_client_ops *ops,
+				   bool driver_power_control)
 {
 	return register_client(pdev, ops, -1,
-			       pdev == vga_default_device());
+			       pdev == vga_default_device(), driver_power_control);
 }
 EXPORT_SYMBOL(vga_switcheroo_register_client);
 
@@ -171,7 +175,7 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
 					 const struct vga_switcheroo_client_ops *ops,
 					 int id, bool active)
 {
-	return register_client(pdev, ops, id | ID_BIT_AUDIO, active);
+	return register_client(pdev, ops, id | ID_BIT_AUDIO, active, false);
 }
 EXPORT_SYMBOL(vga_switcheroo_register_audio_client);
 
@@ -258,10 +262,11 @@ static int vga_switcheroo_show(struct seq_file *m, void *v)
 	int i = 0;
 	mutex_lock(&vgasr_mutex);
 	list_for_each_entry(client, &vgasr_priv.clients, list) {
-		seq_printf(m, "%d:%s%s:%c:%s:%s\n", i,
+		seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i,
 			   client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD",
 			   client_is_vga(client) ? "" : "-Audio",
 			   client->active ? '+' : ' ',
+			   client->driver_power_control ? "Dyn" : "",
 			   client->pwr_state ? "Pwr" : "Off",
 			   pci_name(client->pdev));
 		i++;
@@ -277,6 +282,8 @@ static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
 
 static int vga_switchon(struct vga_switcheroo_client *client)
 {
+	if (client->driver_power_control)
+		return 0;
 	if (vgasr_priv.handler->power_state)
 		vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON);
 	/* call the driver callback to turn on device */
@@ -287,6 +294,8 @@ static int vga_switchon(struct vga_switcheroo_client *client)
 
 static int vga_switchoff(struct vga_switcheroo_client *client)
 {
+	if (client->driver_power_control)
+		return 0;
 	/* call the driver callback to turn off device */
 	client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF);
 	if (vgasr_priv.handler->power_state)
@@ -402,6 +411,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
 		list_for_each_entry(client, &vgasr_priv.clients, list) {
 			if (client->active || client_is_audio(client))
 				continue;
+			if (client->driver_power_control)
+				continue;
 			set_audio_state(client->id, VGA_SWITCHEROO_OFF);
 			if (client->pwr_state == VGA_SWITCHEROO_ON)
 				vga_switchoff(client);
@@ -413,6 +424,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
 		list_for_each_entry(client, &vgasr_priv.clients, list) {
 			if (client->active || client_is_audio(client))
 				continue;
+			if (client->driver_power_control)
+				continue;
 			if (client->pwr_state == VGA_SWITCHEROO_OFF)
 				vga_switchon(client);
 			set_audio_state(client->id, VGA_SWITCHEROO_ON);
@@ -565,3 +578,127 @@ err:
 	return err;
 }
 EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch);
+
+static void vga_switcheroo_power_switch(struct pci_dev *pdev, enum vga_switcheroo_state state)
+{
+	struct vga_switcheroo_client *client;
+
+	if (!vgasr_priv.handler->power_state)
+		return;
+
+	client = find_client_from_pci(&vgasr_priv.clients, pdev);
+	if (!client)
+		return;
+
+	if (!client->driver_power_control)
+		return;
+
+	vgasr_priv.handler->power_state(client->id, state);
+}
+
+/* force a PCI device to a certain state - mainly to turn off audio clients */
+
+void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic)
+{
+	struct vga_switcheroo_client *client;
+
+	client = find_client_from_pci(&vgasr_priv.clients, pdev);
+	if (!client)
+		return;
+
+	if (!client->driver_power_control)
+		return;
+
+	client->pwr_state = dynamic;
+	set_audio_state(client->id, dynamic);
+}
+EXPORT_SYMBOL(vga_switcheroo_set_dynamic_switch);
+
+/* switcheroo power domain */
+static int vga_switcheroo_runtime_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	int ret;
+
+	ret = dev->bus->pm->runtime_suspend(dev);
+	if (ret)
+		return ret;
+
+	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF);
+	return 0;
+}
+
+static int vga_switcheroo_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	int ret;
+
+	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_ON);
+	ret = dev->bus->pm->runtime_resume(dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* this version is for the case where the power switch is separate
+   to the device being powered down. */
+int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain)
+{
+	/* copy over all the bus versions */
+	if (dev->bus && dev->bus->pm) {
+		domain->ops = *dev->bus->pm;
+		domain->ops.runtime_suspend = vga_switcheroo_runtime_suspend;
+		domain->ops.runtime_resume = vga_switcheroo_runtime_resume;
+
+		dev->pm_domain = domain;
+		return 0;
+	}
+	dev->pm_domain = NULL;
+	return -EINVAL;
+}
+EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_ops);
+
+static int vga_switcheroo_runtime_resume_hdmi_audio(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	int ret;
+	struct vga_switcheroo_client *client, *found = NULL;
+
+	/* we need to check if we have to switch back on the video
+	   device so the audio device can come back */
+	list_for_each_entry(client, &vgasr_priv.clients, list) {
+		if (PCI_SLOT(client->pdev->devfn) == PCI_SLOT(pdev->devfn) && client_is_vga(client)) {
+			found = client;
+			ret = pm_runtime_get_sync(&client->pdev->dev);
+			if (ret) {
+				if (ret != 1)
+					return ret;
+			}
+			break;
+		}
+	}
+	ret = dev->bus->pm->runtime_resume(dev);
+
+	/* put the reference for the gpu */
+	if (found) {
+		pm_runtime_mark_last_busy(&found->pdev->dev);
+		pm_runtime_put_autosuspend(&found->pdev->dev);
+	}
+	return ret;
+}
+
+int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain)
+{
+	/* copy over all the bus versions */
+	if (dev->bus && dev->bus->pm) {
+		domain->ops = *dev->bus->pm;
+		domain->ops.runtime_resume = vga_switcheroo_runtime_resume_hdmi_audio;
+
+		dev->pm_domain = domain;
+		return 0;
+	}
+	dev->pm_domain = NULL;
+	return -EINVAL;
+}
+EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio);
diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h
index ddb419c..502073a 100644
--- a/include/linux/vga_switcheroo.h
+++ b/include/linux/vga_switcheroo.h
@@ -45,7 +45,8 @@ struct vga_switcheroo_client_ops {
 #if defined(CONFIG_VGA_SWITCHEROO)
 void vga_switcheroo_unregister_client(struct pci_dev *dev);
 int vga_switcheroo_register_client(struct pci_dev *dev,
-				   const struct vga_switcheroo_client_ops *ops);
+				   const struct vga_switcheroo_client_ops *ops,
+				   bool driver_power_control);
 int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
 					 const struct vga_switcheroo_client_ops *ops,
 					 int id, bool active);
@@ -60,11 +61,15 @@ int vga_switcheroo_process_delayed_switch(void);
 
 int vga_switcheroo_get_client_state(struct pci_dev *dev);
 
+void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic);
+
+int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain);
+int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain);
 #else
 
 static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {}
 static inline int vga_switcheroo_register_client(struct pci_dev *dev,
-		const struct vga_switcheroo_client_ops *ops) { return 0; }
+		const struct vga_switcheroo_client_ops *ops, bool driver_power_control) { return 0; }
 static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {}
 static inline int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { return 0; }
 static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev,
@@ -74,6 +79,10 @@ static inline void vga_switcheroo_unregister_handler(void) {}
 static inline int vga_switcheroo_process_delayed_switch(void) { return 0; }
 static inline int vga_switcheroo_get_client_state(struct pci_dev *dev) { return VGA_SWITCHEROO_ON; }
 
+static inline void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic) {}
+
+static inline int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain) { return -EINVAL; }
+static inline int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain) { return -EINVAL; }
 
 #endif
 #endif /* _LINUX_VGA_SWITCHEROO_H_ */
-- 
1.8.2.1

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 2/4] drm: allow open of dynamic off devices.
  2013-08-05  2:56 optimus dynamic power patches Dave Airlie
  2013-08-05  2:56 ` [PATCH 1/4] gpu/vga_switcheroo: add driver control power feature. (v3) Dave Airlie
@ 2013-08-05  2:56 ` Dave Airlie
  2013-08-05  2:56 ` [PATCH 3/4] nouveau: add runtime PM support (v0.9) Dave Airlie
  2013-08-05  2:56 ` [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support (v3) Dave Airlie
  3 siblings, 0 replies; 7+ messages in thread
From: Dave Airlie @ 2013-08-05  2:56 UTC (permalink / raw)
  To: dri-devel; +Cc: tiwai

From: Dave Airlie <airlied@redhat.com>

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/drm_fops.c | 2 +-
 include/drm/drmP.h         | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c
index 3a24385..d5429ee 100644
--- a/drivers/gpu/drm/drm_fops.c
+++ b/drivers/gpu/drm/drm_fops.c
@@ -257,7 +257,7 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
 		return -EBUSY;	/* No exclusive opens */
 	if (!drm_cpu_valid())
 		return -EINVAL;
-	if (dev->switch_power_state != DRM_SWITCH_POWER_ON)
+	if (dev->switch_power_state != DRM_SWITCH_POWER_ON && dev->switch_power_state != DRM_SWITCH_POWER_DYNAMIC_OFF)
 		return -EINVAL;
 
 	DRM_DEBUG("pid = %d, minor = %d\n", task_pid_nr(current), minor_id);
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index 12083dc..7f8acaf 100644
--- a/include/drm/drmP.h
+++ b/include/drm/drmP.h
@@ -1223,6 +1223,7 @@ struct drm_device {
 #define DRM_SWITCH_POWER_ON 0
 #define DRM_SWITCH_POWER_OFF 1
 #define DRM_SWITCH_POWER_CHANGING 2
+#define DRM_SWITCH_POWER_DYNAMIC_OFF 3
 
 static __inline__ int drm_core_check_feature(struct drm_device *dev,
 					     int feature)
-- 
1.8.2.1

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 3/4] nouveau: add runtime PM support (v0.9)
  2013-08-05  2:56 optimus dynamic power patches Dave Airlie
  2013-08-05  2:56 ` [PATCH 1/4] gpu/vga_switcheroo: add driver control power feature. (v3) Dave Airlie
  2013-08-05  2:56 ` [PATCH 2/4] drm: allow open of dynamic off devices Dave Airlie
@ 2013-08-05  2:56 ` Dave Airlie
  2013-08-05  2:56 ` [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support (v3) Dave Airlie
  3 siblings, 0 replies; 7+ messages in thread
From: Dave Airlie @ 2013-08-05  2:56 UTC (permalink / raw)
  To: dri-devel; +Cc: tiwai

From: Dave Airlie <airlied@redhat.com>

This hooks nouveau up to the runtime PM system to enable
dynamic power management for secondary GPUs in switchable
and optimus laptops.

a) rewrite suspend/resume printks to hide them during dynamic s/r
to avoid cluttering logs
b) add runtime pm suspend to irq handler, crtc display, ioctl handler,
connector status,
c) handle hdmi audio dynamic power on/off using magic register.

v0.5:
make sure we hit D3 properly
fix fbdev_set_suspend locking interaction, we only will poweroff if we have no
active crtcs/fbcon anyways.
add reference for active crtcs.
sprinkle mark last busy for autosuspend timeout

v0.6:
allow more flexible debugging - to avoid log spam
add option to enable/disable dynpm
got to D3Cold

v0.7:
add hdmi audio support.

v0.8:
call autosuspend from idle, so pci config space access doesn't go straight
back to sleep, this makes starting X faster.
only signal usage if we actually handle the irq, otherwise usb keeps us awake.
fix nv50 display active powerdown

v0.9:
use masking function to enable hdmi audio
set busy when we fail to suspend

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 drivers/gpu/drm/nouveau/core/core/printk.c         |  19 ++
 drivers/gpu/drm/nouveau/core/include/core/printk.h |  13 ++
 drivers/gpu/drm/nouveau/core/subdev/bios/init.c    |   2 +-
 drivers/gpu/drm/nouveau/core/subdev/mc/base.c      |   6 +
 drivers/gpu/drm/nouveau/dispnv04/crtc.c            |  49 +++-
 drivers/gpu/drm/nouveau/nouveau_acpi.c             |  42 +++-
 drivers/gpu/drm/nouveau/nouveau_connector.c        |  27 ++-
 drivers/gpu/drm/nouveau/nouveau_display.c          |  12 +-
 drivers/gpu/drm/nouveau/nouveau_display.h          |   2 +
 drivers/gpu/drm/nouveau/nouveau_drm.c              | 250 +++++++++++++++++++--
 drivers/gpu/drm/nouveau/nouveau_drm.h              |   9 +
 drivers/gpu/drm/nouveau/nouveau_vga.c              |  14 +-
 drivers/gpu/drm/nouveau/nv50_display.c             |   2 +-
 13 files changed, 404 insertions(+), 43 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/core/core/printk.c b/drivers/gpu/drm/nouveau/core/core/printk.c
index 6161eaf..52fb2aa 100644
--- a/drivers/gpu/drm/nouveau/core/core/printk.c
+++ b/drivers/gpu/drm/nouveau/core/core/printk.c
@@ -27,6 +27,8 @@
 #include <core/subdev.h>
 #include <core/printk.h>
 
+int nv_printk_suspend_level = NV_DBG_DEBUG;
+
 void
 nv_printk_(struct nouveau_object *object, const char *pfx, int level,
 	   const char *fmt, ...)
@@ -72,3 +74,20 @@ nv_printk_(struct nouveau_object *object, const char *pfx, int level,
 	vprintk(mfmt, args);
 	va_end(args);
 }
+
+#define CONV_LEVEL(x) case NV_DBG_##x: return NV_PRINTK_##x
+
+const char *nv_printk_level_to_pfx(int level)
+{
+	switch (level) {
+	CONV_LEVEL(FATAL);
+	CONV_LEVEL(ERROR);
+	CONV_LEVEL(WARN);
+	CONV_LEVEL(INFO);
+	CONV_LEVEL(DEBUG);
+	CONV_LEVEL(PARANOIA);
+	CONV_LEVEL(TRACE);
+	CONV_LEVEL(SPAM);
+	}
+	return NV_PRINTK_DEBUG;
+}
diff --git a/drivers/gpu/drm/nouveau/core/include/core/printk.h b/drivers/gpu/drm/nouveau/core/include/core/printk.h
index febed2e..d87836e 100644
--- a/drivers/gpu/drm/nouveau/core/include/core/printk.h
+++ b/drivers/gpu/drm/nouveau/core/include/core/printk.h
@@ -15,6 +15,12 @@ struct nouveau_object;
 #define NV_PRINTK_TRACE    KERN_DEBUG
 #define NV_PRINTK_SPAM     KERN_DEBUG
 
+extern int nv_printk_suspend_level;
+
+#define NV_DBG_SUSPEND (nv_printk_suspend_level)
+#define NV_PRINTK_SUSPEND  (nv_printk_level_to_pfx(nv_printk_suspend_level))
+
+const char *nv_printk_level_to_pfx(int level);
 void __printf(4, 5)
 nv_printk_(struct nouveau_object *, const char *, int, const char *, ...);
 
@@ -31,6 +37,13 @@ nv_printk_(struct nouveau_object *, const char *, int, const char *, ...);
 #define nv_trace(o,f,a...) nv_printk((o), TRACE, f, ##a)
 #define nv_spam(o,f,a...) nv_printk((o), SPAM, f, ##a)
 
+#define nv_suspend(o,f,a...) nv_printk((o), SUSPEND, f, ##a)
+
+static inline void nv_suspend_set_printk_level(int level)
+{
+	nv_printk_suspend_level = level;
+}
+
 #define nv_assert(f,a...) do {                                                 \
 	if (NV_DBG_FATAL <= CONFIG_NOUVEAU_DEBUG)                              \
 		nv_printk_(NULL, NV_PRINTK_FATAL, NV_DBG_FATAL, f "\n", ##a);  \
diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c
index 0687e64..2e11ea0 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c
@@ -2165,7 +2165,7 @@ nvbios_init(struct nouveau_subdev *subdev, bool execute)
 	u16 data;
 
 	if (execute)
-		nv_info(bios, "running init tables\n");
+		nv_suspend(bios, "running init tables\n");
 	while (!ret && (data = (init_script(bios, ++i)))) {
 		struct nvbios_init init = {
 			.subdev = subdev,
diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/base.c b/drivers/gpu/drm/nouveau/core/subdev/mc/base.c
index 1c0330b..2e7c5fd 100644
--- a/drivers/gpu/drm/nouveau/core/subdev/mc/base.c
+++ b/drivers/gpu/drm/nouveau/core/subdev/mc/base.c
@@ -23,16 +23,20 @@
  */
 
 #include <subdev/mc.h>
+#include <linux/pm_runtime.h>
 
 static irqreturn_t
 nouveau_mc_intr(int irq, void *arg)
 {
 	struct nouveau_mc *pmc = arg;
 	const struct nouveau_mc_intr *map = pmc->intr_map;
+	struct nouveau_device *device = nv_device(pmc);
 	struct nouveau_subdev *unit;
 	u32 stat, intr;
 
 	intr = stat = nv_rd32(pmc, 0x000100);
+	if (intr == 0xffffffff)
+		return IRQ_NONE;
 	while (stat && map->stat) {
 		if (stat & map->stat) {
 			unit = nouveau_subdev(pmc, map->unit);
@@ -47,6 +51,8 @@ nouveau_mc_intr(int irq, void *arg)
 		nv_error(pmc, "unknown intr 0x%08x\n", stat);
 	}
 
+	if (stat == IRQ_HANDLED)
+		pm_runtime_mark_last_busy(&device->pdev->dev);
 	return stat ? IRQ_HANDLED : IRQ_NONE;
 }
 
diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
index 0782bd2..6413552 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
@@ -22,6 +22,7 @@
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */
+#include <linux/pm_runtime.h>
 
 #include <drm/drmP.h>
 #include <drm/drm_crtc_helper.h>
@@ -1007,13 +1008,59 @@ nv04_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
 	return 0;
 }
 
+int
+nouveau_crtc_set_config(struct drm_mode_set *set)
+{
+	struct drm_device *dev;
+	struct nouveau_drm *drm;
+	int ret;
+	struct drm_crtc *crtc;
+	bool active = false;
+	if (!set || !set->crtc)
+		return -EINVAL;
+
+	dev = set->crtc->dev;
+
+	/* get a pm reference here */
+	ret = pm_runtime_get_sync(dev->dev);
+	if (ret < 0)
+		return ret;
+
+	ret = drm_crtc_helper_set_config(set);
+
+	drm = nouveau_drm(dev);
+
+	/* if we get here with no crtcs active then we can drop a reference */
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		if (crtc->enabled)
+			active = true;
+	}
+
+	pm_runtime_mark_last_busy(dev->dev);
+	/* if we have active crtcs and we don't have a power ref,
+	   take the current one */
+	if (active && !drm->have_disp_power_ref) {
+		drm->have_disp_power_ref = true;
+		return ret;
+	}
+	/* if we have no active crtcs, then drop the power ref
+	   we got before */
+	if (!active && drm->have_disp_power_ref) {
+		pm_runtime_put_autosuspend(dev->dev);
+		drm->have_disp_power_ref = false;
+	}
+	/* drop the power reference we got coming in here */
+	pm_runtime_put_autosuspend(dev->dev);
+	return ret;
+}
+
 static const struct drm_crtc_funcs nv04_crtc_funcs = {
 	.save = nv_crtc_save,
 	.restore = nv_crtc_restore,
 	.cursor_set = nv04_crtc_cursor_set,
 	.cursor_move = nv04_crtc_cursor_move,
 	.gamma_set = nv_crtc_gamma_set,
-	.set_config = drm_crtc_helper_set_config,
+	.set_config = nouveau_crtc_set_config,
 	.page_flip = nouveau_crtc_page_flip,
 	.destroy = nv_crtc_destroy,
 };
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c
index d97f200..dd7d2e1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.c
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c
@@ -25,8 +25,27 @@
 #define NOUVEAU_DSM_POWER_SPEED 0x01
 #define NOUVEAU_DSM_POWER_STAMINA 0x02
 
-#define NOUVEAU_DSM_OPTIMUS_FN 0x1A
-#define NOUVEAU_DSM_OPTIMUS_ARGS 0x03000001
+#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;
@@ -251,9 +270,18 @@ static int nouveau_dsm_pci_probe(struct pci_dev *pdev)
 		retval |= NOUVEAU_DSM_HAS_MUX;
 
 	if (nouveau_test_dsm(dhandle, nouveau_optimus_dsm,
-		NOUVEAU_DSM_OPTIMUS_FN))
+		NOUVEAU_DSM_OPTIMUS_CAPS))
 		retval |= NOUVEAU_DSM_HAS_OPT;
 
+	if (retval & NOUVEAU_DSM_HAS_OPT) {
+		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" : "");
+	}
 	if (retval)
 		nouveau_dsm_priv.dhandle = dhandle;
 
@@ -328,8 +356,12 @@ void nouveau_switcheroo_optimus_dsm(void)
 	if (!nouveau_dsm_priv.optimus_detected)
 		return;
 
-	nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FN,
-		NOUVEAU_DSM_OPTIMUS_ARGS, &result);
+	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)
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index 4da776f..c5b36f9 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -26,6 +26,8 @@
 
 #include <acpi/button.h>
 
+#include <linux/pm_runtime.h>
+
 #include <drm/drmP.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_crtc_helper.h>
@@ -240,6 +242,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 	struct nouveau_encoder *nv_partner;
 	struct nouveau_i2c_port *i2c;
 	int type;
+	int ret;
+	enum drm_connector_status conn_status = connector_status_disconnected;
 
 	/* Cleanup the previous EDID block. */
 	if (nv_connector->edid) {
@@ -248,6 +252,10 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 		nv_connector->edid = NULL;
 	}
 
+	ret = pm_runtime_get_sync(connector->dev->dev);
+	if (ret < 0)
+		return conn_status;
+
 	i2c = nouveau_connector_ddc_detect(connector, &nv_encoder);
 	if (i2c) {
 		nv_connector->edid = drm_get_edid(connector, &i2c->adapter);
@@ -263,7 +271,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 		    !nouveau_dp_detect(to_drm_encoder(nv_encoder))) {
 			NV_ERROR(drm, "Detected %s, but failed init\n",
 				 drm_get_connector_name(connector));
-			return connector_status_disconnected;
+			conn_status = connector_status_disconnected;
+			goto out;
 		}
 
 		/* Override encoder type for DVI-I based on whether EDID
@@ -290,13 +299,15 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 		}
 
 		nouveau_connector_set_encoder(connector, nv_encoder);
-		return connector_status_connected;
+		conn_status = connector_status_connected;
+		goto out;
 	}
 
 	nv_encoder = nouveau_connector_of_detect(connector);
 	if (nv_encoder) {
 		nouveau_connector_set_encoder(connector, nv_encoder);
-		return connector_status_connected;
+		conn_status = connector_status_connected;
+		goto out;
 	}
 
 detect_analog:
@@ -311,12 +322,18 @@ detect_analog:
 		if (helper->detect(encoder, connector) ==
 						connector_status_connected) {
 			nouveau_connector_set_encoder(connector, nv_encoder);
-			return connector_status_connected;
+			conn_status = connector_status_connected;
+			goto out;
 		}
 
 	}
 
-	return connector_status_disconnected;
+ out:
+
+	pm_runtime_mark_last_busy(connector->dev->dev);
+	pm_runtime_put_autosuspend(connector->dev->dev);
+
+	return conn_status;
 }
 
 static enum drm_connector_status
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index 907d20e..448022e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -394,7 +394,7 @@ nouveau_display_suspend(struct drm_device *dev)
 
 	nouveau_display_fini(dev);
 
-	NV_INFO(drm, "unpinning framebuffer(s)...\n");
+	NV_SUSPEND(drm, "unpinning framebuffer(s)...\n");
 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 		struct nouveau_framebuffer *nouveau_fb;
 
@@ -416,7 +416,7 @@ nouveau_display_suspend(struct drm_device *dev)
 }
 
 void
-nouveau_display_resume(struct drm_device *dev)
+nouveau_display_repin(struct drm_device *dev)
 {
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct drm_crtc *crtc;
@@ -441,10 +441,12 @@ nouveau_display_resume(struct drm_device *dev)
 		if (ret)
 			NV_ERROR(drm, "Could not pin/map cursor.\n");
 	}
+}
 
-	nouveau_fbcon_set_suspend(dev, 0);
-	nouveau_fbcon_zfill_all(dev);
-
+void
+nouveau_display_resume(struct drm_device *dev)
+{
+	struct drm_crtc *crtc;
 	nouveau_display_init(dev);
 
 	/* Force CLUT to get re-loaded during modeset */
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.h b/drivers/gpu/drm/nouveau/nouveau_display.h
index 1ea3e47..229ac3f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.h
+++ b/drivers/gpu/drm/nouveau/nouveau_display.h
@@ -57,6 +57,7 @@ void nouveau_display_destroy(struct drm_device *dev);
 int  nouveau_display_init(struct drm_device *dev);
 void nouveau_display_fini(struct drm_device *dev);
 int  nouveau_display_suspend(struct drm_device *dev);
+void nouveau_display_repin(struct drm_device *dev);
 void nouveau_display_resume(struct drm_device *dev);
 
 int  nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
@@ -73,6 +74,7 @@ int  nouveau_display_dumb_destroy(struct drm_file *, struct drm_device *,
 
 void nouveau_hdmi_mode_set(struct drm_encoder *, struct drm_display_mode *);
 
+int nouveau_crtc_set_config(struct drm_mode_set *set);
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
 extern int nouveau_backlight_init(struct drm_device *);
 extern void nouveau_backlight_exit(struct drm_device *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 6197266..d207b07 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -25,7 +25,10 @@
 #include <linux/console.h>
 #include <linux/module.h>
 #include <linux/pci.h>
-
+#include <linux/pm_runtime.h>
+#include <linux/vga_switcheroo.h>
+#include "drmP.h"
+#include "drm_crtc_helper.h"
 #include <core/device.h>
 #include <core/client.h>
 #include <core/gpuobj.h>
@@ -69,6 +72,10 @@ MODULE_PARM_DESC(modeset, "enable driver (default: auto, "
 int nouveau_modeset = -1;
 module_param_named(modeset, nouveau_modeset, int, 0400);
 
+MODULE_PARM_DESC(runpm, "disable (0), force enable (1), optimus only default (-1)");
+int nouveau_runtime_pm = -1;
+module_param_named(runpm, nouveau_runtime_pm, int, 0400);
+
 static struct drm_driver driver;
 
 static int
@@ -296,6 +303,31 @@ static int nouveau_drm_probe(struct pci_dev *pdev,
 	return 0;
 }
 
+#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403
+
+static void
+nouveau_get_hdmi_dev(struct drm_device *dev)
+{
+	struct nouveau_drm *drm = dev->dev_private;
+	struct pci_dev *pdev = dev->pdev;
+
+	/* subfunction one is a hdmi audio device? */
+	drm->hdmi_device = pci_get_bus_and_slot((unsigned int)pdev->bus->number,
+						PCI_DEVFN(PCI_SLOT(pdev->devfn), 1));
+
+	if (!drm->hdmi_device) {
+		DRM_INFO("hdmi device  not found %d %d %d\n", pdev->bus->number, PCI_SLOT(pdev->devfn), 1);
+		return;
+	}
+
+	if ((drm->hdmi_device->class >> 8) != PCI_CLASS_MULTIMEDIA_HD_AUDIO) {
+		DRM_INFO("possible hdmi device  not audio %d\n", drm->hdmi_device->class);
+		pci_dev_put(drm->hdmi_device);
+		drm->hdmi_device = NULL;
+		return;
+	}
+}
+
 static int
 nouveau_drm_load(struct drm_device *dev, unsigned long flags)
 {
@@ -314,6 +346,8 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
 	INIT_LIST_HEAD(&drm->clients);
 	spin_lock_init(&drm->tile.lock);
 
+	nouveau_get_hdmi_dev(dev);
+
 	/* make sure AGP controller is in a consistent state before we
 	 * (possibly) execute vbios init tables (see nouveau_agp.h)
 	 */
@@ -388,6 +422,15 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags)
 
 	nouveau_accel_init(drm);
 	nouveau_fbcon_init(dev);
+
+	if (nouveau_runtime_pm != 0) {
+		pm_runtime_use_autosuspend(dev->dev);
+		pm_runtime_set_autosuspend_delay(dev->dev, 5000);
+		pm_runtime_set_active(dev->dev);
+		pm_runtime_allow(dev->dev);
+		pm_runtime_mark_last_busy(dev->dev);
+		pm_runtime_put(dev->dev);
+	}
 	return 0;
 
 fail_dispinit:
@@ -409,6 +452,7 @@ nouveau_drm_unload(struct drm_device *dev)
 {
 	struct nouveau_drm *drm = nouveau_drm(dev);
 
+	pm_runtime_get_sync(dev->dev);
 	nouveau_fbcon_fini(dev);
 	nouveau_accel_fini(drm);
 
@@ -424,6 +468,8 @@ nouveau_drm_unload(struct drm_device *dev)
 	nouveau_agp_fini(drm);
 	nouveau_vga_fini(drm);
 
+	if (drm->hdmi_device)
+		pci_dev_put(drm->hdmi_device);
 	nouveau_cli_destroy(&drm->client);
 	return 0;
 }
@@ -450,19 +496,16 @@ nouveau_do_suspend(struct drm_device *dev)
 	int ret;
 
 	if (dev->mode_config.num_crtc) {
-		NV_INFO(drm, "suspending fbcon...\n");
-		nouveau_fbcon_set_suspend(dev, 1);
-
-		NV_INFO(drm, "suspending display...\n");
+		NV_SUSPEND(drm, "suspending display...\n");
 		ret = nouveau_display_suspend(dev);
 		if (ret)
 			return ret;
 	}
 
-	NV_INFO(drm, "evicting buffers...\n");
+	NV_SUSPEND(drm, "evicting buffers...\n");
 	ttm_bo_evict_mm(&drm->ttm.bdev, TTM_PL_VRAM);
 
-	NV_INFO(drm, "waiting for kernel channels to go idle...\n");
+	NV_SUSPEND(drm, "waiting for kernel channels to go idle...\n");
 	if (drm->cechan) {
 		ret = nouveau_channel_idle(drm->cechan);
 		if (ret)
@@ -475,7 +518,7 @@ nouveau_do_suspend(struct drm_device *dev)
 			return ret;
 	}
 
-	NV_INFO(drm, "suspending client object trees...\n");
+	NV_SUSPEND(drm, "suspending client object trees...\n");
 	if (drm->fence && nouveau_fence(drm)->suspend) {
 		if (!nouveau_fence(drm)->suspend(drm))
 			return -ENOMEM;
@@ -487,7 +530,7 @@ nouveau_do_suspend(struct drm_device *dev)
 			goto fail_client;
 	}
 
-	NV_INFO(drm, "suspending kernel object tree...\n");
+	NV_SUSPEND(drm, "suspending kernel object tree...\n");
 	ret = nouveau_client_fini(&drm->client.base, true);
 	if (ret)
 		goto fail_client;
@@ -501,7 +544,7 @@ fail_client:
 	}
 
 	if (dev->mode_config.num_crtc) {
-		NV_INFO(drm, "resuming display...\n");
+		NV_SUSPEND(drm, "resuming display...\n");
 		nouveau_display_resume(dev);
 	}
 	return ret;
@@ -513,9 +556,14 @@ int nouveau_pmops_suspend(struct device *dev)
 	struct drm_device *drm_dev = pci_get_drvdata(pdev);
 	int ret;
 
-	if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF)
+	if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF ||
+	    drm_dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF)
 		return 0;
 
+	if (drm_dev->mode_config.num_crtc)
+		nouveau_fbcon_set_suspend(drm_dev, 1);
+
+	nv_suspend_set_printk_level(NV_DBG_INFO);
 	ret = nouveau_do_suspend(drm_dev);
 	if (ret)
 		return ret;
@@ -523,6 +571,7 @@ int nouveau_pmops_suspend(struct device *dev)
 	pci_save_state(pdev);
 	pci_disable_device(pdev);
 	pci_set_power_state(pdev, PCI_D3hot);
+	nv_suspend_set_printk_level(NV_DBG_DEBUG);
 
 	return 0;
 }
@@ -533,15 +582,15 @@ nouveau_do_resume(struct drm_device *dev)
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nouveau_cli *cli;
 
-	NV_INFO(drm, "re-enabling device...\n");
+	NV_SUSPEND(drm, "re-enabling device...\n");
 
 	nouveau_agp_reset(drm);
 
-	NV_INFO(drm, "resuming kernel object tree...\n");
+	NV_SUSPEND(drm, "resuming kernel object tree...\n");
 	nouveau_client_init(&drm->client.base);
 	nouveau_agp_init(drm);
 
-	NV_INFO(drm, "resuming client object trees...\n");
+	NV_SUSPEND(drm, "resuming client object trees...\n");
 	if (drm->fence && nouveau_fence(drm)->resume)
 		nouveau_fence(drm)->resume(drm);
 
@@ -553,9 +602,10 @@ nouveau_do_resume(struct drm_device *dev)
 	nouveau_pm_resume(dev);
 
 	if (dev->mode_config.num_crtc) {
-		NV_INFO(drm, "resuming display...\n");
-		nouveau_display_resume(dev);
+		NV_SUSPEND(drm, "resuming display...\n");
+		nouveau_display_repin(dev);
 	}
+
 	return 0;
 }
 
@@ -565,7 +615,8 @@ int nouveau_pmops_resume(struct device *dev)
 	struct drm_device *drm_dev = pci_get_drvdata(pdev);
 	int ret;
 
-	if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF)
+	if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF ||
+	    drm_dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF)
 		return 0;
 
 	pci_set_power_state(pdev, PCI_D0);
@@ -575,23 +626,54 @@ int nouveau_pmops_resume(struct device *dev)
 		return ret;
 	pci_set_master(pdev);
 
-	return nouveau_do_resume(drm_dev);
+	nv_suspend_set_printk_level(NV_DBG_INFO);
+	ret = nouveau_do_resume(drm_dev);
+	if (ret) {
+		nv_suspend_set_printk_level(NV_DBG_DEBUG);
+		return ret;
+	}
+	if (drm_dev->mode_config.num_crtc)
+		nouveau_fbcon_set_suspend(drm_dev, 0);
+
+	nouveau_fbcon_zfill_all(drm_dev);
+	nouveau_display_resume(drm_dev);
+	nv_suspend_set_printk_level(NV_DBG_DEBUG);
+	return 0;
 }
 
 static int nouveau_pmops_freeze(struct device *dev)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
 	struct drm_device *drm_dev = pci_get_drvdata(pdev);
+	int ret;
+
+	nv_suspend_set_printk_level(NV_DBG_INFO);
+	if (drm_dev->mode_config.num_crtc)
+		nouveau_fbcon_set_suspend(drm_dev, 1);
 
-	return nouveau_do_suspend(drm_dev);
+	ret = nouveau_do_suspend(drm_dev);
+	nv_suspend_set_printk_level(NV_DBG_DEBUG);
+	return ret;
 }
 
 static int nouveau_pmops_thaw(struct device *dev)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
 	struct drm_device *drm_dev = pci_get_drvdata(pdev);
+	int ret;
 
-	return nouveau_do_resume(drm_dev);
+	nv_suspend_set_printk_level(NV_DBG_INFO);
+	ret = nouveau_do_resume(drm_dev);
+	if (ret) {
+		nv_suspend_set_printk_level(NV_DBG_DEBUG);
+		return ret;
+	}
+	if (drm_dev->mode_config.num_crtc)
+		nouveau_fbcon_set_suspend(drm_dev, 0);
+	nouveau_fbcon_zfill_all(drm_dev);
+	nouveau_display_resume(drm_dev);
+	nv_suspend_set_printk_level(NV_DBG_DEBUG);
+	return 0;
 }
 
 
@@ -604,19 +686,24 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv)
 	char name[32], tmpname[TASK_COMM_LEN];
 	int ret;
 
+	/* need to bring up power immediately if opening device */
+	ret = pm_runtime_get_sync(dev->dev);
+	if (ret < 0)
+		return ret;
+
 	get_task_comm(tmpname, current);
 	snprintf(name, sizeof(name), "%s[%d]", tmpname, pid_nr(fpriv->pid));
 
 	ret = nouveau_cli_create(pdev, name, sizeof(*cli), (void **)&cli);
 	if (ret)
-		return ret;
+		goto out_suspend;
 
 	if (nv_device(drm->device)->card_type >= NV_50) {
 		ret = nouveau_vm_new(nv_device(drm->device), 0, (1ULL << 40),
 				     0x1000, &cli->base.vm);
 		if (ret) {
 			nouveau_cli_destroy(cli);
-			return ret;
+			goto out_suspend;
 		}
 	}
 
@@ -625,7 +712,12 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv)
 	mutex_lock(&drm->client.mutex);
 	list_add(&cli->head, &drm->clients);
 	mutex_unlock(&drm->client.mutex);
-	return 0;
+
+out_suspend:
+	pm_runtime_mark_last_busy(dev->dev);
+	pm_runtime_put_autosuspend(dev->dev);
+
+	return ret;
 }
 
 static void
@@ -634,12 +726,15 @@ nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv)
 	struct nouveau_cli *cli = nouveau_cli(fpriv);
 	struct nouveau_drm *drm = nouveau_drm(dev);
 
+	pm_runtime_get_sync(dev->dev);
+
 	if (cli->abi16)
 		nouveau_abi16_fini(cli->abi16);
 
 	mutex_lock(&drm->client.mutex);
 	list_del(&cli->head);
 	mutex_unlock(&drm->client.mutex);
+
 }
 
 static void
@@ -647,6 +742,8 @@ nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv)
 {
 	struct nouveau_cli *cli = nouveau_cli(fpriv);
 	nouveau_cli_destroy(cli);
+	pm_runtime_mark_last_busy(dev->dev);
+	pm_runtime_put_autosuspend(dev->dev);
 }
 
 static struct drm_ioctl_desc
@@ -665,12 +762,30 @@ nouveau_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_INFO, nouveau_gem_ioctl_info, DRM_UNLOCKED|DRM_AUTH),
 };
 
+long nouveau_drm_ioctl(struct file *filp,
+		       unsigned int cmd, unsigned long arg)
+{
+	struct drm_file *file_priv = filp->private_data;
+	struct drm_device *dev;
+	long ret;
+	dev = file_priv->minor->dev;
+
+	ret = pm_runtime_get_sync(dev->dev);
+	if (ret < 0)
+		return ret;
+
+	ret = drm_ioctl(filp, cmd, arg);
+
+	pm_runtime_mark_last_busy(dev->dev);
+	pm_runtime_put_autosuspend(dev->dev);
+	return ret;
+}
 static const struct file_operations
 nouveau_driver_fops = {
 	.owner = THIS_MODULE,
 	.open = drm_open,
 	.release = drm_release,
-	.unlocked_ioctl = drm_ioctl,
+	.unlocked_ioctl = nouveau_drm_ioctl,
 	.mmap = nouveau_ttm_mmap,
 	.poll = drm_poll,
 	.fasync = drm_fasync,
@@ -753,6 +868,90 @@ nouveau_drm_pci_table[] = {
 	{}
 };
 
+static int nouveau_pmops_runtime_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct drm_device *drm_dev = pci_get_drvdata(pdev);
+	int ret;
+
+	if (nouveau_runtime_pm == 0)
+		return -EINVAL;
+
+	drm_kms_helper_poll_disable(drm_dev);
+	vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_OFF);
+	nouveau_switcheroo_optimus_dsm();
+	ret = nouveau_do_suspend(drm_dev);
+	pci_save_state(pdev);
+	pci_disable_device(pdev);
+	pci_set_power_state(pdev, PCI_D3cold);
+	drm_dev->switch_power_state = DRM_SWITCH_POWER_DYNAMIC_OFF;
+	return ret;
+}
+
+static int nouveau_pmops_runtime_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct drm_device *drm_dev = pci_get_drvdata(pdev);
+	struct nouveau_device *device = nouveau_dev(drm_dev);
+	int ret;
+
+	if (nouveau_runtime_pm == 0)
+		return -EINVAL;
+
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+	ret = pci_enable_device(pdev);
+	if (ret)
+		return ret;
+	pci_set_master(pdev);
+
+	ret = nouveau_do_resume(drm_dev);
+	nouveau_display_resume(drm_dev);
+	drm_kms_helper_poll_enable(drm_dev);
+	/* do magic */
+	nv_mask(device, 0x88488, (1 << 25), (1 << 25));
+	vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_ON);
+	drm_dev->switch_power_state = DRM_SWITCH_POWER_ON;
+	return ret;
+}
+
+static int nouveau_pmops_runtime_idle(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct drm_device *drm_dev = pci_get_drvdata(pdev);
+	struct nouveau_drm *drm = nouveau_drm(drm_dev);
+	struct drm_crtc *crtc;
+
+	if (nouveau_runtime_pm == 0)
+		return -EBUSY;
+
+	/* are we optimus enabled? */
+	if (nouveau_runtime_pm == -1 && !nouveau_is_optimus() && !nouveau_is_v1_dsm()) {
+		DRM_DEBUG_DRIVER("failing to power off - not optimus\n");
+		return -EBUSY;
+	}
+
+	/* if we have a hdmi audio device - make sure it has a driver loaded */
+	if (drm->hdmi_device) {
+		if (!drm->hdmi_device->driver) {
+			DRM_DEBUG_DRIVER("failing to power off - no HDMI audio driver loaded\n");
+			pm_runtime_mark_last_busy(dev);
+			return -EBUSY;
+		}
+	}
+
+	list_for_each_entry(crtc, &drm->dev->mode_config.crtc_list, head) {
+		if (crtc->enabled) {
+			DRM_DEBUG_DRIVER("failing to power off - crtc active\n");
+			return -EBUSY;
+		}
+	}
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_autosuspend(dev);
+	/* we don't want the main rpm_idle to call suspend - we want to autosuspend */
+	return 1;
+}
+
 static const struct dev_pm_ops nouveau_pm_ops = {
 	.suspend = nouveau_pmops_suspend,
 	.resume = nouveau_pmops_resume,
@@ -760,6 +959,9 @@ static const struct dev_pm_ops nouveau_pm_ops = {
 	.thaw = nouveau_pmops_thaw,
 	.poweroff = nouveau_pmops_freeze,
 	.restore = nouveau_pmops_resume,
+	.runtime_suspend = nouveau_pmops_runtime_suspend,
+	.runtime_resume = nouveau_pmops_runtime_resume,
+	.runtime_idle = nouveau_pmops_runtime_idle,
 };
 
 static struct pci_driver
diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.h b/drivers/gpu/drm/nouveau/nouveau_drm.h
index 41ff7e0..994fd6e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.h
@@ -70,6 +70,8 @@ nouveau_cli(struct drm_file *fpriv)
 	return fpriv ? fpriv->driver_priv : NULL;
 }
 
+extern int nouveau_runtime_pm;
+
 struct nouveau_drm {
 	struct nouveau_cli client;
 	struct drm_device *dev;
@@ -129,6 +131,12 @@ struct nouveau_drm {
 
 	/* power management */
 	struct nouveau_pm *pm;
+
+	/* display power reference */
+	bool have_disp_power_ref;
+
+	struct dev_pm_domain vga_pm_domain;
+	struct pci_dev *hdmi_device;
 };
 
 static inline struct nouveau_drm *
@@ -146,6 +154,7 @@ nouveau_dev(struct drm_device *dev)
 int nouveau_pmops_suspend(struct device *);
 int nouveau_pmops_resume(struct device *);
 
+#define NV_SUSPEND(cli, fmt, args...) nv_suspend((cli), fmt, ##args)
 #define NV_FATAL(cli, fmt, args...) nv_fatal((cli), fmt, ##args)
 #define NV_ERROR(cli, fmt, args...) nv_error((cli), fmt, ##args)
 #define NV_WARN(cli, fmt, args...) nv_warn((cli), fmt, ##args)
diff --git a/drivers/gpu/drm/nouveau/nouveau_vga.c b/drivers/gpu/drm/nouveau/nouveau_vga.c
index 40a09f1..81638d7 100644
--- a/drivers/gpu/drm/nouveau/nouveau_vga.c
+++ b/drivers/gpu/drm/nouveau/nouveau_vga.c
@@ -32,6 +32,9 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev,
 {
 	struct drm_device *dev = pci_get_drvdata(pdev);
 
+	if ((nouveau_is_optimus() || nouveau_is_v1_dsm()) && state == VGA_SWITCHEROO_OFF)
+		return;
+
 	if (state == VGA_SWITCHEROO_ON) {
 		printk(KERN_ERR "VGA switcheroo: switched nouveau on\n");
 		dev->switch_power_state = DRM_SWITCH_POWER_CHANGING;
@@ -78,8 +81,17 @@ void
 nouveau_vga_init(struct nouveau_drm *drm)
 {
 	struct drm_device *dev = drm->dev;
+	bool runtime = false;
 	vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode);
-	vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, false);
+
+	if (nouveau_runtime_pm == 1)
+		runtime = true;
+	if ((nouveau_runtime_pm == -1) && (nouveau_is_optimus() || nouveau_is_v1_dsm()))
+		runtime = true;
+	vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, runtime);
+
+	if (runtime && nouveau_is_v1_dsm() && !nouveau_is_optimus())
+		vga_switcheroo_init_domain_pm_ops(drm->dev->dev, &drm->vga_pm_domain);
 }
 
 void
diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c
index 8b40a36..9d2092a 100644
--- a/drivers/gpu/drm/nouveau/nv50_display.c
+++ b/drivers/gpu/drm/nouveau/nv50_display.c
@@ -1326,7 +1326,7 @@ static const struct drm_crtc_funcs nv50_crtc_func = {
 	.cursor_set = nv50_crtc_cursor_set,
 	.cursor_move = nv50_crtc_cursor_move,
 	.gamma_set = nv50_crtc_gamma_set,
-	.set_config = drm_crtc_helper_set_config,
+	.set_config = nouveau_crtc_set_config,
 	.destroy = nv50_crtc_destroy,
 	.page_flip = nouveau_crtc_page_flip,
 };
-- 
1.8.2.1

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support (v3)
  2013-08-05  2:56 optimus dynamic power patches Dave Airlie
                   ` (2 preceding siblings ...)
  2013-08-05  2:56 ` [PATCH 3/4] nouveau: add runtime PM support (v0.9) Dave Airlie
@ 2013-08-05  2:56 ` Dave Airlie
  2013-08-06  7:14   ` Takashi Iwai
  3 siblings, 1 reply; 7+ messages in thread
From: Dave Airlie @ 2013-08-05  2:56 UTC (permalink / raw)
  To: dri-devel; +Cc: tiwai

Add support for HDMI audio device on VGA cards that powerdown
to D3cold using non-standard ACPI/PCI infrastructure (optimus).

This does a couple of things to make it work:

a) add a set of power ops for the hdmi domain, and enables them
via vga_switcheroo when we are a switcheroo controlled card. This
just replaces the runtime resume operation so that when the card
is in D3cold the userspace pci config space access via sysfs,
the vga switcheroon runtime resume gets called first and it calls
the GPU resume callback before calling the sound card runtime
resume.

b) standard ACPI/PCI stacks won't put a device into D3cold without
an ACPI handle, but since the hdmi audio devices on gpus don't have
an ACPI handle, we need to manually force the device into D3cold
after suspend from the switcheroo path only.

c) don't try and do runtime s/r when the GPU is off.

d) call runtime suspend/resume during switcheroo suspend/resume
this is to make sure the runtime stack knows to try and resume
the hdmi audio device for pci config space access.

v2: fix incorrect runtime call suspend->resume.

v3: rework irq handler to avoid false irq when we are resuming
but haven't runtime resumed yet, don't bother trying D3cold,
it won't work, just set it manually ourselves, move runtime s/r
calls outside the main s/r hook. enable dnyamic pm properly by
dropping reference.

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
 sound/pci/hda/hda_intel.c | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)

diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 8860dd5..4c4f546 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -555,6 +555,9 @@ struct azx {
 #ifdef CONFIG_SND_HDA_DSP_LOADER
 	struct azx_dev saved_azx_dev;
 #endif
+
+	/* secondary power domain for hdmi audio under vga device */
+	struct dev_pm_domain hdmi_pm_domain;
 };
 
 #define CREATE_TRACE_POINTS
@@ -1396,11 +1399,6 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
 	u8 sd_status;
 	int i, ok;
 
-#ifdef CONFIG_PM_RUNTIME
-	if (chip->pci->dev.power.runtime_status != RPM_ACTIVE)
-		return IRQ_NONE;
-#endif
-
 	spin_lock(&chip->reg_lock);
 
 	if (chip->disabled) {
@@ -1409,7 +1407,7 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
 	}
 
 	status = azx_readl(chip, INTSTS);
-	if (status == 0) {
+	if (status == 0 || status == 0xffffffff) {
 		spin_unlock(&chip->reg_lock);
 		return IRQ_NONE;
 	}
@@ -2971,6 +2969,12 @@ static int azx_runtime_suspend(struct device *dev)
 	struct snd_card *card = dev_get_drvdata(dev);
 	struct azx *chip = card->private_data;
 
+	if (chip->disabled)
+		return 0;
+
+	if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
+		return 0;
+
 	azx_stop_chip(chip);
 	azx_enter_link_reset(chip);
 	azx_clear_irq_pending(chip);
@@ -2984,6 +2988,12 @@ static int azx_runtime_resume(struct device *dev)
 	struct snd_card *card = dev_get_drvdata(dev);
 	struct azx *chip = card->private_data;
 
+	if (chip->disabled)
+		return 0;
+
+	if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
+		return 0;
+
 	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
 		hda_display_power(true);
 	azx_init_pci(chip);
@@ -2996,6 +3006,9 @@ static int azx_runtime_idle(struct device *dev)
 	struct snd_card *card = dev_get_drvdata(dev);
 	struct azx *chip = card->private_data;
 
+	if (chip->disabled)
+		return 0;
+
 	if (!power_save_controller ||
 	    !(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
 		return -EBUSY;
@@ -3078,13 +3091,19 @@ static void azx_vs_set_state(struct pci_dev *pci,
 			   "%s: %s via VGA-switcheroo\n", pci_name(chip->pci),
 			   disabled ? "Disabling" : "Enabling");
 		if (disabled) {
+			pm_runtime_put_sync_suspend(&pci->dev);
 			azx_suspend(&pci->dev);
+			/* when we get suspended by vga switcheroo we end up in D3cold,
+			 * however we have no ACPI handle, so pci/acpi can't put us there,
+			 * put ourselves there */
+			pci->current_state = PCI_D3cold;
 			chip->disabled = true;
 			if (snd_hda_lock_devices(chip->bus))
 				snd_printk(KERN_WARNING SFX "%s: Cannot lock devices!\n",
 					   pci_name(chip->pci));
 		} else {
 			snd_hda_unlock_devices(chip->bus);
+			pm_runtime_get_noresume(&pci->dev);
 			chip->disabled = false;
 			azx_resume(&pci->dev);
 		}
@@ -3139,6 +3158,9 @@ static int register_vga_switcheroo(struct azx *chip)
 	if (err < 0)
 		return err;
 	chip->vga_switcheroo_registered = 1;
+
+	/* register as an optimus hdmi audio power domain */
+	vga_switcheroo_init_domain_pm_optimus_hdmi_audio(&chip->pci->dev, &chip->hdmi_pm_domain);
 	return 0;
 }
 #else
@@ -3887,7 +3909,7 @@ static int azx_probe_continue(struct azx *chip)
 	power_down_all_codecs(chip);
 	azx_notifier_register(chip);
 	azx_add_card_list(chip);
-	if (chip->driver_caps & AZX_DCAPS_PM_RUNTIME)
+	if ((chip->driver_caps & AZX_DCAPS_PM_RUNTIME) || chip->use_vga_switcheroo)
 		pm_runtime_put_noidle(&pci->dev);
 
 	return 0;
-- 
1.8.2.1

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support (v3)
  2013-08-05  2:56 ` [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support (v3) Dave Airlie
@ 2013-08-06  7:14   ` Takashi Iwai
  2013-08-19  4:50     ` Dave Airlie
  0 siblings, 1 reply; 7+ messages in thread
From: Takashi Iwai @ 2013-08-06  7:14 UTC (permalink / raw)
  To: Dave Airlie; +Cc: dri-devel

At Mon,  5 Aug 2013 12:56:04 +1000,
Dave Airlie wrote:
> 
> Add support for HDMI audio device on VGA cards that powerdown
> to D3cold using non-standard ACPI/PCI infrastructure (optimus).
> 
> This does a couple of things to make it work:
> 
> a) add a set of power ops for the hdmi domain, and enables them
> via vga_switcheroo when we are a switcheroo controlled card. This
> just replaces the runtime resume operation so that when the card
> is in D3cold the userspace pci config space access via sysfs,
> the vga switcheroon runtime resume gets called first and it calls
> the GPU resume callback before calling the sound card runtime
> resume.
> 
> b) standard ACPI/PCI stacks won't put a device into D3cold without
> an ACPI handle, but since the hdmi audio devices on gpus don't have
> an ACPI handle, we need to manually force the device into D3cold
> after suspend from the switcheroo path only.
> 
> c) don't try and do runtime s/r when the GPU is off.
> 
> d) call runtime suspend/resume during switcheroo suspend/resume
> this is to make sure the runtime stack knows to try and resume
> the hdmi audio device for pci config space access.
> 
> v2: fix incorrect runtime call suspend->resume.
> 
> v3: rework irq handler to avoid false irq when we are resuming
> but haven't runtime resumed yet, don't bother trying D3cold,
> it won't work, just set it manually ourselves, move runtime s/r
> calls outside the main s/r hook. enable dnyamic pm properly by
> dropping reference.
> 
> Signed-off-by: Dave Airlie <airlied@redhat.com>
> ---
>  sound/pci/hda/hda_intel.c | 36 +++++++++++++++++++++++++++++-------
>  1 file changed, 29 insertions(+), 7 deletions(-)
> 
> diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
> index 8860dd5..4c4f546 100644
> --- a/sound/pci/hda/hda_intel.c
> +++ b/sound/pci/hda/hda_intel.c
> @@ -555,6 +555,9 @@ struct azx {
>  #ifdef CONFIG_SND_HDA_DSP_LOADER
>  	struct azx_dev saved_azx_dev;
>  #endif
> +
> +	/* secondary power domain for hdmi audio under vga device */
> +	struct dev_pm_domain hdmi_pm_domain;
>  };
>  
>  #define CREATE_TRACE_POINTS
> @@ -1396,11 +1399,6 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
>  	u8 sd_status;
>  	int i, ok;
>  
> -#ifdef CONFIG_PM_RUNTIME
> -	if (chip->pci->dev.power.runtime_status != RPM_ACTIVE)
> -		return IRQ_NONE;
> -#endif
> -

This brings me a slight concern.  It might break Intel's runtime PM
case.

>  	spin_lock(&chip->reg_lock);
>  
>  	if (chip->disabled) {
> @@ -1409,7 +1407,7 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
>  	}
>  
>  	status = azx_readl(chip, INTSTS);
> -	if (status == 0) {
> +	if (status == 0 || status == 0xffffffff) {
>  		spin_unlock(&chip->reg_lock);
>  		return IRQ_NONE;
>  	}
> @@ -2971,6 +2969,12 @@ static int azx_runtime_suspend(struct device *dev)
>  	struct snd_card *card = dev_get_drvdata(dev);
>  	struct azx *chip = card->private_data;
>  
> +	if (chip->disabled)
> +		return 0;
> +
> +	if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
> +		return 0;

Hmm, why this check?  I couldn't see the logic clearly.
I thought you didn't add AZX_DCAPS_PM_RUNTIME to Nvidia controllers,
so only Intel (Haswell & co) can pass this check.

>  	azx_stop_chip(chip);
>  	azx_enter_link_reset(chip);
>  	azx_clear_irq_pending(chip);
> @@ -2984,6 +2988,12 @@ static int azx_runtime_resume(struct device *dev)
>  	struct snd_card *card = dev_get_drvdata(dev);
>  	struct azx *chip = card->private_data;
>  
> +	if (chip->disabled)
> +		return 0;
> +
> +	if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
> +		return 0;
> +
>  	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
>  		hda_display_power(true);
>  	azx_init_pci(chip);
> @@ -2996,6 +3006,9 @@ static int azx_runtime_idle(struct device *dev)
>  	struct snd_card *card = dev_get_drvdata(dev);
>  	struct azx *chip = card->private_data;
>  
> +	if (chip->disabled)
> +		return 0;
> +
>  	if (!power_save_controller ||
>  	    !(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
>  		return -EBUSY;
> @@ -3078,13 +3091,19 @@ static void azx_vs_set_state(struct pci_dev *pci,
>  			   "%s: %s via VGA-switcheroo\n", pci_name(chip->pci),
>  			   disabled ? "Disabling" : "Enabling");
>  		if (disabled) {
> +			pm_runtime_put_sync_suspend(&pci->dev);
>  			azx_suspend(&pci->dev);

Isn't it better to swap these two calls, first azx_suspend() and then
pm_runtime_put_sync_suspend()?  Otherwise the controller may be shut
down before suspending the underlying codecs.

> +			/* when we get suspended by vga switcheroo we end up in D3cold,
> +			 * however we have no ACPI handle, so pci/acpi can't put us there,
> +			 * put ourselves there */
> +			pci->current_state = PCI_D3cold;
>  			chip->disabled = true;
>  			if (snd_hda_lock_devices(chip->bus))
>  				snd_printk(KERN_WARNING SFX "%s: Cannot lock devices!\n",
>  					   pci_name(chip->pci));
>  		} else {
>  			snd_hda_unlock_devices(chip->bus);
> +			pm_runtime_get_noresume(&pci->dev);
>  			chip->disabled = false;

This call should be after chip->disabled = false.  Otherwise the check
you added in azx_runtime_resume() will always hit.

>  			azx_resume(&pci->dev);
>  		}
> @@ -3139,6 +3158,9 @@ static int register_vga_switcheroo(struct azx *chip)
>  	if (err < 0)
>  		return err;
>  	chip->vga_switcheroo_registered = 1;
> +
> +	/* register as an optimus hdmi audio power domain */
> +	vga_switcheroo_init_domain_pm_optimus_hdmi_audio(&chip->pci->dev, &chip->hdmi_pm_domain);
>  	return 0;
>  }
>  #else
> @@ -3887,7 +3909,7 @@ static int azx_probe_continue(struct azx *chip)
>  	power_down_all_codecs(chip);
>  	azx_notifier_register(chip);
>  	azx_add_card_list(chip);
> -	if (chip->driver_caps & AZX_DCAPS_PM_RUNTIME)
> +	if ((chip->driver_caps & AZX_DCAPS_PM_RUNTIME) || chip->use_vga_switcheroo)
>  		pm_runtime_put_noidle(&pci->dev);

Maybe we can now call pm_runtime_put_noidle() and
pm_runtime_get_noresume() unconditionally, since there is already a
check in azx_runtime_idle(), so the unexpected runtime suspend should
be already filtered there.


BTW, how would you like to keep patches in git tree(s)?
There's been already some changes about runtime PM in hda_intel.c for
Haswell, so your changes might conflict.  If you can give a persistent
git branch I can pull, I'll cut off a new branch for this and merge
with the current Haswell stuff, then merge back for linux-next in
sound git tree.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support (v3)
  2013-08-06  7:14   ` Takashi Iwai
@ 2013-08-19  4:50     ` Dave Airlie
  0 siblings, 0 replies; 7+ messages in thread
From: Dave Airlie @ 2013-08-19  4:50 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: dri-devel

On Tue, Aug 6, 2013 at 5:14 PM, Takashi Iwai <tiwai@suse.de> wrote:
> At Mon,  5 Aug 2013 12:56:04 +1000,
> Dave Airlie wrote:
>>
>> Add support for HDMI audio device on VGA cards that powerdown
>> to D3cold using non-standard ACPI/PCI infrastructure (optimus).
>>
>> This does a couple of things to make it work:
>>
>> a) add a set of power ops for the hdmi domain, and enables them
>> via vga_switcheroo when we are a switcheroo controlled card. This
>> just replaces the runtime resume operation so that when the card
>> is in D3cold the userspace pci config space access via sysfs,
>> the vga switcheroon runtime resume gets called first and it calls
>> the GPU resume callback before calling the sound card runtime
>> resume.
>>
>> b) standard ACPI/PCI stacks won't put a device into D3cold without
>> an ACPI handle, but since the hdmi audio devices on gpus don't have
>> an ACPI handle, we need to manually force the device into D3cold
>> after suspend from the switcheroo path only.
>>
>> c) don't try and do runtime s/r when the GPU is off.
>>
>> d) call runtime suspend/resume during switcheroo suspend/resume
>> this is to make sure the runtime stack knows to try and resume
>> the hdmi audio device for pci config space access.
>>
>> v2: fix incorrect runtime call suspend->resume.
>>
>> v3: rework irq handler to avoid false irq when we are resuming
>> but haven't runtime resumed yet, don't bother trying D3cold,
>> it won't work, just set it manually ourselves, move runtime s/r
>> calls outside the main s/r hook. enable dnyamic pm properly by
>> dropping reference.
>>
>> Signed-off-by: Dave Airlie <airlied@redhat.com>
>> ---
>>  sound/pci/hda/hda_intel.c | 36 +++++++++++++++++++++++++++++-------
>>  1 file changed, 29 insertions(+), 7 deletions(-)
>>
>> diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
>> index 8860dd5..4c4f546 100644
>> --- a/sound/pci/hda/hda_intel.c
>> +++ b/sound/pci/hda/hda_intel.c
>> @@ -555,6 +555,9 @@ struct azx {
>>  #ifdef CONFIG_SND_HDA_DSP_LOADER
>>       struct azx_dev saved_azx_dev;
>>  #endif
>> +
>> +     /* secondary power domain for hdmi audio under vga device */
>> +     struct dev_pm_domain hdmi_pm_domain;
>>  };
>>
>>  #define CREATE_TRACE_POINTS
>> @@ -1396,11 +1399,6 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
>>       u8 sd_status;
>>       int i, ok;
>>
>> -#ifdef CONFIG_PM_RUNTIME
>> -     if (chip->pci->dev.power.runtime_status != RPM_ACTIVE)
>> -             return IRQ_NONE;
>> -#endif
>> -
>
> This brings me a slight concern.  It might break Intel's runtime PM
> case.

I do wonder how well the intel runtime PM stuff works sometimes, I'm not even
sure how to test it, seems pointless to have runtime support that
nobody turns on,
and doesn't seem to be on by default. how do I best test it? using powertop?

Why would the intel dynamic pm code not be able to handle an irq though, if the
hw is off the 0xffffffff check I put it in should handle it.

>
>>       spin_lock(&chip->reg_lock);
>>
>>       if (chip->disabled) {
>> @@ -1409,7 +1407,7 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
>>       }
>>
>>       status = azx_readl(chip, INTSTS);
>> -     if (status == 0) {
>> +     if (status == 0 || status == 0xffffffff) {
>>               spin_unlock(&chip->reg_lock);
>>               return IRQ_NONE;
>>       }
>> @@ -2971,6 +2969,12 @@ static int azx_runtime_suspend(struct device *dev)
>>       struct snd_card *card = dev_get_drvdata(dev);
>>       struct azx *chip = card->private_data;
>>
>> +     if (chip->disabled)
>> +             return 0;
>> +
>> +     if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
>> +             return 0;
>
> Hmm, why this check?  I couldn't see the logic clearly.
> I thought you didn't add AZX_DCAPS_PM_RUNTIME to Nvidia controllers,
> so only Intel (Haswell & co) can pass this check.

This is mainly to force the secondary GPU to not actually runtime
suspend without
the CAP, I only want to use the runtime infrastructure to enable
getting the device out of
D3cold, its the only method, in order to do that I don't really want
to use the device driver
runtime PM in normal situtations on these devices, I just want to have
the upper layers
of the stack think the device is runtime suspended when really I've
stuck it into D3cold via
the GPU driver runtime s/r code.

>
>>       azx_stop_chip(chip);
>>       azx_enter_link_reset(chip);
>>       azx_clear_irq_pending(chip);
>> @@ -2984,6 +2988,12 @@ static int azx_runtime_resume(struct device *dev)
>>       struct snd_card *card = dev_get_drvdata(dev);
>>       struct azx *chip = card->private_data;
>>
>> +     if (chip->disabled)
>> +             return 0;
>> +
>> +     if (!(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
>> +             return 0;
>> +
>>       if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
>>               hda_display_power(true);
>>       azx_init_pci(chip);
>> @@ -2996,6 +3006,9 @@ static int azx_runtime_idle(struct device *dev)
>>       struct snd_card *card = dev_get_drvdata(dev);
>>       struct azx *chip = card->private_data;
>>
>> +     if (chip->disabled)
>> +             return 0;
>> +
>>       if (!power_save_controller ||
>>           !(chip->driver_caps & AZX_DCAPS_PM_RUNTIME))
>>               return -EBUSY;
>> @@ -3078,13 +3091,19 @@ static void azx_vs_set_state(struct pci_dev *pci,
>>                          "%s: %s via VGA-switcheroo\n", pci_name(chip->pci),
>>                          disabled ? "Disabling" : "Enabling");
>>               if (disabled) {
>> +                     pm_runtime_put_sync_suspend(&pci->dev);
>>                       azx_suspend(&pci->dev);
>
> Isn't it better to swap these two calls, first azx_suspend() and then
> pm_runtime_put_sync_suspend()?  Otherwise the controller may be shut
> down before suspending the underlying codecs.

Again I don't really want the device driver doing any runtime pm
stuff, its going
to get powered off, so I really just want the upper stack layers to
understand that
the only way to get it back is to call the runtime PM stuff, which will work via
the power domain in the vgaswitcheroo and not via the hda_intel code.

>
>> +                     /* when we get suspended by vga switcheroo we end up in D3cold,
>> +                      * however we have no ACPI handle, so pci/acpi can't put us there,
>> +                      * put ourselves there */
>> +                     pci->current_state = PCI_D3cold;
>>                       chip->disabled = true;
>>                       if (snd_hda_lock_devices(chip->bus))
>>                               snd_printk(KERN_WARNING SFX "%s: Cannot lock devices!\n",
>>                                          pci_name(chip->pci));
>>               } else {
>>                       snd_hda_unlock_devices(chip->bus);
>> +                     pm_runtime_get_noresume(&pci->dev);
>>                       chip->disabled = false;
>
> This call should be after chip->disabled = false.  Otherwise the check
> you added in azx_runtime_resume() will always hit.

Same reason as above, I really don't want the driver doing anything also at this
stage we are already inside a resume handler, as if the device is in D3cold,
config space access will call runtime resume, which will enter the vga
switcheroo
power domain for this device, which will then enter gpu resume, which
will end up
back here via switcheroo paths, we really don't want it re-entering
the driver code again,
hence the get with no resume, as the driver will be resumed no matter
what later.

>
>>                       azx_resume(&pci->dev);
>>               }
>> @@ -3139,6 +3158,9 @@ static int register_vga_switcheroo(struct azx *chip)
>>       if (err < 0)
>>               return err;
>>       chip->vga_switcheroo_registered = 1;
>> +
>> +     /* register as an optimus hdmi audio power domain */
>> +     vga_switcheroo_init_domain_pm_optimus_hdmi_audio(&chip->pci->dev, &chip->hdmi_pm_domain);
>>       return 0;
>>  }
>>  #else
>> @@ -3887,7 +3909,7 @@ static int azx_probe_continue(struct azx *chip)
>>       power_down_all_codecs(chip);
>>       azx_notifier_register(chip);
>>       azx_add_card_list(chip);
>> -     if (chip->driver_caps & AZX_DCAPS_PM_RUNTIME)
>> +     if ((chip->driver_caps & AZX_DCAPS_PM_RUNTIME) || chip->use_vga_switcheroo)
>>               pm_runtime_put_noidle(&pci->dev);
>
> Maybe we can now call pm_runtime_put_noidle() and
> pm_runtime_get_noresume() unconditionally, since there is already a
> check in azx_runtime_idle(), so the unexpected runtime suspend should
> be already filtered there.

Yeah I wasn't sure, I'm happy to leave this for now until we know,
this stuff can be fragile
enough :-)

>
>
> BTW, how would you like to keep patches in git tree(s)?
> There's been already some changes about runtime PM in hda_intel.c for
> Haswell, so your changes might conflict.  If you can give a persistent
> git branch I can pull, I'll cut off a new branch for this and merge
> with the current Haswell stuff, then merge back for linux-next in
> sound git tree.

I'll get a stable git tree for this stuff soon, I have the patches in git,
I just want to do a bit more testing on them and I'll push it out.

Dave.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2013-08-19  4:50 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-08-05  2:56 optimus dynamic power patches Dave Airlie
2013-08-05  2:56 ` [PATCH 1/4] gpu/vga_switcheroo: add driver control power feature. (v3) Dave Airlie
2013-08-05  2:56 ` [PATCH 2/4] drm: allow open of dynamic off devices Dave Airlie
2013-08-05  2:56 ` [PATCH 3/4] nouveau: add runtime PM support (v0.9) Dave Airlie
2013-08-05  2:56 ` [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support (v3) Dave Airlie
2013-08-06  7:14   ` Takashi Iwai
2013-08-19  4:50     ` Dave Airlie

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.