[alsa-devel] [PATCH 00/13] A few more fixes / enhancements for HD-audio
Not surprisingly, there are yet a few more patches for HD-audio. All patches are found in for-next branch of sound git tree as usual.
Takashi
The fixup function is called multiple times before parsing the pins, so snd_BUG_ON() hits when loaded. Move it to the proper place in the if block.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/patch_realtek.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 604fe5e..515f83b 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -2729,12 +2729,13 @@ static void alc271_hp_gate_mic_jack(struct hda_codec *codec, { struct alc_spec *spec = codec->spec;
- if (snd_BUG_ON(!spec->gen.am_entry[1].pin || - !spec->gen.autocfg.hp_pins[0])) - return; - if (action == HDA_FIXUP_ACT_PROBE) + if (action == HDA_FIXUP_ACT_PROBE) { + if (snd_BUG_ON(!spec->gen.am_entry[1].pin || + !spec->gen.autocfg.hp_pins[0])) + return; snd_hda_jack_set_gating_jack(codec, spec->gen.am_entry[1].pin, spec->gen.autocfg.hp_pins[0]); + } }
enum {
AD1988 family and AD1882 codecs have another mixer widget (0x21) between the analog-loopback mixer widget (0x20) and the actual outputs. Due to this hole, the analog-loopbacks aren't sent properly to the output pins.
As a band-aid fix, introduce another fields holding the aamix merge path, and activate it.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_generic.c | 23 ++++++++++++++++++++++- sound/pci/hda/hda_generic.h | 2 ++ sound/pci/hda/patch_analog.c | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 758dcc1..06e203d 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -688,7 +688,7 @@ static void activate_amp_in(struct hda_codec *codec, struct nid_path *path, * when aa-mixer is available, we need to enable the path as well */ for (n = 0; n < nums; n++) { - if (n != idx && (!add_aamix || conn[n] != spec->mixer_nid)) + if (n != idx && (!add_aamix || conn[n] != spec->mixer_merge_nid)) continue; activate_amp(codec, nid, HDA_INPUT, n, idx, enable); } @@ -2492,6 +2492,19 @@ static int new_analog_input(struct hda_codec *codec, int input_idx,
path->active = true; add_loopback_list(spec, mix_nid, idx); + + if (spec->mixer_nid != spec->mixer_merge_nid && + !spec->loopback_merge_path) { + path = snd_hda_add_new_path(codec, spec->mixer_nid, + spec->mixer_merge_nid, 0); + if (path) { + print_nid_path("loopback-merge", path); + path->active = true; + spec->loopback_merge_path = + snd_hda_get_path_idx(codec, path); + } + } + return 0; }
@@ -3847,6 +3860,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
parse_user_hints(codec);
+ if (spec->mixer_nid && !spec->mixer_merge_nid) + spec->mixer_merge_nid = spec->mixer_nid; + if (cfg != &spec->autocfg) { spec->autocfg = *cfg; cfg = &spec->autocfg; @@ -4673,6 +4689,11 @@ static void init_analog_input(struct hda_codec *codec) if (path) snd_hda_activate_path(codec, path, path->active, false); + path = snd_hda_get_path_from_idx(codec, + spec->loopback_merge_path); + if (path) + snd_hda_activate_path(codec, path, path->active, + false); } } } diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h index 980707f..d226856 100644 --- a/sound/pci/hda/hda_generic.h +++ b/sound/pci/hda/hda_generic.h @@ -107,6 +107,7 @@ struct hda_gen_spec { hda_nid_t adc_nids[AUTO_CFG_MAX_INS]; hda_nid_t dig_in_nid; /* digital-in NID; optional */ hda_nid_t mixer_nid; /* analog-mixer NID */ + hda_nid_t mixer_merge_nid; /* aamix merge-point NID (optional) */ const char *input_labels[HDA_MAX_NUM_INPUTS]; int input_label_idxs[HDA_MAX_NUM_INPUTS];
@@ -163,6 +164,7 @@ struct hda_gen_spec { int digout_paths[AUTO_CFG_MAX_OUTS]; int input_paths[HDA_MAX_NUM_INPUTS][AUTO_CFG_MAX_INS]; int loopback_paths[HDA_MAX_NUM_INPUTS]; + int loopback_merge_path; int digin_path;
/* auto-mic stuff */ diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index 9d82aab..df8014b 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -3235,6 +3235,7 @@ static int ad1988_parse_auto_config(struct hda_codec *codec) spec = codec->spec;
spec->gen.mixer_nid = 0x20; + spec->gen.mixer_merge_nid = 0x21; spec->beep_dev_nid = 0x10; set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); err = ad198x_parse_auto_config(codec); @@ -5153,6 +5154,7 @@ static int ad1882_parse_auto_config(struct hda_codec *codec) spec = codec->spec;
spec->gen.mixer_nid = 0x20; + spec->gen.mixer_merge_nid = 0x21; spec->beep_dev_nid = 0x10; set_beep_amp(spec, 0x10, 0, HDA_OUTPUT); err = ad198x_parse_auto_config(codec);
Introduce a helper function to do the same thing.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_generic.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-)
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 06e203d..921582d 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -735,6 +735,14 @@ static void set_pin_eapd(struct hda_codec *codec, hda_nid_t pin, bool enable) enable ? 0x02 : 0x00); }
+/* re-initialize the path specified by the given path index */ +static void resume_path_from_idx(struct hda_codec *codec, int path_idx) +{ + struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx); + if (path) + snd_hda_activate_path(codec, path, path->active, false); +} +
/* * Helper functions for creating mixer ctl elements @@ -4684,16 +4692,8 @@ static void init_analog_input(struct hda_codec *codec)
/* init loopback inputs */ if (spec->mixer_nid) { - struct nid_path *path; - path = snd_hda_get_path_from_idx(codec, spec->loopback_paths[i]); - if (path) - snd_hda_activate_path(codec, path, - path->active, false); - path = snd_hda_get_path_from_idx(codec, - spec->loopback_merge_path); - if (path) - snd_hda_activate_path(codec, path, path->active, - false); + resume_path_from_idx(codec, spec->loopback_paths[i]); + resume_path_from_idx(codec, spec->loopback_merge_path); } } } @@ -4741,11 +4741,8 @@ static void init_digital(struct hda_codec *codec) set_output_and_unmute(codec, spec->digout_paths[i]); pin = spec->autocfg.dig_in_pin; if (pin) { - struct nid_path *path; restore_pin_ctl(codec, pin); - path = snd_hda_get_path_from_idx(codec, spec->digin_path); - if (path) - snd_hda_activate_path(codec, path, path->active, false); + resume_path_from_idx(codec, spec->digin_path); } }
Sometimes we want to call a fixup after applying other existing fixups, but currently the fixup chain mechanism allows only the call the others after the target fixup. This patch adds a new flag, chained_before, to struct hda_fixup, for allowing the chained call before the current execution.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_auto_parser.c | 18 +++++++++++------- sound/pci/hda/hda_local.h | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index 0088bb0..96a05c4 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -696,20 +696,18 @@ static void set_pin_targets(struct hda_codec *codec, snd_hda_set_pin_ctl_cache(codec, cfg->nid, cfg->val); }
-void snd_hda_apply_fixup(struct hda_codec *codec, int action) +static void apply_fixup(struct hda_codec *codec, int id, int action, int depth) { - int id = codec->fixup_id; #ifdef CONFIG_SND_DEBUG_VERBOSE const char *modelname = codec->fixup_name; #endif - int depth = 0; - - if (!codec->fixup_list) - return;
while (id >= 0) { const struct hda_fixup *fix = codec->fixup_list + id;
+ if (fix->chained_before) + apply_fixup(codec, fix->chain_id, action, depth + 1); + switch (fix->type) { case HDA_FIXUP_PINS: if (action != HDA_FIXUP_ACT_PRE_PROBE || !fix->v.pins) @@ -749,13 +747,19 @@ void snd_hda_apply_fixup(struct hda_codec *codec, int action) codec->chip_name, fix->type); break; } - if (!fix->chained) + if (!fix->chained || fix->chained_before) break; if (++depth > 10) break; id = fix->chain_id; } } + +void snd_hda_apply_fixup(struct hda_codec *codec, int action) +{ + if (codec->fixup_list) + apply_fixup(codec, codec->fixup_id, action, 0); +} EXPORT_SYMBOL_HDA(snd_hda_apply_fixup);
void snd_hda_pick_fixup(struct hda_codec *codec, diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index f92979c..2ff62dc 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -401,7 +401,8 @@ struct hda_model_fixup {
struct hda_fixup { int type; - bool chained; + bool chained:1; /* call the chained fixup(s) after this */ + bool chained_before:1; /* call the chained fixup(s) before this */ int chain_id; union { const struct hda_pintbl *pins;
A Packard-Bell desktop machine gives no proper pin configuration from BIOS. It's almost equivalent with the 6stack+fp standard config, just take the existing fixup.
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=901846
Cc: stable@vger.kernel.org Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/patch_realtek.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index cf38861..a4b9364 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -4694,6 +4694,7 @@ static const struct snd_pci_quirk alc880_fixup_tbl[] = { SND_PCI_QUIRK(0x1584, 0x9077, "Uniwill P53", ALC880_FIXUP_VOL_KNOB), SND_PCI_QUIRK(0x161f, 0x203d, "W810", ALC880_FIXUP_W810), SND_PCI_QUIRK(0x161f, 0x205d, "Medion Rim 2150", ALC880_FIXUP_MEDION_RIM), + SND_PCI_QUIRK(0x1631, 0xe011, "PB 13201056", ALC880_FIXUP_6ST), SND_PCI_QUIRK(0x1734, 0x107c, "FSC F1734", ALC880_FIXUP_F1734), SND_PCI_QUIRK(0x1734, 0x1094, "FSC Amilo M1451G", ALC880_FIXUP_FUJITSU), SND_PCI_QUIRK(0x1734, 0x10ac, "FSC AMILO Xi 1526", ALC880_FIXUP_F1734),
Using the new chained_before flag, we can correct the headphone jack detection capability easily over the existing ALC880 6stack model (which disables the jack detection intentionally for compatibility reason).
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=901846
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/patch_realtek.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 446ed11..dc75607 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1034,6 +1034,7 @@ enum { ALC880_FIXUP_6ST_BASE, ALC880_FIXUP_6ST, ALC880_FIXUP_6ST_DIG, + ALC880_FIXUP_6ST_AUTOMUTE, };
/* enable the volume-knob widget support on NID 0x21 */ @@ -1294,6 +1295,15 @@ static const struct hda_fixup alc880_fixups[] = { .chained = true, .chain_id = ALC880_FIXUP_6ST_BASE, }, + [ALC880_FIXUP_6ST_AUTOMUTE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x0121401f }, /* HP with jack detect */ + { } + }, + .chained_before = true, + .chain_id = ALC880_FIXUP_6ST_BASE, + }, };
static const struct snd_pci_quirk alc880_fixup_tbl[] = { @@ -1308,7 +1318,7 @@ static const struct snd_pci_quirk alc880_fixup_tbl[] = { SND_PCI_QUIRK(0x1584, 0x9077, "Uniwill P53", ALC880_FIXUP_VOL_KNOB), SND_PCI_QUIRK(0x161f, 0x203d, "W810", ALC880_FIXUP_W810), SND_PCI_QUIRK(0x161f, 0x205d, "Medion Rim 2150", ALC880_FIXUP_MEDION_RIM), - SND_PCI_QUIRK(0x1631, 0xe011, "PB 13201056", ALC880_FIXUP_6ST), + SND_PCI_QUIRK(0x1631, 0xe011, "PB 13201056", ALC880_FIXUP_6ST_AUTOMUTE), SND_PCI_QUIRK(0x1734, 0x107c, "FSC F1734", ALC880_FIXUP_F1734), SND_PCI_QUIRK(0x1734, 0x1094, "FSC Amilo M1451G", ALC880_FIXUP_FUJITSU), SND_PCI_QUIRK(0x1734, 0x10ac, "FSC AMILO Xi 1526", ALC880_FIXUP_F1734), @@ -1371,6 +1381,7 @@ static const struct hda_model_fixup alc880_fixup_models[] = { {.id = ALC880_FIXUP_5ST_DIG, .name = "5stack-digout"}, {.id = ALC880_FIXUP_6ST, .name = "6stack"}, {.id = ALC880_FIXUP_6ST_DIG, .name = "6stack-digout"}, + {.id = ALC880_FIXUP_6ST_AUTOMUTE, .name = "6stack-automute"}, {} };
The arguments to call is_active_nid() in activate_amp() were swapped, and this resulted in the muted amp on some SPDIF output pins.
Also, the index to be passed to is_active_nid() must be idx_to_check. Otherwise it checks the wrong connection in the case of implicit aamix connection paths.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_generic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 921582d..31ffd66 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -569,7 +569,7 @@ static bool has_amp_out(struct hda_codec *codec, struct nid_path *path, int idx)
/* check whether the given (nid,dir,idx) is active */ static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid, - unsigned int idx, unsigned int dir) + unsigned int dir, unsigned int idx) { struct hda_gen_spec *spec = codec->spec; int i, n; @@ -642,7 +642,7 @@ static void activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir, unsigned int caps; unsigned int mask, val;
- if (!enable && is_active_nid(codec, nid, dir, idx)) + if (!enable && is_active_nid(codec, nid, dir, idx_to_check)) return;
caps = query_amp_caps(codec, nid, dir);
AC_VERB_GET_POWER_STATE returns the combined bits of the actual state and the target state. Thus, comparing the obtained value directly with the target value can't work. The value has to be shifted and masked properly.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/patch_via.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index 9d9583c..5bd4b0c 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -240,8 +240,10 @@ static void set_widgets_power_state(struct hda_codec *codec) static void update_power_state(struct hda_codec *codec, hda_nid_t nid, unsigned int parm) { - if (snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_POWER_STATE, 0) == parm) + unsigned int state = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + state = (state >> 4) & 0x0f; + if (state == parm) return; snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, parm); } @@ -251,8 +253,10 @@ static void update_conv_power_state(struct hda_codec *codec, hda_nid_t nid, { struct via_spec *spec = codec->spec; unsigned int format; - if (snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_POWER_STATE, 0) == parm) + unsigned int state = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + state = (state >> 4) & 0x0f; + if (state == parm) return; format = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); if (format && (spec->dac_stream_tag[index] != format))
Add a hook to struct hda_codec for filtering the target power state of each widget when powering up/down. The current hackish EAPD check is implemented as the default hook pointer, too.
This allows codec drivers to implement own power filter. In the upcoming changes, the generic parser will have the better power filter based on the active paths.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 39 +++++++++++++++++++++++++-------------- sound/pci/hda/hda_codec.h | 7 +++++-- sound/pci/hda/patch_conexant.c | 2 +- sound/pci/hda/patch_sigmatel.c | 2 +- 4 files changed, 32 insertions(+), 18 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index e4e0501..19ff923 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1276,6 +1276,8 @@ static bool snd_hda_codec_get_supported_ps(struct hda_codec *codec,
static unsigned int hda_set_power_state(struct hda_codec *codec, unsigned int power_state); +static unsigned int default_power_filter(struct hda_codec *codec, hda_nid_t nid, + unsigned int power_state);
/** * snd_hda_codec_new - create a HDA codec @@ -1396,6 +1398,7 @@ int snd_hda_codec_new(struct hda_bus *bus, #endif codec->epss = snd_hda_codec_get_supported_ps(codec, fg, AC_PWRST_EPSS); + codec->power_filter = default_power_filter;
/* power-up all before initialization */ hda_set_power_state(codec, AC_PWRST_D0); @@ -3649,29 +3652,23 @@ void snd_hda_codec_flush_cache(struct hda_codec *codec) EXPORT_SYMBOL_HDA(snd_hda_codec_flush_cache);
void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg, - unsigned int power_state, - bool eapd_workaround) + unsigned int power_state) { 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); + unsigned int state = power_state; if (!(wcaps & AC_WCAP_POWER)) continue; - /* don't power down the widget if it controls eapd and - * EAPD_BTLENABLE is set. - */ - if (eapd_workaround && power_state == AC_PWRST_D3 && - get_wcaps_type(wcaps) == AC_WID_PIN && - (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) { - int eapd = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_EAPD_BTLENABLE, 0); - if (eapd & 0x02) + if (codec->power_filter) { + state = codec->power_filter(codec, nid, power_state); + if (state != power_state && power_state == AC_PWRST_D3) continue; } snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, - power_state); + state); } } EXPORT_SYMBOL_HDA(snd_hda_codec_set_power_to_all); @@ -3718,6 +3715,21 @@ static unsigned int hda_sync_power_state(struct hda_codec *codec, return state; }
+/* don't power down the widget if it controls eapd and EAPD_BTLENABLE is set */ +static unsigned int default_power_filter(struct hda_codec *codec, hda_nid_t nid, + unsigned int power_state) +{ + if (power_state == AC_PWRST_D3 && + get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_PIN && + (snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) { + int eapd = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_EAPD_BTLENABLE, 0); + if (eapd & 0x02) + return AC_PWRST_D0; + } + return power_state; +} + /* * set power state of the codec, and return the power state */ @@ -3743,8 +3755,7 @@ static unsigned int hda_set_power_state(struct hda_codec *codec, snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); - snd_hda_codec_set_power_to_all(codec, fg, power_state, - true); + snd_hda_codec_set_power_to_all(codec, fg, power_state); } state = hda_sync_power_state(codec, fg, power_state); if (!(state & AC_PWRST_ERROR)) diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h index cc73287..fbedcf3 100644 --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -886,6 +886,10 @@ struct hda_codec { spinlock_t power_lock; #endif
+ /* filter the requested power state per nid */ + unsigned int (*power_filter)(struct hda_codec *codec, hda_nid_t nid, + unsigned int power_state); + /* codec-specific additional proc output */ void (*proc_widget_hook)(struct snd_info_buffer *buffer, struct hda_codec *codec, hda_nid_t nid); @@ -1047,8 +1051,7 @@ extern const struct snd_pcm_chmap_elem snd_pcm_2_1_chmaps[]; void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen); void snd_hda_bus_reboot_notify(struct hda_bus *bus); void snd_hda_codec_set_power_to_all(struct hda_codec *codec, hda_nid_t fg, - unsigned int power_state, - bool eapd_workaround); + unsigned int power_state);
int snd_hda_lock_devices(struct hda_bus *bus); void snd_hda_unlock_devices(struct hda_bus *bus); diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index d98d470..7d941ef 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -435,7 +435,7 @@ static void conexant_set_power(struct hda_codec *codec, hda_nid_t fg, /* partial workaround for "azx_get_response timeout" */ if (power_state == AC_PWRST_D0) msleep(10); - snd_hda_codec_set_power_to_all(codec, fg, power_state, true); + snd_hda_codec_set_power_to_all(codec, fg, power_state); }
static int conexant_init(struct hda_codec *codec) diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 0aa0ceb..5895d8f 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -3724,7 +3724,7 @@ static void stac_set_power_state(struct hda_codec *codec, hda_nid_t fg, } snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE, afg_power_state); - snd_hda_codec_set_power_to_all(codec, fg, power_state, true); + snd_hda_codec_set_power_to_all(codec, fg, power_state); } #else #define stac_suspend NULL
Put the power state synchronization at the end of the parsing of codec. This is necessary when the power filter is changed during the codec probe. Since the first power-up sequence is performed without the special filter, all widgets are supposed to be ON at this point.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 19ff923..2311114 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -3765,6 +3765,37 @@ static unsigned int hda_set_power_state(struct hda_codec *codec, return state; }
+/* sync power states of all widgets; + * this is called at the end of codec parsing + */ +static void sync_power_up_states(struct hda_codec *codec) +{ + hda_nid_t nid = codec->start_nid; + int i; + + /* don't care if no or standard filter is used */ + if (!codec->power_filter || codec->power_filter == default_power_filter) + return; + + for (i = 0; i < codec->num_nodes; i++, nid++) { + unsigned int wcaps = get_wcaps(codec, nid); + unsigned int state, target; + if (!(wcaps & AC_WCAP_POWER)) + continue; + target = codec->power_filter(codec, nid, AC_PWRST_D0); + if (target == AC_PWRST_D0) + continue; + state = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + if (state & AC_PWRST_ERROR) + continue; + state = (state >> 4) & 0x0f; + if (state != target) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_POWER_STATE, target); + } +} + #ifdef CONFIG_SND_HDA_HWDEP /* execute additional init verbs */ static void hda_exec_init_verbs(struct hda_codec *codec) @@ -3952,6 +3983,7 @@ int snd_hda_codec_build_controls(struct hda_codec *codec) hda_jackpoll_work(&codec->jackpoll_work.work); else snd_hda_jack_report_sync(codec); /* call at the last init point */ + sync_power_up_states(codec); return 0; }
... for small refactoring.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_codec.c | 9 ++------- sound/pci/hda/hda_local.h | 13 +++++++++++++ sound/pci/hda/patch_via.c | 11 +++-------- 3 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 2311114..f82a64d 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -3779,18 +3779,13 @@ static void sync_power_up_states(struct hda_codec *codec)
for (i = 0; i < codec->num_nodes; i++, nid++) { unsigned int wcaps = get_wcaps(codec, nid); - unsigned int state, target; + unsigned int target; if (!(wcaps & AC_WCAP_POWER)) continue; target = codec->power_filter(codec, nid, AC_PWRST_D0); if (target == AC_PWRST_D0) continue; - state = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_POWER_STATE, 0); - if (state & AC_PWRST_ERROR) - continue; - state = (state >> 4) & 0x0f; - if (state != target) + if (!snd_hda_check_power_state(codec, nid, target)) snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, target); } diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 2ff62dc..05f1d59 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -657,6 +657,19 @@ int snd_hda_check_amp_list_power(struct hda_codec *codec, struct hda_loopback_check *check, hda_nid_t nid);
+/* check whether the actual power state matches with the target state */ +static inline bool +snd_hda_check_power_state(struct hda_codec *codec, hda_nid_t nid, + unsigned int target_state) +{ + unsigned int state = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + if (state & AC_PWRST_ERROR) + return true; + state = (state >> 4) & 0x0f; + return (state != target_state); +} + /* * AMP control callbacks */ diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index 5bd4b0c..9641c0e 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -240,10 +240,7 @@ static void set_widgets_power_state(struct hda_codec *codec) static void update_power_state(struct hda_codec *codec, hda_nid_t nid, unsigned int parm) { - unsigned int state = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_POWER_STATE, 0); - state = (state >> 4) & 0x0f; - if (state == parm) + if (snd_hda_check_power_state(codec, nid, parm)) return; snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, parm); } @@ -253,10 +250,8 @@ static void update_conv_power_state(struct hda_codec *codec, hda_nid_t nid, { struct via_spec *spec = codec->spec; unsigned int format; - unsigned int state = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_POWER_STATE, 0); - state = (state >> 4) & 0x0f; - if (state == parm) + + if (snd_hda_check_power_state(codec, nid, parm)) return; format = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); if (format && (spec->dac_stream_tag[index] != format))
This patch adds a better power filter hook for powering down unused widgets in the generic parser.
The feature is enabled by setting hda_gen_spec.power_down_unused flag.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/hda_generic.c | 73 +++++++++++++++++++++++++++++++++++++++++---- sound/pci/hda/hda_generic.h | 1 + 2 files changed, 68 insertions(+), 6 deletions(-)
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c index 31ffd66..19d014a 100644 --- a/sound/pci/hda/hda_generic.c +++ b/sound/pci/hda/hda_generic.c @@ -24,6 +24,7 @@ #include <linux/slab.h> #include <linux/export.h> #include <linux/sort.h> +#include <linux/delay.h> #include <linux/ctype.h> #include <linux/string.h> #include <linux/bitops.h> @@ -153,6 +154,9 @@ static void parse_user_hints(struct hda_codec *codec) val = snd_hda_get_bool_hint(codec, "add_in_jack_modes"); if (val >= 0) spec->add_in_jack_modes = !!val; + val = snd_hda_get_bool_hint(codec, "power_down_unused"); + if (val >= 0) + spec->power_down_unused = !!val;
if (!snd_hda_get_int_hint(codec, "mixer_nid", &val)) spec->mixer_nid = val; @@ -700,14 +704,23 @@ static void activate_amp_in(struct hda_codec *codec, struct nid_path *path, void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path, bool enable, bool add_aamix) { + struct hda_gen_spec *spec = codec->spec; int i;
if (!enable) path->active = false;
for (i = path->depth - 1; i >= 0; i--) { + hda_nid_t nid = path->path[i]; + if (enable && spec->power_down_unused) { + /* make sure the widget is powered up */ + if (!snd_hda_check_power_state(codec, nid, AC_PWRST_D0)) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_POWER_STATE, + AC_PWRST_D0); + } if (enable && path->multi[i]) - snd_hda_codec_write_cache(codec, path->path[i], 0, + snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, path->idx[i]); if (has_amp_in(codec, path, i)) @@ -721,6 +734,33 @@ void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path, } EXPORT_SYMBOL_HDA(snd_hda_activate_path);
+/* if the given path is inactive, put widgets into D3 (only if suitable) */ +static void path_power_down_sync(struct hda_codec *codec, struct nid_path *path) +{ + struct hda_gen_spec *spec = codec->spec; + bool changed; + int i; + + if (!spec->power_down_unused || path->active) + return; + + for (i = 0; i < path->depth; i++) { + hda_nid_t nid = path->path[i]; + if (!snd_hda_check_power_state(codec, nid, AC_PWRST_D3)) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_POWER_STATE, + AC_PWRST_D3); + changed = true; + } + } + + if (changed) { + msleep(10); + snd_hda_codec_read(codec, path->path[0], 0, + AC_VERB_GET_POWER_STATE, 0); + } +} + /* turn on/off EAPD on the given pin */ static void set_pin_eapd(struct hda_codec *codec, hda_nid_t pin, bool enable) { @@ -2007,6 +2047,7 @@ static int set_multi_io(struct hda_codec *codec, int idx, bool output) set_pin_eapd(codec, nid, false); snd_hda_activate_path(codec, path, false, true); set_pin_target(codec, nid, spec->multi_io[idx].ctl_in, true); + path_power_down_sync(codec, path); }
/* update jack retasking in case it modifies any of them */ @@ -2093,9 +2134,11 @@ static void update_aamix_paths(struct hda_codec *codec, bool do_mix, if (do_mix) { snd_hda_activate_path(codec, nomix_path, false, true); snd_hda_activate_path(codec, mix_path, true, true); + path_power_down_sync(codec, nomix_path); } else { snd_hda_activate_path(codec, mix_path, false, true); snd_hda_activate_path(codec, nomix_path, true, true); + path_power_down_sync(codec, mix_path); } }
@@ -3356,7 +3399,7 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx, { struct hda_gen_spec *spec = codec->spec; const struct hda_input_mux *imux; - struct nid_path *path; + struct nid_path *old_path, *path;
imux = &spec->input_mux; if (!imux->num_items) @@ -3367,11 +3410,11 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx, if (spec->cur_mux[adc_idx] == idx) return 0;
- path = get_input_path(codec, adc_idx, spec->cur_mux[adc_idx]); - if (!path) + old_path = get_input_path(codec, adc_idx, spec->cur_mux[adc_idx]); + if (!old_path) return 0; - if (path->active) - snd_hda_activate_path(codec, path, false, false); + if (old_path->active) + snd_hda_activate_path(codec, old_path, false, false);
spec->cur_mux[adc_idx] = idx;
@@ -3389,6 +3432,7 @@ static int mux_select(struct hda_codec *codec, unsigned int adc_idx, snd_hda_activate_path(codec, path, true, false); if (spec->cap_sync_hook) spec->cap_sync_hook(codec, NULL); + path_power_down_sync(codec, old_path); return 1; }
@@ -3853,6 +3897,20 @@ static int check_auto_mic_availability(struct hda_codec *codec) return 0; }
+/* power_filter hook; make inactive widgets into power down */ +static unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec, + hda_nid_t nid, + unsigned int power_state) +{ + if (power_state != AC_PWRST_D0) + return power_state; + if (get_wcaps_type(get_wcaps(codec, nid)) >= AC_WID_POWER) + return power_state; + if (is_active_nid(codec, nid, HDA_OUTPUT, 0)) + return power_state; + return AC_PWRST_D3; +} +
/* * Parse the given BIOS configuration and set up the hda_gen_spec @@ -3980,6 +4038,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec, dig_only: parse_digital(codec);
+ if (spec->power_down_unused) + codec->power_filter = snd_hda_gen_path_power_filter; + return 1; } EXPORT_SYMBOL_HDA(snd_hda_gen_parse_auto_config); diff --git a/sound/pci/hda/hda_generic.h b/sound/pci/hda/hda_generic.h index d226856..065fcc7 100644 --- a/sound/pci/hda/hda_generic.h +++ b/sound/pci/hda/hda_generic.h @@ -211,6 +211,7 @@ struct hda_gen_spec { unsigned int add_stereo_mix_input:1; /* add aamix as a capture src */ unsigned int add_out_jack_modes:1; /* add output jack mode enum ctls */ unsigned int add_in_jack_modes:1; /* add input jack mode enum ctls */ + unsigned int power_down_unused:1; /* power down unused widgets */
/* other internal flags */ unsigned int no_analog:1; /* digital I/O only */
IDT codecs can work well with this new feature, so let's enable it.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/pci/hda/patch_sigmatel.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index 5895d8f..1c49861 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -3609,14 +3609,6 @@ static int stac_init(struct hda_codec *codec) } }
- /* power down unused DACs */ - for (i = 0; i < spec->gen.num_all_dacs; i++) { - if (!snd_hda_get_nid_path(codec, spec->gen.all_dacs[i], 0)) - snd_hda_codec_write(codec, spec->gen.all_dacs[i], 0, - AC_VERB_SET_POWER_STATE, - AC_PWRST_D3); - } - return 0; }
@@ -3871,6 +3863,7 @@ static int patch_stac92hd73xx(struct hda_codec *codec) spec->pwr_nids = stac92hd73xx_pwr_nids;
spec->gen.own_eapd_ctl = 1; + spec->gen.power_down_unused = 1;
codec->patch_ops = stac_patch_ops;
@@ -3933,6 +3926,7 @@ static int patch_stac92hd83xxx(struct hda_codec *codec) spec = codec->spec; spec->linear_tone_beep = 0; spec->gen.own_eapd_ctl = 1; + spec->gen.power_down_unused = 1; spec->gen.mixer_nid = 0x1b;
spec->digbeep_nid = 0x21; @@ -3976,6 +3970,7 @@ static int patch_stac92hd71bxx(struct hda_codec *codec) spec = codec->spec; spec->linear_tone_beep = 0; spec->gen.own_eapd_ctl = 1; + spec->gen.power_down_unused = 1; spec->gen.mixer_nid = 0x17; spec->have_spdif_mux = 1;
participants (1)
-
Takashi Iwai