#include <linux/slab.h>
#include <linux/export.h>
#include <linux/sort.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/bitops.h>
#include <sound/core.h>
#include <sound/jack.h>
#include "hda_codec.h"
int snd_hda_gen_spec_init(struct hda_gen_spec *spec)
{
snd_array_init(&spec->kctls, sizeof(struct snd_kcontrol_new), 32);
- snd_array_init(&spec->bind_ctls, sizeof(struct hda_bind_ctls *), 8);
snd_array_init(&spec->paths, sizeof(struct nid_path), 8);
+ mutex_init(&spec->pcm_mutex);
return 0;
}
EXPORT_SYMBOL_HDA(snd_hda_gen_spec_init);
snd_array_free(&spec->kctls);
}
-static struct hda_bind_ctls *new_bind_ctl(struct hda_codec *codec,
- unsigned int nums,
- struct hda_ctl_ops *ops)
+void snd_hda_gen_spec_free(struct hda_gen_spec *spec)
+{
+ if (!spec)
+ return;
+ free_kctls(spec);
+ snd_array_free(&spec->paths);
+}
+EXPORT_SYMBOL_HDA(snd_hda_gen_spec_free);
+
+/*
+ * store user hints
+ */
+static void parse_user_hints(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
- struct hda_bind_ctls **ctlp, *ctl;
- ctlp = snd_array_new(&spec->bind_ctls);
- if (!ctlp)
- return NULL;
- ctl = kzalloc(sizeof(*ctl) + sizeof(long) * (nums + 1), GFP_KERNEL);
- *ctlp = ctl;
- if (ctl)
- ctl->ops = ops;
- return ctl;
+ int val;
+
+ val = snd_hda_get_bool_hint(codec, "jack_detect");
+ if (val >= 0)
+ codec->no_jack_detect = !val;
+ val = snd_hda_get_bool_hint(codec, "inv_jack_detect");
+ if (val >= 0)
+ codec->inv_jack_detect = !!val;
+ val = snd_hda_get_bool_hint(codec, "trigger_sense");
+ if (val >= 0)
+ codec->no_trigger_sense = !val;
+ val = snd_hda_get_bool_hint(codec, "inv_eapd");
+ if (val >= 0)
+ codec->inv_eapd = !!val;
+ val = snd_hda_get_bool_hint(codec, "pcm_format_first");
+ if (val >= 0)
+ codec->pcm_format_first = !!val;
+ val = snd_hda_get_bool_hint(codec, "sticky_stream");
+ if (val >= 0)
+ codec->no_sticky_stream = !val;
+ val = snd_hda_get_bool_hint(codec, "spdif_status_reset");
+ if (val >= 0)
+ codec->spdif_status_reset = !!val;
+ val = snd_hda_get_bool_hint(codec, "pin_amp_workaround");
+ if (val >= 0)
+ codec->pin_amp_workaround = !!val;
+ val = snd_hda_get_bool_hint(codec, "single_adc_amp");
+ if (val >= 0)
+ codec->single_adc_amp = !!val;
+
+ val = snd_hda_get_bool_hint(codec, "auto_mute");
+ if (val >= 0)
+ spec->suppress_auto_mute = !val;
+ val = snd_hda_get_bool_hint(codec, "auto_mic");
+ if (val >= 0)
+ spec->suppress_auto_mic = !val;
+ val = snd_hda_get_bool_hint(codec, "line_in_auto_switch");
+ if (val >= 0)
+ spec->line_in_auto_switch = !!val;
+ val = snd_hda_get_bool_hint(codec, "need_dac_fix");
+ if (val >= 0)
+ spec->need_dac_fix = !!val;
+ val = snd_hda_get_bool_hint(codec, "primary_hp");
+ if (val >= 0)
+ spec->no_primary_hp = !val;
+ val = snd_hda_get_bool_hint(codec, "multi_cap_vol");
+ if (val >= 0)
+ spec->multi_cap_vol = !!val;
+ val = snd_hda_get_bool_hint(codec, "inv_dmic_split");
+ if (val >= 0)
+ spec->inv_dmic_split = !!val;
+ val = snd_hda_get_bool_hint(codec, "indep_hp");
+ if (val >= 0)
+ spec->indep_hp = !!val;
+ val = snd_hda_get_bool_hint(codec, "add_stereo_mix_input");
+ if (val >= 0)
+ spec->add_stereo_mix_input = !!val;
+ val = snd_hda_get_bool_hint(codec, "add_out_jack_modes");
+ if (val >= 0)
+ spec->add_out_jack_modes = !!val;
+ val = snd_hda_get_bool_hint(codec, "add_in_jack_modes");
+ if (val >= 0)
+ spec->add_in_jack_modes = !!val;
+
+ if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
+ spec->mixer_nid = val;
}
-static void free_bind_ctls(struct hda_gen_spec *spec)
+/*
+ * pin control value accesses
+ */
+
+#define update_pin_ctl(codec, pin, val) \
+ snd_hda_codec_update_cache(codec, pin, 0, \
+ AC_VERB_SET_PIN_WIDGET_CONTROL, val)
+
+/* restore the pinctl based on the cached value */
+static inline void restore_pin_ctl(struct hda_codec *codec, hda_nid_t pin)
{
- if (spec->bind_ctls.list) {
- struct hda_bind_ctls **ctl = spec->bind_ctls.list;
- int i;
- for (i = 0; i < spec->bind_ctls.used; i++)
- kfree(ctl[i]);
- }
- snd_array_free(&spec->bind_ctls);
+ update_pin_ctl(codec, pin, snd_hda_codec_get_pin_target(codec, pin));
}
-void snd_hda_gen_spec_free(struct hda_gen_spec *spec)
+/* set the pinctl target value and write it if requested */
+static void set_pin_target(struct hda_codec *codec, hda_nid_t pin,
+ unsigned int val, bool do_write)
{
- if (!spec)
+ if (!pin)
return;
- free_kctls(spec);
- free_bind_ctls(spec);
- snd_array_free(&spec->paths);
+ val = snd_hda_correct_pin_ctl(codec, pin, val);
+ snd_hda_codec_set_pin_target(codec, pin, val);
+ if (do_write)
+ update_pin_ctl(codec, pin, val);
+}
+
+/* set pinctl target values for all given pins */
+static void set_pin_targets(struct hda_codec *codec, int num_pins,
+ hda_nid_t *pins, unsigned int val)
+{
+ int i;
+ for (i = 0; i < num_pins; i++)
+ set_pin_target(codec, pins[i], val, false);
}
-EXPORT_SYMBOL_HDA(snd_hda_gen_spec_free);
/*
* parsing paths
*/
-/* get the path between the given NIDs;
- * passing 0 to either @pin or @dac behaves as a wildcard
- */
-struct nid_path *snd_hda_get_nid_path(struct hda_codec *codec,
- hda_nid_t from_nid, hda_nid_t to_nid)
+/* return the position of NID in the list, or -1 if not found */
+static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
+{
+ int i;
+ for (i = 0; i < nums; i++)
+ if (list[i] == nid)
+ return i;
+ return -1;
+}
+
+/* return true if the given NID is contained in the path */
+static bool is_nid_contained(struct nid_path *path, hda_nid_t nid)
+{
+ return find_idx_in_nid_list(nid, path->path, path->depth) >= 0;
+}
+
+static struct nid_path *get_nid_path(struct hda_codec *codec,
+ hda_nid_t from_nid, hda_nid_t to_nid,
+ int anchor_nid)
{
struct hda_gen_spec *spec = codec->spec;
int i;
if (path->depth <= 0)
continue;
if ((!from_nid || path->path[0] == from_nid) &&
- (!to_nid || path->path[path->depth - 1] == to_nid))
- return path;
+ (!to_nid || path->path[path->depth - 1] == to_nid)) {
+ if (!anchor_nid ||
+ (anchor_nid > 0 && is_nid_contained(path, anchor_nid)) ||
+ (anchor_nid < 0 && !is_nid_contained(path, anchor_nid)))
+ return path;
+ }
}
return NULL;
}
+
+/* get the path between the given NIDs;
+ * passing 0 to either @pin or @dac behaves as a wildcard
+ */
+struct nid_path *snd_hda_get_nid_path(struct hda_codec *codec,
+ hda_nid_t from_nid, hda_nid_t to_nid)
+{
+ return get_nid_path(codec, from_nid, to_nid, 0);
+}
EXPORT_SYMBOL_HDA(snd_hda_get_nid_path);
+/* get the index number corresponding to the path instance;
+ * the index starts from 1, for easier checking the invalid value
+ */
+int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *array = spec->paths.list;
+ ssize_t idx;
+
+ if (!spec->paths.used)
+ return 0;
+ idx = path - array;
+ if (idx < 0 || idx >= spec->paths.used)
+ return 0;
+ return idx + 1;
+}
+
+/* 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)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (idx <= 0 || idx > spec->paths.used)
+ return NULL;
+ return snd_array_elem(&spec->paths, idx - 1);
+}
+
/* 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)
+{
+ char buf[40];
+ int i;
+
+
+ buf[0] = 0;
+ for (i = 0; i < path->depth; i++) {
+ char tmp[4];
+ sprintf(tmp, ":%02x", path->path[i]);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ snd_printdd("%s path: depth=%d %s\n", pfx, path->depth, buf);
}
/* called recursively */
static bool __parse_nid_path(struct hda_codec *codec,
hda_nid_t from_nid, hda_nid_t to_nid,
- int with_aa_mix, struct nid_path *path, int depth)
+ int anchor_nid, struct nid_path *path,
+ int depth)
{
- struct hda_gen_spec *spec = codec->spec;
- hda_nid_t conn[16];
+ const hda_nid_t *conn;
int i, nums;
- if (to_nid == spec->mixer_nid) {
- if (!with_aa_mix)
- return false;
- with_aa_mix = 2; /* mark aa-mix is included */
- }
+ if (to_nid == anchor_nid)
+ anchor_nid = 0; /* anchor passed */
+ else if (to_nid == (hda_nid_t)(-anchor_nid))
+ return false; /* hit the exclusive nid */
- nums = snd_hda_get_connections(codec, to_nid, conn, ARRAY_SIZE(conn));
+ nums = snd_hda_get_conn_list(codec, to_nid, &conn);
for (i = 0; i < nums; i++) {
if (conn[i] != from_nid) {
/* special case: when from_nid is 0,
is_dac_already_used(codec, conn[i]))
continue;
}
- /* aa-mix is requested but not included? */
- if (!(spec->mixer_nid && with_aa_mix == 1))
+ /* anchor is not requested or already passed? */
+ if (anchor_nid <= 0)
goto found;
}
if (depth >= MAX_NID_PATH_DEPTH)
type == AC_WID_PIN)
continue;
if (__parse_nid_path(codec, from_nid, conn[i],
- with_aa_mix, path, depth + 1))
+ anchor_nid, path, depth + 1))
goto found;
}
return false;
/* parse the widget path from the given nid to the target nid;
* when @from_nid is 0, try to find an empty DAC;
- * when @with_aa_mix is 0, paths with spec->mixer_nid are excluded.
- * when @with_aa_mix is 1, paths without spec->mixer_nid are excluded.
- * when @with_aa_mix is 2, no special handling about spec->mixer_nid.
+ * when @anchor_nid is set to a positive value, only paths through the widget
+ * with the given value are evaluated.
+ * when @anchor_nid is set to a negative value, paths through the widget
+ * with the negative of given value are excluded, only other paths are chosen.
+ * when @anchor_nid is zero, no special handling about path selection.
*/
bool snd_hda_parse_nid_path(struct hda_codec *codec, hda_nid_t from_nid,
- hda_nid_t to_nid, int with_aa_mix,
+ hda_nid_t to_nid, int anchor_nid,
struct nid_path *path)
{
- if (__parse_nid_path(codec, from_nid, to_nid, with_aa_mix, path, 1)) {
+ if (__parse_nid_path(codec, from_nid, to_nid, anchor_nid, path, 1)) {
path->path[path->depth] = to_nid;
path->depth++;
-#if 0
- snd_printdd("path: depth=%d, %02x/%02x/%02x/%02x/%02x\n",
- path->depth, path->path[0], path->path[1],
- path->path[2], path->path[3], path->path[4]);
-#endif
return true;
}
return false;
*/
struct nid_path *
snd_hda_add_new_path(struct hda_codec *codec, hda_nid_t from_nid,
- hda_nid_t to_nid, int with_aa_mix)
+ hda_nid_t to_nid, int anchor_nid)
{
struct hda_gen_spec *spec = codec->spec;
struct nid_path *path;
if (from_nid && to_nid && !is_reachable_path(codec, from_nid, to_nid))
return NULL;
+ /* check whether the path has been already added */
+ path = get_nid_path(codec, from_nid, to_nid, anchor_nid);
+ if (path)
+ return path;
+
path = snd_array_new(&spec->paths);
if (!path)
return NULL;
memset(path, 0, sizeof(*path));
- if (snd_hda_parse_nid_path(codec, from_nid, to_nid, with_aa_mix, path))
+ if (snd_hda_parse_nid_path(codec, from_nid, to_nid, anchor_nid, path))
return path;
/* push back */
spec->paths.used--;
}
EXPORT_SYMBOL_HDA(snd_hda_add_new_path);
+/* clear the given path as invalid so that it won't be picked up later */
+static void invalidate_nid_path(struct hda_codec *codec, int idx)
+{
+ struct nid_path *path = snd_hda_get_path_from_idx(codec, idx);
+ if (!path)
+ return;
+ memset(path, 0, sizeof(*path));
+}
+
/* look for an empty DAC slot */
static hda_nid_t look_for_dac(struct hda_codec *codec, hda_nid_t pin,
bool is_digital)
return false;
}
+static bool same_amp_caps(struct hda_codec *codec, hda_nid_t nid1,
+ hda_nid_t nid2, int dir)
+{
+ if (!(get_wcaps(codec, nid1) & (1 << (dir + 1))))
+ return !(get_wcaps(codec, nid2) & (1 << (dir + 1)));
+ return (query_amp_caps(codec, nid1, dir) ==
+ query_amp_caps(codec, nid2, dir));
+}
+
#define nid_has_mute(codec, nid, dir) \
check_amp_caps(codec, nid, dir, AC_AMPCAP_MUTE)
#define nid_has_volume(codec, nid, dir) \
/* 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) ||
- is_active_nid(codec, nid, dir, idx))
+ unsigned int caps;
+ unsigned int mask, val;
+
+ if (!enable && is_active_nid(codec, nid, dir, idx))
+ return;
+
+ 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 = get_amp_val_to_activate(codec, nid, dir, enable);
- snd_hda_codec_amp_stereo(codec, nid, dir, idx, 0xff, val);
+
+ 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,
int i, bool enable, bool add_aamix)
{
struct hda_gen_spec *spec = codec->spec;
- hda_nid_t conn[16];
+ const hda_nid_t *conn;
int n, nums, idx;
int type;
hda_nid_t nid = path->path[i];
- nums = snd_hda_get_connections(codec, nid, conn, ARRAY_SIZE(conn));
+ nums = snd_hda_get_conn_list(codec, nid, &conn);
type = get_wcaps_type(get_wcaps(codec, nid));
if (type == AC_WID_PIN ||
(type == AC_WID_AUD_IN && codec->single_adc_amp)) {
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))
continue;
- activate_amp(codec, nid, HDA_INPUT, n, enable);
+ activate_amp(codec, nid, HDA_INPUT, n, idx, enable);
}
}
}
EXPORT_SYMBOL_HDA(snd_hda_activate_path);
+/* turn on/off EAPD on the given pin */
+static void set_pin_eapd(struct hda_codec *codec, hda_nid_t pin, bool enable)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ if (spec->own_eapd_ctl ||
+ !(snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_EAPD))
+ return;
+ if (codec->inv_eapd)
+ enable = !enable;
+ snd_hda_codec_update_cache(codec, pin, 0,
+ AC_VERB_SET_EAPD_BTLENABLE,
+ enable ? 0x02 : 0x00);
+}
+
/*
* Helper functions for creating mixer ctl elements
HDA_CTL_WIDGET_VOL,
HDA_CTL_WIDGET_MUTE,
HDA_CTL_BIND_MUTE,
- HDA_CTL_BIND_VOL,
- HDA_CTL_BIND_SW,
};
static const struct snd_kcontrol_new control_templates[] = {
HDA_CODEC_VOLUME(NULL, 0, 0, 0),
HDA_CODEC_MUTE(NULL, 0, 0, 0),
HDA_BIND_MUTE(NULL, 0, 0, 0),
- HDA_BIND_VOL(NULL, 0),
- HDA_BIND_SW(NULL, 0),
};
/* 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";
BAD_SHARED_VOL = 0x10,
};
-/* look for widgets in the path between the given NIDs appropriate for
+/* look for widgets in the given path which are appropriate for
* volume and mute controls, and assign the values to ctls[].
*
* When no appropriate widget is found in the path, the badness value
* is incremented depending on the situation. The function returns the
* total badness for both volume and mute controls.
*/
-static int assign_out_path_ctls(struct hda_codec *codec, hda_nid_t pin,
- hda_nid_t dac)
+static int assign_out_path_ctls(struct hda_codec *codec, struct nid_path *path)
{
- struct nid_path *path = snd_hda_get_nid_path(codec, dac, pin);
hda_nid_t nid;
unsigned int val;
int badness = 0;
if (!path)
return BAD_SHARED_VOL * 2;
+
+ if (path->ctls[NID_PATH_VOL_CTL] ||
+ path->ctls[NID_PATH_MUTE_CTL])
+ return 0; /* already evaluated */
+
nid = look_for_out_vol_nid(codec, path);
if (nid) {
val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT);
.shared_surr_main = BAD_NO_EXTRA_SURR_DAC,
};
+/* get the DAC of the primary output corresponding to the given array index */
+static hda_nid_t get_primary_out(struct hda_codec *codec, int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+
+ if (cfg->line_outs > idx)
+ return spec->private_dac_nids[idx];
+ idx -= cfg->line_outs;
+ if (spec->multi_ios > idx)
+ return spec->multi_io[idx].dac;
+ return 0;
+}
+
+/* return the DAC if it's reachable, otherwise zero */
+static inline hda_nid_t try_dac(struct hda_codec *codec,
+ hda_nid_t dac, hda_nid_t pin)
+{
+ return is_reachable_path(codec, dac, pin) ? dac : 0;
+}
+
/* try to assign DACs to pins and return the resultant badness */
static int try_assign_dacs(struct hda_codec *codec, int num_outs,
const hda_nid_t *pins, hda_nid_t *dacs,
+ int *path_idx,
const struct badness_table *bad)
{
struct hda_gen_spec *spec = codec->spec;
- struct auto_pin_cfg *cfg = &spec->autocfg;
int i, j;
int badness = 0;
hda_nid_t dac;
return 0;
for (i = 0; i < num_outs; i++) {
+ struct nid_path *path;
hda_nid_t pin = pins[i];
- if (!dacs[i])
- dacs[i] = look_for_dac(codec, pin, false);
+
+ path = snd_hda_get_path_from_idx(codec, path_idx[i]);
+ if (path) {
+ badness += assign_out_path_ctls(codec, path);
+ continue;
+ }
+
+ dacs[i] = look_for_dac(codec, pin, false);
if (!dacs[i] && !i) {
+ /* try to steal the DAC of surrounds for the front */
for (j = 1; j < num_outs; j++) {
if (is_reachable_path(codec, dacs[j], pin)) {
dacs[0] = dacs[j];
dacs[j] = 0;
+ invalidate_nid_path(codec, path_idx[j]);
+ path_idx[j] = 0;
break;
}
}
}
dac = dacs[i];
if (!dac) {
- if (is_reachable_path(codec, dacs[0], pin))
- dac = dacs[0];
- else if (cfg->line_outs > i &&
- is_reachable_path(codec, spec->private_dac_nids[i], pin))
- dac = spec->private_dac_nids[i];
+ if (num_outs > 2)
+ dac = try_dac(codec, get_primary_out(codec, i), pin);
+ if (!dac)
+ dac = try_dac(codec, dacs[0], pin);
+ if (!dac)
+ dac = try_dac(codec, get_primary_out(codec, i), pin);
if (dac) {
if (!i)
badness += bad->shared_primary;
else
badness += bad->no_dac;
}
- if (!snd_hda_add_new_path(codec, dac, pin, 0))
+ 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)
dac = dacs[i] = 0;
- if (dac)
- badness += assign_out_path_ctls(codec, pin, 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);
+ }
}
return badness;
return true;
}
+/* count the number of input pins that are capable to be multi-io */
+static int count_multiio_pins(struct hda_codec *codec, hda_nid_t reference_pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct auto_pin_cfg *cfg = &spec->autocfg;
+ unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin);
+ unsigned int location = get_defcfg_location(defcfg);
+ int type, i;
+ int num_pins = 0;
+
+ for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
+ for (i = 0; i < cfg->num_inputs; i++) {
+ if (cfg->inputs[i].type != type)
+ continue;
+ if (can_be_multiio_pin(codec, location,
+ cfg->inputs[i].pin))
+ num_pins++;
+ }
+ }
+ return num_pins;
+}
+
/*
* multi-io helper
*
*/
static int fill_multi_ios(struct hda_codec *codec,
hda_nid_t reference_pin,
- bool hardwired, int offset)
+ bool hardwired)
{
struct hda_gen_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
- int type, i, j, dacs, num_pins, old_pins;
+ int type, i, j, num_pins, old_pins;
unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin);
unsigned int location = get_defcfg_location(defcfg);
int badness = 0;
+ struct nid_path *path;
old_pins = spec->multi_ios;
if (old_pins >= 2)
goto end_fill;
- num_pins = 0;
- for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
- for (i = 0; i < cfg->num_inputs; i++) {
- if (cfg->inputs[i].type != type)
- continue;
- if (can_be_multiio_pin(codec, location,
- cfg->inputs[i].pin))
- num_pins++;
- }
- }
+ num_pins = count_multiio_pins(codec, reference_pin);
if (num_pins < 2)
goto end_fill;
- dacs = spec->multiout.num_dacs;
for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) {
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t nid = cfg->inputs[i].pin;
if (j < spec->multi_ios)
continue;
- if (offset && offset + spec->multi_ios < dacs) {
- dac = spec->private_dac_nids[offset + spec->multi_ios];
- if (!is_reachable_path(codec, dac, nid))
- dac = 0;
- }
if (hardwired)
dac = get_dac_if_single(codec, nid);
else if (!dac)
badness++;
continue;
}
- if (!snd_hda_add_new_path(codec, dac, nid, 0)) {
+ path = snd_hda_add_new_path(codec, dac, nid,
+ -spec->mixer_nid);
+ if (!path) {
badness++;
continue;
}
+ /* 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] =
+ snd_hda_get_path_idx(codec, path);
spec->multi_ios++;
if (spec->multi_ios >= 2)
break;
}
/* assign volume and mute controls */
- for (i = old_pins; i < spec->multi_ios; i++)
- badness += assign_out_path_ctls(codec, spec->multi_io[i].pin,
- spec->multi_io[i].dac);
+ for (i = old_pins; i < spec->multi_ios; i++) {
+ path = snd_hda_get_path_from_idx(codec, spec->out_paths[cfg->line_outs + i]);
+ badness += assign_out_path_ctls(codec, path);
+ }
return badness;
}
/* map DACs for all pins in the list if they are single connections */
static bool map_singles(struct hda_codec *codec, int outs,
- const hda_nid_t *pins, hda_nid_t *dacs)
+ const hda_nid_t *pins, hda_nid_t *dacs, int *path_idx)
{
+ struct hda_gen_spec *spec = codec->spec;
int i;
bool found = false;
for (i = 0; i < outs; i++) {
+ struct nid_path *path;
hda_nid_t dac;
if (dacs[i])
continue;
dac = get_dac_if_single(codec, pins[i]);
if (!dac)
continue;
- if (snd_hda_add_new_path(codec, dac, pins[i], 0)) {
+ path = snd_hda_add_new_path(codec, dac, pins[i],
+ -spec->mixer_nid);
+ if (!path && !i && spec->mixer_nid)
+ path = snd_hda_add_new_path(codec, dac, pins[i], 0);
+ if (path) {
dacs[i] = dac;
found = true;
+ /* print_nid_path("output", path); */
+ path->active = true;
+ path_idx[i] = snd_hda_get_path_idx(codec, path);
}
}
return found;
}
+/* create a new path including aamix if available, and return its index */
+static int check_aamix_out_path(struct hda_codec *codec, int path_idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
+
+ 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);
+ if (!path)
+ return 0;
+ /* print_nid_path("output-aamix", path); */
+ path->active = false; /* unused as default */
+ return snd_hda_get_path_idx(codec, path);
+}
+
+/* fill the empty entries in the dac array for speaker/hp with the
+ * shared dac pointed by the paths
+ */
+static void refill_shared_dacs(struct hda_codec *codec, int num_outs,
+ hda_nid_t *dacs, int *path_idx)
+{
+ struct nid_path *path;
+ int i;
+
+ for (i = 0; i < num_outs; i++) {
+ if (dacs[i])
+ continue;
+ path = snd_hda_get_path_from_idx(codec, path_idx[i]);
+ if (!path)
+ continue;
+ dacs[i] = path->path[0];
+ }
+}
+
/* fill in the dac_nids table from the parsed pin configuration */
static int fill_and_eval_dacs(struct hda_codec *codec,
bool fill_hardwired,
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;
memset(spec->multiout.extra_out_nid, 0, sizeof(spec->multiout.extra_out_nid));
spec->multi_ios = 0;
snd_array_free(&spec->paths);
+
+ /* clear path indices */
+ memset(spec->out_paths, 0, sizeof(spec->out_paths));
+ memset(spec->hp_paths, 0, sizeof(spec->hp_paths));
+ memset(spec->speaker_paths, 0, sizeof(spec->speaker_paths));
+ memset(spec->aamix_out_paths, 0, sizeof(spec->aamix_out_paths));
+ memset(spec->digout_paths, 0, sizeof(spec->digout_paths));
+ memset(spec->input_paths, 0, sizeof(spec->input_paths));
+ memset(spec->loopback_paths, 0, sizeof(spec->loopback_paths));
+ memset(&spec->digin_path, 0, sizeof(spec->digin_path));
+
badness = 0;
/* fill hard-wired DACs first */
do {
mapped = map_singles(codec, cfg->line_outs,
cfg->line_out_pins,
- spec->private_dac_nids);
+ spec->private_dac_nids,
+ spec->out_paths);
mapped |= map_singles(codec, cfg->hp_outs,
cfg->hp_pins,
- spec->multiout.hp_out_nid);
+ spec->multiout.hp_out_nid,
+ spec->hp_paths);
mapped |= map_singles(codec, cfg->speaker_outs,
cfg->speaker_pins,
- spec->multiout.extra_out_nid);
+ spec->multiout.extra_out_nid,
+ spec->speaker_paths);
if (fill_mio_first && cfg->line_outs == 1 &&
cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
- err = fill_multi_ios(codec, cfg->line_out_pins[0], true, 0);
+ err = fill_multi_ios(codec, cfg->line_out_pins[0], true);
if (!err)
mapped = true;
}
}
badness += try_assign_dacs(codec, cfg->line_outs, cfg->line_out_pins,
- spec->private_dac_nids,
+ spec->private_dac_nids, spec->out_paths,
&main_out_badness);
- /* re-count num_dacs and squash invalid entries */
- spec->multiout.num_dacs = 0;
- for (i = 0; i < cfg->line_outs; i++) {
- if (spec->private_dac_nids[i])
- spec->multiout.num_dacs++;
- else {
- memmove(spec->private_dac_nids + i,
- spec->private_dac_nids + i + 1,
- sizeof(hda_nid_t) * (cfg->line_outs - i - 1));
- spec->private_dac_nids[cfg->line_outs - 1] = 0;
- }
- }
-
if (fill_mio_first &&
cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
/* try to fill multi-io first */
- err = fill_multi_ios(codec, cfg->line_out_pins[0], false, 0);
+ err = fill_multi_ios(codec, cfg->line_out_pins[0], false);
if (err < 0)
return err;
/* we don't count badness at this stage yet */
if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
err = try_assign_dacs(codec, cfg->hp_outs, cfg->hp_pins,
spec->multiout.hp_out_nid,
+ spec->hp_paths,
&extra_out_badness);
if (err < 0)
return err;
err = try_assign_dacs(codec, cfg->speaker_outs,
cfg->speaker_pins,
spec->multiout.extra_out_nid,
- &extra_out_badness);
+ spec->speaker_paths,
+ &extra_out_badness);
if (err < 0)
return err;
badness += err;
}
if (cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
- err = fill_multi_ios(codec, cfg->line_out_pins[0], false, 0);
+ err = fill_multi_ios(codec, cfg->line_out_pins[0], false);
if (err < 0)
return err;
badness += err;
}
- if (cfg->hp_outs && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) {
- /* try multi-ios with HP + inputs */
- int offset = 0;
- if (cfg->line_outs >= 3)
- offset = 1;
- err = fill_multi_ios(codec, cfg->hp_pins[0], false, offset);
- if (err < 0)
- return err;
- badness += err;
+
+ if (spec->mixer_nid) {
+ spec->aamix_out_paths[0] =
+ check_aamix_out_path(codec, spec->out_paths[0]);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ spec->aamix_out_paths[1] =
+ check_aamix_out_path(codec, spec->hp_paths[0]);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ spec->aamix_out_paths[2] =
+ check_aamix_out_path(codec, spec->speaker_paths[0]);
+ }
+
+ if (cfg->hp_outs && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
+ if (count_multiio_pins(codec, cfg->hp_pins[0]) >= 2)
+ spec->multi_ios = 1; /* give badness */
+
+ /* re-count num_dacs and squash invalid entries */
+ spec->multiout.num_dacs = 0;
+ for (i = 0; i < cfg->line_outs; i++) {
+ if (spec->private_dac_nids[i])
+ spec->multiout.num_dacs++;
+ else {
+ memmove(spec->private_dac_nids + i,
+ spec->private_dac_nids + i + 1,
+ sizeof(hda_nid_t) * (cfg->line_outs - i - 1));
+ spec->private_dac_nids[cfg->line_outs - 1] = 0;
+ }
}
+ spec->ext_channel_count = spec->min_channel_count =
+ spec->multiout.num_dacs * 2;
+
if (spec->multi_ios == 2) {
for (i = 0; i < 2; i++)
spec->private_dac_nids[spec->multiout.num_dacs++] =
spec->multi_io[i].dac;
- spec->ext_channel_count = 2;
} else if (spec->multi_ios) {
spec->multi_ios = 0;
badness += BAD_MULTI_IO;
}
+ /* re-fill the shared DAC for speaker / headphone */
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ refill_shared_dacs(codec, cfg->hp_outs,
+ spec->multiout.hp_out_nid,
+ spec->hp_paths);
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ refill_shared_dacs(codec, cfg->speaker_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)
{
- debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x\n",
- cfg->line_out_pins[0], cfg->line_out_pins[1],
- cfg->line_out_pins[2], cfg->line_out_pins[2],
+ 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)
+{
+ struct hda_gen_spec *spec = codec->spec;
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+ static const char * const lo_type[3] = { "LO", "SP", "HP" };
+#endif
+ 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[2],
+ 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)
bool best_wired = true, best_mio = true;
bool hp_spk_swapped = false;
- fill_all_dac_nids(codec);
-
best_cfg = kmalloc(sizeof(*best_cfg), GFP_KERNEL);
if (!best_cfg)
return -ENOMEM;
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;
}
if (badness) {
+ debug_badness("==> restoring best_cfg\n");
*cfg = *best_cfg;
fill_and_eval_dacs(codec, best_wired, best_mio);
}
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;
- path = snd_hda_get_nid_path(codec,
- spec->multiout.dac_nids[0],
- cfg->line_out_pins[0]);
+ path = snd_hda_get_path_from_idx(codec, spec->out_paths[0]);
if (path)
spec->vmaster_nid = look_for_out_vol_nid(codec, path);
+ if (spec->vmaster_nid)
+ snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
+ HDA_OUTPUT, spec->vmaster_tlv);
}
kfree(best_cfg);
for (i = 0; i < noutputs; i++) {
const char *name;
int index;
- hda_nid_t dac, pin;
struct nid_path *path;
- dac = spec->multiout.dac_nids[i];
- if (!dac)
- continue;
- if (i >= cfg->line_outs) {
- pin = spec->multi_io[i - 1].pin;
- index = 0;
- name = channel_name[i];
- } else {
- pin = cfg->line_out_pins[i];
- name = get_line_out_pfx(spec, i, true, &index);
- }
-
- path = snd_hda_get_nid_path(codec, dac, pin);
+ 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 int create_extra_out(struct hda_codec *codec, hda_nid_t pin,
- hda_nid_t dac, const char *pfx, int cidx)
+static int create_extra_out(struct hda_codec *codec, int path_idx,
+ const char *pfx, int cidx)
{
struct nid_path *path;
int err;
- path = snd_hda_get_nid_path(codec, dac, pin);
+ path = snd_hda_get_path_from_idx(codec, path_idx);
if (!path)
return 0;
- /* bind volume control will be created in the case of dac = 0 */
- if (dac) {
- err = add_stereo_vol(codec, pfx, cidx, path);
- if (err < 0)
- return err;
- }
+ err = add_stereo_vol(codec, pfx, cidx, path);
+ if (err < 0)
+ return err;
err = add_stereo_sw(codec, pfx, cidx, path);
if (err < 0)
return err;
/* add playback controls for speaker and HP outputs */
static int create_extra_outs(struct hda_codec *codec, int num_pins,
- const hda_nid_t *pins, const hda_nid_t *dacs,
- const char *pfx)
+ const int *paths, const char *pfx)
{
- struct hda_gen_spec *spec = codec->spec;
- struct hda_bind_ctls *ctl;
- char name[32];
- int i, n, err;
-
- if (!num_pins || !pins[0])
- return 0;
-
- if (num_pins == 1) {
- hda_nid_t dac = *dacs;
- if (!dac)
- dac = spec->multiout.dac_nids[0];
- return create_extra_out(codec, *pins, dac, pfx, 0);
- }
+ int i;
for (i = 0; i < num_pins; i++) {
- hda_nid_t dac;
- if (dacs[num_pins - 1])
- dac = dacs[i]; /* with individual volumes */
- else
- dac = 0;
- if (num_pins == 2 && i == 1 && !strcmp(pfx, "Speaker")) {
- err = create_extra_out(codec, pins[i], dac,
- "Bass Speaker", 0);
- } else if (num_pins >= 3) {
- snprintf(name, sizeof(name), "%s %s",
+ const char *name;
+ char tmp[44];
+ int err, idx = 0;
+
+ if (num_pins == 2 && i == 1 && !strcmp(pfx, "Speaker"))
+ name = "Bass Speaker";
+ else if (num_pins >= 3) {
+ snprintf(tmp, sizeof(tmp), "%s %s",
pfx, channel_name[i]);
- err = create_extra_out(codec, pins[i], dac, name, 0);
+ name = tmp;
} else {
- err = create_extra_out(codec, pins[i], dac, pfx, i);
+ name = pfx;
+ idx = i;
}
- if (err < 0)
- return err;
- }
- if (dacs[num_pins - 1])
- return 0;
-
- /* Let's create a bind-controls for volumes */
- ctl = new_bind_ctl(codec, num_pins, &snd_hda_bind_vol);
- if (!ctl)
- return -ENOMEM;
- n = 0;
- for (i = 0; i < num_pins; i++) {
- hda_nid_t vol;
- struct nid_path *path;
- if (!pins[i] || !dacs[i])
- continue;
- path = snd_hda_get_nid_path(codec, dacs[i], pins[i]);
- if (!path)
- continue;
- vol = look_for_out_vol_nid(codec, path);
- if (vol)
- ctl->values[n++] =
- HDA_COMPOSE_AMP_VAL(vol, 3, 0, HDA_OUTPUT);
- }
- if (n) {
- snprintf(name, sizeof(name), "%s Playback Volume", pfx);
- err = add_control(spec, HDA_CTL_BIND_VOL, name, 0, (long)ctl);
+ err = create_extra_out(codec, paths[i], name, idx);
if (err < 0)
return err;
}
{
struct hda_gen_spec *spec = codec->spec;
return create_extra_outs(codec, spec->autocfg.hp_outs,
- spec->autocfg.hp_pins,
- spec->multiout.hp_out_nid,
+ spec->hp_paths,
"Headphone");
}
{
struct hda_gen_spec *spec = codec->spec;
return create_extra_outs(codec, spec->autocfg.speaker_outs,
- spec->autocfg.speaker_pins,
- spec->multiout.extra_out_nid,
+ spec->speaker_paths,
"Speaker");
}
+/*
+ * independent HP controls
+ */
+
+static int indep_hp_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_hda_enum_bool_helper_info(kcontrol, uinfo);
+}
+
+static int indep_hp_get(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;
+ ucontrol->value.enumerated.item[0] = spec->indep_hp_enabled;
+ return 0;
+}
+
+static int indep_hp_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;
+ unsigned int select = ucontrol->value.enumerated.item[0];
+ int ret = 0;
+
+ mutex_lock(&spec->pcm_mutex);
+ if (spec->active_streams) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (spec->indep_hp_enabled != select) {
+ spec->indep_hp_enabled = select;
+ if (spec->indep_hp_enabled)
+ spec->multiout.hp_out_nid[0] = 0;
+ else
+ spec->multiout.hp_out_nid[0] = spec->alt_dac_nid;
+ ret = 1;
+ }
+ unlock:
+ mutex_unlock(&spec->pcm_mutex);
+ return ret;
+}
+
+static const struct snd_kcontrol_new indep_hp_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Independent HP",
+ .info = indep_hp_info,
+ .get = indep_hp_get,
+ .put = indep_hp_put,
+};
+
+
+static int create_indep_hp_ctls(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (!spec->indep_hp)
+ return 0;
+ if (!spec->multiout.hp_out_nid[0]) {
+ spec->indep_hp = 0;
+ return 0;
+ }
+
+ spec->indep_hp_enabled = false;
+ spec->alt_dac_nid = spec->multiout.hp_out_nid[0];
+ if (!snd_hda_gen_add_kctl(spec, NULL, &indep_hp_ctl))
+ return -ENOMEM;
+ return 0;
+}
+
/*
* channel mode enum control
*/
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gen_spec *spec = codec->spec;
+ int chs;
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = spec->multi_ios + 1;
if (uinfo->value.enumerated.item > spec->multi_ios)
uinfo->value.enumerated.item = spec->multi_ios;
- sprintf(uinfo->value.enumerated.name, "%dch",
- (uinfo->value.enumerated.item + 1) * 2);
+ chs = uinfo->value.enumerated.item * 2 + spec->min_channel_count;
+ sprintf(uinfo->value.enumerated.name, "%dch", chs);
return 0;
}
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gen_spec *spec = codec->spec;
- ucontrol->value.enumerated.item[0] = (spec->ext_channel_count - 1) / 2;
+ ucontrol->value.enumerated.item[0] =
+ (spec->ext_channel_count - spec->min_channel_count) / 2;
return 0;
}
+static inline struct nid_path *
+get_multiio_path(struct hda_codec *codec, int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ return snd_hda_get_path_from_idx(codec,
+ spec->out_paths[spec->autocfg.line_outs + idx]);
+}
+
+static void update_automute_all(struct hda_codec *codec);
+
static int set_multi_io(struct hda_codec *codec, int idx, bool output)
{
struct hda_gen_spec *spec = codec->spec;
hda_nid_t nid = spec->multi_io[idx].pin;
struct nid_path *path;
- path = snd_hda_get_nid_path(codec, spec->multi_io[idx].dac, nid);
+ path = get_multiio_path(codec, idx);
if (!path)
return -EINVAL;
return 0;
if (output) {
- snd_hda_set_pin_ctl_cache(codec, nid, PIN_OUT);
+ set_pin_target(codec, nid, PIN_OUT, true);
snd_hda_activate_path(codec, path, true, true);
+ set_pin_eapd(codec, nid, true);
} else {
+ set_pin_eapd(codec, nid, false);
snd_hda_activate_path(codec, path, false, true);
- snd_hda_set_pin_ctl_cache(codec, nid,
- spec->multi_io[idx].ctl_in);
+ set_pin_target(codec, nid, spec->multi_io[idx].ctl_in, true);
}
+
+ /* update jack retasking in case it modifies any of them */
+ update_automute_all(codec);
+
return 0;
}
ch = ucontrol->value.enumerated.item[0];
if (ch < 0 || ch > spec->multi_ios)
return -EINVAL;
- if (ch == (spec->ext_channel_count - 1) / 2)
+ if (ch == (spec->ext_channel_count - spec->min_channel_count) / 2)
return 0;
- spec->ext_channel_count = (ch + 1) * 2;
+ spec->ext_channel_count = ch * 2 + spec->min_channel_count;
for (i = 0; i < spec->multi_ios; i++)
set_multi_io(codec, i, i < ch);
spec->multiout.max_channels = max(spec->ext_channel_count,
return 0;
}
+/*
+ * aamix loopback enable/disable switch
+ */
+
+#define loopback_mixing_info indep_hp_info
+
+static int loopback_mixing_get(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;
+ ucontrol->value.enumerated.item[0] = spec->aamix_mode;
+ return 0;
+}
+
+static void update_aamix_paths(struct hda_codec *codec, bool do_mix,
+ int nomix_path_idx, int mix_path_idx)
+{
+ 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 (do_mix) {
+ snd_hda_activate_path(codec, nomix_path, false, true);
+ snd_hda_activate_path(codec, mix_path, true, true);
+ } else {
+ snd_hda_activate_path(codec, mix_path, false, true);
+ snd_hda_activate_path(codec, nomix_path, true, true);
+ }
+}
+
+static int loopback_mixing_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;
+ unsigned int val = ucontrol->value.enumerated.item[0];
+
+ if (val == spec->aamix_mode)
+ return 0;
+ spec->aamix_mode = val;
+ update_aamix_paths(codec, val, spec->out_paths[0],
+ spec->aamix_out_paths[0]);
+ update_aamix_paths(codec, val, spec->hp_paths[0],
+ spec->aamix_out_paths[1]);
+ update_aamix_paths(codec, val, spec->speaker_paths[0],
+ spec->aamix_out_paths[2]);
+ return 1;
+}
+
+static const struct snd_kcontrol_new loopback_mixing_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Loopback Mixing",
+ .info = loopback_mixing_info,
+ .get = loopback_mixing_get,
+ .put = loopback_mixing_put,
+};
+
+static int create_loopback_mixing_ctl(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (!spec->mixer_nid)
+ return 0;
+ if (!(spec->aamix_out_paths[0] || spec->aamix_out_paths[1] ||
+ spec->aamix_out_paths[2]))
+ return 0;
+ if (!snd_hda_gen_add_kctl(spec, NULL, &loopback_mixing_enum))
+ return -ENOMEM;
+ return 0;
+}
+
/*
* shared headphone/mic handling
*/
const hda_nid_t vref_pin = spec->shared_mic_vref_pin;
unsigned int vref_val = snd_hda_get_default_vref(codec, vref_pin);
if (vref_val != AC_PINCTL_VREF_HIZ)
- snd_hda_set_pin_ctl(codec, vref_pin, PIN_IN | (set_as_mic ? vref_val : 0));
+ snd_hda_set_pin_ctl_cache(codec, vref_pin,
+ PIN_IN | (set_as_mic ? vref_val : 0));
}
val = set_as_mic ? val | PIN_IN : PIN_HP;
- snd_hda_set_pin_ctl(codec, pin, val);
+ set_pin_target(codec, pin, val, true);
spec->automute_speaker = !set_as_mic;
call_update_outputs(codec);
return 0;
}
+/*
+ * output jack mode
+ */
+static int out_jack_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "Line Out", "Headphone Out",
+ };
+ return snd_hda_enum_helper_info(kcontrol, uinfo, 2, texts);
+}
+
+static int out_jack_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ if (snd_hda_codec_get_pin_target(codec, nid) == PIN_HP)
+ ucontrol->value.enumerated.item[0] = 1;
+ else
+ ucontrol->value.enumerated.item[0] = 0;
+ return 0;
+}
+
+static int out_jack_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int val;
+
+ val = ucontrol->value.enumerated.item[0] ? PIN_HP : PIN_OUT;
+ if (snd_hda_codec_get_pin_target(codec, nid) == val)
+ return 0;
+ snd_hda_set_pin_ctl_cache(codec, nid, val);
+ return 1;
+}
+
+static const struct snd_kcontrol_new out_jack_mode_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = out_jack_mode_info,
+ .get = out_jack_mode_get,
+ .put = out_jack_mode_put,
+};
+
+static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < spec->kctls.used; i++) {
+ struct snd_kcontrol_new *kctl = snd_array_elem(&spec->kctls, i);
+ if (!strcmp(kctl->name, name) && kctl->index == idx)
+ return true;
+ }
+ return false;
+}
+
+static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin,
+ char *name, size_t name_len)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int idx = 0;
+
+ snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx);
+ strlcat(name, " Jack Mode", name_len);
+
+ for (; find_kctl_name(codec, name, idx); idx++)
+ ;
+}
+
+static int create_out_jack_modes(struct hda_codec *codec, int num_pins,
+ hda_nid_t *pins)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int i;
+
+ for (i = 0; i < num_pins; i++) {
+ hda_nid_t pin = pins[i];
+ unsigned int pincap = snd_hda_query_pin_caps(codec, pin);
+ if ((pincap & AC_PINCAP_OUT) && (pincap & AC_PINCAP_HP_DRV)) {
+ struct snd_kcontrol_new *knew;
+ char name[44];
+ get_jack_mode_name(codec, pin, name, sizeof(name));
+ knew = snd_hda_gen_add_kctl(spec, name,
+ &out_jack_mode_enum);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = pin;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * input jack mode
+ */
+
+/* from AC_PINCTL_VREF_HIZ to AC_PINCTL_VREF_100 */
+#define NUM_VREFS 6
+
+static const char * const vref_texts[NUM_VREFS] = {
+ "Line In", "Mic 50pc Bias", "Mic 0V Bias",
+ "", "Mic 80pc Bias", "Mic 100pc Bias"
+};
+
+static unsigned int get_vref_caps(struct hda_codec *codec, hda_nid_t pin)
+{
+ unsigned int pincap;
+
+ pincap = snd_hda_query_pin_caps(codec, pin);
+ pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT;
+ /* filter out unusual vrefs */
+ pincap &= ~(AC_PINCAP_VREF_GRD | AC_PINCAP_VREF_100);
+ return pincap;
+}
+
+/* convert from the enum item index to the vref ctl index (0=HIZ, 1=50%...) */
+static int get_vref_idx(unsigned int vref_caps, unsigned int item_idx)
+{
+ unsigned int i, n = 0;
+
+ for (i = 0; i < NUM_VREFS; i++) {
+ if (vref_caps & (1 << i)) {
+ if (n == item_idx)
+ return i;
+ n++;
+ }
+ }
+ return 0;
+}
+
+/* convert back from the vref ctl index to the enum item index */
+static int cvt_from_vref_idx(unsigned int vref_caps, unsigned int idx)
+{
+ unsigned int i, n = 0;
+
+ for (i = 0; i < NUM_VREFS; i++) {
+ if (i == idx)
+ return n;
+ if (vref_caps & (1 << i))
+ n++;
+ }
+ return 0;
+}
+
+static int in_jack_mode_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+
+ snd_hda_enum_helper_info(kcontrol, uinfo, hweight32(vref_caps),
+ vref_texts);
+ /* set the right text */
+ strcpy(uinfo->value.enumerated.name,
+ vref_texts[get_vref_idx(vref_caps, uinfo->value.enumerated.item)]);
+ return 0;
+}
+
+static int in_jack_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+ unsigned int idx;
+
+ idx = snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_VREFEN;
+ ucontrol->value.enumerated.item[0] = cvt_from_vref_idx(vref_caps, idx);
+ return 0;
+}
+
+static int in_jack_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int vref_caps = get_vref_caps(codec, nid);
+ unsigned int val, idx;
+
+ val = snd_hda_codec_get_pin_target(codec, nid);
+ idx = cvt_from_vref_idx(vref_caps, val & AC_PINCTL_VREFEN);
+ if (idx == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ val &= ~AC_PINCTL_VREFEN;
+ val |= get_vref_idx(vref_caps, ucontrol->value.enumerated.item[0]);
+ snd_hda_set_pin_ctl_cache(codec, nid, val);
+ return 1;
+}
+
+static const struct snd_kcontrol_new in_jack_mode_enum = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = in_jack_mode_info,
+ .get = in_jack_mode_get,
+ .put = in_jack_mode_put,
+};
+
+static int create_in_jack_mode(struct hda_codec *codec, hda_nid_t pin)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ unsigned int defcfg;
+ struct snd_kcontrol_new *knew;
+ char name[44];
+
+ /* no jack mode for fixed pins */
+ defcfg = snd_hda_codec_get_pincfg(codec, pin);
+ if (snd_hda_get_input_pin_attr(defcfg) == INPUT_PIN_ATTR_INT)
+ return 0;
+
+ /* no multiple vref caps? */
+ if (hweight32(get_vref_caps(codec, pin)) <= 1)
+ return 0;
+
+ get_jack_mode_name(codec, pin, name, sizeof(name));
+ knew = snd_hda_gen_add_kctl(spec, name, &in_jack_mode_enum);
+ if (!knew)
+ return -ENOMEM;
+ knew->private_value = pin;
+ return 0;
+}
+
/*
* Parse input paths
#endif
/* create input playback/capture controls for the given pin */
-static int new_analog_input(struct hda_codec *codec, hda_nid_t pin,
- const char *ctlname, int ctlidx,
+static int new_analog_input(struct hda_codec *codec, int input_idx,
+ hda_nid_t pin, const char *ctlname, int ctlidx,
hda_nid_t mix_nid)
{
struct hda_gen_spec *spec = codec->spec;
!nid_has_mute(codec, mix_nid, HDA_INPUT))
return 0; /* no need for analog loopback */
- path = snd_hda_add_new_path(codec, pin, mix_nid, 2);
+ path = snd_hda_add_new_path(codec, pin, mix_nid, 0);
if (!path)
return -EINVAL;
+ print_nid_path("loopback", path);
+ spec->loopback_paths[input_idx] = snd_hda_get_path_idx(codec, path);
idx = path->idx[path->depth - 1];
if (nid_has_volume(codec, mix_nid, HDA_INPUT)) {
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;
}
{
struct hda_gen_spec *spec = codec->spec;
struct hda_input_mux *imux = &spec->input_mux;
- hda_nid_t adc_nids[ARRAY_SIZE(spec->adc_nids)];
+ unsigned int ok_bits;
int i, n, nums;
- hda_nid_t pin, adc;
again:
nums = 0;
+ ok_bits = 0;
for (n = 0; n < spec->num_adc_nids; n++) {
- adc = spec->adc_nids[n];
for (i = 0; i < imux->num_items; i++) {
- pin = spec->imux_pins[i];
- if (!is_reachable_path(codec, pin, adc))
+ if (!spec->input_paths[i][n])
break;
}
- if (i >= imux->num_items)
- adc_nids[nums++] = adc;
+ if (i >= imux->num_items) {
+ ok_bits |= (1 << n);
+ nums++;
+ }
}
- if (!nums) {
+ if (!ok_bits) {
if (spec->shared_mic_hp) {
spec->shared_mic_hp = 0;
imux->num_items = 1;
/* check whether ADC-switch is possible */
for (i = 0; i < imux->num_items; i++) {
- pin = spec->imux_pins[i];
for (n = 0; n < spec->num_adc_nids; n++) {
- adc = spec->adc_nids[n];
- if (is_reachable_path(codec, pin, adc)) {
+ if (spec->input_paths[i][n]) {
spec->dyn_adc_idx[i] = n;
break;
}
snd_printdd("hda-codec: enabling ADC switching\n");
spec->dyn_adc_switch = 1;
} else if (nums != spec->num_adc_nids) {
- memcpy(spec->adc_nids, adc_nids, nums * sizeof(hda_nid_t));
+ /* shrink the invalid adcs and input paths */
+ nums = 0;
+ for (n = 0; n < spec->num_adc_nids; n++) {
+ if (!(ok_bits & (1 << n)))
+ continue;
+ if (n != nums) {
+ spec->adc_nids[nums] = spec->adc_nids[n];
+ for (i = 0; i < imux->num_items; i++) {
+ invalidate_nid_path(codec,
+ spec->input_paths[i][nums]);
+ spec->input_paths[i][nums] =
+ spec->input_paths[i][n];
+ }
+ }
+ nums++;
+ }
spec->num_adc_nids = nums;
}
return 0;
}
+/* 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 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;
+ int imux_idx = imux->num_items;
+ bool imux_added = false;
+ int c;
+
+ for (c = 0; c < num_adcs; c++) {
+ struct nid_path *path;
+ hda_nid_t adc = spec->adc_nids[c];
+
+ if (!is_reachable_path(codec, pin, adc))
+ continue;
+ path = snd_hda_add_new_path(codec, pin, adc, anchor);
+ if (!path)
+ continue;
+ print_nid_path("input", path);
+ spec->input_paths[imux_idx][c] =
+ snd_hda_get_path_idx(codec, path);
+
+ if (!imux_added) {
+ spec->imux_pins[imux->num_items] = pin;
+ snd_hda_add_imux_item(imux, label, cfg_idx, NULL);
+ imux_added = true;
+ }
+ }
+
+ return 0;
+}
+
/*
* 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;
- struct hda_input_mux *imux = &spec->input_mux;
int num_adcs;
- int i, c, 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;
- bool imux_added;
pin = cfg->inputs[i].pin;
if (!is_input_pin(codec, pin))
continue;
- label = hda_get_autocfg_input_label(codec, cfg, i);
- if (spec->shared_mic_hp && !strcmp(label, "Misc"))
- label = "Headphone Mic";
- 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);
+ set_pin_target(codec, pin, val, false);
if (mixer) {
if (is_reachable_path(codec, pin, mixer)) {
- err = new_analog_input(codec, pin,
- label, type_idx, mixer);
+ err = new_analog_input(codec, i, pin,
+ spec->input_labels[i],
+ spec->input_label_idxs[i],
+ mixer);
if (err < 0)
return err;
}
}
- imux_added = false;
- for (c = 0; c < num_adcs; c++) {
- struct nid_path *path;
- hda_nid_t adc = spec->adc_nids[c];
-
- if (!is_reachable_path(codec, pin, adc))
- continue;
- path = snd_array_new(&spec->paths);
- if (!path)
- return -ENOMEM;
- memset(path, 0, sizeof(*path));
- if (!snd_hda_parse_nid_path(codec, pin, adc, 2, path)) {
- snd_printd(KERN_ERR
- "invalid input path 0x%x -> 0x%x\n",
- pin, adc);
- spec->paths.used--;
- continue;
- }
+ err = parse_capture_source(codec, pin, i, num_adcs,
+ spec->input_labels[i], -mixer);
+ if (err < 0)
+ return err;
- if (!imux_added) {
- spec->imux_pins[imux->num_items] = pin;
- snd_hda_add_imux_item(imux, label,
- imux->num_items, NULL);
- imux_added = true;
- }
+ if (spec->add_in_jack_modes) {
+ err = create_in_jack_mode(codec, pin);
+ if (err < 0)
+ return err;
}
}
+ if (mixer && spec->add_stereo_mix_input) {
+ err = parse_capture_source(codec, mixer, CFG_IDX_MIX, num_adcs,
+ "Stereo Mix", 0);
+ if (err < 0)
+ return err;
+ }
+
return 0;
}
* input source mux
*/
-/* get the ADC NID corresponding to the given index */
-static hda_nid_t get_adc_nid(struct hda_codec *codec, int adc_idx, int imux_idx)
+/* get the input path specified by the given adc and imux indices */
+static struct nid_path *get_input_path(struct hda_codec *codec, int adc_idx, int imux_idx)
{
struct hda_gen_spec *spec = codec->spec;
+ if (imux_idx < 0 || imux_idx >= HDA_MAX_NUM_INPUTS) {
+ snd_BUG();
+ return NULL;
+ }
if (spec->dyn_adc_switch)
adc_idx = spec->dyn_adc_idx[imux_idx];
- return spec->adc_nids[adc_idx];
+ if (adc_idx < 0 || adc_idx >= AUTO_CFG_MAX_INS) {
+ snd_BUG();
+ return NULL;
+ }
+ return snd_hda_get_path_from_idx(codec, spec->input_paths[imux_idx][adc_idx]);
}
static int mux_select(struct hda_codec *codec, unsigned int adc_idx,
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
struct hda_gen_spec *spec = codec->spec;
+ /* 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];
int i, adc_idx, err = 0;
imux = &spec->input_mux;
- adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ adc_idx = kcontrol->id.index;
mutex_lock(&codec->control_mutex);
/* we use the cache-only update at first since multiple input paths
* may shared the same amp; by updating only caches, the redundant
*/
codec->cached_write = 1;
for (i = 0; i < imux->num_items; i++) {
- path = snd_hda_get_nid_path(codec, spec->imux_pins[i],
- get_adc_nid(codec, adc_idx, i));
- if (!path->ctls[type])
+ path = get_input_path(codec, adc_idx, i);
+ if (!path || !path->ctls[type])
continue;
kcontrol->private_value = path->ctls[type];
err = func(kcontrol, ucontrol);
mutex_unlock(&codec->control_mutex);
snd_hda_codec_flush_amp_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;
}
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 */
/* return the vol ctl when used first in the imux list */
static unsigned int get_first_cap_ctl(struct hda_codec *codec, int idx, int type)
{
- struct hda_gen_spec *spec = codec->spec;
struct nid_path *path;
unsigned int ctl;
int i;
- path = snd_hda_get_nid_path(codec, spec->imux_pins[idx],
- get_adc_nid(codec, 0, idx));
+ path = get_input_path(codec, 0, idx);
if (!path)
return 0;
ctl = path->ctls[type];
if (!ctl)
return 0;
for (i = 0; i < idx - 1; i++) {
- path = snd_hda_get_nid_path(codec, spec->imux_pins[i],
- get_adc_nid(codec, 0, i));
+ path = get_input_path(codec, 0, i);
if (path && path->ctls[type] == ctl)
return 0;
}
{
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)
for (n = 0; n < nums; n++) {
bool multi = false;
+ bool multi_cap_vol = spec->multi_cap_vol;
bool inv_dmic = false;
int vol, sw;
vol = sw = 0;
for (i = 0; i < imux->num_items; i++) {
struct nid_path *path;
- path = snd_hda_get_nid_path(codec, spec->imux_pins[i],
- get_adc_nid(codec, n, i));
+ path = get_input_path(codec, n, i);
if (!path)
continue;
parse_capvol_in_path(codec, path);
if (!vol)
vol = path->ctls[NID_PATH_VOL_CTL];
- else if (vol != path->ctls[NID_PATH_VOL_CTL])
+ else if (vol != path->ctls[NID_PATH_VOL_CTL]) {
multi = true;
+ if (!same_amp_caps(codec, vol,
+ path->ctls[NID_PATH_VOL_CTL], HDA_INPUT))
+ multi_cap_vol = true;
+ }
if (!sw)
sw = path->ctls[NID_PATH_MUTE_CTL];
- else if (sw != path->ctls[NID_PATH_MUTE_CTL])
+ else if (sw != path->ctls[NID_PATH_MUTE_CTL]) {
multi = true;
+ if (!same_amp_caps(codec, sw,
+ path->ctls[NID_PATH_MUTE_CTL], HDA_INPUT))
+ multi_cap_vol = true;
+ }
if (is_inv_dmic_pin(codec, spec->imux_pins[i]))
inv_dmic = true;
}
- if (!multi)
- err = create_single_cap_vol_ctl(codec, n, vol, sw,
- inv_dmic);
- else if (!spec->multi_cap_vol)
- err = create_bind_cap_vol_ctl(codec, n, vol, sw);
- else
- err = create_multi_cap_vol_ctl(codec);
- if (err < 0)
- return err;
+ if (!multi)
+ err = create_single_cap_vol_ctl(codec, n, vol, sw,
+ inv_dmic);
+ else if (!multi_cap_vol)
+ err = create_bind_cap_vol_ctl(codec, n, vol, sw);
+ else
+ err = create_multi_cap_vol_ctl(codec);
+ if (err < 0)
+ return err;
+ }
+
+ return 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 0;
+ return val;
}
-/*
- * add mic boosts if needed
- */
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[32];
- struct nid_path *path;
- unsigned int val;
+ if (!spec->num_adc_nids)
+ return 0;
- label = hda_get_autocfg_input_label(codec, cfg, i);
- if (spec->shared_mic_hp && !strcmp(label, "Misc"))
- label = "Headphone Mic";
- if (prev_label && !strcmp(label, prev_label))
- type_idx++;
- else
- type_idx = 0;
- prev_label = label;
+ for (i = 0; i < imux->num_items; i++) {
+ struct nid_path *path;
+ unsigned int val;
+ int idx;
+ char boost_label[44];
- 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;
+ idx = imux->items[i].index;
+ if (idx >= imux->num_items)
+ continue;
- path = snd_hda_get_nid_path(codec, nid, 0);
- if (path)
- path->ctls[NID_PATH_BOOST_CTL] = val;
- }
+ /* check only line-in and mic pins */
+ if (cfg->inputs[idx].type > AUTO_PIN_LINE_IN)
+ continue;
+
+ 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;
}
static void parse_digital(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
+ struct nid_path *path;
int i, nums;
- hda_nid_t dig_nid;
+ hda_nid_t dig_nid, pin;
/* support multiple SPDIFs; the secondary is set up as a slave */
nums = 0;
for (i = 0; i < spec->autocfg.dig_outs; i++) {
- hda_nid_t pin = spec->autocfg.dig_out_pins[i];
+ pin = spec->autocfg.dig_out_pins[i];
dig_nid = look_for_dac(codec, pin, true);
if (!dig_nid)
continue;
- if (!snd_hda_add_new_path(codec, dig_nid, pin, 2))
+ path = snd_hda_add_new_path(codec, dig_nid, pin, 0);
+ if (!path)
continue;
+ print_nid_path("digout", path);
+ path->active = true;
+ spec->digout_paths[i] = snd_hda_get_path_idx(codec, path);
+ set_pin_target(codec, pin, PIN_OUT, false);
if (!nums) {
spec->multiout.dig_out_nid = dig_nid;
spec->dig_out_type = spec->autocfg.dig_out_type[0];
}
if (spec->autocfg.dig_in_pin) {
+ pin = spec->autocfg.dig_in_pin;
dig_nid = codec->start_nid;
for (i = 0; i < codec->num_nodes; i++, dig_nid++) {
- struct nid_path *path;
unsigned int wcaps = get_wcaps(codec, dig_nid);
if (get_wcaps_type(wcaps) != AC_WID_AUD_IN)
continue;
if (!(wcaps & AC_WCAP_DIGITAL))
continue;
- path = snd_hda_add_new_path(codec,
- spec->autocfg.dig_in_pin,
- dig_nid, 2);
+ path = snd_hda_add_new_path(codec, pin, dig_nid, 0);
if (path) {
+ print_nid_path("digin", path);
path->active = true;
spec->dig_in_nid = dig_nid;
+ spec->digin_path = snd_hda_get_path_idx(codec, path);
+ set_pin_target(codec, pin, PIN_IN, false);
break;
}
}
if (spec->cur_mux[adc_idx] == idx)
return 0;
- path = snd_hda_get_nid_path(codec,
- spec->imux_pins[spec->cur_mux[adc_idx]],
- spec->adc_nids[adc_idx]);
+ path = get_input_path(codec, adc_idx, spec->cur_mux[adc_idx]);
if (!path)
return 0;
if (path->active)
if (spec->dyn_adc_switch)
dyn_adc_pcm_resetup(codec, idx);
- path = snd_hda_get_nid_path(codec, spec->imux_pins[idx],
- get_adc_nid(codec, adc_idx, idx));
+ path = get_input_path(codec, adc_idx, idx);
if (!path)
return 0;
if (path->active)
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);
return 1;
}
hda_nid_t nid = pins[i];
if (!nid)
break;
+ /* don't detect pins retasked as inputs */
+ if (snd_hda_codec_get_pin_target(codec, nid) & AC_PINCTL_IN_EN)
+ continue;
present |= snd_hda_jack_detect(codec, nid);
}
return present;
/* standard HP/line-out auto-mute helper */
static void do_automute(struct hda_codec *codec, int num_pins, hda_nid_t *pins,
- bool mute, bool hp_out)
+ bool mute)
{
struct hda_gen_spec *spec = codec->spec;
- unsigned int pin_bits = mute ? 0 : (hp_out ? PIN_HP : PIN_OUT);
int i;
for (i = 0; i < num_pins; i++) {
/* don't reset VREF value in case it's controlling
* the amp (see alc861_fixup_asus_amp_vref_0f())
*/
- if (spec->keep_vref_in_automute) {
- val = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
- val &= ~PIN_HP;
- } else
+ if (spec->keep_vref_in_automute)
+ val = snd_hda_codec_get_pin_target(codec, nid) & ~PIN_HP;
+ else
val = 0;
- val |= pin_bits;
- snd_hda_set_pin_ctl(codec, nid, val);
+ if (!mute)
+ val |= snd_hda_codec_get_pin_target(codec, nid);
+ /* here we call update_pin_ctl() so that the pinctl is changed
+ * without changing the pinctl target value;
+ * the original target value will be still referred at the
+ * init / resume again
+ */
+ update_pin_ctl(codec, nid, val);
+ set_pin_eapd(codec, nid, !mute);
}
}
*/
if (!spec->shared_mic_hp) /* don't change HP-pin when shared with mic */
do_automute(codec, ARRAY_SIZE(spec->autocfg.hp_pins),
- spec->autocfg.hp_pins, spec->master_mute, true);
+ spec->autocfg.hp_pins, spec->master_mute);
if (!spec->automute_speaker)
on = 0;
else
on = spec->hp_jack_present | spec->line_jack_present;
on |= spec->master_mute;
+ spec->speaker_muted = on;
do_automute(codec, ARRAY_SIZE(spec->autocfg.speaker_pins),
- spec->autocfg.speaker_pins, on, false);
+ spec->autocfg.speaker_pins, on);
/* toggle line-out mutes if needed, too */
/* if LO is a copy of either HP or Speaker, don't need to handle it */
else
on = spec->hp_jack_present;
on |= spec->master_mute;
+ spec->line_out_muted = on;
do_automute(codec, ARRAY_SIZE(spec->autocfg.line_out_pins),
- spec->autocfg.line_out_pins, on, false);
+ spec->autocfg.line_out_pins, on);
}
EXPORT_SYMBOL_HDA(snd_hda_gen_update_outputs);
return;
for (i = spec->am_num_entries - 1; i > 0; i--) {
- if (snd_hda_jack_detect(codec, spec->am_entry[i].pin)) {
+ hda_nid_t pin = spec->am_entry[i].pin;
+ /* don't detect pins retasked as outputs */
+ if (snd_hda_codec_get_pin_target(codec, pin) & AC_PINCTL_OUT_EN)
+ continue;
+ if (snd_hda_jack_detect(codec, pin)) {
mux_select(codec, 0, spec->am_entry[i].idx);
return;
}
}
EXPORT_SYMBOL_HDA(snd_hda_gen_mic_autoswitch);
+/* update jack retasking */
+static void update_automute_all(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (spec->hp_automute_hook)
+ spec->hp_automute_hook(codec, NULL);
+ else
+ snd_hda_gen_hp_automute(codec, NULL);
+ if (spec->line_automute_hook)
+ spec->line_automute_hook(codec, NULL);
+ else
+ snd_hda_gen_line_automute(codec, NULL);
+ if (spec->mic_autoswitch_hook)
+ spec->mic_autoswitch_hook(codec, NULL);
+ else
+ snd_hda_gen_mic_autoswitch(codec, NULL);
+}
+
/*
* Auto-Mute mode mixer enum support
*/
int present = 0;
int i, err;
+ if (spec->suppress_auto_mute)
+ return 0;
+
if (cfg->hp_pins[0])
present++;
if (cfg->line_out_pins[0])
snd_printdd("hda-codec: Enable HP auto-muting on NID 0x%x\n",
nid);
snd_hda_jack_detect_enable_callback(codec, nid, HDA_GEN_HP_EVENT,
+ spec->hp_automute_hook ?
+ spec->hp_automute_hook :
snd_hda_gen_hp_automute);
spec->detect_hp = 1;
}
snd_printdd("hda-codec: Enable Line-Out auto-muting on NID 0x%x\n", nid);
snd_hda_jack_detect_enable_callback(codec, nid,
HDA_GEN_FRONT_EVENT,
+ spec->line_automute_hook ?
+ spec->line_automute_hook :
snd_hda_gen_line_automute);
spec->detect_lo = 1;
}
return 0;
}
-/* return the position of NID in the list, or -1 if not found */
-static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums)
-{
- int i;
- for (i = 0; i < nums; i++)
- if (list[i] == nid)
- return i;
- return -1;
-}
-
/* check whether all auto-mic pins are valid; setup indices if OK */
static bool auto_mic_check_imux(struct hda_codec *codec)
{
snd_hda_jack_detect_enable_callback(codec,
spec->am_entry[i].pin,
HDA_GEN_MIC_EVENT,
+ spec->mic_autoswitch_hook ?
+ spec->mic_autoswitch_hook :
snd_hda_gen_mic_autoswitch);
return true;
}
unsigned int types;
int i, num_pins;
+ if (spec->suppress_auto_mic)
+ return 0;
+
types = 0;
num_pins = 0;
for (i = 0; i < cfg->num_inputs; i++) {
struct hda_gen_spec *spec = codec->spec;
int err;
+ parse_user_hints(codec);
+
if (cfg != &spec->autocfg) {
spec->autocfg = *cfg;
cfg = &spec->autocfg;
}
+ fill_all_dac_nids(codec);
+
if (!cfg->line_outs) {
if (cfg->dig_outs || cfg->dig_in_pin) {
spec->multiout.max_channels = 2;
if (err < 0)
return err;
err = create_speaker_out_ctls(codec);
+ if (err < 0)
+ return err;
+ err = create_indep_hp_ctls(codec);
+ if (err < 0)
+ return err;
+ err = create_loopback_mixing_ctl(codec);
if (err < 0)
return err;
err = create_shared_input(codec);
if (err < 0)
return err;
- /* check the multiple speaker pins */
- if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT)
- spec->const_channel_count = cfg->line_outs * 2;
- else
- spec->const_channel_count = cfg->speaker_outs * 2;
-
- if (spec->multi_ios > 0)
- spec->multiout.max_channels = max(spec->ext_channel_count,
- spec->const_channel_count);
- else
- spec->multiout.max_channels = spec->multiout.num_dacs * 2;
+ spec->const_channel_count = spec->ext_channel_count;
+ /* check the multiple speaker and headphone pins */
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
+ spec->const_channel_count = max(spec->const_channel_count,
+ cfg->speaker_outs * 2);
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT)
+ spec->const_channel_count = max(spec->const_channel_count,
+ cfg->hp_outs * 2);
+ spec->multiout.max_channels = max(spec->ext_channel_count,
+ spec->const_channel_count);
err = check_auto_mute_availability(codec);
if (err < 0)
if (err < 0)
return err;
+ if (spec->add_out_jack_modes) {
+ if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
+ err = create_out_jack_modes(codec, cfg->line_outs,
+ cfg->line_out_pins);
+ if (err < 0)
+ return err;
+ }
+ if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
+ err = create_out_jack_modes(codec, cfg->hp_outs,
+ cfg->hp_pins);
+ if (err < 0)
+ return err;
+ }
+ }
+
dig_only:
parse_digital(codec);
"Front", "Surround", "Center", "LFE", "Side",
"Headphone", "Speaker", "Mono", "Line Out",
"CLFE", "Bass Speaker", "PCM",
+ "Speaker Front", "Speaker Surround", "Speaker CLFE", "Speaker Side",
+ "Headphone Front", "Headphone Surround", "Headphone CLFE",
+ "Headphone Side",
NULL,
};
/* if we have no master control, let's create it */
if (!spec->no_analog &&
!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
- unsigned int vmaster_tlv[4];
- snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid,
- HDA_OUTPUT, vmaster_tlv);
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
- vmaster_tlv, slave_pfxs,
+ spec->vmaster_tlv, slave_pfxs,
"Playback Volume");
if (err < 0)
return err;
* PCM definitions
*/
+static void call_pcm_playback_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_playback_hook)
+ 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
*/
struct snd_pcm_substream *substream)
{
struct hda_gen_spec *spec = codec->spec;
- return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+ int err;
+
+ mutex_lock(&spec->pcm_mutex);
+ err = snd_hda_multi_out_analog_open(codec,
+ &spec->multiout, substream,
hinfo);
+ if (!err) {
+ spec->active_streams |= 1 << STREAM_MULTI_OUT;
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_OPEN);
+ }
+ mutex_unlock(&spec->pcm_mutex);
+ return err;
}
static int playback_pcm_prepare(struct hda_pcm_stream *hinfo,
struct snd_pcm_substream *substream)
{
struct hda_gen_spec *spec = codec->spec;
- return snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
- stream_tag, format, substream);
+ int err;
+
+ err = snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
+ stream_tag, format, substream);
+ if (!err)
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
+ return err;
}
static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
struct snd_pcm_substream *substream)
{
struct hda_gen_spec *spec = codec->spec;
- return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+ int err;
+
+ err = snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+ if (!err)
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
+ return err;
+}
+
+static int playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ mutex_lock(&spec->pcm_mutex);
+ spec->active_streams &= ~(1 << STREAM_MULTI_OUT);
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLOSE);
+ mutex_unlock(&spec->pcm_mutex);
+ 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)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ int err = 0;
+
+ mutex_lock(&spec->pcm_mutex);
+ if (!spec->indep_hp_enabled)
+ err = -EBUSY;
+ else
+ spec->active_streams |= 1 << STREAM_INDEP_HP;
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_OPEN);
+ mutex_unlock(&spec->pcm_mutex);
+ return err;
+}
+
+static int alt_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ struct snd_pcm_substream *substream)
+{
+ struct hda_gen_spec *spec = codec->spec;
+ mutex_lock(&spec->pcm_mutex);
+ spec->active_streams &= ~(1 << STREAM_INDEP_HP);
+ call_pcm_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLOSE);
+ mutex_unlock(&spec->pcm_mutex);
+ return 0;
+}
+
+static int alt_playback_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_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_PREPARE);
+ return 0;
+}
+
+static int alt_playback_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_playback_hook(hinfo, codec, substream,
+ HDA_GEN_PCM_ACT_CLEANUP);
+ return 0;
}
/*
/*
* 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;
}
/* NID is set in build_pcms */
.ops = {
.open = playback_pcm_open,
+ .close = playback_pcm_close,
.prepare = playback_pcm_prepare,
.cleanup = playback_pcm_cleanup
},
.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_min = 2,
.channels_max = 2,
/* NID is set in build_pcms */
+ .ops = {
+ .open = alt_playback_pcm_open,
+ .close = alt_playback_pcm_close,
+ .prepare = alt_playback_pcm_prepare,
+ .cleanup = alt_playback_pcm_cleanup
+ },
};
static const struct hda_pcm_stream pcm_analog_alt_capture = {
.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
},
},
};
+static void fill_pcm_stream_name(char *str, size_t len, const char *sfx,
+ const char *chip_name)
+{
+ char *p;
+
+ if (*str)
+ return;
+ strlcpy(str, chip_name, len);
+
+ /* drop non-alnum chars after a space */
+ for (p = strchr(str, ' '); p; p = strchr(p + 1, ' ')) {
+ if (!isalnum(p[1])) {
+ *p = 0;
+ break;
+ }
+ }
+ strlcat(str, sfx, len);
+}
+
/* build PCM streams based on the parsed results */
int snd_hda_gen_build_pcms(struct hda_codec *codec)
{
struct hda_pcm *info = spec->pcm_rec;
const struct hda_pcm_stream *p;
bool have_multi_adcs;
- int i;
codec->num_pcms = 1;
codec->pcm_info = info;
if (spec->no_analog)
goto skip_analog;
- snprintf(spec->stream_name_analog, sizeof(spec->stream_name_analog),
- "%s Analog", codec->chip_name);
+ fill_pcm_stream_name(spec->stream_name_analog,
+ sizeof(spec->stream_name_analog),
+ " Analog", codec->chip_name);
info->name = spec->stream_name_analog;
if (spec->multiout.num_dacs > 0) {
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0];
}
- if (spec->channel_mode) {
- info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 0;
- for (i = 0; i < spec->num_channel_mode; i++) {
- if (spec->channel_mode[i].channels > info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max) {
- info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = spec->channel_mode[i].channels;
- }
- }
- }
-
skip_analog:
/* SPDIF for stream index #1 */
if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
- snprintf(spec->stream_name_digital,
- sizeof(spec->stream_name_digital),
- "%s Digital", codec->chip_name);
+ fill_pcm_stream_name(spec->stream_name_digital,
+ sizeof(spec->stream_name_digital),
+ " Digital", codec->chip_name);
codec->num_pcms = 2;
codec->slave_dig_outs = spec->multiout.slave_dig_outs;
info = spec->pcm_rec + 1;
* Standard auto-parser initializations
*/
-/* configure the path from the given dac to the pin as the proper output */
-static void set_output_and_unmute(struct hda_codec *codec, hda_nid_t pin,
- int pin_type, hda_nid_t dac)
+/* configure the given path as a proper output */
+static void set_output_and_unmute(struct hda_codec *codec, int path_idx)
{
- struct hda_gen_spec *spec = codec->spec;
struct nid_path *path;
+ hda_nid_t pin;
- snd_hda_set_pin_ctl_cache(codec, pin, pin_type);
- path = snd_hda_get_nid_path(codec, dac, pin);
- if (!path)
+ path = snd_hda_get_path_from_idx(codec, path_idx);
+ if (!path || !path->depth)
return;
- if (path->active)
- return;
- snd_hda_activate_path(codec, path, true, true);
-
- if (!spec->own_eapd_ctl &&
- (snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_EAPD))
- snd_hda_codec_update_cache(codec, pin, 0,
- AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+ pin = path->path[path->depth - 1];
+ restore_pin_ctl(codec, pin);
+ snd_hda_activate_path(codec, path, path->active, true);
+ set_pin_eapd(codec, pin, path->active);
}
/* initialize primary output paths */
static void init_multi_out(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
- int pin_type;
int i;
- if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
- pin_type = PIN_HP;
- else
- pin_type = PIN_OUT;
+ for (i = 0; i < spec->autocfg.line_outs; i++)
+ set_output_and_unmute(codec, spec->out_paths[i]);
+}
- for (i = 0; i <= HDA_SIDE; i++) {
- hda_nid_t nid = spec->autocfg.line_out_pins[i];
- if (nid)
- set_output_and_unmute(codec, nid, pin_type,
- spec->multiout.dac_nids[i]);
- }
+static void __init_extra_out(struct hda_codec *codec, int num_outs, int *paths)
+{
+ int i;
+
+ for (i = 0; i < num_outs; i++)
+ set_output_and_unmute(codec, paths[i]);
}
/* initialize hp and speaker paths */
static void init_extra_out(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
- int i;
- hda_nid_t pin, dac;
- for (i = 0; i < spec->autocfg.hp_outs; i++) {
- if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
- break;
- pin = spec->autocfg.hp_pins[i];
- if (!pin)
- break;
- dac = spec->multiout.hp_out_nid[i];
- if (!dac) {
- if (i > 0 && spec->multiout.hp_out_nid[0])
- dac = spec->multiout.hp_out_nid[0];
- else
- dac = spec->multiout.dac_nids[0];
- }
- set_output_and_unmute(codec, pin, PIN_HP, dac);
- }
- for (i = 0; i < spec->autocfg.speaker_outs; i++) {
- if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
- break;
- pin = spec->autocfg.speaker_pins[i];
- if (!pin)
- break;
- dac = spec->multiout.extra_out_nid[i];
- if (!dac) {
- if (i > 0 && spec->multiout.extra_out_nid[0])
- dac = spec->multiout.extra_out_nid[0];
- else
- dac = spec->multiout.dac_nids[0];
- }
- set_output_and_unmute(codec, pin, PIN_OUT, dac);
- }
+ if (spec->autocfg.line_out_type != AUTO_PIN_HP_OUT)
+ __init_extra_out(codec, spec->autocfg.hp_outs, spec->hp_paths);
+ if (spec->autocfg.line_out_type != AUTO_PIN_SPEAKER_OUT)
+ __init_extra_out(codec, spec->autocfg.speaker_outs,
+ spec->speaker_paths);
}
/* initialize multi-io paths */
for (i = 0; i < spec->multi_ios; i++) {
hda_nid_t pin = spec->multi_io[i].pin;
struct nid_path *path;
- path = snd_hda_get_nid_path(codec, spec->multi_io[i].dac, pin);
+ path = get_multiio_path(codec, i);
if (!path)
continue;
if (!spec->multi_io[i].ctl_in)
spec->multi_io[i].ctl_in =
- snd_hda_codec_update_cache(codec, pin, 0,
- AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_codec_get_pin_target(codec, pin);
snd_hda_activate_path(codec, path, path->active, true);
}
}
-/* set up the input pin config, depending on the given auto-pin type */
-static void set_input_pin(struct hda_codec *codec, hda_nid_t nid,
- int auto_pin_type)
-{
- unsigned int val = PIN_IN;
- if (auto_pin_type == AUTO_PIN_MIC)
- val |= snd_hda_get_default_vref(codec, nid);
- snd_hda_set_pin_ctl(codec, nid, val);
-}
-
/* set up input pins and loopback paths */
static void init_analog_input(struct hda_codec *codec)
{
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t nid = cfg->inputs[i].pin;
if (is_input_pin(codec, nid))
- set_input_pin(codec, nid, cfg->inputs[i].type);
+ restore_pin_ctl(codec, nid);
/* init loopback inputs */
if (spec->mixer_nid) {
struct nid_path *path;
- path = snd_hda_get_nid_path(codec, nid, spec->mixer_nid);
+ path = snd_hda_get_path_from_idx(codec, spec->loopback_paths[i]);
if (path)
snd_hda_activate_path(codec, path,
path->active, false);
for (c = 0; c < nums; c++) {
for (i = 0; i < imux->num_items; i++) {
- path = snd_hda_get_nid_path(codec, spec->imux_pins[i],
- get_adc_nid(codec, c, i));
+ path = get_input_path(codec, c, i);
if (path) {
bool active = path->active;
if (i == spec->cur_mux[c])
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 */
int i;
hda_nid_t pin;
- for (i = 0; i < spec->autocfg.dig_outs; i++) {
- pin = spec->autocfg.dig_out_pins[i];
- if (!pin)
- continue;
- set_output_and_unmute(codec, pin, PIN_OUT, 0);
- }
+ for (i = 0; i < spec->autocfg.dig_outs; i++)
+ set_output_and_unmute(codec, spec->digout_paths[i]);
pin = spec->autocfg.dig_in_pin;
- if (pin)
- snd_hda_set_pin_ctl(codec, pin, PIN_IN);
+ 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);
+ }
+}
+
+/* clear unsol-event tags on unused pins; Conexant codecs seem to leave
+ * invalid unsol tags by some reason
+ */
+static void clear_unsol_on_unused_pins(struct hda_codec *codec)
+{
+ int i;
+
+ for (i = 0; i < codec->init_pins.used; i++) {
+ struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i);
+ hda_nid_t nid = pin->nid;
+ if (is_jack_detectable(codec, nid) &&
+ !snd_hda_jack_tbl_get(codec, nid))
+ snd_hda_codec_update_cache(codec, nid, 0,
+ AC_VERB_SET_UNSOLICITED_ENABLE, 0);
+ }
}
+/*
+ * initialize the generic spec;
+ * this can be put as patch_ops.init function
+ */
int snd_hda_gen_init(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
init_input_src(codec);
init_digital(codec);
+ clear_unsol_on_unused_pins(codec);
+
/* call init functions of standard auto-mute helpers */
- snd_hda_gen_hp_automute(codec, NULL);
- snd_hda_gen_line_automute(codec, NULL);
- snd_hda_gen_mic_autoswitch(codec, NULL);
+ update_automute_all(codec);
snd_hda_codec_flush_amp_cache(codec);
snd_hda_codec_flush_cmd_cache(codec);
hda_call_check_power_status(codec, 0x01);
return 0;
}
-EXPORT_SYMBOL(snd_hda_gen_init);
-
+EXPORT_SYMBOL_HDA(snd_hda_gen_init);
/*
- * the generic codec support
+ * free the generic spec;
+ * this can be put as patch_ops.free function
*/
+void snd_hda_gen_free(struct hda_codec *codec)
+{
+ snd_hda_gen_spec_free(codec->spec);
+ kfree(codec->spec);
+ codec->spec = NULL;
+}
+EXPORT_SYMBOL_HDA(snd_hda_gen_free);
#ifdef CONFIG_PM
-static int generic_check_power_status(struct hda_codec *codec, hda_nid_t nid)
+/*
+ * check the loopback power save state;
+ * this can be put as patch_ops.check_power_status function
+ */
+int snd_hda_gen_check_power_status(struct hda_codec *codec, hda_nid_t nid)
{
struct hda_gen_spec *spec = codec->spec;
return snd_hda_check_amp_list_power(codec, &spec->loopback, nid);
}
+EXPORT_SYMBOL_HDA(snd_hda_gen_check_power_status);
#endif
-static void generic_free(struct hda_codec *codec)
-{
- snd_hda_gen_spec_free(codec->spec);
- kfree(codec->spec);
- codec->spec = NULL;
-}
+
+/*
+ * the generic codec support
+ */
static const struct hda_codec_ops generic_patch_ops = {
.build_controls = snd_hda_gen_build_controls,
.build_pcms = snd_hda_gen_build_pcms,
.init = snd_hda_gen_init,
- .free = generic_free,
+ .free = snd_hda_gen_free,
.unsol_event = snd_hda_jack_unsol_event,
#ifdef CONFIG_PM
- .check_power_status = generic_check_power_status,
+ .check_power_status = snd_hda_gen_check_power_status,
#endif
};
return 0;
error:
- generic_free(codec);
+ snd_hda_gen_free(codec);
return err;
}
-EXPORT_SYMBOL(snd_hda_parse_generic_codec);
+EXPORT_SYMBOL_HDA(snd_hda_parse_generic_codec);