[PATCH v3 3/3] ALSA: hda: allow direct complete with a powered-off GPU

From: Andre Eikmeyer

Date: Wed Aug 12 2026 - 10:54:43 EST


The HDA controller normally needs different WAKEEN settings for runtime and
system suspend, so azx_prepare() prevents direct complete. For a switcheroo
audio client whose bound GPU is already runtime suspended and switched off,
however, the controller is physically inaccessible and cannot signal wake
events.

Resuming the HDA function during system suspend also acquires its runtime
PM supplier. This powers the discrete GPU back on only to suspend it again
and can expose failures while restoring an otherwise unused GPU.

Direct complete is allowed when the bound VGA device is both runtime
suspended and reported off by vga_switcheroo. Active GPUs and HDA
controllers without a switcheroo binding retain the existing WAKEEN
transition.

This was tested as part of the MacBookPro15,1 hybrid graphics series on
both the 2018 and 2019 revisions. The discrete GPU remains powered off
across system suspend when it is unused, and suspend and resume complete
successfully.

Reviewed-by: Takashi Iwai <tiwai@xxxxxxx>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
sound/hda/controllers/intel.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)

diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c
index 28c55c5..194d962 100644
--- a/sound/hda/controllers/intel.c
+++ b/sound/hda/controllers/intel.c
@@ -334,9 +334,11 @@ enum {
#ifdef SUPPORT_VGA_SWITCHEROO
#define use_vga_switcheroo(chip) ((chip)->use_vga_switcheroo)
#define needs_eld_notify_link(chip) ((chip)->bus.keep_power)
+static bool azx_vga_is_powered_off(struct azx *chip);
#else
#define use_vga_switcheroo(chip) 0
#define needs_eld_notify_link(chip) false
+#define azx_vga_is_powered_off(chip) false
#endif

static const char * const driver_short_names[] = {
@@ -1025,6 +1027,10 @@ static int azx_prepare(struct device *dev)
return 0;

chip = card->private_data;
+ /* A positive return allows the PM core to use direct complete. */
+ if (azx_vga_is_powered_off(chip))
+ return 1;
+
chip->pm_prepared = 1;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);

@@ -1185,6 +1191,24 @@ static int azx_probe_continue(struct azx *chip);
#ifdef SUPPORT_VGA_SWITCHEROO
static struct pci_dev *get_bound_vga(struct pci_dev *pci);

+static bool azx_vga_is_powered_off(struct azx *chip)
+{
+ struct pci_dev *pci;
+ bool powered_off = false;
+
+ if (!use_vga_switcheroo(container_of(chip, struct hda_intel, chip)))
+ return false;
+
+ pci = get_bound_vga(chip->pci);
+ if (pci) {
+ powered_off = pm_runtime_suspended(&pci->dev) &&
+ vga_switcheroo_get_client_state(pci) == VGA_SWITCHEROO_OFF;
+ pci_dev_put(pci);
+ }
+
+ return powered_off;
+}
+
static void azx_vs_set_state(struct pci_dev *pci,
enum vga_switcheroo_state state)
{
--
2.55.0