/** * snd_hda_gen_add_kctl - Add a new kctl_new struct from the template * @spec: hda_gen_spec object * @name: name string to override the template, NULL if unchanged * @temp: template for the new kctl * * Add a new kctl (actually snd_kcontrol_new to be instantiated later) * element based on the given snd_kcontrol_new template @temp and the * name string @name to the list in @spec. * Returns the newly created object or NULL as error.
*/ struct snd_kcontrol_new *
snd_hda_gen_add_kctl(struct hda_gen_spec *spec, constchar *name, conststruct snd_kcontrol_new *temp)
{ struct snd_kcontrol_new *knew = snd_array_new(&spec->kctls); if (!knew) return NULL;
*knew = *temp; if (name)
knew->name = kstrdup(name, GFP_KERNEL); elseif (knew->name)
knew->name = kstrdup(knew->name, GFP_KERNEL); if (!knew->name) return NULL; return knew;
}
EXPORT_SYMBOL_GPL(snd_hda_gen_add_kctl);
staticvoid free_kctls(struct hda_gen_spec *spec)
{ if (spec->kctls.list) { struct snd_kcontrol_new *kctl = spec->kctls.list; int i; for (i = 0; i < spec->kctls.used; i++)
kfree(kctl[i].name);
}
snd_array_free(&spec->kctls);
}
staticvoid snd_hda_gen_spec_free(struct hda_gen_spec *spec)
{ if (!spec) return;
free_kctls(spec);
snd_array_free(&spec->paths);
snd_array_free(&spec->loopback_list); #ifdef CONFIG_SND_HDA_GENERIC_LEDS if (spec->led_cdevs[LED_AUDIO_MUTE])
led_classdev_unregister(spec->led_cdevs[LED_AUDIO_MUTE]); if (spec->led_cdevs[LED_AUDIO_MICMUTE])
led_classdev_unregister(spec->led_cdevs[LED_AUDIO_MICMUTE]); #endif
}
/* * store user hints
*/ staticvoid parse_user_hints(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; 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, "power_save_node"); if (val >= 0)
codec->power_save_node = !!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, "auto_mute_via_amp"); if (val >= 0)
spec->auto_mute_via_amp = !!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_io"); if (val >= 0)
spec->no_multi_io = !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; /* the following two are just for compatibility */
val = snd_hda_get_bool_hint(codec, "add_out_jack_modes"); if (val >= 0)
spec->add_jack_modes = !!val;
val = snd_hda_get_bool_hint(codec, "add_in_jack_modes"); if (val >= 0)
spec->add_jack_modes = !!val;
val = snd_hda_get_bool_hint(codec, "add_jack_modes"); if (val >= 0)
spec->add_jack_modes = !!val;
val = snd_hda_get_bool_hint(codec, "power_down_unused"); if (val >= 0)
spec->power_down_unused = !!val;
val = snd_hda_get_bool_hint(codec, "add_hp_mic"); if (val >= 0)
spec->hp_mic = !!val;
val = snd_hda_get_bool_hint(codec, "hp_mic_detect"); if (val >= 0)
spec->suppress_hp_mic_detect = !val;
val = snd_hda_get_bool_hint(codec, "vmaster"); if (val >= 0)
spec->suppress_vmaster = !val;
if (!snd_hda_get_int_hint(codec, "mixer_nid", &val))
spec->mixer_nid = val;
}
/* restore the pinctl based on the cached value */ staticinlinevoid restore_pin_ctl(struct hda_codec *codec, hda_nid_t pin)
{
update_pin_ctl(codec, pin, snd_hda_codec_get_pin_target(codec, pin));
}
/* set the pinctl target value and write it if requested */ staticvoid set_pin_target(struct hda_codec *codec, hda_nid_t pin, unsignedint val, bool do_write)
{ if (!pin) return;
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 */ staticvoid set_pin_targets(struct hda_codec *codec, int num_pins,
hda_nid_t *pins, unsignedint val)
{ int i; for (i = 0; i < num_pins; i++)
set_pin_target(codec, pins[i], val, false);
}
/* * parsing paths
*/
/* return the position of NID in the list, or -1 if not found */ staticint 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 */ staticbool is_nid_contained(struct nid_path *path, hda_nid_t nid)
{ return find_idx_in_nid_list(nid, path->path, path->depth) >= 0;
}
/** * snd_hda_get_path_idx - get the index number corresponding to the path * instance * @codec: the HDA codec * @path: nid_path object * * The returned index starts from 1, i.e. the actual array index with offset 1, * and zero is handled as an invalid path
*/ 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;
/** * snd_hda_get_path_from_idx - get the path instance corresponding to the * given index number * @codec: the HDA codec * @idx: the path index
*/ struct nid_path *snd_hda_get_path_from_idx(struct hda_codec *codec, int idx)
{ struct hda_gen_spec *spec = codec->spec;
/* check whether the given DAC is already found in any existing paths */ staticbool is_dac_already_used(struct hda_codec *codec, hda_nid_t nid)
{ struct hda_gen_spec *spec = codec->spec; conststruct nid_path *path; int i;
snd_array_for_each(&spec->paths, i, path) { if (path->path[0] == nid) returntrue;
} returnfalse;
}
/* check whether the given two widgets can be connected */ staticbool is_reachable_path(struct hda_codec *codec,
hda_nid_t from_nid, hda_nid_t to_nid)
{ if (!from_nid || !to_nid) returnfalse; return snd_hda_get_conn_index(codec, to_nid, from_nid, true) >= 0;
}
/* nid, dir and idx */ #define AMP_VAL_COMPARE_MASK (0xffff | (1U << 18) | (0x0f << 19))
/* check whether the given ctl is already assigned in any path elements */ staticbool is_ctl_used(struct hda_codec *codec, unsignedint val, int type)
{ struct hda_gen_spec *spec = codec->spec; conststruct nid_path *path; int i;
val &= AMP_VAL_COMPARE_MASK;
snd_array_for_each(&spec->paths, i, path) { if ((path->ctls[type] & AMP_VAL_COMPARE_MASK) == val) returntrue;
} returnfalse;
}
/* check whether a control with the given (nid, dir, idx) was assigned */ staticbool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid, int dir, int idx, int type)
{ unsignedint val = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir); return is_ctl_used(codec, val, type);
}
/* called recursively */ staticbool __parse_nid_path(struct hda_codec *codec,
hda_nid_t from_nid, hda_nid_t to_nid, int anchor_nid, struct nid_path *path, int depth)
{ const hda_nid_t *conn; int i, nums;
if (to_nid == anchor_nid)
anchor_nid = 0; /* anchor passed */ elseif (to_nid == (hda_nid_t)(-anchor_nid)) returnfalse; /* hit the exclusive nid */
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, * try to find an empty DAC
*/ if (from_nid ||
get_wcaps_type(get_wcaps(codec, conn[i])) != AC_WID_AUD_OUT ||
is_dac_already_used(codec, conn[i])) continue;
} /* anchor is not requested or already passed? */ if (anchor_nid <= 0) goto found;
} if (depth >= MAX_NID_PATH_DEPTH) returnfalse; for (i = 0; i < nums; i++) { unsignedint type;
type = get_wcaps_type(get_wcaps(codec, conn[i])); if (type == AC_WID_AUD_OUT || type == AC_WID_AUD_IN ||
type == AC_WID_PIN) continue; if (__parse_nid_path(codec, from_nid, conn[i],
anchor_nid, path, depth + 1)) goto found;
} returnfalse;
/* * snd_hda_parse_nid_path - parse the widget path from the given nid to * the target nid * @codec: the HDA codec * @from_nid: the NID where the path start from * @to_nid: the NID where the path ends at * @anchor_nid: the anchor indication * @path: the path object to store the result * * Returns true if a matching path is found. * * The parsing behavior depends on parameters: * when @from_nid is 0, try to find an empty DAC; * 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.
*/ staticbool snd_hda_parse_nid_path(struct hda_codec *codec, hda_nid_t from_nid,
hda_nid_t to_nid, int anchor_nid, struct nid_path *path)
{ if (__parse_nid_path(codec, from_nid, to_nid, anchor_nid, path, 1)) {
path->path[path->depth] = to_nid;
path->depth++; returntrue;
} returnfalse;
}
/** * snd_hda_add_new_path - parse the path between the given NIDs and * add to the path list * @codec: the HDA codec * @from_nid: the NID where the path start from * @to_nid: the NID where the path ends at * @anchor_nid: the anchor indication, see snd_hda_parse_nid_path() * * If no valid path is found, returns NULL.
*/ struct nid_path *
snd_hda_add_new_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; 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, anchor_nid, path)) return path; /* push back */
spec->paths.used--; return NULL;
}
EXPORT_SYMBOL_GPL(snd_hda_add_new_path);
/* clear the given path as invalid so that it won't be picked up later */ staticvoid 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));
}
/* return a DAC if paired to the given pin by codec driver */ static hda_nid_t get_preferred_dac(struct hda_codec *codec, hda_nid_t pin)
{ struct hda_gen_spec *spec = codec->spec; const hda_nid_t *list = spec->preferred_dacs;
if (!list) return 0; for (; *list; list += 2) if (*list == pin) return list[1]; return 0;
}
/* look for an empty DAC slot */ static hda_nid_t look_for_dac(struct hda_codec *codec, hda_nid_t pin, bool is_digital)
{ struct hda_gen_spec *spec = codec->spec; bool cap_digital; int i;
for (i = 0; i < spec->num_all_dacs; i++) {
hda_nid_t nid = spec->all_dacs[i]; if (!nid || is_dac_already_used(codec, nid)) continue;
cap_digital = !!(get_wcaps(codec, nid) & AC_WCAP_DIGITAL); if (is_digital != cap_digital) continue; if (is_reachable_path(codec, nid, pin)) return nid;
} return 0;
}
/* replace the channels in the composed amp value with the given number */ staticunsignedint amp_val_replace_channels(unsignedint val, unsignedint chs)
{
val &= ~(0x3U << 16);
val |= chs << 16; return val;
}
/* look for a widget suitable for assigning a mute switch in the path */ static hda_nid_t look_for_out_mute_nid(struct hda_codec *codec, struct nid_path *path)
{ int i;
for (i = path->depth - 1; i >= 0; i--) { if (nid_has_mute(codec, path->path[i], HDA_OUTPUT)) return path->path[i]; if (i != path->depth - 1 && i != 0 &&
nid_has_mute(codec, path->path[i], HDA_INPUT)) return path->path[i];
} return 0;
}
/* look for a widget suitable for assigning a volume ctl in the path */ static hda_nid_t look_for_out_vol_nid(struct hda_codec *codec, struct nid_path *path)
{ struct hda_gen_spec *spec = codec->spec; int i;
for (i = path->depth - 1; i >= 0; i--) {
hda_nid_t nid = path->path[i]; if ((spec->out_vol_mask >> nid) & 1) continue; if (nid_has_volume(codec, nid, HDA_OUTPUT)) return nid;
} return 0;
}
/* * path activation / deactivation
*/
/* can have the amp-in capability? */ staticbool has_amp_in(struct hda_codec *codec, struct nid_path *path, int idx)
{
hda_nid_t nid = path->path[idx]; unsignedint caps = get_wcaps(codec, nid); unsignedint type = get_wcaps_type(caps);
if (!(caps & AC_WCAP_IN_AMP)) returnfalse; if (type == AC_WID_PIN && idx > 0) /* only for input pins */ returnfalse; returntrue;
}
/* can have the amp-out capability? */ staticbool has_amp_out(struct hda_codec *codec, struct nid_path *path, int idx)
{
hda_nid_t nid = path->path[idx]; unsignedint caps = get_wcaps(codec, nid); unsignedint type = get_wcaps_type(caps);
if (!(caps & AC_WCAP_OUT_AMP)) returnfalse; if (type == AC_WID_PIN && !idx) /* only for output pins */ returnfalse; returntrue;
}
/* check whether the given (nid,dir,idx) is active */ staticbool is_active_nid(struct hda_codec *codec, hda_nid_t nid, unsignedint dir, unsignedint idx)
{ struct hda_gen_spec *spec = codec->spec; int type = get_wcaps_type(get_wcaps(codec, nid)); conststruct nid_path *path; int i, n;
if (nid == codec->core.afg) returntrue;
snd_array_for_each(&spec->paths, n, path) { if (!path->active) continue; if (codec->power_save_node) { if (!path->stream_enabled) continue; /* ignore unplugged paths except for DAC/ADC */ if (!(path->pin_enabled || path->pin_fixed) &&
type != AC_WID_AUD_OUT && type != AC_WID_AUD_IN) continue;
} for (i = 0; i < path->depth; i++) { if (path->path[i] == nid) { if (dir == HDA_OUTPUT || idx == -1 ||
path->idx[i] == idx) returntrue; break;
}
}
} returnfalse;
}
/* check whether the NID is referred by any active paths */ #define is_active_nid_for_any(codec, nid) \
is_active_nid(codec, nid, HDA_OUTPUT, -1)
/* get the default amp value for the target state */ staticint get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid, int dir, unsignedint caps, bool enable)
{ unsignedint val = 0;
if (caps & AC_AMPCAP_NUM_STEPS) { /* set to 0dB */ if (enable)
val = (caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
} if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) { if (!enable)
val |= HDA_AMP_MUTE;
} return val;
}
/* is this a stereo widget or a stereo-to-mono mix? */ staticbool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid, int dir)
{ unsignedint wcaps = get_wcaps(codec, nid);
hda_nid_t conn;
if (wcaps & AC_WCAP_STEREO) returntrue; if (dir != HDA_INPUT || get_wcaps_type(wcaps) != AC_WID_AUD_MIX) returnfalse; if (snd_hda_get_num_conns(codec, nid) != 1) returnfalse; if (snd_hda_get_connections(codec, nid, &conn, 1) < 0) returnfalse; return !!(get_wcaps(codec, conn) & AC_WCAP_STEREO);
}
/* initialize the amp value (only at the first time) */ staticvoid init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx)
{ unsignedint caps = query_amp_caps(codec, nid, dir); int val = get_amp_val_to_activate(codec, nid, dir, caps, false);
/* update the amp, doing in stereo or mono depending on NID */ staticint update_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx, unsignedint mask, unsignedint val)
{ if (is_stereo_amps(codec, nid, dir)) return snd_hda_codec_amp_stereo(codec, nid, dir, idx,
mask, val); else return snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
mask, val);
}
/* calculate amp value mask we can modify; * if the given amp is controlled by mixers, don't touch it
*/ staticunsignedint get_amp_mask_to_modify(struct hda_codec *codec,
hda_nid_t nid, int dir, int idx, unsignedint caps)
{ unsignedint mask = 0xff;
val &= mask;
update_amp(codec, nid, dir, idx, mask, val);
}
staticvoid check_and_activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx, int idx_to_check, bool enable)
{ /* check whether the given amp is still used by others */ if (!enable && is_active_nid(codec, nid, dir, idx_to_check)) return;
activate_amp(codec, nid, dir, idx, idx_to_check, enable);
}
staticvoid activate_amp_out(struct hda_codec *codec, struct nid_path *path, int i, bool enable)
{
hda_nid_t nid = path->path[i];
init_amp(codec, nid, HDA_OUTPUT, 0);
check_and_activate_amp(codec, nid, HDA_OUTPUT, 0, 0, enable);
}
staticvoid 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; const hda_nid_t *conn; int n, nums, idx; int type;
hda_nid_t nid = path->path[i];
for (n = 0; n < nums; n++)
init_amp(codec, nid, HDA_INPUT, n);
/* 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) { if (conn[n] != spec->mixer_merge_nid) continue; /* when aamix is disabled, force to off */ if (!add_aamix) {
activate_amp(codec, nid, HDA_INPUT, n, n, false); continue;
}
}
check_and_activate_amp(codec, nid, HDA_INPUT, n, idx, enable);
}
}
/* sync power of each widget in the given path */ static hda_nid_t path_power_update(struct hda_codec *codec, struct nid_path *path, bool allow_powerdown)
{
hda_nid_t nid, changed = 0; int i, state, power;
for (i = 0; i < path->depth; i++) {
nid = path->path[i]; if (!(get_wcaps(codec, nid) & AC_WCAP_POWER)) continue; if (nid == codec->core.afg) continue; if (!allow_powerdown || is_active_nid_for_any(codec, nid))
state = AC_PWRST_D0; else
state = AC_PWRST_D3;
power = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_POWER_STATE, 0); if (power != (state | (state << 4))) {
snd_hda_codec_write(codec, nid, 0,
AC_VERB_SET_POWER_STATE, state);
changed = nid; /* all known codecs seem to be capable to handl * widgets state even in D3, so far. * if any new codecs need to restore the widget * states after D0 transition, call the function * below.
*/ #if 0 /* disabled */ if (state == AC_PWRST_D0)
snd_hdac_regmap_sync_node(&codec->core, nid); #endif
}
} return changed;
}
/* do sync with the last power state change */ staticvoid sync_power_state_change(struct hda_codec *codec, hda_nid_t nid)
{ if (nid) {
msleep(10);
snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
}
}
/** * snd_hda_activate_path - activate or deactivate the given path * @codec: the HDA codec * @path: the path to activate/deactivate * @enable: flag to activate or not * @add_aamix: enable the input from aamix NID * * If @add_aamix is set, enable the input from aa-mix NID as well (if any).
*/ void snd_hda_activate_path(struct hda_codec *codec, struct nid_path *path, bool enable, bool add_aamix)
{ struct hda_gen_spec *spec = codec->spec; int i;
path->active = enable;
/* make sure the widget is powered up */ if (enable && (spec->power_down_unused || codec->power_save_node))
path_power_update(codec, path, codec->power_save_node);
for (i = path->depth - 1; i >= 0; i--) {
hda_nid_t nid = path->path[i];
if (enable && path->multi[i])
snd_hda_codec_write_cache(codec, nid, 0,
AC_VERB_SET_CONNECT_SEL,
path->idx[i]); if (has_amp_in(codec, path, i))
activate_amp_in(codec, path, i, enable, add_aamix); if (has_amp_out(codec, path, i))
activate_amp_out(codec, path, i, enable);
}
}
EXPORT_SYMBOL_GPL(snd_hda_activate_path);
/* if the given path is inactive, put widgets into D3 (only if suitable) */ staticvoid path_power_down_sync(struct hda_codec *codec, struct nid_path *path)
{ struct hda_gen_spec *spec = codec->spec;
/* turn on/off EAPD on the given pin */ staticvoid 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 (spec->keep_eapd_on && !enable) return; if (codec->inv_eapd)
enable = !enable;
snd_hda_codec_write_cache(codec, pin, 0,
AC_VERB_SET_EAPD_BTLENABLE,
enable ? 0x02 : 0x00);
}
/* re-initialize the path specified by the given path index */ staticvoid resume_path_from_idx(struct hda_codec *codec, int path_idx)
{ struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx); if (path)
snd_hda_activate_path(codec, path, path->active, false);
}
/* * Helper functions for creating mixer ctl elements
*/
/* create a mute-switch for the given mixer widget; * if it has multiple sources (e.g. DAC and loopback), create a bind-mute
*/ staticint add_sw_ctl(struct hda_codec *codec, constchar *pfx, int cidx, unsignedint chs, struct nid_path *path)
{ unsignedint val; int type = HDA_CTL_WIDGET_MUTE;
if (!path) return 0;
val = path->ctls[NID_PATH_MUTE_CTL]; if (!val) return 0;
val = amp_val_replace_channels(val, chs); if (get_amp_direction_(val) == HDA_INPUT) {
hda_nid_t nid = get_amp_nid_(val); int nums = snd_hda_get_num_conns(codec, nid); if (nums > 1) {
type = HDA_CTL_BIND_MUTE;
val |= nums << 19;
}
} return __add_pb_sw_ctrl(codec->spec, type, pfx, cidx, val);
}
mutex_lock(&codec->control_mutex);
pval = kcontrol->private_value;
indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT; for (i = 0; i < indices; i++) {
kcontrol->private_value = (pval & ~AMP_VAL_IDX_MASK) |
(i << AMP_VAL_IDX_SHIFT);
err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); if (err < 0) break;
change |= err;
}
kcontrol->private_value = pval;
mutex_unlock(&codec->control_mutex); return err < 0 ? err : change;
}
/* any ctl assigned to the path with the given index? */ staticbool 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];
}
/* give some appropriate ctl name prefix for the given line out channel */ staticconstchar *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;
/* if there is really a single DAC used in the whole output paths, * use it master (or "PCM" if a vmaster hook is present)
*/ if (spec->multiout.num_dacs == 1 && !spec->mixer_nid &&
!codec->force_pin_prefix &&
!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) goto fixed_name;
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"; case AUTO_PIN_LINE_OUT: /* This deals with the case where one HP or one Speaker or * one HP + one Speaker need to share the DAC with LO
*/ if (!ch) { bool hp_lo_shared = false, spk_lo_shared = false;
if (cfg->speaker_outs)
spk_lo_shared = !path_has_mixer(codec,
spec->speaker_paths[0], ctl_type); if (cfg->hp_outs)
hp_lo_shared = !path_has_mixer(codec, spec->hp_paths[0], ctl_type); if (hp_lo_shared && spk_lo_shared) return spec->vmaster_mute.hook ? "PCM" : "Master"; if (hp_lo_shared) return"Headphone+LO"; if (spk_lo_shared) return"Speaker+LO";
}
}
/* for a single channel output, we don't have to name the channel */ if (cfg->line_outs == 1 && !spec->multi_ios) return"Line Out";
fixed_name: if (ch >= ARRAY_SIZE(channel_name)) {
snd_BUG(); return"PCM";
}
return channel_name[ch];
}
/* * Parse output paths
*/
/* badness definition */ enum { /* No primary DAC is found for the main output */
BAD_NO_PRIMARY_DAC = 0x10000, /* No DAC is found for the extra output */
BAD_NO_DAC = 0x4000, /* No possible multi-ios */
BAD_MULTI_IO = 0x120, /* No individual DAC for extra output */
BAD_NO_EXTRA_DAC = 0x102, /* No individual DAC for extra surrounds */
BAD_NO_EXTRA_SURR_DAC = 0x101, /* Primary DAC shared with main surrounds */
BAD_SHARED_SURROUND = 0x100, /* No independent HP possible */
BAD_NO_INDEP_HP = 0x10, /* Primary DAC shared with main CLFE */
BAD_SHARED_CLFE = 0x10, /* Primary DAC shared with extra surrounds */
BAD_SHARED_EXTRA_SURROUND = 0x10, /* Volume widget is shared */
BAD_SHARED_VOL = 0x10,
};
/* 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.
*/ staticint assign_out_path_ctls(struct hda_codec *codec, struct nid_path *path)
{ struct hda_gen_spec *spec = codec->spec;
hda_nid_t nid; unsignedint 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); if (spec->dac_min_mute)
val |= HDA_AMP_VAL_MIN_MUTE; if (is_ctl_used(codec, val, NID_PATH_VOL_CTL))
badness += BAD_SHARED_VOL; else
path->ctls[NID_PATH_VOL_CTL] = val;
} else
badness += BAD_SHARED_VOL;
nid = look_for_out_mute_nid(codec, path); if (nid) { unsignedint wid_type = get_wcaps_type(get_wcaps(codec, nid)); if (wid_type == AC_WID_PIN || wid_type == AC_WID_AUD_OUT ||
nid_has_mute(codec, nid, HDA_OUTPUT))
val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); else
val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT); if (is_ctl_used(codec, val, NID_PATH_MUTE_CTL))
badness += BAD_SHARED_VOL; else
path->ctls[NID_PATH_MUTE_CTL] = val;
} else
badness += BAD_SHARED_VOL; return badness;
}
/* 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 */ staticinline 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 */ staticint try_assign_dacs(struct hda_codec *codec, int num_outs, const hda_nid_t *pins, hda_nid_t *dacs, int *path_idx, conststruct badness_table *bad)
{ struct hda_gen_spec *spec = codec->spec; int i, j; int badness = 0;
hda_nid_t dac;
if (!num_outs) return 0;
for (i = 0; i < num_outs; i++) { struct nid_path *path;
hda_nid_t pin = pins[i];
if (!spec->preferred_dacs) {
path = snd_hda_get_path_from_idx(codec, path_idx[i]); if (path) {
badness += assign_out_path_ctls(codec, path); continue;
}
}
if (!dacs[i])
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 (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; elseif (i == 1)
badness += bad->shared_surr; else
badness += bad->shared_clfe;
} elseif (is_reachable_path(codec, spec->private_dac_nids[0], pin)) {
dac = spec->private_dac_nids[0];
badness += bad->shared_surr_main;
} elseif (!i)
badness += bad->no_primary_dac; else
badness += bad->no_dac;
} if (!dac) continue;
path = snd_hda_add_new_path(codec, dac, pin, -spec->mixer_nid); if (!path && !i && spec->mixer_nid) { /* try with aamix */
path = snd_hda_add_new_path(codec, dac, pin, 0);
} if (!path) {
dacs[i] = 0;
badness += bad->no_dac;
} else { /* print_nid_path(codec, "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 NID if the given pin has only a single connection to a certain DAC */ static hda_nid_t get_dac_if_single(struct hda_codec *codec, hda_nid_t pin)
{ struct hda_gen_spec *spec = codec->spec; int i;
hda_nid_t nid_found = 0;
for (i = 0; i < spec->num_all_dacs; i++) {
hda_nid_t nid = spec->all_dacs[i]; if (!nid || is_dac_already_used(codec, nid)) continue; if (is_reachable_path(codec, nid, pin)) { if (nid_found) return 0;
nid_found = nid;
}
} return nid_found;
}
/* check whether the given pin can be a multi-io pin */ staticbool can_be_multiio_pin(struct hda_codec *codec, unsignedint location, hda_nid_t nid)
{ unsignedint defcfg, caps;
defcfg = snd_hda_codec_get_pincfg(codec, nid); if (get_defcfg_connect(defcfg) != AC_JACK_PORT_COMPLEX) returnfalse; if (location && get_defcfg_location(defcfg) != location) returnfalse;
caps = snd_hda_query_pin_caps(codec, nid); if (!(caps & AC_PINCAP_OUT)) returnfalse; returntrue;
}
/* count the number of input pins that are capable to be multi-io */ staticint 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; unsignedint defcfg = snd_hda_codec_get_pincfg(codec, reference_pin); unsignedint 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 * * When hardwired is set, try to fill ony hardwired pins, and returns * zero if any pins are filled, non-zero if nothing found. * When hardwired is off, try to fill possible input pins, and returns * the badness value.
*/ staticint fill_multi_ios(struct hda_codec *codec,
hda_nid_t reference_pin, bool hardwired)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; int type, i, j, num_pins, old_pins; unsignedint defcfg = snd_hda_codec_get_pincfg(codec, reference_pin); unsignedint 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 = count_multiio_pins(codec, reference_pin); if (num_pins < 2) goto end_fill;
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;
hda_nid_t dac = 0;
if (cfg->inputs[i].type != type) continue; if (!can_be_multiio_pin(codec, location, nid)) continue; for (j = 0; j < spec->multi_ios; j++) { if (nid == spec->multi_io[j].pin) break;
} if (j < spec->multi_ios) continue;
if (hardwired)
dac = get_dac_if_single(codec, nid); elseif (!dac)
dac = look_for_dac(codec, nid, false); if (!dac) {
badness++; continue;
}
path = snd_hda_add_new_path(codec, dac, nid,
-spec->mixer_nid); if (!path) {
badness++; continue;
} /* print_nid_path(codec, "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;
}
}
end_fill: if (badness)
badness = BAD_MULTI_IO; if (old_pins == spec->multi_ios) { if (hardwired) return 1; /* nothing found */ else return badness; /* no badness if nothing found */
} if (!hardwired && spec->multi_ios < 2) { /* cancel newly assigned paths */
spec->paths.used -= spec->multi_ios - old_pins;
spec->multi_ios = old_pins; return badness;
}
/* assign volume and mute controls */ 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 */ staticbool map_singles(struct hda_codec *codec, int outs, 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;
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(codec, "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 */ staticint check_aamix_out_path(struct hda_codec *codec, int path_idx)
{ struct hda_gen_spec *spec = codec->spec; struct nid_path *path;
hda_nid_t path_dac, dac, pin;
/* check whether the independent HP is available with the current config */ staticbool indep_hp_possible(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; struct nid_path *path; int i, idx;
if (cfg->line_out_type == AUTO_PIN_HP_OUT)
idx = spec->out_paths[0]; else
idx = spec->hp_paths[0];
path = snd_hda_get_path_from_idx(codec, idx); if (!path) returnfalse;
/* assume no path conflicts unless aamix is involved */ if (!spec->mixer_nid || !is_nid_contained(path, spec->mixer_nid)) returntrue;
/* check whether output paths contain aamix */ for (i = 0; i < cfg->line_outs; i++) { if (spec->out_paths[i] == idx) break;
path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]); if (path && is_nid_contained(path, spec->mixer_nid)) returnfalse;
} for (i = 0; i < cfg->speaker_outs; i++) {
path = snd_hda_get_path_from_idx(codec, spec->speaker_paths[i]); if (path && is_nid_contained(path, spec->mixer_nid)) returnfalse;
}
returntrue;
}
/* fill the empty entries in the dac array for speaker/hp with the * shared dac pointed by the paths
*/ staticvoid 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 */ staticint fill_and_eval_dacs(struct hda_codec *codec, bool fill_hardwired, bool fill_mio_first)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; int i, err, badness;
/* set num_dacs once to full for look_for_dac() */
spec->multiout.num_dacs = cfg->line_outs;
spec->multiout.dac_nids = spec->private_dac_nids;
memset(spec->private_dac_nids, 0, sizeof(spec->private_dac_nids));
memset(spec->multiout.hp_out_nid, 0, sizeof(spec->multiout.hp_out_nid));
memset(spec->multiout.extra_out_nid, 0, sizeof(spec->multiout.extra_out_nid));
spec->multi_ios = 0;
snd_array_free(&spec->paths);
/* add playback controls for speaker and HP outputs */ staticint create_extra_outs(struct hda_codec *codec, int num_pins, constint *paths, constchar *pfx)
{ int i;
for (i = 0; i < num_pins; i++) { constchar *name; char tmp[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int err, idx = 0;
if (num_pins == 2 && i == 1 && !strcmp(pfx, "Speaker"))
name = "Bass Speaker"; elseif (num_pins >= 3) {
snprintf(tmp, sizeof(tmp), "%s %s",
pfx, channel_name[i]);
name = tmp;
} else {
name = pfx;
idx = i;
}
err = create_extra_out(codec, paths[i], name, idx); if (err < 0) return err;
} return 0;
}
mutex_lock(&spec->pcm_mutex); if (spec->active_streams) {
ret = -EBUSY; goto unlock;
}
if (spec->indep_hp_enabled != select) {
hda_nid_t *dacp; if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
dacp = &spec->private_dac_nids[0]; else
dacp = &spec->multiout.hp_out_nid[0];
/* update HP aamix paths in case it conflicts with indep HP */ if (spec->have_aamix_ctl) { if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
update_aamix_paths(codec, spec->aamix_mode,
spec->out_paths[0],
spec->aamix_out_paths[0],
spec->autocfg.line_out_type); else
update_aamix_paths(codec, spec->aamix_mode,
spec->hp_paths[0],
spec->aamix_out_paths[1],
AUTO_PIN_HP_OUT);
}
/* Default value to be passed as aamix argument for snd_hda_activate_path(); * used for output paths
*/ staticbool aamix_default(struct hda_gen_spec *spec)
{ return !spec->have_aamix_ctl || spec->aamix_mode;
}
/* if HP aamix path is driven from a different DAC and the * independent HP mode is ON, can't turn on aamix path
*/ if (out_type == AUTO_PIN_HP_OUT && spec->indep_hp_enabled &&
mix_path->path[0] != spec->alt_dac_nid)
do_mix = false;
/* re-initialize the output paths; only called from loopback_mixing_put() */ staticvoid update_output_paths(struct hda_codec *codec, int num_outs, constint *paths)
{ struct hda_gen_spec *spec = codec->spec; struct nid_path *path; int i;
for (i = 0; i < num_outs; i++) {
path = snd_hda_get_path_from_idx(codec, paths[i]); if (path)
snd_hda_activate_path(codec, path, path->active,
spec->aamix_mode);
}
}
if (!force) {
val = snd_hda_codec_get_pin_target(codec, pin); if (as_mic) { if (val & PIN_IN) return;
} else { if (val & PIN_OUT) return;
}
}
val = snd_hda_get_default_vref(codec, pin); /* if the HP pin doesn't support VREF and the codec driver gives an * alternative pin, set up the VREF on that pin instead
*/ if (val == AC_PINCTL_VREF_HIZ && spec->shared_mic_vref_pin) { const hda_nid_t vref_pin = spec->shared_mic_vref_pin; unsignedint vref_val = snd_hda_get_default_vref(codec, vref_pin); if (vref_val != AC_PINCTL_VREF_HIZ)
snd_hda_set_pin_ctl_cache(codec, vref_pin,
PIN_IN | (as_mic ? vref_val : 0));
}
if (!spec->hp_mic_jack_modes) { if (as_mic)
val |= PIN_IN; else
val = PIN_HP;
set_pin_target(codec, pin, val, true);
call_hp_automute(codec, NULL);
}
}
/* create a shared input with the headphone out */ staticint create_hp_mic(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; unsignedint defcfg;
hda_nid_t nid;
if (!spec->hp_mic) { if (spec->suppress_hp_mic_detect) return 0; /* automatic detection: only if no input or a single internal * input pin is found, try to detect the shared hp/mic
*/ if (cfg->num_inputs > 1) return 0; elseif (cfg->num_inputs == 1) {
defcfg = snd_hda_codec_get_pincfg(codec, cfg->inputs[0].pin); if (snd_hda_get_input_pin_attr(defcfg) != INPUT_PIN_ATTR_INT) return 0;
}
}
spec->hp_mic = 0; /* clear once */ if (cfg->num_inputs >= AUTO_CFG_MAX_INS) return 0;
nid = 0; if (cfg->line_out_type == AUTO_PIN_HP_OUT && cfg->line_outs > 0)
nid = cfg->line_out_pins[0]; elseif (cfg->hp_outs > 0)
nid = cfg->hp_pins[0]; if (!nid) return 0;
if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_IN)) return 0; /* no input */
cfg->inputs[cfg->num_inputs].pin = nid;
cfg->inputs[cfg->num_inputs].type = AUTO_PIN_MIC;
cfg->inputs[cfg->num_inputs].is_headphone_mic = 1;
cfg->num_inputs++;
spec->hp_mic = 1;
spec->hp_mic_pin = nid; /* we can't handle auto-mic together with HP-mic */
spec->suppress_auto_mic = 1;
codec_dbg(codec, "Enable shared I/O jack on NID 0x%x\n", nid); return 0;
}
/* convert from the enum item index to the vref ctl index (0=HIZ, 1=50%...) */ staticint get_vref_idx(unsignedint vref_caps, unsignedint item_idx)
{ unsignedint 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 */ staticint cvt_from_vref_idx(unsignedint vref_caps, unsignedint idx)
{ unsignedint i, n = 0;
for (i = 0; i < NUM_VREFS; i++) { if (i == idx) return n; if (vref_caps & (1 << i))
n++;
} return 0;
}
snd_hda_enum_helper_info(kcontrol, uinfo, hweight32(vref_caps),
vref_texts); /* set the right text */
strscpy(uinfo->value.enumerated.name,
vref_texts[get_vref_idx(vref_caps, uinfo->value.enumerated.item)]); return 0;
}
if (pin == spec->hp_mic_pin) return 0; /* already done in create_out_jack_mode() */
/* 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 (get_in_jack_num_items(codec, pin) <= 1) return 0;
/* return true if either a volume or a mute amp is found for the given * aamix path; the amp has to be either in the mixer node or its direct leaf
*/ staticbool look_for_mix_leaf_ctls(struct hda_codec *codec, hda_nid_t mix_nid,
hda_nid_t pin, unsignedint *mix_val, unsignedint *mute_val)
{ int idx, num_conns; const hda_nid_t *list;
hda_nid_t nid;
/* 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;
}
/* filter out invalid adc_nids that don't give all active input pins; * if needed, check whether dynamic ADC-switching is available
*/ staticint check_dyn_adc_switch(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct hda_input_mux *imux = &spec->input_mux; unsignedint ok_bits; int i, n, nums;
nums = 0;
ok_bits = 0; for (n = 0; n < spec->num_adc_nids; n++) { for (i = 0; i < imux->num_items; i++) { if (!spec->input_paths[i][n]) break;
} if (i >= imux->num_items) {
ok_bits |= (1 << n);
nums++;
}
}
if (!ok_bits) { /* check whether ADC-switch is possible */ for (i = 0; i < imux->num_items; i++) { for (n = 0; n < spec->num_adc_nids; n++) { if (spec->input_paths[i][n]) {
spec->dyn_adc_idx[i] = n; break;
}
}
}
codec_dbg(codec, "enabling ADC switching\n");
spec->dyn_adc_switch = 1;
} elseif (nums != spec->num_adc_nids) { /* 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];
spec->input_paths[i][n] = 0;
}
}
nums++;
}
spec->num_adc_nids = nums;
}
if (imux->num_items == 1 ||
(imux->num_items == 2 && spec->hp_mic)) {
codec_dbg(codec, "reducing to a single ADC\n");
spec->num_adc_nids = 1; /* reduce to a single ADC */
}
/* single index for individual volumes ctls */ if (!spec->dyn_adc_switch && spec->multi_cap_vol)
spec->num_adc_nids = 1;
return 0;
}
/* parse capture source paths from the given pin and create imux items */ staticint parse_capture_source(struct hda_codec *codec, hda_nid_t pin, int cfg_idx, int num_adcs, constchar *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 (!imux_added) { if (spec->hp_mic_pin == pin)
spec->hp_mic_mux_idx = imux->num_items;
spec->imux_pins[imux->num_items] = pin;
snd_hda_add_imux_item(codec, imux, label, cfg_idx, NULL);
imux_added = true; if (spec->dyn_adc_switch)
spec->dyn_adc_idx[imux_idx] = c;
}
}
return 0;
}
/* * create playback/capture controls for input pins
*/
/* fill the label for each input at first */ staticint fill_input_pin_labels(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; conststruct auto_pin_cfg *cfg = &spec->autocfg; int i;
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t pin = cfg->inputs[i].pin; constchar *label; int j, idx;
/* call the given amp update function for all amps in the imux list at once */ staticint cap_put_caller(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol,
put_call_t func, int type)
{ struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct hda_gen_spec *spec = codec->spec; conststruct hda_input_mux *imux; struct nid_path *path; int i, adc_idx, ret, err = 0;
imux = &spec->input_mux;
adc_idx = kcontrol->id.index;
mutex_lock(&codec->control_mutex); for (i = 0; i < imux->num_items; i++) {
path = get_input_path(codec, adc_idx, i); if (!path || !path->ctls[type]) continue;
kcontrol->private_value = path->ctls[type];
ret = func(kcontrol, ucontrol); if (ret < 0) {
err = ret; break;
} if (ret > 0)
err = 1;
}
mutex_unlock(&codec->control_mutex); if (err >= 0 && spec->cap_sync_hook)
spec->cap_sync_hook(codec, kcontrol, ucontrol); return err;
}
if (!spec->inv_dmic_split) returnfalse; for (i = 0; i < cfg->num_inputs; i++) { if (cfg->inputs[i].pin != nid) continue; if (cfg->inputs[i].type != AUTO_PIN_MIC) returnfalse;
val = snd_hda_codec_get_pincfg(codec, nid); return snd_hda_get_input_pin_attr(val) == INPUT_PIN_ATTR_INT;
} returnfalse;
}
/* capture switch put callback for a single control with hook call */ staticint 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, kcontrol, ucontrol);
/* return the vol ctl when used first in the imux list */ staticunsignedint get_first_cap_ctl(struct hda_codec *codec, int idx, int type)
{ struct nid_path *path; unsignedint ctl; int i;
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 = get_input_path(codec, 0, i); if (path && path->ctls[type] == ctl) return 0;
} return ctl;
}
/* create individual capture volume and switch controls per input */ staticint create_multi_cap_vol_ctl(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct hda_input_mux *imux = &spec->input_mux; int i, err, type;
for (i = 0; i < imux->num_items; i++) { bool inv_dmic; int idx;
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 = get_input_path(codec, n, i); if (!path) continue;
parse_capvol_in_path(codec, path); if (!vol)
vol = path->ctls[NID_PATH_VOL_CTL]; elseif (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]; elseif (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); elseif (!multi_cap_vol && !inv_dmic)
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 */ staticbool check_boost_vol(struct hda_codec *codec, hda_nid_t nid, int dir, int idx)
{ unsignedint step;
/* look for a boost amp in a widget close to the pin */ staticunsignedint look_for_boost_amp(struct hda_codec *codec, struct nid_path *path)
{ unsignedint 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;
} elseif (check_boost_vol(codec, nid, HDA_INPUT,
path->idx[depth])) {
val = HDA_COMPOSE_AMP_VAL(nid, 3, path->idx[depth],
HDA_INPUT); break;
}
}
/** * snd_hda_gen_add_mute_led_cdev - Create a LED classdev and enable as vmaster mute LED * @codec: the HDA codec * @callback: the callback for LED classdev brightness_set_blocking
*/ int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec, int (*callback)(struct led_classdev *, enum led_brightness))
{ struct hda_gen_spec *spec = codec->spec; int err;
if (callback) {
err = create_mute_led_cdev(codec, callback, false); if (err) {
codec_warn(codec, "failed to create a mute LED cdev\n"); return err;
}
}
if (spec->vmaster_mute.hook)
codec_err(codec, "vmaster hook already present before cdev!\n");
/** * snd_hda_gen_add_micmute_led_cdev - Create a LED classdev and enable as mic-mute LED * @codec: the HDA codec * @callback: the callback for LED classdev brightness_set_blocking * * Called from the codec drivers for offering the mic mute LED controls. * This creates a LED classdev and sets up the cap_sync_hook that is called at * each time when the capture mixer switch changes. * * When NULL is passed to @callback, no classdev is created but only the * LED-trigger is set up. * * Returns 0 or a negative error.
*/ int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec, int (*callback)(struct led_classdev *, enum led_brightness))
{ struct hda_gen_spec *spec = codec->spec; int err;
if (callback) {
err = create_mute_led_cdev(codec, callback, true); if (err) {
codec_warn(codec, "failed to create a mic-mute LED cdev\n"); return err;
}
}
/* * parse digital I/Os and set up NIDs in BIOS auto-parse mode
*/ staticvoid 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, pin;
/* support multiple SPDIFs; the secondary is set up as a follower */
nums = 0; for (i = 0; i < spec->autocfg.dig_outs; i++) {
pin = spec->autocfg.dig_out_pins[i];
dig_nid = look_for_dac(codec, pin, true); if (!dig_nid) continue;
path = snd_hda_add_new_path(codec, dig_nid, pin, 0); if (!path) continue;
print_nid_path(codec, "digout", path);
path->active = true;
path->pin_fixed = true; /* no jack detection */
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];
} else {
spec->multiout.follower_dig_outs = spec->follower_dig_outs; if (nums >= ARRAY_SIZE(spec->follower_dig_outs) - 1) break;
spec->follower_dig_outs[nums - 1] = dig_nid;
}
nums++;
}
staticbool dyn_adc_pcm_resetup(struct hda_codec *codec, int cur);
/* select the given imux item; either unmute exclusively or select the route */ staticint mux_select(struct hda_codec *codec, unsignedint adc_idx, unsignedint idx)
{ struct hda_gen_spec *spec = codec->spec; conststruct hda_input_mux *imux; struct nid_path *old_path, *path;
imux = &spec->input_mux; if (!imux->num_items) return 0;
if (idx >= imux->num_items)
idx = imux->num_items - 1; if (spec->cur_mux[adc_idx] == idx) return 0;
old_path = get_input_path(codec, adc_idx, spec->cur_mux[adc_idx]); if (!old_path) return 0; if (old_path->active)
snd_hda_activate_path(codec, old_path, false, false);
spec->cur_mux[adc_idx] = idx;
if (spec->hp_mic)
update_hp_mic(codec, adc_idx, false);
if (spec->dyn_adc_switch)
dyn_adc_pcm_resetup(codec, 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, NULL, NULL);
path_power_down_sync(codec, old_path); return 1;
}
/* power up/down widgets in the all paths that match with the given NID * as terminals (either start- or endpoint) * * returns the last changed NID, or zero if unchanged.
*/ static hda_nid_t set_path_power(struct hda_codec *codec, hda_nid_t nid, int pin_state, int stream_state)
{ struct hda_gen_spec *spec = codec->spec;
hda_nid_t last, changed = 0; struct nid_path *path; int n;
snd_array_for_each(&spec->paths, n, path) { if (!path->depth) continue; if (path->path[0] == nid ||
path->path[path->depth - 1] == nid) { bool pin_old = path->pin_enabled; bool stream_old = path->stream_enabled;
if (pin_state >= 0)
path->pin_enabled = pin_state; if (stream_state >= 0)
path->stream_enabled = stream_state; if ((!path->pin_fixed && path->pin_enabled != pin_old)
|| path->stream_enabled != stream_old) {
last = path_power_update(codec, path, true); if (last)
changed = last;
}
}
} return changed;
}
/* check the jack status for power control */ staticbool detect_pin_state(struct hda_codec *codec, hda_nid_t pin)
{ if (!is_jack_detectable(codec, pin)) returntrue; return snd_hda_jack_detect_state(codec, pin) != HDA_JACK_NOT_PRESENT;
}
/* power up/down the paths of the given pin according to the jack state; * power = 0/1 : only power up/down if it matches with the jack state, * < 0 : force power up/down to follow the jack sate * * returns the last changed NID, or zero if unchanged.
*/ static hda_nid_t set_pin_power_jack(struct hda_codec *codec, hda_nid_t pin, int power)
{ bool on;
if (!codec->power_save_node) return 0;
on = detect_pin_state(codec, pin);
if (power >= 0 && on != power) return 0; return set_path_power(codec, pin, on, -1);
}
/* callback only doing power up -- called at first */ staticvoid pin_power_up_callback(struct hda_codec *codec, struct hda_jack_callback *jack)
{
pin_power_callback(codec, jack, true);
}
/* callback only doing power down -- called at last */ staticvoid pin_power_down_callback(struct hda_codec *codec, struct hda_jack_callback *jack)
{
pin_power_callback(codec, jack, false);
}
/* set up the power up/down callbacks */ staticvoid add_pin_power_ctls(struct hda_codec *codec, int num_pins, const hda_nid_t *pins, bool on)
{ int i;
hda_jack_callback_fn cb =
on ? pin_power_up_callback : pin_power_down_callback;
for (i = 0; i < num_pins && pins[i]; i++) { if (is_jack_detectable(codec, pins[i]))
snd_hda_jack_detect_enable_callback(codec, pins[i], cb); else
set_path_power(codec, pins[i], true, -1);
}
}
/* enabled power callback to each available I/O pin with jack detections; * the digital I/O pins are excluded because of the unreliable detectsion
*/ staticvoid add_all_pin_power_ctls(struct hda_codec *codec, bool on)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; int i;
if (!codec->power_save_node) return;
add_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins, on); if (cfg->line_out_type != AUTO_PIN_HP_OUT)
add_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins, on); if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
add_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins, on); for (i = 0; i < cfg->num_inputs; i++)
add_pin_power_ctls(codec, 1, &cfg->inputs[i].pin, on);
}
/* sync path power up/down with the jack states of given pins */ staticvoid sync_pin_power_ctls(struct hda_codec *codec, int num_pins, const hda_nid_t *pins)
{ int i;
for (i = 0; i < num_pins && pins[i]; i++) if (is_jack_detectable(codec, pins[i]))
set_pin_power_jack(codec, pins[i], -1);
}
/* sync path power up/down with pins; called at init and resume */ staticvoid sync_all_pin_power_ctls(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; int i;
if (!codec->power_save_node) return;
sync_pin_power_ctls(codec, cfg->line_outs, cfg->line_out_pins); if (cfg->line_out_type != AUTO_PIN_HP_OUT)
sync_pin_power_ctls(codec, cfg->hp_outs, cfg->hp_pins); if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT)
sync_pin_power_ctls(codec, cfg->speaker_outs, cfg->speaker_pins); for (i = 0; i < cfg->num_inputs; i++)
sync_pin_power_ctls(codec, 1, &cfg->inputs[i].pin);
}
/* add fake paths if not present yet */ staticint add_fake_paths(struct hda_codec *codec, hda_nid_t nid, int num_pins, const hda_nid_t *pins)
{ struct hda_gen_spec *spec = codec->spec; struct nid_path *path; int i;
for (i = 0; i < num_pins; i++) { if (!pins[i]) break; if (get_nid_path(codec, nid, pins[i], 0)) continue;
path = snd_array_new(&spec->paths); if (!path) return -ENOMEM;
memset(path, 0, sizeof(*path));
path->depth = 2;
path->path[0] = nid;
path->path[1] = pins[i];
path->active = true;
} return 0;
}
/* create fake paths to all outputs from beep */ staticint add_fake_beep_paths(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg;
hda_nid_t nid = spec->beep_nid; int err;
if (!codec->power_save_node || !nid) return 0;
err = add_fake_paths(codec, nid, cfg->line_outs, cfg->line_out_pins); if (err < 0) return err; if (cfg->line_out_type != AUTO_PIN_HP_OUT) {
err = add_fake_paths(codec, nid, cfg->hp_outs, cfg->hp_pins); if (err < 0) return err;
} if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) {
err = add_fake_paths(codec, nid, cfg->speaker_outs,
cfg->speaker_pins); if (err < 0) return err;
} return 0;
}
/* power up/down beep widget and its output paths */ staticvoid beep_power_hook(struct hda_beep *beep, bool on)
{
set_path_power(beep->codec, beep->nid, -1, on);
}
/** * snd_hda_gen_fix_pin_power - Fix the power of the given pin widget to D0 * @codec: the HDA codec * @pin: NID of pin to fix
*/ int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin)
{ struct hda_gen_spec *spec = codec->spec; struct nid_path *path;
/* * Jack detections for HP auto-mute and mic-switch
*/
/* check each pin in the given array; returns true if any of them is plugged */ staticbool detect_jacks(struct hda_codec *codec, int num_pins, const hda_nid_t *pins)
{ int i; bool present = false;
for (i = 0; i < num_pins; i++) {
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; if (snd_hda_jack_detect_state(codec, nid) == HDA_JACK_PRESENT)
present = true;
} return present;
}
/* standard HP/line-out auto-mute helper */ staticvoid do_automute(struct hda_codec *codec, int num_pins, const hda_nid_t *pins, int *paths, bool mute)
{ struct hda_gen_spec *spec = codec->spec; int i;
for (i = 0; i < num_pins; i++) {
hda_nid_t nid = pins[i]; unsignedint val, oldval; if (!nid) break;
oldval = snd_hda_codec_get_pin_target(codec, nid); if (oldval & PIN_IN) continue; /* no mute for inputs */
if (spec->auto_mute_via_amp) { struct nid_path *path;
hda_nid_t mute_nid;
path = snd_hda_get_path_from_idx(codec, paths[i]); if (!path) continue;
mute_nid = get_amp_nid_(path->ctls[NID_PATH_MUTE_CTL]); if (!mute_nid) continue; if (mute)
spec->mute_bits |= (1ULL << mute_nid); else
spec->mute_bits &= ~(1ULL << mute_nid); continue;
} else { /* 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 = oldval & ~PIN_HP; else
val = 0; if (!mute)
val |= oldval; /* 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 (codec->power_save_node) { bool on = !mute; if (on)
on = detect_pin_state(codec, nid);
set_path_power(codec, nid, on, -1);
}
}
}
/** * snd_hda_gen_update_outputs - Toggle outputs muting * @codec: the HDA codec * * Update the mute status of all outputs based on the current jack states.
*/ void snd_hda_gen_update_outputs(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; int *paths; int on;
/* Control HP pins/amps depending on master_mute state; * in general, HP pins/amps control should be enabled in all cases, * but currently set only for master_mute, just to be safe
*/ if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT)
paths = spec->out_paths; else
paths = spec->hp_paths;
do_automute(codec, ARRAY_SIZE(spec->autocfg.hp_pins),
spec->autocfg.hp_pins, paths, 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; if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
paths = spec->out_paths; else
paths = spec->speaker_paths;
do_automute(codec, ARRAY_SIZE(spec->autocfg.speaker_pins),
spec->autocfg.speaker_pins, paths, on);
/* toggle line-out mutes if needed, too */ /* if LO is a copy of either HP or Speaker, don't need to handle it */ if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0] ||
spec->autocfg.line_out_pins[0] == spec->autocfg.speaker_pins[0]) return; if (!spec->automute_lo)
on = 0; else
on = spec->hp_jack_present;
on |= spec->master_mute;
spec->line_out_muted = on;
paths = spec->out_paths;
do_automute(codec, ARRAY_SIZE(spec->autocfg.line_out_pins),
spec->autocfg.line_out_pins, paths, on);
}
EXPORT_SYMBOL_GPL(snd_hda_gen_update_outputs);
/* sync the whole vmaster followers to reflect the new auto-mute status */ if (spec->auto_mute_via_amp && !codec->bus->shutdown)
snd_ctl_sync_vmaster(spec->vmaster_mute.sw_kctl, false);
}
/** * snd_hda_gen_hp_automute - standard HP-automute helper * @codec: the HDA codec * @jack: jack object, NULL for the whole
*/ void snd_hda_gen_hp_automute(struct hda_codec *codec, struct hda_jack_callback *jack)
{ struct hda_gen_spec *spec = codec->spec;
hda_nid_t *pins = spec->autocfg.hp_pins; int num_pins = ARRAY_SIZE(spec->autocfg.hp_pins);
/* No detection for the first HP jack during indep-HP mode */ if (spec->indep_hp_enabled) {
pins++;
num_pins--;
}
/** * snd_hda_gen_line_automute - standard line-out-automute helper * @codec: the HDA codec * @jack: jack object, NULL for the whole
*/ void snd_hda_gen_line_automute(struct hda_codec *codec, struct hda_jack_callback *jack)
{ struct hda_gen_spec *spec = codec->spec;
if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) return; /* check LO jack only when it's different from HP */ if (spec->autocfg.line_out_pins[0] == spec->autocfg.hp_pins[0]) return;
if (!snd_hda_gen_add_kctl(spec, NULL, &automute_mode_enum)) return -ENOMEM; return 0;
}
/* * Check the availability of HP/line-out auto-mute; * Set up appropriately if really supported
*/ staticint check_auto_mute_availability(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; 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])
present++; if (cfg->speaker_pins[0])
present++; if (present < 2) /* need two different output types */ return 0;
for (i = 0; i < cfg->hp_outs; i++) {
hda_nid_t nid = cfg->hp_pins[i]; if (!is_jack_detectable(codec, nid)) continue;
codec_dbg(codec, "Enable HP auto-muting on NID 0x%x\n", nid);
snd_hda_jack_detect_enable_callback(codec, nid,
call_hp_automute);
spec->detect_hp = 1;
}
if (cfg->line_out_type == AUTO_PIN_LINE_OUT && cfg->line_outs) { if (cfg->speaker_outs) for (i = 0; i < cfg->line_outs; i++) {
hda_nid_t nid = cfg->line_out_pins[i]; if (!is_jack_detectable(codec, nid)) continue;
codec_dbg(codec, "Enable Line-Out auto-muting on NID 0x%x\n", nid);
snd_hda_jack_detect_enable_callback(codec, nid,
call_line_automute);
spec->detect_lo = 1;
}
spec->automute_lo_possible = spec->detect_hp;
}
if (spec->automute_speaker_possible || spec->automute_lo_possible) { /* create a control for automute mode */
err = add_automute_mode_enum(codec); if (err < 0) return err;
} return 0;
}
/* check whether all auto-mic pins are valid; setup indices if OK */ staticbool auto_mic_check_imux(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; conststruct hda_input_mux *imux; int i;
imux = &spec->input_mux; for (i = 0; i < spec->am_num_entries; i++) {
spec->am_entry[i].idx =
find_idx_in_nid_list(spec->am_entry[i].pin,
spec->imux_pins, imux->num_items); if (spec->am_entry[i].idx < 0) returnfalse; /* no corresponding imux */
}
/* we don't need the jack detection for the first pin */ for (i = 1; i < spec->am_num_entries; i++)
snd_hda_jack_detect_enable_callback(codec,
spec->am_entry[i].pin,
call_mic_autoswitch); returntrue;
}
/* * Check the availability of auto-mic switch; * Set up if really supported
*/ staticint check_auto_mic_availability(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; unsignedint 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++) {
hda_nid_t nid = cfg->inputs[i].pin; unsignedint attr;
attr = snd_hda_codec_get_pincfg(codec, nid);
attr = snd_hda_get_input_pin_attr(attr); if (types & (1 << attr)) return 0; /* already occupied */ switch (attr) { case INPUT_PIN_ATTR_INT: if (cfg->inputs[i].type != AUTO_PIN_MIC) return 0; /* invalid type */ break; case INPUT_PIN_ATTR_UNUSED: return 0; /* invalid entry */ default: if (cfg->inputs[i].type > AUTO_PIN_LINE_IN) return 0; /* invalid type */ if (!spec->line_in_auto_switch &&
cfg->inputs[i].type != AUTO_PIN_MIC) return 0; /* only mic is allowed */ if (!is_jack_detectable(codec, nid)) return 0; /* no unsol support */ break;
} if (num_pins >= MAX_AUTO_MIC_PINS) return 0;
types |= (1 << attr);
spec->am_entry[num_pins].pin = nid;
spec->am_entry[num_pins].attr = attr;
num_pins++;
}
if (num_pins < 2) return 0;
spec->am_num_entries = num_pins; /* sort the am_entry in the order of attr so that the pin with a * higher attr will be selected when the jack is plugged.
*/
sort(spec->am_entry, num_pins, sizeof(spec->am_entry[0]),
compare_attr, NULL);
if (!auto_mic_check_imux(codec)) return 0;
spec->auto_mic = 1;
spec->num_adc_nids = 1;
spec->cur_mux[0] = spec->am_entry[0].idx;
codec_dbg(codec, "Enable auto-mic switch on NID 0x%x/0x%x/0x%x\n",
spec->am_entry[0].pin,
spec->am_entry[1].pin,
spec->am_entry[2].pin);
return 0;
}
/** * snd_hda_gen_path_power_filter - power_filter hook to make inactive widgets * into power down * @codec: the HDA codec * @nid: NID to evalute * @power_state: target power state
*/ unsignedint snd_hda_gen_path_power_filter(struct hda_codec *codec,
hda_nid_t nid, unsignedint power_state)
{ struct hda_gen_spec *spec = codec->spec;
if (!spec->power_down_unused && !codec->power_save_node) return power_state; if (power_state != AC_PWRST_D0 || nid == codec->core.afg) return power_state; if (get_wcaps_type(get_wcaps(codec, nid)) >= AC_WID_POWER) return power_state; if (is_active_nid_for_any(codec, nid)) return power_state; return AC_PWRST_D3;
}
EXPORT_SYMBOL_GPL(snd_hda_gen_path_power_filter);
/* mute all aamix inputs initially; parse up to the first leaves */ staticvoid mute_all_mixer_nid(struct hda_codec *codec, hda_nid_t mix)
{ int i, nums; const hda_nid_t *conn; bool has_amp;
nums = snd_hda_get_conn_list(codec, mix, &conn);
has_amp = nid_has_mute(codec, mix, HDA_INPUT); for (i = 0; i < nums; i++) { if (has_amp)
update_amp(codec, mix, HDA_INPUT, i,
0xff, HDA_AMP_MUTE); elseif (nid_has_volume(codec, conn[i], HDA_OUTPUT))
update_amp(codec, conn[i], HDA_OUTPUT, 0,
0xff, HDA_AMP_MUTE);
}
}
/** * snd_hda_gen_stream_pm - Stream power management callback * @codec: the HDA codec * @nid: audio widget * @on: power on/off flag * * Set this in hda_codec_ops.stream_pm. Only valid with power_save_node flag.
*/ void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on)
{ if (codec->power_save_node)
set_path_power(codec, nid, -1, on);
}
EXPORT_SYMBOL_GPL(snd_hda_gen_stream_pm);
/* forcibly mute the speaker output without caching; return true if updated */ staticbool force_mute_output_path(struct hda_codec *codec, hda_nid_t nid)
{ if (!nid) returnfalse; if (!nid_has_mute(codec, nid, HDA_OUTPUT)) returnfalse; /* no mute, skip */ if (snd_hda_codec_amp_read(codec, nid, 0, HDA_OUTPUT, 0) &
snd_hda_codec_amp_read(codec, nid, 1, HDA_OUTPUT, 0) &
HDA_AMP_MUTE) returnfalse; /* both channels already muted, skip */
/* direct amp update without caching */
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
AC_AMP_SET_OUTPUT | AC_AMP_SET_LEFT |
AC_AMP_SET_RIGHT | HDA_AMP_MUTE); returntrue;
}
/** * snd_hda_gen_shutup_speakers - Forcibly mute the speaker outputs * @codec: the HDA codec * * Forcibly mute the speaker outputs, to be called at suspend or shutdown. * * The mute state done by this function isn't cached, hence the original state * will be restored at resume. * * Return true if the mute state has been changed.
*/ bool snd_hda_gen_shutup_speakers(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; constint *paths; conststruct nid_path *path; int i, p, num_paths; bool updated = false;
/* if already powered off, do nothing */ if (!snd_hdac_is_power_on(&codec->core)) returnfalse;
for (i = 0; i < num_paths; i++) {
path = snd_hda_get_path_from_idx(codec, paths[i]); if (!path) continue; for (p = 0; p < path->depth; p++) if (force_mute_output_path(codec, path->path[p]))
updated = true;
}
/** * snd_hda_gen_parse_auto_config - Parse the given BIOS configuration and * set up the hda_gen_spec * @codec: the HDA codec * @cfg: Parsed pin configuration * * return 1 if successful, 0 if the proper config is not found, * or a negative error code
*/ int snd_hda_gen_parse_auto_config(struct hda_codec *codec, struct auto_pin_cfg *cfg)
{ struct hda_gen_spec *spec = codec->spec; int err;
parse_user_hints(codec);
if (spec->vmaster_mute_led || spec->mic_mute_led)
snd_ctl_led_request();
if (spec->mixer_nid && !spec->mixer_merge_nid)
spec->mixer_merge_nid = spec->mixer_nid;
if (!spec->main_out_badness)
spec->main_out_badness = &hda_main_out_badness; if (!spec->extra_out_badness)
spec->extra_out_badness = &hda_extra_out_badness;
if (!spec->no_primary_hp &&
cfg->line_out_type == AUTO_PIN_SPEAKER_OUT &&
cfg->line_outs <= cfg->hp_outs) { /* use HP as primary out */
cfg->speaker_outs = cfg->line_outs;
memcpy(cfg->speaker_pins, cfg->line_out_pins, sizeof(cfg->speaker_pins));
cfg->line_outs = cfg->hp_outs;
memcpy(cfg->line_out_pins, cfg->hp_pins, sizeof(cfg->hp_pins));
cfg->hp_outs = 0;
memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins));
cfg->line_out_type = AUTO_PIN_HP_OUT;
}
err = parse_output_paths(codec); if (err < 0) return err;
err = create_multi_channel_mode(codec); if (err < 0) return err;
err = create_multi_out_ctls(codec, cfg); if (err < 0) return err;
err = create_hp_out_ctls(codec); 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_hp_mic(codec); if (err < 0) return err;
err = create_input_ctls(codec); if (err < 0) return err;
/* add power-down pin callbacks at first */
add_all_pin_power_ctls(codec, false);
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) return err;
err = check_dyn_adc_switch(codec); if (err < 0) return err;
err = check_auto_mic_availability(codec); if (err < 0) return err;
/* add stereo mix if available and not enabled yet */ if (!spec->auto_mic && spec->mixer_nid &&
spec->add_stereo_mix_input == HDA_HINT_STEREO_MIX_AUTO &&
spec->input_mux.num_items > 1) {
err = parse_capture_source(codec, spec->mixer_nid,
CFG_IDX_MIX, spec->num_all_adcs, "Stereo Mix", 0); if (err < 0) return err;
}
err = create_capture_mixers(codec); if (err < 0) return err;
err = parse_mic_boost(codec); if (err < 0) return err;
/* create "Headphone Mic Jack Mode" if no input selection is * available (or user specifies add_jack_modes hint)
*/ if (spec->hp_mic_pin &&
(spec->auto_mic || spec->input_mux.num_items == 1 ||
spec->add_jack_modes)) {
err = create_hp_mic_jack_mode(codec, spec->hp_mic_pin); if (err < 0) return err;
}
if (spec->add_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;
}
}
/* add power-up pin callbacks at last */
add_all_pin_power_ctls(codec, true);
/* mute all aamix input initially */ if (spec->mixer_nid)
mute_all_mixer_nid(codec, spec->mixer_nid);
dig_only:
parse_digital(codec);
if (spec->power_down_unused || codec->power_save_node) { if (!codec->power_filter)
codec->power_filter = snd_hda_gen_path_power_filter;
}
if (!spec->no_analog && spec->beep_nid) {
err = snd_hda_attach_beep_device(codec, spec->beep_nid); if (err < 0) return err; if (codec->beep && codec->power_save_node) {
err = add_fake_beep_paths(codec); if (err < 0) return err;
codec->beep->power_hook = beep_power_hook;
}
}
/** * snd_hda_gen_build_controls - Build controls from the parsed results * @codec: the HDA codec * * Pass this to build_controls hda_codec_ops.
*/ int snd_hda_gen_build_controls(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; int err;
if (spec->kctls.used) {
err = snd_hda_add_new_ctls(codec, spec->kctls.list); if (err < 0) return err;
}
if (spec->multiout.dig_out_nid) {
err = snd_hda_create_dig_out_ctls(codec,
spec->multiout.dig_out_nid,
spec->multiout.dig_out_nid,
spec->pcm_rec[1]->pcm_type); if (err < 0) return err; if (!spec->no_analog) {
err = snd_hda_create_spdif_share_sw(codec,
&spec->multiout); if (err < 0) return err;
spec->multiout.share_spdif = 1;
}
} if (spec->dig_in_nid) {
err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); if (err < 0) return err;
}
/* if we have no master control, let's create it */ if (!spec->no_analog && !spec->suppress_vmaster &&
!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) {
err = snd_hda_add_vmaster(codec, "Master Playback Volume",
spec->vmaster_tlv, follower_pfxs, "Playback Volume", 0); if (err < 0) return err;
} if (!spec->no_analog && !spec->suppress_vmaster &&
!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) {
err = __snd_hda_add_vmaster(codec, "Master Playback Switch",
NULL, follower_pfxs, "Playback Switch", true,
spec->vmaster_mute_led ?
SNDRV_CTL_ELEM_ACCESS_SPK_LED : 0,
&spec->vmaster_mute.sw_kctl); if (err < 0) return err; if (spec->vmaster_mute.hook) {
snd_hda_add_vmaster_hook(codec, &spec->vmaster_mute);
snd_hda_sync_vmaster_hook(&spec->vmaster_mute);
}
}
free_kctls(spec); /* no longer needed */
err = snd_hda_jack_add_kctls(codec, &spec->autocfg); if (err < 0) return err;
/*
*/ staticconststruct hda_pcm_stream pcm_analog_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 8, /* NID is set in build_pcms */
.ops = {
.open = playback_pcm_open,
.close = playback_pcm_close,
.prepare = playback_pcm_prepare,
.cleanup = playback_pcm_cleanup
},
};
staticconststruct hda_pcm_stream pcm_analog_capture = {
.substreams = 1,
.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
},
};
staticconststruct hda_pcm_stream pcm_analog_alt_playback = {
.substreams = 1,
.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
},
};
staticconststruct hda_pcm_stream pcm_analog_alt_capture = {
.substreams = 2, /* can be overridden */
.channels_min = 2,
.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
},
};
staticconststruct hda_pcm_stream pcm_digital_playback = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2, /* NID is set in build_pcms */
.ops = {
.open = dig_playback_pcm_open,
.close = dig_playback_pcm_close,
.prepare = dig_playback_pcm_prepare,
.cleanup = dig_playback_pcm_cleanup
},
};
staticconststruct hda_pcm_stream pcm_digital_capture = {
.substreams = 1,
.channels_min = 2,
.channels_max = 2, /* NID is set in build_pcms */
};
/* Used by build_pcms to flag that a PCM has no playback stream */ staticconststruct hda_pcm_stream pcm_null_stream = {
.substreams = 0,
.channels_min = 0,
.channels_max = 0,
};
skip_analog: /* SPDIF for stream index #1 */ if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
fill_pcm_stream_name(spec->stream_name_digital, sizeof(spec->stream_name_digital), " Digital", codec->core.chip_name);
info = snd_hda_codec_pcm_new(codec, "%s",
spec->stream_name_digital); if (!info) return -ENOMEM;
codec->follower_dig_outs = spec->multiout.follower_dig_outs;
spec->pcm_rec[1] = info; if (spec->dig_out_type)
info->pcm_type = spec->dig_out_type; else
info->pcm_type = HDA_PCM_TYPE_SPDIF; if (spec->multiout.dig_out_nid)
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
&pcm_digital_playback,
spec->stream_digital_playback,
spec->multiout.dig_out_nid); if (spec->dig_in_nid)
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
&pcm_digital_capture,
spec->stream_digital_capture,
spec->dig_in_nid);
}
if (spec->no_analog) return 0;
/* If the use of more than one ADC is requested for the current * model, configure a second analog capture-only PCM.
*/
have_multi_adcs = (spec->num_adc_nids > 1) &&
!spec->dyn_adc_switch && !spec->auto_mic; /* Additional Analaog capture for index #2 */ if (spec->alt_dac_nid || have_multi_adcs) {
fill_pcm_stream_name(spec->stream_name_alt_analog, sizeof(spec->stream_name_alt_analog), " Alt Analog", codec->core.chip_name);
info = snd_hda_codec_pcm_new(codec, "%s",
spec->stream_name_alt_analog); if (!info) return -ENOMEM;
spec->pcm_rec[2] = info; if (spec->alt_dac_nid)
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
&pcm_analog_alt_playback,
spec->stream_analog_alt_playback,
spec->alt_dac_nid); else
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
&pcm_null_stream, NULL, 0); if (have_multi_adcs) {
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
&pcm_analog_alt_capture,
spec->stream_analog_alt_capture,
spec->adc_nids[1]);
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams =
spec->num_adc_nids - 1;
} else {
setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_CAPTURE],
&pcm_null_stream, NULL, 0);
}
}
/* configure the given path as a proper output */ staticvoid set_output_and_unmute(struct hda_codec *codec, int path_idx)
{ struct nid_path *path;
hda_nid_t pin;
if (spec->dyn_adc_switch)
nums = 1; else
nums = spec->num_adc_nids;
for (c = 0; c < nums; c++) { for (i = 0; i < imux->num_items; i++) {
path = get_input_path(codec, c, i); if (path) { bool active = path->active; if (i == spec->cur_mux[c])
active = true;
snd_hda_activate_path(codec, path, active, false);
}
} if (spec->hp_mic)
update_hp_mic(codec, c, true);
}
if (spec->cap_sync_hook)
spec->cap_sync_hook(codec, NULL, NULL);
}
/* set right pin controls for digital I/O */ staticvoid init_digital(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec; int i;
hda_nid_t pin;
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) {
restore_pin_ctl(codec, pin);
resume_path_from_idx(codec, spec->digin_path);
}
}
/* clear unsol-event tags on unused pins; Conexant codecs seem to leave * invalid unsol tags by some reason
*/ staticvoid clear_unsol_on_unused_pins(struct hda_codec *codec)
{ conststruct hda_pincfg *pin; int i;
snd_array_for_each(&codec->init_pins, i, pin) {
hda_nid_t nid = pin->nid; if (is_jack_detectable(codec, nid) &&
!snd_hda_jack_tbl_get(codec, nid))
snd_hda_codec_write_cache(codec, nid, 0,
AC_VERB_SET_UNSOLICITED_ENABLE, 0);
}
}
/** * snd_hda_gen_init - initialize the generic spec * @codec: the HDA codec * * This can be put as hda_codec_ops init function.
*/ int snd_hda_gen_init(struct hda_codec *codec)
{ struct hda_gen_spec *spec = codec->spec;
if (spec->init_hook)
spec->init_hook(codec);
if (!spec->skip_verbs)
snd_hda_apply_verbs(codec);
/** * snd_hda_gen_remove - free the generic spec * @codec: the HDA codec * * This can be put as hda_codec_ops remove function.
*/ void snd_hda_gen_remove(struct hda_codec *codec)
{
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_FREE);
snd_hda_gen_spec_free(codec->spec);
kfree(codec->spec);
codec->spec = NULL;
}
EXPORT_SYMBOL_GPL(snd_hda_gen_remove);
/** * snd_hda_gen_check_power_status - check the loopback power save state * @codec: the HDA codec * @nid: NID to inspect * * This can be put as hda_codec_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_GPL(snd_hda_gen_check_power_status);
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.144Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.