Skip to contentSkip to conversation

nouveau optimus dynamic power off

PATCH In review Dave Airlie 3 participants 8 messages Jul 29, 2013 – Aug 1, 2013

Subsystems (7)
lore
Share & tools
lore

Dave Airlie

author maintainer
#0 lore raw

I finally got back to debugging the HDMI audio interactions that stopped me last time,

This adds switcheroo + nouveau + hda_intel support for turning off the secondary GPU in optimus laptops, saving a lot of power.

The GPU should come back on for things like lspci on the devices or for DRI_PRIME= or X starting up.

author maintainer
#1 lore raw

PATCH 1/4 gpu/vga_switcheroo: add driver control power feature. (v3)

From: Dave Airlie <[email protected]>

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

---
 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(-)
drivers/gpu/drm/i915/i915_dma.c +1 -1
index b152068..5f930a1 100644
@@ -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;
 
drivers/gpu/drm/nouveau/nouveau_vga.c +1 -1
index 25d3495..40a09f1 100644
@@ -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
drivers/gpu/drm/radeon/radeon_device.c +1 -1
index 82335e3..0610ca4 100644
@@ -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)
drivers/gpu/vga/vga_switcheroo.c +142 -5

221 lines not on this page · raw message

include/linux/vga_switcheroo.h +11 -2
index ddb419c..502073a 100644
@@ -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_ */
Signature · 1 line
1.8.2.1
#2 lore raw

Re: [PATCH 1/4] gpu/vga_switcheroo: add driver control power feature. (v3)

Hi Dave,

I don't know anything of PM domains, but there was one minor thing I wanted to mention, see below.

Quoted 1 line · On Monday 29 July 2013 16:06:56 Dave Airlie wrote:

From: Dave Airlie <[email protected]>

Something went wrong here I guess?

Quoted 12 lines

[..]

drivers/gpu/vga/vga_switcheroo.c +10 -0
+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 ((client->pdev->devfn & ~(0x7)) == (pdev->devfn & ~(0x7)) &&
Quoted 1 line

client_is_vga(client)) {

Less obfuscated:

    PCI_SLOT(client->pdev->devfn) == PCI_SLOT(pdev->devfn) 
Quoted 9 lines
drivers/gpu/vga/vga_switcheroo.c +9 -0
+			found = client;
+			ret = pm_runtime_get_sync(&client->pdev->dev);
+			if (ret) {
+				if (ret != 1)
+					return ret;
+			}
+			break;
+		}
+	}

drivers/gpu/vga/vga_switcheroo.c err: · line 689

Quoted 10 lines
drivers/gpu/vga/vga_switcheroo.c +9 -0
+	ret = dev->bus->pm->runtime_resume(dev);
+	if (ret)
+		return ret;
+
+	/* put the reference for the gpu */
+	if (found)
+		pm_runtime_put_autosuspend(&found->pdev->dev);
+	return 0;
+}
[..]

Regards,
Peter

author maintainer
#3 lore raw

PATCH 2/4 drm: allow open of dynamic off devices.

In reply to Dave Airlie

From: Dave Airlie <[email protected]>

---
 drivers/gpu/drm/drm_fops.c | 2 +-
 include/drm/drmP.h         | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)
drivers/gpu/drm/drm_fops.c +1 -1
index 3a24385..d5429ee 100644
@@ -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);
include/drm/drmP.h +1 -0
index 12083dc..7f8acaf 100644
@@ -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)
Signature · 1 line
1.8.2.1
author maintainer
#4 lore raw

PATCH 3/4 nouveau: add runtime PM support (v0.7)

In reply to Dave Airlie

From: Dave Airlie <[email protected]>

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.

---
 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      |   5 +
 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              | 247 +++++++++++++++++++--
 drivers/gpu/drm/nouveau/nouveau_drm.h              |   9 +
 drivers/gpu/drm/nouveau/nouveau_vga.c              |  14 +-
 12 files changed, 399 insertions(+), 42 deletions(-)
drivers/gpu/drm/nouveau/core/core/printk.c +19 -0
index 6161eaf..52fb2aa 100644
@@ -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;
+}
drivers/gpu/drm/nouveau/core/include/core/printk.h +13 -0
index febed2e..d87836e 100644
@@ -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);  \
drivers/gpu/drm/nouveau/core/subdev/bios/init.c +1 -1
index 0687e64..2e11ea0 100644
@@ -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,
drivers/gpu/drm/nouveau/core/subdev/mc/base.c +5 -0
index 1c0330b..b6afd98 100644
@@ -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,7 @@ nouveau_mc_intr(int irq, void *arg)
 		nv_error(pmc, "unknown intr 0x%08x\n", stat);
 	}
 
+	pm_runtime_mark_last_busy(&device->pdev->dev);
 	return stat ? IRQ_HANDLED : IRQ_NONE;
 }
 
drivers/gpu/drm/nouveau/dispnv04/crtc.c +48 -1

67 lines not on this page · raw message

drivers/gpu/drm/nouveau/nouveau_acpi.c +37 -5

62 lines not on this page · raw message

drivers/gpu/drm/nouveau/nouveau_connector.c +22 -5
index 4da776f..c5b36f9 100644
@@ -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
drivers/gpu/drm/nouveau/nouveau_display.c +7 -5
index 907d20e..448022e 100644
@@ -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 */
drivers/gpu/drm/nouveau/nouveau_display.h +2 -0
index 1ea3e47..229ac3f 100644
@@ -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 *);
drivers/gpu/drm/nouveau/nouveau_drm.c +223 -24

443 lines not on this page · raw message

drivers/gpu/drm/nouveau/nouveau_drm.h +9 -0
index 41ff7e0..994fd6e 100644
@@ -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)
drivers/gpu/drm/nouveau/nouveau_vga.c +13 -1
index 40a09f1..81638d7 100644
@@ -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
Signature · 1 line
1.8.2.1
author maintainer
#5 lore raw

PATCH 4/4 snd/hda: add runtime suspend/resume on optimus support

In reply to Dave Airlie

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.

---
 sound/pci/hda/hda_intel.c | 40 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 37 insertions(+), 3 deletions(-)
sound/pci/hda/hda_intel.c +37 -3
index 8860dd5..4b4d05b 100644
@@ -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
@@ -2898,7 +2901,7 @@ static int param_set_xint(const char *val, const struct kernel_param *kp)
 /*
  * power management
  */
-static int azx_suspend(struct device *dev)
+static int azx_do_suspend(struct device *dev, pci_power_t state)
 {
 	struct pci_dev *pci = to_pci_dev(dev);
 	struct snd_card *card = dev_get_drvdata(dev);
@@ -2920,16 +2923,30 @@ static int azx_suspend(struct device *dev)
 		free_irq(chip->irq, chip);
 		chip->irq = -1;
 	}
+
+	/*
+	 * for vga switcheroo suspend we need to
+	 * force runtime suspend so lspci works.
+	 */
+	if (state == PCI_D3cold)
+		pm_runtime_suspend(&pci->dev);
+
 	if (chip->msi)
 		pci_disable_msi(chip->pci);
 	pci_disable_device(pci);
 	pci_save_state(pci);
-	pci_set_power_state(pci, PCI_D3hot);
+
+	pci_set_power_state(pci, state);
 	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
 		hda_display_power(false);
 	return 0;
 }
 
+static int azx_suspend(struct device *dev)
+{
+	return azx_do_suspend(dev, PCI_D3hot);
+}
+
 static int azx_resume(struct device *dev)
 {
 	struct pci_dev *pci = to_pci_dev(dev);
@@ -2971,6 +2988,9 @@ 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;
+
 	azx_stop_chip(chip);
 	azx_enter_link_reset(chip);
 	azx_clear_irq_pending(chip);
@@ -2984,6 +3004,9 @@ 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_I915_POWERWELL)
 		hda_display_power(true);
 	azx_init_pci(chip);
@@ -2996,6 +3019,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,7 +3104,11 @@ 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) {
-			azx_suspend(&pci->dev);
+			azx_do_suspend(&pci->dev, PCI_D3cold);
+			/* 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",
@@ -3087,6 +3117,7 @@ static void azx_vs_set_state(struct pci_dev *pci,
 			snd_hda_unlock_devices(chip->bus);
 			chip->disabled = false;
 			azx_resume(&pci->dev);
+			pm_runtime_suspend(&pci->dev);
 		}
 	}
 }
@@ -3139,6 +3170,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
Signature · 1 line
1.8.2.1
#6 lore raw

Re: [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support

At Mon, 29 Jul 2013 16:06:59 +1000,

Quoted 129 lines · Dave Airlie

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.

---
 sound/pci/hda/hda_intel.c | 40 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 37 insertions(+), 3 deletions(-)
sound/pci/hda/hda_intel.c +33 -3
index 8860dd5..4b4d05b 100644
@@ -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
@@ -2898,7 +2901,7 @@ static int param_set_xint(const char *val, const struct kernel_param *kp)
 /*
  * power management
  */
-static int azx_suspend(struct device *dev)
+static int azx_do_suspend(struct device *dev, pci_power_t state)
 {
 	struct pci_dev *pci = to_pci_dev(dev);
 	struct snd_card *card = dev_get_drvdata(dev);
@@ -2920,16 +2923,30 @@ static int azx_suspend(struct device *dev)
 		free_irq(chip->irq, chip);
 		chip->irq = -1;
 	}
+
+	/*
+	 * for vga switcheroo suspend we need to
+	 * force runtime suspend so lspci works.
+	 */
+	if (state == PCI_D3cold)
+		pm_runtime_suspend(&pci->dev);
+
 	if (chip->msi)
 		pci_disable_msi(chip->pci);
 	pci_disable_device(pci);
 	pci_save_state(pci);
-	pci_set_power_state(pci, PCI_D3hot);
+
+	pci_set_power_state(pci, state);
 	if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
 		hda_display_power(false);
 	return 0;
 }
 
+static int azx_suspend(struct device *dev)
+{
+	return azx_do_suspend(dev, PCI_D3hot);
+}
+
 static int azx_resume(struct device *dev)
 {
 	struct pci_dev *pci = to_pci_dev(dev);
@@ -2971,6 +2988,9 @@ 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;
+
 	azx_stop_chip(chip);
 	azx_enter_link_reset(chip);
 	azx_clear_irq_pending(chip);
@@ -2984,6 +3004,9 @@ 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_I915_POWERWELL)
 		hda_display_power(true);
 	azx_init_pci(chip);
@@ -2996,6 +3019,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,7 +3104,11 @@ 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) {
-			azx_suspend(&pci->dev);
+			azx_do_suspend(&pci->dev, PCI_D3cold);
+			/* 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",

sound/pci/hda/hda_intel.c static void azx_vs_set_state(struct pci_dev *pci, · line 3120

Quoted 4 lines
sound/pci/hda/hda_intel.c +1 -0
@@ -3087,6 +3117,7 @@ static void azx_vs_set_state(struct pci_dev *pci,
 			snd_hda_unlock_devices(chip->bus);
 			chip->disabled = false;
 			azx_resume(&pci->dev);
+			pm_runtime_suspend(&pci->dev);

Is this meant as pm_runtime_resume()?

thanks,

Takashi

Quoted 17 lines
sound/pci/hda/hda_intel.c +3 -0
 		}
 	}
 }
@@ -3139,6 +3170,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
Signature · 1 line
1.8.2.1
author maintainer
#7 lore raw

Re: [PATCH 4/4] snd/hda: add runtime suspend/resume on optimus support

Quoted 129 lines · Takashi Iwai

At Mon, 29 Jul 2013 16:06:59 +1000,

Quoted 127 lines · Dave Airlie

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.

---
 sound/pci/hda/hda_intel.c | 40 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 37 insertions(+), 3 deletions(-)
sound/pci/hda/hda_intel.c +33 -3
index 8860dd5..4b4d05b 100644
@@ -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
@@ -2898,7 +2901,7 @@ static int param_set_xint(const char *val, const struct kernel_param *kp)
 /*
  * power management
  */
-static int azx_suspend(struct device *dev)
+static int azx_do_suspend(struct device *dev, pci_power_t state)
 {
      struct pci_dev *pci = to_pci_dev(dev);
      struct snd_card *card = dev_get_drvdata(dev);
@@ -2920,16 +2923,30 @@ static int azx_suspend(struct device *dev)
              free_irq(chip->irq, chip);
              chip->irq = -1;
      }
+
+     /*
+      * for vga switcheroo suspend we need to
+      * force runtime suspend so lspci works.
+      */
+     if (state == PCI_D3cold)
+             pm_runtime_suspend(&pci->dev);
+
      if (chip->msi)
              pci_disable_msi(chip->pci);
      pci_disable_device(pci);
      pci_save_state(pci);
-     pci_set_power_state(pci, PCI_D3hot);
+
+     pci_set_power_state(pci, state);
      if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)
              hda_display_power(false);
      return 0;
 }
 
+static int azx_suspend(struct device *dev)
+{
+     return azx_do_suspend(dev, PCI_D3hot);
+}
+
 static int azx_resume(struct device *dev)
 {
      struct pci_dev *pci = to_pci_dev(dev);
@@ -2971,6 +2988,9 @@ 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;
+
      azx_stop_chip(chip);
      azx_enter_link_reset(chip);
      azx_clear_irq_pending(chip);
@@ -2984,6 +3004,9 @@ 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_I915_POWERWELL)
              hda_display_power(true);
      azx_init_pci(chip);
@@ -2996,6 +3019,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,7 +3104,11 @@ 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) {
-                     azx_suspend(&pci->dev);
+                     azx_do_suspend(&pci->dev, PCI_D3cold);
+                     /* 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))
Quoted 9 lines

sound/pci/hda/hda_intel.c static void azx_vs_set_state(struct pci_dev *pci, · line 3120

Quoted 6 lines
sound/pci/hda/hda_intel.c +1 -0
                              snd_printk(KERN_WARNING SFX "%s: Cannot lock devices!\n",
@@ -3087,6 +3117,7 @@ static void azx_vs_set_state(struct pci_dev *pci,
                      snd_hda_unlock_devices(chip->bus);
                      chip->disabled = false;
                      azx_resume(&pci->dev);
+                     pm_runtime_suspend(&pci->dev);

Is this meant as pm_runtime_resume()?

Oh probably that seems likely alright!

Dave.