#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>
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;
return 0;
return idx + 1;
}
+EXPORT_SYMBOL_HDA(snd_hda_get_path_idx);
/* get the path instance corresponding to the given index number */
struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx)
return NULL;
return snd_array_elem(&spec->paths, idx - 1);
}
+EXPORT_SYMBOL_HDA(snd_hda_get_path_from_idx);
/* check whether the given DAC is already found in any existing paths */
static bool is_dac_already_used(struct hda_codec *codec, hda_nid_t nid)
/* check whether a control with the given (nid, dir, idx) was assigned */
static bool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid,
- int dir, int idx)
+ int dir, int idx, int type)
{
unsigned int val = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir);
- return is_ctl_used(codec, val, NID_PATH_VOL_CTL) ||
- is_ctl_used(codec, val, NID_PATH_MUTE_CTL);
+ return is_ctl_used(codec, val, type);
}
static void print_nid_path(const char *pfx, struct nid_path *path)
/* 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;
/* get the default amp value for the target state */
static int get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid,
- int dir, bool enable)
+ int dir, unsigned int caps, bool enable)
{
- unsigned int caps;
unsigned int val = 0;
- caps = query_amp_caps(codec, nid, dir);
if (caps & AC_AMPCAP_NUM_STEPS) {
/* set to 0dB */
if (enable)
/* initialize the amp value (only at the first time) */
static void init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx)
{
- int val = get_amp_val_to_activate(codec, nid, dir, false);
+ unsigned int caps = query_amp_caps(codec, nid, dir);
+ int val = get_amp_val_to_activate(codec, nid, dir, caps, false);
snd_hda_codec_amp_init_stereo(codec, nid, dir, idx, 0xff, val);
}
+/* calculate amp value mask we can modify;
+ * if the given amp is controlled by mixers, don't touch it
+ */
+static unsigned int get_amp_mask_to_modify(struct hda_codec *codec,
+ hda_nid_t nid, int dir, int idx,
+ unsigned int caps)
+{
+ unsigned int mask = 0xff;
+
+ if (caps & AC_AMPCAP_MUTE) {
+ if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_MUTE_CTL))
+ mask &= ~0x80;
+ }
+ if (caps & AC_AMPCAP_NUM_STEPS) {
+ if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) ||
+ is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL))
+ mask &= ~0x7f;
+ }
+ return mask;
+}
+
static void activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir,
- int idx, bool enable)
+ int idx, int idx_to_check, bool enable)
{
- int val;
- if (is_ctl_associated(codec, nid, dir, idx) ||
- (!enable && is_active_nid(codec, nid, dir, idx)))
+ unsigned int caps;
+ unsigned int mask, val;
+
+ if (!enable && is_active_nid(codec, nid, dir, idx_to_check))
return;
- val = get_amp_val_to_activate(codec, nid, dir, enable);
- snd_hda_codec_amp_stereo(codec, nid, dir, idx, 0xff, val);
+
+ caps = query_amp_caps(codec, nid, dir);
+ val = get_amp_val_to_activate(codec, nid, dir, caps, enable);
+ mask = get_amp_mask_to_modify(codec, nid, dir, idx_to_check, caps);
+ if (!mask)
+ return;
+
+ val &= mask;
+ snd_hda_codec_amp_stereo(codec, nid, dir, idx, mask, val);
}
static void activate_amp_out(struct hda_codec *codec, struct nid_path *path,
{
hda_nid_t nid = path->path[i];
init_amp(codec, nid, HDA_OUTPUT, 0);
- activate_amp(codec, nid, HDA_OUTPUT, 0, enable);
+ activate_amp(codec, nid, HDA_OUTPUT, 0, 0, enable);
}
static void activate_amp_in(struct hda_codec *codec, struct nid_path *path,
for (n = 0; n < nums; n++)
init_amp(codec, nid, HDA_INPUT, n);
- if (is_ctl_associated(codec, nid, HDA_INPUT, idx))
- return;
-
/* here is a little bit tricky in comparison with activate_amp_out();
* 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, enable);
+ activate_amp(codec, nid, HDA_INPUT, n, idx, enable);
}
}
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))
}
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)
{
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
};
/* add dynamic controls from template */
-static int add_control(struct hda_gen_spec *spec, int type, const char *name,
+static struct snd_kcontrol_new *
+add_control(struct hda_gen_spec *spec, int type, const char *name,
int cidx, unsigned long val)
{
struct snd_kcontrol_new *knew;
knew = snd_hda_gen_add_kctl(spec, name, &control_templates[type]);
if (!knew)
- return -ENOMEM;
+ return NULL;
knew->index = cidx;
if (get_amp_nid_(val))
knew->subdevice = HDA_SUBDEV_AMP_FLAG;
knew->private_value = val;
- return 0;
+ return knew;
}
static int add_control_with_pfx(struct hda_gen_spec *spec, int type,
{
char name[32];
snprintf(name, sizeof(name), "%s %s %s", pfx, dir, sfx);
- return add_control(spec, type, name, cidx, val);
+ if (!add_control(spec, type, name, cidx, val))
+ return -ENOMEM;
+ return 0;
}
#define add_pb_vol_ctrl(spec, type, pfx, val) \
return add_sw_ctl(codec, pfx, cidx, chs, path);
}
+/* any ctl assigned to the path with the given index? */
+static bool path_has_mixer(struct hda_codec *codec, int path_idx, int ctl_type)
+{
+ struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx);
+ return path && path->ctls[ctl_type];
+}
+
static const char * const channel_name[4] = {
"Front", "Surround", "CLFE", "Side"
};
/* give some appropriate ctl name prefix for the given line out channel */
-static const char *get_line_out_pfx(struct hda_gen_spec *spec, int ch,
- bool can_be_master, int *index)
+static const char *get_line_out_pfx(struct hda_codec *codec, int ch,
+ int *index, int ctl_type)
{
+ struct hda_gen_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
*index = 0;
if (cfg->line_outs == 1 && !spec->multi_ios &&
- !cfg->hp_outs && !cfg->speaker_outs && can_be_master)
+ !cfg->hp_outs && !cfg->speaker_outs)
return spec->vmaster_mute.hook ? "PCM" : "Master";
/* if there is really a single DAC used in the whole output paths,
!spec->multiout.hp_out_nid[0] && !spec->multiout.extra_out_nid[0])
return spec->vmaster_mute.hook ? "PCM" : "Master";
+ /* multi-io channels */
+ if (ch >= cfg->line_outs)
+ return channel_name[ch];
+
switch (cfg->line_out_type) {
case AUTO_PIN_SPEAKER_OUT:
+ /* if the primary channel vol/mute is shared with HP volume,
+ * don't name it as Speaker
+ */
+ if (!ch && cfg->hp_outs &&
+ !path_has_mixer(codec, spec->hp_paths[0], ctl_type))
+ break;
if (cfg->line_outs == 1)
return "Speaker";
if (cfg->line_outs == 2)
return ch ? "Bass Speaker" : "Speaker";
break;
case AUTO_PIN_HP_OUT:
+ /* if the primary channel vol/mute is shared with spk volume,
+ * don't name it as Headphone
+ */
+ if (!ch && cfg->speaker_outs &&
+ !path_has_mixer(codec, spec->speaker_paths[0], ctl_type))
+ break;
/* for multi-io case, only the primary out */
if (ch && spec->multi_ios)
break;
*index = ch;
return "Headphone";
- default:
- if (cfg->line_outs == 1 && !spec->multi_ios)
- return "PCM";
- break;
}
+
+ /* for a single channel output, we don't have to name the channel */
+ if (cfg->line_outs == 1 && !spec->multi_ios)
+ return "PCM";
+
if (ch >= ARRAY_SIZE(channel_name)) {
snd_BUG();
return "PCM";
else
badness += bad->no_dac;
}
+ if (!dac)
+ continue;
path = snd_hda_add_new_path(codec, dac, pin, -spec->mixer_nid);
if (!path && !i && spec->mixer_nid) {
/* try with aamix */
path = snd_hda_add_new_path(codec, dac, pin, 0);
}
- if (!path)
+ if (!path) {
dac = dacs[i] = 0;
- else {
- print_nid_path("output", path);
+ badness += bad->no_dac;
+ } else {
+ /* print_nid_path("output", path); */
path->active = true;
path_idx[i] = snd_hda_get_path_idx(codec, path);
badness += assign_out_path_ctls(codec, path);
badness++;
continue;
}
- print_nid_path("multiio", path);
+ /* print_nid_path("multiio", path); */
spec->multi_io[spec->multi_ios].pin = nid;
spec->multi_io[spec->multi_ios].dac = dac;
spec->out_paths[cfg->line_outs + spec->multi_ios] =
if (path) {
dacs[i] = dac;
found = true;
- print_nid_path("output", path);
+ /* print_nid_path("output", path); */
path->active = true;
path_idx[i] = snd_hda_get_path_idx(codec, path);
}
{
struct hda_gen_spec *spec = codec->spec;
struct nid_path *path;
+ hda_nid_t dac, pin;
path = snd_hda_get_path_from_idx(codec, path_idx);
if (!path || !path->depth ||
is_nid_contained(path, spec->mixer_nid))
return 0;
- path = snd_hda_add_new_path(codec, path->path[0],
- path->path[path->depth - 1],
- spec->mixer_nid);
+ dac = path->path[0];
+ pin = path->path[path->depth - 1];
+ path = snd_hda_add_new_path(codec, dac, pin, spec->mixer_nid);
+ if (!path) {
+ if (dac != spec->multiout.dac_nids[0])
+ dac = spec->multiout.dac_nids[0];
+ else if (spec->multiout.hp_out_nid[0])
+ dac = spec->multiout.hp_out_nid[0];
+ else if (spec->multiout.extra_out_nid[0])
+ dac = spec->multiout.extra_out_nid[0];
+ if (dac)
+ path = snd_hda_add_new_path(codec, dac, pin,
+ spec->mixer_nid);
+ }
if (!path)
return 0;
- print_nid_path("output-aamix", path);
+ /* print_nid_path("output-aamix", path); */
path->active = false; /* unused as default */
return snd_hda_get_path_idx(codec, path);
}
struct hda_gen_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
int i, err, badness;
- unsigned int val;
/* set num_dacs once to full for look_for_dac() */
spec->multiout.num_dacs = cfg->line_outs;
spec->multiout.extra_out_nid,
spec->speaker_paths);
- /* set initial pinctl targets */
- if (spec->prefer_hp_amp || cfg->line_out_type == AUTO_PIN_HP_OUT)
- val = PIN_HP;
- else
- val = PIN_OUT;
- set_pin_targets(codec, cfg->line_outs, cfg->line_out_pins, val);
- if (cfg->line_out_type != AUTO_PIN_HP_OUT)
- set_pin_targets(codec, cfg->hp_outs, cfg->hp_pins, PIN_HP);
- if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
- val = spec->prefer_hp_amp ? PIN_HP : PIN_OUT;
- set_pin_targets(codec, cfg->speaker_outs,
- cfg->speaker_pins, val);
- }
-
return badness;
}
#define debug_badness(...)
#endif
-static void debug_show_configs(struct hda_gen_spec *spec, struct auto_pin_cfg *cfg)
+#ifdef DEBUG_BADNESS
+static inline void print_nid_path_idx(struct hda_codec *codec,
+ const char *pfx, int idx)
+{
+ struct nid_path *path;
+
+ path = snd_hda_get_path_from_idx(codec, idx);
+ if (path)
+ print_nid_path(pfx, path);
+}
+
+static void debug_show_configs(struct hda_codec *codec,
+ struct auto_pin_cfg *cfg)
{
- debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+ struct hda_gen_spec *spec = codec->spec;
+ static const char * const lo_type[3] = { "LO", "SP", "HP" };
+ int i;
+
+ debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x (type %s)\n",
cfg->line_out_pins[0], cfg->line_out_pins[1],
cfg->line_out_pins[2], cfg->line_out_pins[3],
spec->multiout.dac_nids[0],
spec->multiout.dac_nids[1],
spec->multiout.dac_nids[2],
- spec->multiout.dac_nids[3]);
+ spec->multiout.dac_nids[3],
+ lo_type[cfg->line_out_type]);
+ for (i = 0; i < cfg->line_outs; i++)
+ print_nid_path_idx(codec, " out", spec->out_paths[i]);
if (spec->multi_ios > 0)
debug_badness("multi_ios(%d) = %x/%x : %x/%x\n",
spec->multi_ios,
spec->multi_io[0].pin, spec->multi_io[1].pin,
spec->multi_io[0].dac, spec->multi_io[1].dac);
- debug_badness("hp_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+ for (i = 0; i < spec->multi_ios; i++)
+ print_nid_path_idx(codec, " mio",
+ spec->out_paths[cfg->line_outs + i]);
+ if (cfg->hp_outs)
+ debug_badness("hp_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
cfg->hp_pins[0], cfg->hp_pins[1],
cfg->hp_pins[2], cfg->hp_pins[3],
spec->multiout.hp_out_nid[0],
spec->multiout.hp_out_nid[1],
spec->multiout.hp_out_nid[2],
spec->multiout.hp_out_nid[3]);
- debug_badness("spk_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
+ for (i = 0; i < cfg->hp_outs; i++)
+ print_nid_path_idx(codec, " hp ", spec->hp_paths[i]);
+ if (cfg->speaker_outs)
+ debug_badness("spk_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
cfg->speaker_pins[0], cfg->speaker_pins[1],
cfg->speaker_pins[2], cfg->speaker_pins[3],
spec->multiout.extra_out_nid[0],
spec->multiout.extra_out_nid[1],
spec->multiout.extra_out_nid[2],
spec->multiout.extra_out_nid[3]);
+ for (i = 0; i < cfg->speaker_outs; i++)
+ print_nid_path_idx(codec, " spk", spec->speaker_paths[i]);
+ for (i = 0; i < 3; i++)
+ print_nid_path_idx(codec, " mix", spec->aamix_out_paths[i]);
}
+#else
+#define debug_show_configs(codec, cfg) /* NOP */
+#endif
/* find all available DACs of the codec */
static void fill_all_dac_nids(struct hda_codec *codec)
struct hda_gen_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
struct auto_pin_cfg *best_cfg;
+ unsigned int val;
int best_badness = INT_MAX;
int badness;
bool fill_hardwired = true, fill_mio_first = true;
debug_badness("==> lo_type=%d, wired=%d, mio=%d, badness=0x%x\n",
cfg->line_out_type, fill_hardwired, fill_mio_first,
badness);
- debug_show_configs(spec, cfg);
+ debug_show_configs(codec, cfg);
if (badness < best_badness) {
best_badness = badness;
*best_cfg = *cfg;
}
debug_badness("==> Best config: lo_type=%d, wired=%d, mio=%d\n",
cfg->line_out_type, best_wired, best_mio);
- debug_show_configs(spec, cfg);
+ debug_show_configs(codec, cfg);
if (cfg->line_out_pins[0]) {
struct nid_path *path;
HDA_OUTPUT, spec->vmaster_tlv);
}
+ /* set initial pinctl targets */
+ if (spec->prefer_hp_amp || cfg->line_out_type == AUTO_PIN_HP_OUT)
+ val = PIN_HP;
+ else
+ val = PIN_OUT;
+ set_pin_targets(codec, cfg->line_outs, cfg->line_out_pins, val);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ set_pin_targets(codec, cfg->hp_outs, cfg->hp_pins, PIN_HP);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ val = spec->prefer_hp_amp ? PIN_HP : PIN_OUT;
+ set_pin_targets(codec, cfg->speaker_outs,
+ cfg->speaker_pins, val);
+ }
+
kfree(best_cfg);
return 0;
}
int index;
struct nid_path *path;
- if (i >= cfg->line_outs) {
- index = 0;
- name = channel_name[i];
- } else {
- name = get_line_out_pfx(spec, i, true, &index);
- }
-
path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]);
if (!path)
continue;
+
+ name = get_line_out_pfx(codec, i, &index, NID_PATH_VOL_CTL);
if (!name || !strcmp(name, "CLFE")) {
/* Center/LFE */
err = add_vol_ctl(codec, "Center", 0, 1, path);
err = add_vol_ctl(codec, "LFE", 0, 2, path);
if (err < 0)
return err;
+ } else {
+ err = add_stereo_vol(codec, name, index, path);
+ if (err < 0)
+ return err;
+ }
+
+ name = get_line_out_pfx(codec, i, &index, NID_PATH_MUTE_CTL);
+ if (!name || !strcmp(name, "CLFE")) {
err = add_sw_ctl(codec, "Center", 0, 1, path);
if (err < 0)
return err;
if (err < 0)
return err;
} else {
- err = add_stereo_vol(codec, name, index, path);
- if (err < 0)
- return err;
err = add_stereo_sw(codec, name, index, path);
if (err < 0)
return err;
return 0;
}
+static void update_aamix_paths(struct hda_codec *codec, bool do_mix,
+ int nomix_path_idx, int mix_path_idx,
+ int out_type);
+
static int indep_hp_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
}
if (spec->indep_hp_enabled != select) {
+ hda_nid_t *dacp;
+ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
+ dacp = &spec->private_dac_nids[0];
+ else
+ dacp = &spec->multiout.hp_out_nid[0];
+
+ /* update HP aamix paths in case it conflicts with indep HP */
+ if (spec->have_aamix_ctl) {
+ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
+ update_aamix_paths(codec, spec->aamix_mode,
+ spec->out_paths[0],
+ spec->aamix_out_paths[0],
+ spec->autocfg.line_out_type);
+ else
+ update_aamix_paths(codec, spec->aamix_mode,
+ spec->hp_paths[0],
+ spec->aamix_out_paths[1],
+ AUTO_PIN_HP_OUT);
+ }
+
spec->indep_hp_enabled = select;
if (spec->indep_hp_enabled)
- spec->multiout.hp_out_nid[0] = 0;
+ *dacp = 0;
+ else
+ *dacp = spec->alt_dac_nid;
+
+ /* update HP auto-mute state too */
+ if (spec->hp_automute_hook)
+ spec->hp_automute_hook(codec, NULL);
else
- spec->multiout.hp_out_nid[0] = spec->alt_dac_nid;
+ snd_hda_gen_hp_automute(codec, NULL);
+
ret = 1;
}
unlock:
static int create_indep_hp_ctls(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t dac;
if (!spec->indep_hp)
return 0;
- if (!spec->multiout.hp_out_nid[0]) {
+ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
+ dac = spec->multiout.dac_nids[0];
+ else
+ dac = spec->multiout.hp_out_nid[0];
+ if (!dac) {
spec->indep_hp = 0;
return 0;
}
spec->indep_hp_enabled = false;
- spec->alt_dac_nid = spec->multiout.hp_out_nid[0];
+ spec->alt_dac_nid = dac;
if (!snd_hda_gen_add_kctl(spec, NULL, &indep_hp_ctl))
return -ENOMEM;
return 0;
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 */
}
static void update_aamix_paths(struct hda_codec *codec, bool do_mix,
- int nomix_path_idx, int mix_path_idx)
+ int nomix_path_idx, int mix_path_idx,
+ int out_type)
{
+ struct hda_gen_spec *spec = codec->spec;
struct nid_path *nomix_path, *mix_path;
nomix_path = snd_hda_get_path_from_idx(codec, nomix_path_idx);
mix_path = snd_hda_get_path_from_idx(codec, mix_path_idx);
if (!nomix_path || !mix_path)
return;
+
+ /* if HP aamix path is driven from a different DAC and the
+ * independent HP mode is ON, can't turn on aamix path
+ */
+ if (out_type == AUTO_PIN_HP_OUT && spec->indep_hp_enabled &&
+ mix_path->path[0] != spec->alt_dac_nid)
+ do_mix = false;
+
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);
}
}
return 0;
spec->aamix_mode = val;
update_aamix_paths(codec, val, spec->out_paths[0],
- spec->aamix_out_paths[0]);
+ spec->aamix_out_paths[0],
+ spec->autocfg.line_out_type);
update_aamix_paths(codec, val, spec->hp_paths[0],
- spec->aamix_out_paths[1]);
+ spec->aamix_out_paths[1],
+ AUTO_PIN_HP_OUT);
update_aamix_paths(codec, val, spec->speaker_paths[0],
- spec->aamix_out_paths[2]);
+ spec->aamix_out_paths[2],
+ AUTO_PIN_SPEAKER_OUT);
return 1;
}
return 0;
if (!snd_hda_gen_add_kctl(spec, NULL, &loopback_mixing_enum))
return -ENOMEM;
+ spec->have_aamix_ctl = 1;
return 0;
}
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;
}
break;
}
spec->num_adc_nids = nums;
+
+ /* copy the detected ADCs to all_adcs[] */
+ spec->num_all_adcs = nums;
+ memcpy(spec->all_adcs, spec->adc_nids, nums * sizeof(hda_nid_t));
+
return nums;
}
/* parse capture source paths from the given pin and create imux items */
static int parse_capture_source(struct hda_codec *codec, hda_nid_t pin,
- int num_adcs, const char *label, int anchor)
+ int cfg_idx, int num_adcs,
+ const char *label, int anchor)
{
struct hda_gen_spec *spec = codec->spec;
struct hda_input_mux *imux = &spec->input_mux;
if (!imux_added) {
spec->imux_pins[imux->num_items] = pin;
- snd_hda_add_imux_item(imux, label,
- imux->num_items, NULL);
+ snd_hda_add_imux_item(imux, label, cfg_idx, NULL);
imux_added = true;
}
}
/*
* create playback/capture controls for input pins
*/
+
+/* fill the label for each input at first */
+static int fill_input_pin_labels(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ const struct auto_pin_cfg *cfg = &spec->autocfg;
+ int i;
+
+ for (i = 0; i < cfg->num_inputs; i++) {
+ hda_nid_t pin = cfg->inputs[i].pin;
+ const char *label;
+ int j, idx;
+
+ if (!is_input_pin(codec, pin))
+ continue;
+
+ label = hda_get_autocfg_input_label(codec, cfg, i);
+ idx = 0;
+ for (j = i - 1; j >= 0; j--) {
+ if (spec->input_labels[j] &&
+ !strcmp(spec->input_labels[j], label)) {
+ idx = spec->input_label_idxs[j] + 1;
+ break;
+ }
+ }
+
+ spec->input_labels[i] = label;
+ spec->input_label_idxs[i] = idx;
+ }
+
+ return 0;
+}
+
+#define CFG_IDX_MIX 99 /* a dummy cfg->input idx for stereo mix */
+
static int create_input_ctls(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
const struct auto_pin_cfg *cfg = &spec->autocfg;
hda_nid_t mixer = spec->mixer_nid;
int num_adcs;
- int i, err, type_idx = 0;
- const char *prev_label = NULL;
+ int i, err;
unsigned int val;
num_adcs = fill_adc_nids(codec);
if (num_adcs < 0)
return 0;
+ err = fill_input_pin_labels(codec);
+ if (err < 0)
+ return err;
+
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t pin;
- const char *label;
pin = cfg->inputs[i].pin;
if (!is_input_pin(codec, pin))
continue;
- label = hda_get_autocfg_input_label(codec, cfg, i);
- if (prev_label && !strcmp(label, prev_label))
- type_idx++;
- else
- type_idx = 0;
- prev_label = label;
-
val = PIN_IN;
if (cfg->inputs[i].type == AUTO_PIN_MIC)
val |= snd_hda_get_default_vref(codec, pin);
if (mixer) {
if (is_reachable_path(codec, pin, mixer)) {
err = new_analog_input(codec, i, pin,
- label, type_idx, mixer);
+ spec->input_labels[i],
+ spec->input_label_idxs[i],
+ mixer);
if (err < 0)
return err;
}
}
- err = parse_capture_source(codec, pin, num_adcs, label, -mixer);
+ err = parse_capture_source(codec, pin, i, num_adcs,
+ spec->input_labels[i], -mixer);
if (err < 0)
return err;
}
if (mixer && spec->add_stereo_mix_input) {
- err = parse_capture_source(codec, mixer, num_adcs,
+ err = parse_capture_source(codec, mixer, CFG_IDX_MIX, num_adcs,
"Stereo Mix", 0);
if (err < 0)
return err;
}
if (spec->dyn_adc_switch)
adc_idx = spec->dyn_adc_idx[imux_idx];
- if (adc_idx < 0 || adc_idx >= AUTO_CFG_MAX_OUTS) {
+ if (adc_idx < 0 || adc_idx >= AUTO_CFG_MAX_INS) {
snd_BUG();
return NULL;
}
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gen_spec *spec = codec->spec;
- unsigned int adc_idx = kcontrol->id.index;
+ /* the ctls are created at once with multiple counts */
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
return 0;
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
- unsigned int adc_idx = kcontrol->id.index;
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
return mux_select(codec, adc_idx,
ucontrol->value.enumerated.item[0]);
}
error:
codec->cached_write = 0;
mutex_unlock(&codec->control_mutex);
- snd_hda_codec_flush_amp_cache(codec); /* flush the updates */
+ snd_hda_codec_flush_cache(codec); /* flush the updates */
if (err >= 0 && spec->cap_sync_hook)
- spec->cap_sync_hook(codec);
+ spec->cap_sync_hook(codec, ucontrol);
return err;
}
static int cap_sw_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
- struct hda_gen_spec *spec = codec->spec;
- int ret;
-
- ret = cap_put_caller(kcontrol, ucontrol,
+ return cap_put_caller(kcontrol, ucontrol,
snd_hda_mixer_amp_switch_put,
NID_PATH_MUTE_CTL);
- if (ret < 0)
- return ret;
-
- if (spec->capture_switch_hook) {
- bool enable = (ucontrol->value.integer.value[0] ||
- ucontrol->value.integer.value[1]);
- spec->capture_switch_hook(codec, enable);
- }
-
- return ret;
}
static const struct snd_kcontrol_new cap_sw_temp = {
return false;
}
+/* capture switch put callback for a single control with hook call */
+static int cap_single_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gen_spec *spec = codec->spec;
+ int ret;
+
+ ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ if (spec->cap_sync_hook)
+ spec->cap_sync_hook(codec, ucontrol);
+
+ return ret;
+}
+
static int add_single_cap_ctl(struct hda_codec *codec, const char *label,
int idx, bool is_switch, unsigned int ctl,
bool inv_dmic)
int type = is_switch ? HDA_CTL_WIDGET_MUTE : HDA_CTL_WIDGET_VOL;
const char *sfx = is_switch ? "Switch" : "Volume";
unsigned int chs = inv_dmic ? 1 : 3;
- int err;
+ struct snd_kcontrol_new *knew;
if (!ctl)
return 0;
else
snprintf(tmpname, sizeof(tmpname),
"Capture %s", sfx);
- err = add_control(spec, type, tmpname, idx,
- amp_val_replace_channels(ctl, chs));
- if (err < 0 || !inv_dmic)
- return err;
+ knew = add_control(spec, type, tmpname, idx,
+ amp_val_replace_channels(ctl, chs));
+ if (!knew)
+ return -ENOMEM;
+ if (is_switch)
+ knew->put = cap_single_sw_put;
+ if (!inv_dmic)
+ return 0;
/* Make independent right kcontrol */
if (label)
else
snprintf(tmpname, sizeof(tmpname),
"Inverted Capture %s", sfx);
- return add_control(spec, type, tmpname, idx,
+ knew = add_control(spec, type, tmpname, idx,
amp_val_replace_channels(ctl, 2));
+ if (!knew)
+ return -ENOMEM;
+ if (is_switch)
+ knew->put = cap_single_sw_put;
+ return 0;
}
/* create single (and simple) capture volume and switch controls */
{
struct hda_gen_spec *spec = codec->spec;
struct hda_input_mux *imux = &spec->input_mux;
- int i, err, type, type_idx = 0;
- const char *prev_label = NULL;
+ int i, err, type;
for (i = 0; i < imux->num_items; i++) {
- const char *label;
bool inv_dmic;
- label = hda_get_autocfg_input_label(codec, &spec->autocfg, i);
- if (prev_label && !strcmp(label, prev_label))
- type_idx++;
- else
- type_idx = 0;
- prev_label = label;
+ int idx;
+
+ idx = imux->items[i].index;
+ if (idx >= spec->autocfg.num_inputs)
+ continue;
inv_dmic = is_inv_dmic_pin(codec, spec->imux_pins[i]);
for (type = 0; type < 2; type++) {
- err = add_single_cap_ctl(codec, label, type_idx, type,
+ err = add_single_cap_ctl(codec,
+ spec->input_labels[idx],
+ spec->input_label_idxs[idx],
+ type,
get_first_cap_ctl(codec, i, type),
inv_dmic);
if (err < 0)
/*
* add mic boosts if needed
*/
+
+/* check whether the given amp is feasible as a boost volume */
+static bool check_boost_vol(struct hda_codec *codec, hda_nid_t nid,
+ int dir, int idx)
+{
+ unsigned int step;
+
+ if (!nid_has_volume(codec, nid, dir) ||
+ is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) ||
+ is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL))
+ return false;
+
+ step = (query_amp_caps(codec, nid, dir) & AC_AMPCAP_STEP_SIZE)
+ >> AC_AMPCAP_STEP_SIZE_SHIFT;
+ if (step < 0x20)
+ return false;
+ return true;
+}
+
+/* look for a boost amp in a widget close to the pin */
+static unsigned int look_for_boost_amp(struct hda_codec *codec,
+ struct nid_path *path)
+{
+ unsigned int val = 0;
+ hda_nid_t nid;
+ int depth;
+
+ for (depth = 0; depth < 3; depth++) {
+ if (depth >= path->depth - 1)
+ break;
+ nid = path->path[depth];
+ if (depth && check_boost_vol(codec, nid, HDA_OUTPUT, 0)) {
+ val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
+ break;
+ } else if (check_boost_vol(codec, nid, HDA_INPUT,
+ path->idx[depth])) {
+ val = HDA_COMPOSE_AMP_VAL(nid, 3, path->idx[depth],
+ HDA_INPUT);
+ break;
+ }
+ }
+
+ return val;
+}
+
static int parse_mic_boost(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
- int i, err;
- int type_idx = 0;
- hda_nid_t nid;
- const char *prev_label = NULL;
+ struct hda_input_mux *imux = &spec->input_mux;
+ int i;
- for (i = 0; i < cfg->num_inputs; i++) {
- if (cfg->inputs[i].type > AUTO_PIN_MIC)
- break;
- nid = cfg->inputs[i].pin;
- if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP) {
- const char *label;
- char boost_label[44];
- struct nid_path *path;
- unsigned int val;
+ if (!spec->num_adc_nids)
+ return 0;
- if (!nid_has_volume(codec, nid, HDA_INPUT))
- continue;
+ for (i = 0; i < imux->num_items; i++) {
+ struct nid_path *path;
+ unsigned int val;
+ int idx;
+ char boost_label[44];
- label = hda_get_autocfg_input_label(codec, cfg, i);
- if (prev_label && !strcmp(label, prev_label))
- type_idx++;
- else
- type_idx = 0;
- prev_label = label;
+ idx = imux->items[i].index;
+ if (idx >= imux->num_items)
+ continue;
- snprintf(boost_label, sizeof(boost_label),
- "%s Boost Volume", label);
- val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT);
- err = add_control(spec, HDA_CTL_WIDGET_VOL,
- boost_label, type_idx, val);
- if (err < 0)
- return err;
+ /* check only line-in and mic pins */
+ if (cfg->inputs[idx].type > AUTO_PIN_LINE_IN)
+ continue;
- path = snd_hda_get_nid_path(codec, nid, 0);
- if (path)
- path->ctls[NID_PATH_BOOST_CTL] = val;
- }
+ path = get_input_path(codec, 0, i);
+ if (!path)
+ continue;
+
+ val = look_for_boost_amp(codec, path);
+ if (!val)
+ continue;
+
+ /* create a boost control */
+ snprintf(boost_label, sizeof(boost_label),
+ "%s Boost Volume", spec->input_labels[idx]);
+ if (!add_control(spec, HDA_CTL_WIDGET_VOL, boost_label,
+ spec->input_label_idxs[idx], val))
+ return -ENOMEM;
+
+ path->ctls[NID_PATH_BOOST_CTL] = val;
}
return 0;
}
{
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)
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;
return 0;
snd_hda_activate_path(codec, path, true, false);
if (spec->cap_sync_hook)
- spec->cap_sync_hook(codec);
+ spec->cap_sync_hook(codec, NULL);
+ path_power_down_sync(codec, old_path);
return 1;
}
void snd_hda_gen_hp_automute(struct hda_codec *codec, struct hda_jack_tbl *jack)
{
struct hda_gen_spec *spec = codec->spec;
+ hda_nid_t *pins = spec->autocfg.hp_pins;
+ int num_pins = ARRAY_SIZE(spec->autocfg.hp_pins);
- spec->hp_jack_present =
- detect_jacks(codec, ARRAY_SIZE(spec->autocfg.hp_pins),
- spec->autocfg.hp_pins);
+ /* No detection for the first HP jack during indep-HP mode */
+ if (spec->indep_hp_enabled) {
+ pins++;
+ num_pins--;
+ }
+
+ spec->hp_jack_present = detect_jacks(codec, num_pins, pins);
if (!spec->detect_hp || (!spec->automute_speaker && !spec->automute_lo))
return;
call_update_outputs(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
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;
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);
spec->pcm_playback_hook(hinfo, codec, substream, action);
}
+static void call_pcm_capture_hook(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream,
+ int action)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->pcm_capture_hook)
+ spec->pcm_capture_hook(hinfo, codec, substream, action);
+}
+
/*
* Analog playback callbacks
*/
return 0;
}
+static int capture_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_OPEN);
+ return 0;
+}
+
+static int capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
+ return 0;
+}
+
+static int capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ snd_hda_codec_cleanup_stream(codec, hinfo->nid);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
+ return 0;
+}
+
+static int capture_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ call_pcm_capture_hook(hinfo, codec, substream, HDA_GEN_PCM_ACT_CLOSE);
+ return 0;
+}
+
static int alt_playback_pcm_open(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
struct snd_pcm_substream *substream)
/*
* Analog capture
*/
+#define alt_capture_pcm_open capture_pcm_open
+#define alt_capture_pcm_close capture_pcm_close
+
static int alt_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
unsigned int stream_tag,
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number + 1],
stream_tag, 0, format);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
return 0;
}
snd_hda_codec_cleanup_stream(codec,
spec->adc_nids[substream->number + 1]);
+ call_pcm_capture_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
return 0;
}
.channels_min = 2,
.channels_max = 2,
/* NID is set in build_pcms */
+ .ops = {
+ .open = capture_pcm_open,
+ .close = capture_pcm_close,
+ .prepare = capture_pcm_prepare,
+ .cleanup = capture_pcm_cleanup
+ },
};
static const struct hda_pcm_stream pcm_analog_alt_playback = {
.channels_max = 2,
/* NID is set in build_pcms */
.ops = {
+ .open = alt_capture_pcm_open,
+ .close = alt_capture_pcm_close,
.prepare = alt_capture_pcm_prepare,
.cleanup = alt_capture_pcm_cleanup
},
!spec->dyn_adc_switch && !spec->auto_mic;
/* Additional Analaog capture for index #2 */
if (spec->alt_dac_nid || have_multi_adcs) {
+ fill_pcm_stream_name(spec->stream_name_alt_analog,
+ sizeof(spec->stream_name_alt_analog),
+ " Alt Analog", codec->chip_name);
codec->num_pcms = 3;
info = spec->pcm_rec + 2;
- info->name = spec->stream_name_analog;
+ info->name = spec->stream_name_alt_analog;
if (spec->alt_dac_nid) {
p = spec->stream_analog_alt_playback;
if (!p)
/* 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);
+ resume_path_from_idx(codec, spec->loopback_paths[i]);
+ resume_path_from_idx(codec, spec->loopback_merge_path);
}
}
}
update_shared_mic_hp(codec, spec->cur_mux[0]);
if (spec->cap_sync_hook)
- spec->cap_sync_hook(codec);
+ spec->cap_sync_hook(codec, NULL);
}
/* set right pin controls for digital I/O */
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);
}
}
/* call init functions of standard auto-mute helpers */
update_automute_all(codec);
- snd_hda_codec_flush_amp_cache(codec);
- snd_hda_codec_flush_cmd_cache(codec);
+ snd_hda_codec_flush_cache(codec);
if (spec->vmaster_mute.sw_kctl && spec->vmaster_mute.hook)
snd_hda_sync_vmaster_hook(&spec->vmaster_mute);