diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 4efd2711acd9..0de1f76733d2 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1209,6 +1209,9 @@ static void snd_hda_codec_free(struct hda_codec *codec) kfree(codec); } +static bool snd_hda_codec_get_supported_ps(struct hda_codec *codec, + hda_nid_t fg, unsigned int power_state); + static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, unsigned int power_state); @@ -1317,6 +1320,12 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus, AC_VERB_GET_SUBSYSTEM_ID, 0); } + codec->d3_stop_clk = snd_hda_codec_get_supported_ps(codec, + codec->afg ? codec->afg : codec->mfg, + AC_PWRST_CLKSTOP); + if (!codec->d3_stop_clk) + bus->power_keep_link_on = 1; + /* power-up all before initialization */ hda_set_power_state(codec, codec->afg ? codec->afg : codec->mfg, @@ -3535,6 +3544,8 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, int count; unsigned int state; + codec->d3_stop_clk_ok = 0; + if (codec->patch_ops.set_power_state) { codec->patch_ops.set_power_state(codec, fg, power_state); return; @@ -3557,6 +3568,10 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, if (!(state & AC_PWRST_ERROR)) break; } + + if ((power_state == AC_PWRST_D3) + && codec->d3_stop_clk && (state & AC_PWRST_CLK_STOP_OK)) + codec->d3_stop_clk_ok = 1; } #ifdef CONFIG_SND_HDA_HWDEP @@ -4408,7 +4423,7 @@ static void hda_power_work(struct work_struct *work) hda_call_codec_suspend(codec); if (bus->ops.pm_notify) - bus->ops.pm_notify(bus); + bus->ops.pm_notify(bus, codec); } static void hda_keep_power_on(struct hda_codec *codec) @@ -4466,7 +4481,7 @@ static void __snd_hda_power_up(struct hda_codec *codec, bool wait_power_down) spin_unlock(&codec->power_lock); if (bus->ops.pm_notify) - bus->ops.pm_notify(bus); + bus->ops.pm_notify(bus, codec); hda_call_codec_resume(codec); spin_lock(&codec->power_lock); diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 92ecb2b2a05c..13c834f20440 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -616,7 +616,7 @@ struct hda_bus_ops { void (*bus_reset)(struct hda_bus *bus); #ifdef CONFIG_SND_HDA_POWER_SAVE /* notify power-up/down from codec to controller */ - void (*pm_notify)(struct hda_bus *bus); + void (*pm_notify)(struct hda_bus *bus, struct hda_codec *codec); #endif }; @@ -875,6 +875,9 @@ struct hda_codec { unsigned long power_off_acct; unsigned long power_jiffies; spinlock_t power_lock; + + unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */ + unsigned int d3_stop_clk_ok:1; /* BCLK can stop */ #endif /* codec-specific additional proc output */ diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 209bea435442..726f4208bd05 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -46,6 +46,7 @@ #include #include #include +#include #ifdef CONFIG_X86 /* for snoop control */ #include @@ -1032,7 +1033,7 @@ static unsigned int azx_get_response(struct hda_bus *bus, } #ifdef CONFIG_SND_HDA_POWER_SAVE -static void azx_power_notify(struct hda_bus *bus); +static void azx_power_notify(struct hda_bus *bus, struct hda_codec *codec); #endif /* reset codec link */ @@ -1288,6 +1289,11 @@ 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) { @@ -2400,23 +2406,17 @@ static void azx_stop_chip(struct azx *chip) #ifdef CONFIG_SND_HDA_POWER_SAVE /* power-up/down the controller */ -static void azx_power_notify(struct hda_bus *bus) +static void azx_power_notify(struct hda_bus *bus, struct hda_codec *codec) { struct azx *chip = bus->private_data; - struct hda_codec *c; - int power_on = 0; - list_for_each_entry(c, &bus->codec_list, list) { - if (c->power_on) { - power_on = 1; - break; - } - } - if (power_on) - azx_init_chip(chip, 1); - else if (chip->running && power_save_controller && - !bus->power_keep_link_on) - azx_stop_chip(chip); + if (bus->power_keep_link_on || !codec->d3_stop_clk_ok) + return; + + if (codec->power_on) + pm_runtime_get_sync(&chip->pci->dev); + else + pm_runtime_put_sync(&chip->pci->dev); } static DEFINE_MUTEX(card_list_lock); @@ -2520,11 +2520,43 @@ static int azx_resume(struct device *dev) snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; } -static SIMPLE_DEV_PM_OPS(azx_pm, azx_suspend, azx_resume); +#endif /* CONFIG_PM_SLEEP || SUPPORT_VGA_SWITCHEROO */ + +#ifdef CONFIG_PM_RUNTIME +static int azx_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + if (!power_save_controller) + return -EAGAIN; + + azx_stop_chip(chip); + azx_clear_irq_pending(chip); + return 0; +} + +static int azx_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + azx_init_pci(chip); + azx_init_chip(chip, 1); + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops azx_pm = { + SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) + SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, NULL) +}; + #define AZX_PM_OPS &azx_pm #else #define AZX_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP || SUPPORT_VGA_SWITCHEROO */ +#endif /* CONFIG_PM */ /* @@ -3239,6 +3271,15 @@ static void azx_firmware_cb(const struct firmware *fw, void *context) } #endif +static void rpm_get_all_codecs(struct azx *chip) +{ + struct hda_codec *codec; + + list_for_each_entry(codec, &chip->bus->codec_list, list) { + pm_runtime_get_noresume(&chip->pci->dev); + } +} + static int __devinit azx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { @@ -3290,6 +3331,9 @@ static int __devinit azx_probe(struct pci_dev *pci, pci_set_drvdata(pci, card); + if (pci_dev_run_wake(pci)) + pm_runtime_put_noidle(&pci->dev); + dev++; return 0; @@ -3342,6 +3386,7 @@ static int DELAYED_INIT_MARK azx_probe_continue(struct azx *chip) goto out_free; chip->running = 1; + rpm_get_all_codecs(chip); /* all codecs are active */ power_down_all_codecs(chip); azx_notifier_register(chip); azx_add_card_list(chip); @@ -3356,6 +3401,10 @@ out_free: static void __devexit azx_remove(struct pci_dev *pci) { struct snd_card *card = pci_get_drvdata(pci); + + if (pci_dev_run_wake(pci)) + pm_runtime_get_noresume(&pci->dev); + if (card) snd_card_free(card); pci_set_drvdata(pci, NULL);