Skip to contentSkip to conversation

optimus dynamic power patches

PATCH In review Dave Airlie 2 participants 7 messages Aug 5, 2013 – Aug 19, 2013

Subsystems (7)
lore
Share & tools
lore

Dave Airlie

author maintainer
#0 lore raw

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.

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

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

---
 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
author maintainer
#2 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
#3 lore raw

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

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.

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

---
 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(-)
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 +6 -0
index 1c0330b..2e7c5fd 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,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;
 }
 
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 +226 -24

446 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
drivers/gpu/drm/nouveau/nv50_display.c +1 -1
index 8b40a36..9d2092a 100644
@@ -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,
 };
Signature · 1 line
1.8.2.1
author maintainer
#4 lore raw

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

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.

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.

---
 sound/pci/hda/hda_intel.c | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)
sound/pci/hda/hda_intel.c +29 -7
index 8860dd5..4c4f546 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
@@ -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;
Signature · 1 line
1.8.2.1
#5 lore raw

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

At Mon, 5 Aug 2013 12:56:04 +1000,

Quoted 53 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.

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.

---
 sound/pci/hda/hda_intel.c | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)
sound/pci/hda/hda_intel.c +3 -0
index 8860dd5..4c4f546 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

sound/pci/hda/hda_intel.c static irqreturn_t azx_interrupt(int irq, void *dev_id) · line 1403

Quoted 8 lines
sound/pci/hda/hda_intel.c +0 -5
@@ -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.

Quoted 13 lines
sound/pci/hda/hda_intel.c +1 -1
 	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;
 	}

sound/pci/hda/hda_intel.c static int azx_runtime_suspend(struct device *dev) · line 2976

Quoted 8 lines
sound/pci/hda/hda_intel.c +5 -0
@@ -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.

Quoted 27 lines
sound/pci/hda/hda_intel.c +9 -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;

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

Quoted 5 lines
sound/pci/hda/hda_intel.c +1 -0
@@ -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.

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

Quoted 12 lines
sound/pci/hda/hda_intel.c +5 -0
+			/* 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.

Quoted 13 lines
sound/pci/hda/hda_intel.c +3 -0
 			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

sound/pci/hda/hda_intel.c static int azx_probe_continue(struct azx *chip) · line 3913

Quoted 6 lines
sound/pci/hda/hda_intel.c +1 -1
@@ -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

author maintainer
#6 lore raw

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

Quoted 58 lines · Takashi Iwai

At Mon, 5 Aug 2013 12:56:04 +1000,

Quoted 56 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.

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.

---
 sound/pci/hda/hda_intel.c | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)
sound/pci/hda/hda_intel.c +3 -0
index 8860dd5..4c4f546 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
@@ -1396,11 +1399,6 @@ static irqreturn_t azx_interrupt(int irq, void *dev_id)
      u8 sd_status;
      int i, ok;
 
Quoted 8 lines

sound/pci/hda/hda_intel.c static irqreturn_t azx_interrupt(int irq, void *dev_id) · line 1403

Quoted 5 lines
sound/pci/hda/hda_intel.c +0 -5
-#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.

Quoted 17 lines
Quoted 17 lines
sound/pci/hda/hda_intel.c +1 -1
      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;
 
Quoted 9 lines

sound/pci/hda/hda_intel.c static int azx_runtime_suspend(struct device *dev) · line 2976

Quoted 5 lines
sound/pci/hda/hda_intel.c +5 -0
+     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.

Quoted 21 lines
Quoted 21 lines
sound/pci/hda/hda_intel.c +6 -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;
 
Quoted 16 lines

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

Quoted 12 lines
sound/pci/hda/hda_intel.c +4 -0
+     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.

Quoted 14 lines

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

Quoted 13 lines
sound/pci/hda/hda_intel.c +5 -0
+                     /* 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;
Quoted 2 lines

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.

Quoted 17 lines
Quoted 17 lines
sound/pci/hda/hda_intel.c +3 -0
                      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);
Quoted 8 lines

sound/pci/hda/hda_intel.c static int azx_probe_continue(struct azx *chip) · line 3913

Quoted 3 lines
sound/pci/hda/hda_intel.c +1 -1
-     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 :-)

Quoted 8 lines

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.