From b21bdd0d343c8b2496690283211e27c9af598ed3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 18 Nov 2013 12:03:56 +0100 Subject: [PATCH 1/6] ALSA: hda - Split the generic parser as an individual module Drop the hard dependency on the generic parser code and load / unload the generic parser code dynamically if built as a module. This allows us to avoid the generic parser if only HDMI/DP codecs are found. Signed-off-by: Takashi Iwai --- sound/pci/hda/Makefile | 5 ++- sound/pci/hda/hda_codec.c | 67 +++++++++++++++++++++++++++---------- sound/pci/hda/hda_codec.h | 1 + sound/pci/hda/hda_generic.c | 4 +++ sound/pci/hda/hda_local.h | 7 ---- 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index c091438286a3..5a40c652df41 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -3,7 +3,6 @@ snd-hda-intel-objs := hda_intel.o snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o snd-hda-codec-y := hda_codec.o hda_jack.o hda_auto_parser.o -snd-hda-codec-$(CONFIG_SND_HDA_GENERIC) += hda_generic.o snd-hda-codec-$(CONFIG_PROC_FS) += hda_proc.o snd-hda-codec-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o @@ -12,6 +11,7 @@ snd-hda-codec-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o CFLAGS_hda_codec.o := -I$(src) CFLAGS_hda_intel.o := -I$(src) +snd-hda-codec-generic-objs := hda_generic.o snd-hda-codec-realtek-objs := patch_realtek.o snd-hda-codec-cmedia-objs := patch_cmedia.o snd-hda-codec-analog-objs := patch_analog.o @@ -28,6 +28,9 @@ snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o obj-$(CONFIG_SND_HDA_INTEL) := snd-hda-codec.o # codec drivers (note: CONFIG_SND_HDA_CODEC_XXX are booleans) +ifdef CONFIG_SND_HDA_GENERIC +obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-generic.o +endif ifdef CONFIG_SND_HDA_CODEC_REALTEK obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-codec-realtek.o endif diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 69178c4f4113..4d3f46329eb5 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -945,9 +945,6 @@ find_codec_preset(struct hda_codec *codec) const struct hda_codec_preset *preset; unsigned int mod_requested = 0; - if (is_generic_config(codec)) - return NULL; /* use the generic parser */ - again: mutex_lock(&preset_mutex); list_for_each_entry(tbl, &hda_preset_tables, list) { @@ -1329,6 +1326,28 @@ get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid) return p; } +/* + * Dynamic symbol binding for the codec parsers + */ +#ifdef MODULE +#define load_parser_sym(sym) ((int (*)(struct hda_codec *))symbol_request(sym)) +#define unload_parser_addr(addr) symbol_put_addr(addr) +#else +#define load_parser_sym(sym) (sym) +#define unload_parser_addr(addr) do {} while (0) +#endif + +#define load_parser(codec, sym) \ + ((codec)->parser = load_parser_sym(sym)) + +static void unload_parser(struct hda_codec *codec) +{ + if (codec->parser) { + unload_parser_addr(codec->parser); + codec->parser = NULL; + } +} + /* * codec destructor */ @@ -1356,6 +1375,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) if (!codec->pm_down_notified) /* cancel leftover refcounts */ hda_call_pm_notify(codec->bus, false); #endif + unload_parser(codec); module_put(codec->owner); free_hda_cache(&codec->amp_cache); free_hda_cache(&codec->cmd_cache); @@ -1548,6 +1568,7 @@ EXPORT_SYMBOL_HDA(snd_hda_codec_update_widgets); */ int snd_hda_codec_configure(struct hda_codec *codec) { + int (*patch)(struct hda_codec *) = NULL; int err; codec->preset = find_codec_preset(codec); @@ -1557,29 +1578,38 @@ int snd_hda_codec_configure(struct hda_codec *codec) return err; } - if (is_generic_config(codec)) { - err = snd_hda_parse_generic_codec(codec); - goto patched; - } - if (codec->preset && codec->preset->patch) { - err = codec->preset->patch(codec); - goto patched; + if (!is_generic_config(codec) && codec->preset) + patch = codec->preset->patch; + if (!patch) { + unload_parser(codec); /* to be sure */ +#ifdef CONFIG_SND_HDA_GENERIC + if (!patch) + patch = load_parser(codec, snd_hda_parse_generic_codec); +#endif + if (!patch) { + printk(KERN_ERR "hda-codec: No codec parser is available\n"); + return -ENODEV; + } } - /* call the default parser */ - err = snd_hda_parse_generic_codec(codec); - if (err < 0) - printk(KERN_ERR "hda-codec: No codec parser is available\n"); + err = patch(codec); + if (err < 0) { + unload_parser(codec); + return err; + } - patched: - if (!err && codec->patch_ops.unsol_event) + if (codec->patch_ops.unsol_event) { err = init_unsol_queue(codec->bus); + if (err < 0) + return err; + } + /* audio codec should override the mixer name */ - if (!err && (codec->afg || !*codec->bus->card->mixername)) + if (codec->afg || !*codec->bus->card->mixername) snprintf(codec->bus->card->mixername, sizeof(codec->bus->card->mixername), "%s %s", codec->vendor_name, codec->chip_name); - return err; + return 0; } EXPORT_SYMBOL_HDA(snd_hda_codec_configure); @@ -2610,6 +2640,7 @@ int snd_hda_codec_reset(struct hda_codec *codec) codec->preset = NULL; codec->slave_dig_outs = NULL; codec->spdif_status_reset = 0; + unload_parser(codec); module_put(codec->owner); codec->owner = NULL; diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index 7aa9870040c1..14e4b5a9c43c 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -834,6 +834,7 @@ struct hda_codec { /* detected preset */ const struct hda_codec_preset *preset; struct module *owner; + int (*parser)(struct hda_codec *codec); const char *vendor_name; /* codec vendor name */ const char *chip_name; /* codec chip name */ const char *modelname; /* model name for preset */ diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 3067ed4fe3b2..9b251456f2a0 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include "hda_codec.h" @@ -5291,3 +5292,6 @@ error: return err; } EXPORT_SYMBOL_HDA(snd_hda_parse_generic_codec); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Generic HD-audio codec parser"); diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index d398b648bb5d..4d571a6d9230 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -352,14 +352,7 @@ int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, /* * generic codec parser */ -#ifdef CONFIG_SND_HDA_GENERIC int snd_hda_parse_generic_codec(struct hda_codec *codec); -#else -static inline int snd_hda_parse_generic_codec(struct hda_codec *codec) -{ - return -ENODEV; -} -#endif /* * generic proc interface From f0639272d6c8e4706ea6d1b0dbc6eb83eaaff412 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 18 Nov 2013 12:07:29 +0100 Subject: [PATCH 2/6] ALSA: hda - Bind with HDMI codec parser automatically If a codec contains only the digital outputs, it's very likely a HDMI/DP codec, which isn't supported by the generic parser but via HDMI codec parser code. Detect such a case and bind with the proper parser object if available. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 27 +++++++++++++++++++++++++++ sound/pci/hda/hda_local.h | 1 + sound/pci/hda/patch_hdmi.c | 9 +++++++++ 3 files changed, 37 insertions(+) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 4d3f46329eb5..bada677df8a7 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1557,6 +1557,31 @@ int snd_hda_codec_update_widgets(struct hda_codec *codec) EXPORT_SYMBOL_HDA(snd_hda_codec_update_widgets); +#ifdef CONFIG_SND_HDA_CODEC_HDMI +/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */ +static bool is_likely_hdmi_codec(struct hda_codec *codec) +{ + hda_nid_t nid = codec->start_nid; + int i; + + for (i = 0; i < codec->num_nodes; i++, nid++) { + unsigned int wcaps = get_wcaps(codec, nid); + switch (get_wcaps_type(wcaps)) { + case AC_WID_AUD_IN: + return false; /* HDMI parser supports only HDMI out */ + case AC_WID_AUD_OUT: + if (!(wcaps & AC_WCAP_DIGITAL)) + return false; + break; + } + } + return true; +} +#else +/* no HDMI codec parser support */ +#define is_likely_hdmi_codec(codec) false +#endif /* CONFIG_SND_HDA_CODEC_HDMI */ + /** * snd_hda_codec_configure - (Re-)configure the HD-audio codec * @codec: the HDA codec @@ -1582,6 +1607,8 @@ int snd_hda_codec_configure(struct hda_codec *codec) patch = codec->preset->patch; if (!patch) { unload_parser(codec); /* to be sure */ + if (is_likely_hdmi_codec(codec)) + patch = load_parser(codec, snd_hda_parse_hdmi_codec); #ifdef CONFIG_SND_HDA_GENERIC if (!patch) patch = load_parser(codec, snd_hda_parse_generic_codec); diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 4d571a6d9230..da80c5bd7fd4 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -353,6 +353,7 @@ int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, * generic codec parser */ int snd_hda_parse_generic_codec(struct hda_codec *codec); +int snd_hda_parse_hdmi_codec(struct hda_codec *codec); /* * generic proc interface diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 08407bed093e..a6081ed34b88 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -3213,6 +3213,15 @@ static int patch_via_hdmi(struct hda_codec *codec) return patch_simple_hdmi(codec, VIAHDMI_CVT_NID, VIAHDMI_PIN_NID); } +/* + * called from hda_codec.c for generic HDMI support + */ +int snd_hda_parse_hdmi_codec(struct hda_codec *codec) +{ + return patch_generic_hdmi(codec); +} +EXPORT_SYMBOL_HDA(snd_hda_parse_hdmi_codec); + /* * patch entries */ From a40e0a880b34d95f583552be7fd3905b6b4e6891 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 20 Nov 2013 12:41:20 +0100 Subject: [PATCH 3/6] ALSA: hda - Refactor pm notification in hda_codec.c No functional change. - codec->pm_down_notified flag is renamed to codec->pm_up_notified flag (and takes the reversed meaning), which is managed solely in hda_call_pm_notify() now. - The explicit call of hda_call_pm_notify() is moved into hda_keep_power_on(). - Removed a redundant call of hda_call_pm_notify() in snd_hda_power_up(), as it's called in hda_call_codec_resume() anyway. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 37 ++++++++++++++----------------------- sound/pci/hda/hda_codec.h | 2 +- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index bada677df8a7..8f7295bdccbd 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -99,16 +99,24 @@ EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset); static void hda_power_work(struct work_struct *work); static void hda_keep_power_on(struct hda_codec *codec); #define hda_codec_is_power_on(codec) ((codec)->power_on) -static inline void hda_call_pm_notify(struct hda_bus *bus, bool power_up) + +static void hda_call_pm_notify(struct hda_codec *codec, bool power_up) { + struct hda_bus *bus = codec->bus; + + if ((power_up && codec->pm_up_notified) || + (!power_up && !codec->pm_up_notified)) + return; if (bus->ops.pm_notify) bus->ops.pm_notify(bus, power_up); + codec->pm_up_notified = power_up; } + #else #define codec_in_pm(codec) 0 static inline void hda_keep_power_on(struct hda_codec *codec) {} #define hda_codec_is_power_on(codec) 1 -#define hda_call_pm_notify(bus, state) {} +#define hda_call_pm_notify(codec, state) {} #endif /** @@ -1371,10 +1379,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) codec->bus->caddr_tbl[codec->addr] = NULL; if (codec->patch_ops.free) codec->patch_ops.free(codec); -#ifdef CONFIG_PM - if (!codec->pm_down_notified) /* cancel leftover refcounts */ - hda_call_pm_notify(codec->bus, false); -#endif + hda_call_pm_notify(codec, false); /* cancel leftover refcounts */ unload_parser(codec); module_put(codec->owner); free_hda_cache(&codec->amp_cache); @@ -1453,7 +1458,6 @@ int snd_hda_codec_new(struct hda_bus *bus, * phase. */ hda_keep_power_on(codec); - hda_call_pm_notify(bus, true); #endif if (codec->bus->modelname) { @@ -4058,10 +4062,6 @@ static void hda_call_codec_resume(struct hda_codec *codec) * in the resume / power-save sequence */ hda_keep_power_on(codec); - if (codec->pm_down_notified) { - codec->pm_down_notified = 0; - hda_call_pm_notify(codec->bus, true); - } hda_set_power_state(codec, AC_PWRST_D0); restore_shutup_pins(codec); hda_exec_init_verbs(codec); @@ -4935,11 +4935,8 @@ static void hda_power_work(struct work_struct *work) spin_unlock(&codec->power_lock); state = hda_call_codec_suspend(codec, true); - if (!codec->pm_down_notified && - !bus->power_keep_link_on && (state & AC_PWRST_CLK_STOP_OK)) { - codec->pm_down_notified = 1; - hda_call_pm_notify(bus, false); - } + if (!bus->power_keep_link_on && (state & AC_PWRST_CLK_STOP_OK)) + hda_call_pm_notify(codec, false); } static void hda_keep_power_on(struct hda_codec *codec) @@ -4949,6 +4946,7 @@ static void hda_keep_power_on(struct hda_codec *codec) codec->power_on = 1; codec->power_jiffies = jiffies; spin_unlock(&codec->power_lock); + hda_call_pm_notify(codec, true); } /* update the power on/off account with the current jiffies */ @@ -4968,8 +4966,6 @@ void snd_hda_update_power_acct(struct hda_codec *codec) /* call this with codec->power_lock held! */ static void __snd_hda_power_up(struct hda_codec *codec, bool wait_power_down) { - struct hda_bus *bus = codec->bus; - /* Return if power_on or transitioning to power_on, unless currently * powering down. */ if ((codec->power_on || codec->power_transition > 0) && @@ -4996,11 +4992,6 @@ static void __snd_hda_power_up(struct hda_codec *codec, bool wait_power_down) codec->power_transition = 1; /* avoid reentrance */ spin_unlock(&codec->power_lock); - if (codec->pm_down_notified) { - codec->pm_down_notified = 0; - hda_call_pm_notify(bus, true); - } - 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 14e4b5a9c43c..a3e5f45d74c1 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -908,7 +908,7 @@ struct hda_codec { #ifdef CONFIG_PM unsigned int power_on :1; /* current (global) power-state */ unsigned int d3_stop_clk:1; /* support D3 operation without BCLK */ - unsigned int pm_down_notified:1; /* PM notified to controller */ + unsigned int pm_up_notified:1; /* PM notified to controller */ unsigned int in_pm:1; /* suspend/resume being performed */ int power_transition; /* power-state in transition */ int power_count; /* current (global) power refcount */ From b1920c21102af1d22f27ce36378aa48d97a62bc0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 22 Nov 2013 12:43:25 +0100 Subject: [PATCH 4/6] ALSA: hda - Enable runtime PM on Panther Point Now we fixed the long-standing bugs of runtime PM, let's enable Panther Point again. The runtime PM was disabled in the HDMI codec driver due to the S3 issue, and this should have been fixed now. Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 2 +- sound/pci/hda/patch_hdmi.c | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index c6d230193da6..dfdb96603636 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -3978,7 +3978,7 @@ static DEFINE_PCI_DEVICE_TABLE(azx_ids) = { .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM }, /* Panther Point */ { PCI_DEVICE(0x8086, 0x1e20), - .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH_NOPM }, + .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, /* Lynx Point */ { PCI_DEVICE(0x8086, 0x8c20), .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index a6081ed34b88..1773a640e72e 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -1693,16 +1693,12 @@ static int hdmi_parse_codec(struct hda_codec *codec) } #ifdef CONFIG_PM - /* We're seeing some problems with unsolicited hot plug events on - * PantherPoint after S3, if this is not enabled */ - if (codec->vendor_id == 0x80862806) - codec->bus->power_keep_link_on = 1; /* * G45/IbexPeak don't support EPSS: the unsolicited pin hot plug event * can be lost and presence sense verb will become inaccurate if the * HDA link is powered off at hot plug or hw initialization time. */ - else if (!(snd_hda_param_read(codec, codec->afg, AC_PAR_POWER_STATE) & + if (!(snd_hda_param_read(codec, codec->afg, AC_PAR_POWER_STATE) & AC_PWRST_EPSS)) codec->bus->power_keep_link_on = 1; #endif From 3e9bc58fef3b7a1ee95b36c26671198a985293c7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 26 Nov 2013 09:58:46 +0100 Subject: [PATCH 5/6] ALSA: hda - Disable runtime PM when EPSS is unavailable According to Mengdong, we shouldn't enable runtime PM when a codec doesn't support EPSS, based on the experiences on Windows. We have already this check in HDMI codec drivers, but now apply it in general in hda_codec.c. Credit goes to Mengdong Lin Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 7 +++++-- sound/pci/hda/patch_hdmi.c | 11 ----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 8f7295bdccbd..6deb8d1852cc 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1510,11 +1510,14 @@ int snd_hda_codec_new(struct hda_bus *bus, #ifdef CONFIG_PM codec->d3_stop_clk = snd_hda_codec_get_supported_ps(codec, fg, AC_PWRST_CLKSTOP); - if (!codec->d3_stop_clk) - bus->power_keep_link_on = 1; #endif codec->epss = snd_hda_codec_get_supported_ps(codec, fg, AC_PWRST_EPSS); +#ifdef CONFIG_PM + if (!codec->d3_stop_clk || !codec->epss) + bus->power_keep_link_on = 1; +#endif + /* power-up all before initialization */ hda_set_power_state(codec, AC_PWRST_D0); diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 1773a640e72e..4098196b2300 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -1692,17 +1692,6 @@ static int hdmi_parse_codec(struct hda_codec *codec) } } -#ifdef CONFIG_PM - /* - * G45/IbexPeak don't support EPSS: the unsolicited pin hot plug event - * can be lost and presence sense verb will become inaccurate if the - * HDA link is powered off at hot plug or hw initialization time. - */ - if (!(snd_hda_param_read(codec, codec->afg, AC_PAR_POWER_STATE) & - AC_PWRST_EPSS)) - codec->bus->power_keep_link_on = 1; -#endif - return 0; } From 0e24dbb7f44877a3565ee461d2209b0942b1a753 Mon Sep 17 00:00:00 2001 From: Mengdong Lin Date: Tue, 26 Nov 2013 23:00:51 -0500 Subject: [PATCH 6/6] ALSA: hda - suspend codecs in parallel The time to suspend a single codec may be several hundreds of ms. When runtime power saving is disabled, driver suspend time can be long especially when there are more than one codec on the bus. To reduce driver suspend time, this patch creates a work queue for the bus, and suspends the codecs in parallel if there are multiple codecs on the bus. [fixed cosmetic issues by tiwai] Signed-off-by: Mengdong Lin Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_codec.c | 47 +++++++++++++++++++++++++++++++++++++-- sound/pci/hda/hda_codec.h | 5 +++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 6deb8d1852cc..85886220ee74 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -96,6 +96,7 @@ EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset); #ifdef CONFIG_PM #define codec_in_pm(codec) ((codec)->in_pm) +static void hda_pm_work(struct work_struct *work); static void hda_power_work(struct work_struct *work); static void hda_keep_power_on(struct hda_codec *codec); #define hda_codec_is_power_on(codec) ((codec)->power_on) @@ -839,6 +840,12 @@ static int snd_hda_bus_free(struct hda_bus *bus) bus->ops.private_free(bus); if (bus->workq) destroy_workqueue(bus->workq); + +#ifdef CONFIG_PM + if (bus->pm_wq) + destroy_workqueue(bus->pm_wq); +#endif + kfree(bus); return 0; } @@ -883,6 +890,9 @@ int snd_hda_bus_new(struct snd_card *card, .dev_register = snd_hda_bus_dev_register, .dev_free = snd_hda_bus_dev_free, }; +#ifdef CONFIG_PM + char wqname[16]; +#endif if (snd_BUG_ON(!temp)) return -EINVAL; @@ -919,6 +929,16 @@ int snd_hda_bus_new(struct snd_card *card, return -ENOMEM; } +#ifdef CONFIG_PM + sprintf(wqname, "hda-pm-wq-%d", card->number); + bus->pm_wq = create_workqueue(wqname); + if (!bus->pm_wq) { + snd_printk(KERN_ERR "cannot create PM workqueue\n"); + snd_hda_bus_free(bus); + return -ENOMEM; + } +#endif + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops); if (err < 0) { snd_hda_bus_free(bus); @@ -1388,6 +1408,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) kfree(codec->chip_name); kfree(codec->modelname); kfree(codec->wcaps); + codec->bus->num_codecs--; kfree(codec); } @@ -1453,6 +1474,7 @@ int snd_hda_codec_new(struct hda_bus *bus, #ifdef CONFIG_PM spin_lock_init(&codec->power_lock); INIT_DELAYED_WORK(&codec->power_work, hda_power_work); + INIT_WORK(&codec->pm_work, hda_pm_work); /* snd_hda_codec_new() marks the codec as power-up, and leave it as is. * the caller has to power down appropriatley after initialization * phase. @@ -1469,6 +1491,11 @@ int snd_hda_codec_new(struct hda_bus *bus, } list_add_tail(&codec->list, &bus->codec_list); + bus->num_codecs++; +#ifdef CONFIG_PM + workqueue_set_max_active(bus->pm_wq, bus->num_codecs); +#endif + bus->caddr_tbl[codec_addr] = codec; codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT, @@ -5088,6 +5115,14 @@ int snd_hda_check_amp_list_power(struct hda_codec *codec, return 0; } EXPORT_SYMBOL_HDA(snd_hda_check_amp_list_power); + +static void hda_pm_work(struct work_struct *work) +{ + struct hda_codec *codec = + container_of(work, struct hda_codec, pm_work); + + hda_call_codec_suspend(codec, false); +} #endif /* @@ -5663,9 +5698,17 @@ int snd_hda_suspend(struct hda_bus *bus) list_for_each_entry(codec, &bus->codec_list, list) { cancel_delayed_work_sync(&codec->jackpoll_work); - if (hda_codec_is_power_on(codec)) - hda_call_codec_suspend(codec, false); + if (hda_codec_is_power_on(codec)) { + if (bus->num_codecs > 1) + queue_work(bus->pm_wq, &codec->pm_work); + else + hda_call_codec_suspend(codec, false); + } } + + if (bus->num_codecs > 1) + flush_workqueue(bus->pm_wq); + return 0; } EXPORT_SYMBOL_HDA(snd_hda_suspend); diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index a3e5f45d74c1..9a2798a0313c 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -673,6 +673,7 @@ struct hda_bus { /* codec linked list */ struct list_head codec_list; + unsigned int num_codecs; /* link caddr -> codec */ struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1]; @@ -683,6 +684,9 @@ struct hda_bus { struct hda_bus_unsolicited *unsol; char workq_name[16]; struct workqueue_struct *workq; /* common workqueue for codecs */ +#ifdef CONFIG_PM + struct workqueue_struct *pm_wq; /* workqueue to parallel codec PM */ +#endif /* assigned PCMs */ DECLARE_BITMAP(pcm_dev_bits, SNDRV_PCM_DEVICES); @@ -917,6 +921,7 @@ struct hda_codec { unsigned long power_off_acct; unsigned long power_jiffies; spinlock_t power_lock; + struct work_struct pm_work; /* task to parallel multi-codec PM */ #endif /* filter the requested power state per nid */