/** * 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);
}
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.