#include <linux/slab.h>
#include <linux/export.h>
#include <linux/sort.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
#include <sound/core.h>
#include <sound/jack.h>
#include "hda_codec.h"
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);
* 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)
+static struct nid_path *get_nid_path(struct hda_codec *codec,
+ hda_nid_t from_nid, hda_nid_t to_nid,
+ int with_aa_mix)
{
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 (with_aa_mix == HDA_PARSE_ALL ||
+ path->with_aa_mix == with_aa_mix)
+ 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, HDA_PARSE_ALL);
+}
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)
{
is_ctl_used(codec, val, NID_PATH_MUTE_CTL);
}
+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)
{
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)
+ if (with_aa_mix == HDA_PARSE_NO_AAMIX)
return false;
- with_aa_mix = 2; /* mark aa-mix is included */
+ with_aa_mix = HDA_PARSE_ALL; /* mark aa-mix is included */
}
- 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,
continue;
}
/* aa-mix is requested but not included? */
- if (!(spec->mixer_nid && with_aa_mix == 1))
+ if (!(spec->mixer_nid && with_aa_mix == HDA_PARSE_ONLY_AAMIX))
goto found;
}
if (depth >= MAX_NID_PATH_DEPTH)
found:
path->path[path->depth] = conn[i];
+ if (conn[i] == spec->mixer_nid)
+ path->with_aa_mix = true;
path->idx[path->depth + 1] = i;
if (nums > 1 && get_wcaps_type(get_wcaps(codec, to_nid)) != AC_WID_AUD_MIX)
path->multi[path->depth + 1] = 1;
/* 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 @with_aa_mix is HDA_PARSE_NO_AAMIX, paths with spec->mixer_nid are
+ * excluded, only the paths that don't go through the mixer will be chosen.
+ * when @with_aa_mix is HDA_PARSE_ONLY_AAMIX, only the paths going through
+ * spec->mixer_nid will be chosen.
+ * when @with_aa_mix is HDA_PARSE_ALL, no special handling about mixer widget.
*/
bool snd_hda_parse_nid_path(struct hda_codec *codec, hda_nid_t from_nid,
hda_nid_t to_nid, int with_aa_mix,
if (__parse_nid_path(codec, from_nid, to_nid, with_aa_mix, 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;
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, with_aa_mix);
+ if (path)
+ return path;
+
path = snd_array_new(&spec->paths);
if (!path)
return NULL;
{
int val;
if (is_ctl_associated(codec, nid, dir, idx) ||
- is_active_nid(codec, nid, dir, idx))
+ (!enable && is_active_nid(codec, nid, dir, idx)))
return;
val = get_amp_val_to_activate(codec, nid, dir, enable);
snd_hda_codec_amp_stereo(codec, nid, dir, idx, 0xff, val);
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)) {
}
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
.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);
+
+ if (dacs[i]) {
+ badness += assign_out_path_ctls(codec, pin, dacs[i]);
+ continue;
+ }
+
+ dacs[i] = look_for_dac(codec, pin, false);
if (!dacs[i] && !i) {
for (j = 1; j < num_outs; j++) {
if (is_reachable_path(codec, dacs[j], pin)) {
dacs[0] = dacs[j];
dacs[j] = 0;
+ 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, HDA_PARSE_NO_AAMIX);
+ if (!path && !i && spec->mixer_nid) {
+ /* try with aamix */
+ path = snd_hda_add_new_path(codec, dac, pin, HDA_PARSE_ALL);
+ }
+ if (!path)
dac = dacs[i] = 0;
+ else {
+ print_nid_path("output", path);
+ path->active = true;
+ path_idx[i] = snd_hda_get_path_idx(codec, path);
+ }
if (dac)
badness += assign_out_path_ctls(codec, pin, dac);
}
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;
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++) {
+ struct nid_path *path;
hda_nid_t nid = cfg->inputs[i].pin;
hda_nid_t dac = 0;
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, HDA_PARSE_NO_AAMIX);
+ 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;
/* 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], HDA_PARSE_NO_AAMIX);
+ if (!path && !i && spec->mixer_nid)
+ path = snd_hda_add_new_path(codec, dac, pins[i], HDA_PARSE_ALL);
+ 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 nid_path *path;
+
+ path = snd_hda_get_path_from_idx(codec, path_idx);
+ if (!path || !path->depth || path->with_aa_mix)
+ return 0;
+ path = snd_hda_add_new_path(codec, path->path[0],
+ path->path[path->depth - 1],
+ HDA_PARSE_ONLY_AAMIX);
+ 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 in the dac_nids table from the parsed pin configuration */
static int fill_and_eval_dacs(struct hda_codec *codec,
bool fill_hardwired,
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 */
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 */
+
if (spec->multi_ios == 2) {
for (i = 0; i < 2; i++)
spec->private_dac_nids[spec->multiout.num_dacs++] =
{
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],
+ 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->multi_io[0].dac, spec->multi_io[1].dac);
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],
}
if (badness) {
+ debug_badness("==> restoring best_cfg\n");
*cfg = *best_cfg;
fill_and_eval_dacs(codec, best_wired, best_mio);
}
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);
}
for (i = 0; i < noutputs; i++) {
const char *name;
int index;
- hda_nid_t dac, pin;
+ hda_nid_t dac;
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;
if (!name || !strcmp(name, "CLFE")) {
}
static int create_extra_out(struct hda_codec *codec, hda_nid_t pin,
- hda_nid_t dac, const char *pfx, int cidx)
+ hda_nid_t dac, 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 */
/* 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];
+ char name[44];
int i, n, err;
if (!num_pins || !pins[0])
hda_nid_t dac = *dacs;
if (!dac)
dac = spec->multiout.dac_nids[0];
- return create_extra_out(codec, *pins, dac, pfx, 0);
+ return create_extra_out(codec, *pins, dac, paths[0], pfx, 0);
}
for (i = 0; i < num_pins; i++) {
else
dac = 0;
if (num_pins == 2 && i == 1 && !strcmp(pfx, "Speaker")) {
- err = create_extra_out(codec, pins[i], dac,
+ err = create_extra_out(codec, pins[i], dac, paths[i],
"Bass Speaker", 0);
} else if (num_pins >= 3) {
snprintf(name, sizeof(name), "%s %s",
pfx, channel_name[i]);
- err = create_extra_out(codec, pins[i], dac, name, 0);
+ err = create_extra_out(codec, pins[i], dac, paths[i],
+ name, 0);
} else {
- err = create_extra_out(codec, pins[i], dac, pfx, i);
+ err = create_extra_out(codec, pins[i], dac, paths[i],
+ pfx, i);
}
if (err < 0)
return err;
struct nid_path *path;
if (!pins[i] || !dacs[i])
continue;
- path = snd_hda_get_nid_path(codec, dacs[i], pins[i]);
+ path = snd_hda_get_path_from_idx(codec, paths[i]);
if (!path)
continue;
vol = look_for_out_vol_nid(codec, path);
return create_extra_outs(codec, spec->autocfg.hp_outs,
spec->autocfg.hp_pins,
spec->multiout.hp_out_nid,
+ spec->hp_paths,
"Headphone");
}
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
*/
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 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;
if (output) {
snd_hda_set_pin_ctl_cache(codec, nid, PIN_OUT);
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);
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);
+ snd_hda_set_pin_ctl_cache(codec, pin, val);
spec->automute_speaker = !set_as_mic;
call_update_outputs(codec);
#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, HDA_PARSE_ALL);
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)) {
if (mixer) {
if (is_reachable_path(codec, pin, mixer)) {
- err = new_analog_input(codec, pin,
+ err = new_analog_input(codec, i, pin,
label, type_idx, mixer);
if (err < 0)
return err;
if (!path)
return -ENOMEM;
memset(path, 0, sizeof(*path));
- if (!snd_hda_parse_nid_path(codec, pin, adc, 2, path)) {
+ if (!snd_hda_parse_nid_path(codec, pin, adc, HDA_PARSE_ALL, path)) {
snd_printd(KERN_ERR
"invalid input path 0x%x -> 0x%x\n",
pin, adc);
spec->paths.used--;
continue;
}
+ print_nid_path("input", path);
if (!imux_added) {
spec->imux_pins[imux->num_items] = pin;
ucontrol->value.enumerated.item[0]);
}
-/*
- * capture volume and capture switch ctls
- */
-
static const struct snd_kcontrol_new cap_src_temp = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Input Source",
.put = mux_enum_put,
};
+/*
+ * capture volume and capture switch ctls
+ */
+
typedef int (*put_call_t)(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
+/* call the given amp update function for all amps in the imux list at once */
static int cap_put_caller(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol,
put_call_t func, int type)
imux = &spec->input_mux;
adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
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
+ * writes to hardware can be reduced.
+ */
codec->cached_write = 1;
for (i = 0; i < imux->num_items; i++) {
path = snd_hda_get_nid_path(codec, spec->imux_pins[i],
error:
codec->cached_write = 0;
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);
return err;
if (!spec->auto_mic && imux->num_items > 1) {
struct snd_kcontrol_new *knew;
- knew = snd_hda_gen_add_kctl(spec, NULL, &cap_src_temp);
+ const char *name;
+ name = nums > 1 ? "Input Source" : "Capture Source";
+ knew = snd_hda_gen_add_kctl(spec, name, &cap_src_temp);
if (!knew)
return -ENOMEM;
knew->count = nums;
nid = cfg->inputs[i].pin;
if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP) {
const char *label;
- char boost_label[32];
+ char boost_label[44];
struct nid_path *path;
unsigned int val;
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;
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, HDA_PARSE_ALL);
+ if (!path)
continue;
+ print_nid_path("digout", path);
+ path->active = true;
+ spec->digout_paths[i] = snd_hda_get_path_idx(codec, path);
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) {
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;
continue;
path = snd_hda_add_new_path(codec,
spec->autocfg.dig_in_pin,
- dig_nid, 2);
+ dig_nid, HDA_PARSE_ALL);
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);
break;
}
}
} else
val = 0;
val |= pin_bits;
- snd_hda_set_pin_ctl(codec, nid, val);
+ snd_hda_set_pin_ctl_cache(codec, nid, val);
+ set_pin_eapd(codec, nid, !mute);
}
}
/* Toggle outputs muting */
-static void update_outputs(struct hda_codec *codec)
+void snd_hda_gen_update_outputs(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
int on;
do_automute(codec, ARRAY_SIZE(spec->autocfg.line_out_pins),
spec->autocfg.line_out_pins, on, false);
}
+EXPORT_SYMBOL_HDA(snd_hda_gen_update_outputs);
static void call_update_outputs(struct hda_codec *codec)
{
if (spec->automute_hook)
spec->automute_hook(codec);
else
- update_outputs(codec);
+ snd_hda_gen_update_outputs(codec);
}
/* standard HP-automute helper */
-static void hp_automute(struct hda_codec *codec, struct hda_jack_tbl *jack)
+void snd_hda_gen_hp_automute(struct hda_codec *codec, struct hda_jack_tbl *jack)
{
struct hda_gen_spec *spec = codec->spec;
return;
call_update_outputs(codec);
}
+EXPORT_SYMBOL_HDA(snd_hda_gen_hp_automute);
/* standard line-out-automute helper */
-static void line_automute(struct hda_codec *codec, struct hda_jack_tbl *jack)
+void snd_hda_gen_line_automute(struct hda_codec *codec, struct hda_jack_tbl *jack)
{
struct hda_gen_spec *spec = codec->spec;
return;
call_update_outputs(codec);
}
+EXPORT_SYMBOL_HDA(snd_hda_gen_line_automute);
/* standard mic auto-switch helper */
-static void mic_autoswitch(struct hda_codec *codec, struct hda_jack_tbl *jack)
+void snd_hda_gen_mic_autoswitch(struct hda_codec *codec, struct hda_jack_tbl *jack)
{
struct hda_gen_spec *spec = codec->spec;
int i;
}
mux_select(codec, 0, spec->am_entry[0].idx);
}
+EXPORT_SYMBOL_HDA(snd_hda_gen_mic_autoswitch);
/*
* Auto-Mute mode mixer enum support
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,
- hp_automute);
+ 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,
- line_automute);
+ spec->line_automute_hook ?
+ spec->line_automute_hook :
+ snd_hda_gen_line_automute);
spec->detect_lo = 1;
}
spec->automute_lo_possible = spec->detect_hp;
snd_hda_jack_detect_enable_callback(codec,
spec->am_entry[i].pin,
HDA_GEN_MIC_EVENT,
- mic_autoswitch);
+ spec->mic_autoswitch_hook ?
+ spec->mic_autoswitch_hook :
+ snd_hda_gen_mic_autoswitch);
return true;
}
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);
"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 (err < 0)
return err;
if (spec->vmaster_mute.hook)
- snd_hda_add_vmaster_hook(codec, &spec->vmaster_mute, true);
+ snd_hda_add_vmaster_hook(codec, &spec->vmaster_mute,
+ spec->vmaster_mute_enum);
}
free_kctls(spec); /* no longer needed */
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;
+ mutex_unlock(&spec->pcm_mutex);
+ return err;
}
static int playback_pcm_prepare(struct hda_pcm_stream *hinfo,
return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
}
+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);
+ mutex_unlock(&spec->pcm_mutex);
+ 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;
+ 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);
+ mutex_unlock(&spec->pcm_mutex);
+ return 0;
+}
+
/*
* Digital out
*/
/* 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 = alt_playback_pcm_open,
+ .close = alt_playback_pcm_close
+ },
};
static const struct hda_pcm_stream pcm_analog_alt_capture = {
},
};
+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;
/* 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)
+ int pin_type, int path_idx)
{
- struct hda_gen_spec *spec = codec->spec;
struct nid_path *path;
snd_hda_set_pin_ctl_cache(codec, pin, pin_type);
- path = snd_hda_get_nid_path(codec, dac, pin);
+ path = snd_hda_get_path_from_idx(codec, path_idx);
if (!path)
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);
+ 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;
+ hda_nid_t nid;
int pin_type;
int i;
else
pin_type = PIN_OUT;
- for (i = 0; i <= HDA_SIDE; i++) {
- hda_nid_t nid = spec->autocfg.line_out_pins[i];
+ for (i = 0; i < spec->autocfg.line_outs; i++) {
+ nid = spec->autocfg.line_out_pins[i];
if (nid)
set_output_and_unmute(codec, nid, pin_type,
- spec->multiout.dac_nids[i]);
-
+ spec->out_paths[i]);
}
}
-/* initialize hp and speaker paths */
-static void init_extra_out(struct hda_codec *codec)
+
+static void __init_extra_out(struct hda_codec *codec, int num_outs,
+ hda_nid_t *pins, int *paths, int type)
{
- struct hda_gen_spec *spec = codec->spec;
int i;
- hda_nid_t pin, dac;
+ hda_nid_t pin;
- 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];
+ for (i = 0; i < num_outs; i++) {
+ pin = 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);
+ set_output_and_unmute(codec, pin, type, paths[i]);
}
}
+/* initialize hp and speaker paths */
+static void init_extra_out(struct hda_codec *codec)
+{
+ struct hda_gen_spec *spec = codec->spec;
+
+ if (spec->autocfg.line_out_type != AUTO_PIN_HP_OUT)
+ __init_extra_out(codec, spec->autocfg.hp_outs,
+ spec->autocfg.hp_pins,
+ spec->hp_paths, PIN_HP);
+ if (spec->autocfg.line_out_type != AUTO_PIN_SPEAKER_OUT)
+ __init_extra_out(codec, spec->autocfg.speaker_outs,
+ spec->autocfg.speaker_pins,
+ spec->speaker_paths, PIN_OUT);
+}
+
/* initialize multi-io paths */
static void init_multi_io(struct hda_codec *codec)
{
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)
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);
+ snd_hda_set_pin_ctl_cache(codec, nid, val);
}
/* set up input pins and loopback paths */
/* 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);
pin = spec->autocfg.dig_out_pins[i];
if (!pin)
continue;
- set_output_and_unmute(codec, pin, PIN_OUT, 0);
+ set_output_and_unmute(codec, pin, PIN_OUT,
+ 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;
+ snd_hda_set_pin_ctl_cache(codec, pin, PIN_IN);
+ 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);
+ }
}
int snd_hda_gen_init(struct hda_codec *codec)
snd_hda_apply_verbs(codec);
+ codec->cached_write = 1;
+
init_multi_out(codec);
init_extra_out(codec);
init_multi_io(codec);
init_input_src(codec);
init_digital(codec);
+ clear_unsol_on_unused_pins(codec);
+
/* call init functions of standard auto-mute helpers */
- hp_automute(codec, NULL);
- line_automute(codec, NULL);
- mic_autoswitch(codec, NULL);
+ snd_hda_gen_hp_automute(codec, NULL);
+ snd_hda_gen_line_automute(codec, NULL);
+ snd_hda_gen_mic_autoswitch(codec, NULL);
+
+ snd_hda_codec_flush_amp_cache(codec);
+ snd_hda_codec_flush_cmd_cache(codec);
if (spec->vmaster_mute.sw_kctl && spec->vmaster_mute.hook)
snd_hda_sync_vmaster_hook(&spec->vmaster_mute);