| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Universal Interface for Intel High Definition Audio Codec |
| * |
| * Generic widget tree parser |
| * |
| * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/export.h> |
| #include <linux/sort.h> |
| #include <linux/delay.h> |
| #include <linux/ctype.h> |
| #include <linux/string.h> |
| #include <linux/bitops.h> |
| #include <linux/module.h> |
| #include <linux/leds.h> |
| #include <sound/core.h> |
| #include <sound/jack.h> |
| #include <sound/tlv.h> |
| #include <sound/hda_codec.h> |
| #include "hda_local.h" |
| #include "hda_auto_parser.h" |
| #include "hda_jack.h" |
| #include "hda_beep.h" |
| #include "hda_generic.h" |
| |
| |
| /** |
| * snd_hda_gen_spec_init - initialize hda_gen_spec struct |
| * @spec: hda_gen_spec object to initialize |
| * |
| * Initialize the given hda_gen_spec object. |
| */ |
| int snd_hda_gen_spec_init(struct hda_gen_spec *spec) |
| { |
| snd_array_init(&spec->kctls, sizeof(struct snd_kcontrol_new), 32); |
| snd_array_init(&spec->paths, sizeof(struct nid_path), 8); |
| snd_array_init(&spec->loopback_list, sizeof(struct hda_amp_list), 8); |
| mutex_init(&spec->pcm_mutex); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(snd_hda_gen_spec_init); |
| |
| /** |
| * 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, const char *name, |
| const struct 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); |
| else if (knew->name) |
| knew->name = kstrdup(knew->name, GFP_KERNEL); |
| if (!knew->name) |
| return NULL; |
| return knew; |
| } |
| EXPORT_SYMBOL_GPL(snd_hda_gen_add_kctl); |
| |
| static void 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); |
| } |
| |
| static void 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); |
| } |
| |
| /* |
| * store user hints |
| */ |
| static void 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; |
| } |
| |
| /* |
| * pin control value accesses |
| */ |
| |
| #define update_pin_ctl(codec, pin, val) \ |
| snd_hda_codec_write_cache(codec, pin, 0, \ |
| AC_VERB_SET_PIN_WIDGET_CONTROL, val) |
| |
| /* restore the pinctl based on the cached value */ |
| static inline void restore_pin_ctl(struct hda_codec *codec, hda_nid_t pin) |
| { |
| update_pin_ctl(codec, pin, snd_hda_codec_get_pin_target(codec, pin)); |
| } |
| |
| /* set the pinctl target value and write it if requested */ |
| static void set_pin_target(struct hda_codec *codec, hda_nid_t pin, |
| unsigned int val, bool do_write) |
| { |
| if (!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 */ |
| static void set_pin_targets(struct hda_codec *codec, int num_pins, |
| hda_nid_t *pins, unsigned int val) |
| { |
| int i; |
| for (i = 0; i < num_pins; i++) |
| set_pin_target(codec, pins[i], val, false); |
| } |
| |
| /* |
| * parsing paths |
| */ |
| |
| /* return the position of NID in the list, or -1 if not found */ |
| static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) |
| { |
| int i; |
| for (i = 0; i < nums; i++) |
| if (list[i] == nid) |
| return i; |
| return -1; |
| } |
| |
| /* return true if the given NID is contained in the path */ |
| static bool is_nid_contained(struct nid_path *path, hda_nid_t nid) |
| { |
| return find_idx_in_nid_list(nid, path->path, path->depth) >= 0; |
| } |
| |
| static struct nid_path *get_nid_path(struct hda_codec *codec, |
| hda_nid_t from_nid, hda_nid_t to_nid, |
| int anchor_nid) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| struct nid_path *path; |
| int i; |
| |
| snd_array_for_each(&spec->paths, i, path) { |
| if (path->depth <= 0) |
| continue; |
| if ((!from_nid || path->path[0] == from_nid) && |
| (!to_nid || path->path[path->depth - 1] == to_nid)) { |
| if (!anchor_nid || |
| (anchor_nid > 0 && is_nid_contained(path, anchor_nid)) || |
| (anchor_nid < 0 && !is_nid_contained(path, anchor_nid))) |
| return path; |
| } |
| } |
| return NULL; |
| } |
| |
| /** |
| * 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; |
| |
| if (!spec->paths.used) |
| return 0; |
| idx = path - array; |
| if (idx < 0 || idx >= spec->paths.used) |
| return 0; |
| return idx + 1; |
| } |
| EXPORT_SYMBOL_GPL(snd_hda_get_path_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; |
| |
| if (idx <= 0 || idx > spec->paths.used) |
| return NULL; |
| return snd_array_elem(&spec->paths, idx - 1); |
| } |
| EXPORT_SYMBOL_GPL(snd_hda_get_path_from_idx); |
| |
| /* check whether the given DAC is already found in any existing paths */ |
| static bool is_dac_already_used(struct hda_codec *codec, hda_nid_t nid) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| const struct nid_path *path; |
| int i; |
| |
| snd_array_for_each(&spec->paths, i, path) { |
| if (path->path[0] == nid) |
| return true; |
| } |
| return false; |
| } |
| |
| /* check whether the given two widgets can be connected */ |
| static bool is_reachable_path(struct hda_codec *codec, |
| hda_nid_t from_nid, hda_nid_t to_nid) |
| { |
| if (!from_nid || !to_nid) |
| return false; |
| 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 */ |
| static bool is_ctl_used(struct hda_codec *codec, unsigned int val, int type) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| const struct 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) |
| return true; |
| } |
| return false; |
| } |
| |
| /* check whether a control with the given (nid, dir, idx) was assigned */ |
| static bool is_ctl_associated(struct hda_codec *codec, hda_nid_t nid, |
| int dir, int idx, int type) |
| { |
| unsigned int val = HDA_COMPOSE_AMP_VAL(nid, 3, idx, dir); |
| return is_ctl_used(codec, val, type); |
| } |
| |
| static void print_nid_path(struct hda_codec *codec, |
| const char *pfx, struct nid_path *path) |
| { |
| char buf[40]; |
| char *pos = buf; |
| int i; |
| |
| *pos = 0; |
| for (i = 0; i < path->depth; i++) |
| pos += scnprintf(pos, sizeof(buf) - (pos - buf), "%s%02x", |
| pos != buf ? ":" : "", |
| path->path[i]); |
| |
| codec_dbg(codec, "%s path: depth=%d '%s'\n", pfx, path->depth, buf); |
| } |
| |
| /* called recursively */ |
| static bool __parse_nid_path(struct hda_codec *codec, |
| hda_nid_t from_nid, hda_nid_t to_nid, |
| int 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 */ |
| else if (to_nid == (hda_nid_t)(-anchor_nid)) |
| return false; /* 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) |
| return false; |
| for (i = 0; i < nums; i++) { |
| unsigned int 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; |
| } |
| return false; |
| |
| found: |
| path->path[path->depth] = conn[i]; |
| path->idx[path->depth + 1] = i; |
| if (nums > 1 && get_wcaps_type(get_wcaps(codec, to_nid)) != AC_WID_AUD_MIX) |
| path->multi[path->depth + 1] = 1; |
| path->depth++; |
| return true; |
| } |
| |
| /* |
| * 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. |
| */ |
| static bool 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++; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * 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 */ |
| static void invalidate_nid_path(struct hda_codec *codec, int idx) |
| { |
| struct nid_path *path = snd_hda_get_path_from_idx(codec, idx); |
| if (!path) |
| return; |
| memset(path, 0, sizeof(*path)); |
| } |
| |
| /* 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 */ |
| static unsigned int amp_val_replace_channels(unsigned int val, unsigned int chs) |
| { |
| val &= ~(0x3U << 16); |
| val |= chs << 16; |
| return val; |
| } |
| |
| static bool same_amp_caps(struct hda_codec *codec, hda_nid_t nid1, |
| hda_nid_t nid2, int dir) |
| { |
| if (!(get_wcaps(codec, nid1) & (1 << (dir + 1)))) |
| return !(get_wcaps(codec, nid2) & (1 << (dir + 1))); |
| return (query_amp_caps(codec, nid1, dir) == |
| query_amp_caps(codec, nid2, dir)); |
| } |
| |
| /* 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? */ |
| static bool has_amp_in(struct hda_codec *codec, struct nid_path *path, int idx) |
| { |
| hda_nid_t nid = path->path[idx]; |
| unsigned int caps = get_wcaps(codec, nid); |
| unsigned int type = get_wcaps_type(caps); |
| |
| if (!(caps & AC_WCAP_IN_AMP)) |
| return false; |
| if (type == AC_WID_PIN && idx > 0) /* only for input pins */ |
| return false; |
| return true; |
| } |
| |
| /* can have the amp-out capability? */ |
| static bool has_amp_out(struct hda_codec *codec, struct nid_path *path, int idx) |
| { |
| hda_nid_t nid = path->path[idx]; |
| unsigned int caps = get_wcaps(codec, nid); |
| unsigned int type = get_wcaps_type(caps); |
| |
| if (!(caps & AC_WCAP_OUT_AMP)) |
| return false; |
| if (type == AC_WID_PIN && !idx) /* only for output pins */ |
| return false; |
| return true; |
| } |
| |
| /* check whether the given (nid,dir,idx) is active */ |
| static bool is_active_nid(struct hda_codec *codec, hda_nid_t nid, |
| unsigned int dir, unsigned int idx) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| int type = get_wcaps_type(get_wcaps(codec, nid)); |
| const struct nid_path *path; |
| int i, n; |
| |
| if (nid == codec->core.afg) |
| return true; |
| |
| 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) |
| return true; |
| break; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /* 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 */ |
| static int get_amp_val_to_activate(struct hda_codec *codec, hda_nid_t nid, |
| int dir, unsigned int caps, bool enable) |
| { |
| unsigned int 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? */ |
| static bool is_stereo_amps(struct hda_codec *codec, hda_nid_t nid, int dir) |
| { |
| unsigned int wcaps = get_wcaps(codec, nid); |
| hda_nid_t conn; |
| |
| if (wcaps & AC_WCAP_STEREO) |
| return true; |
| if (dir != HDA_INPUT || get_wcaps_type(wcaps) != AC_WID_AUD_MIX) |
| return false; |
| if (snd_hda_get_num_conns(codec, nid) != 1) |
| return false; |
| if (snd_hda_get_connections(codec, nid, &conn, 1) < 0) |
| return false; |
| return !!(get_wcaps(codec, conn) & AC_WCAP_STEREO); |
| } |
| |
| /* initialize the amp value (only at the first time) */ |
| static void init_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx) |
| { |
| unsigned int caps = query_amp_caps(codec, nid, dir); |
| int val = get_amp_val_to_activate(codec, nid, dir, caps, false); |
| |
| if (is_stereo_amps(codec, nid, dir)) |
| snd_hda_codec_amp_init_stereo(codec, nid, dir, idx, 0xff, val); |
| else |
| snd_hda_codec_amp_init(codec, nid, 0, dir, idx, 0xff, val); |
| } |
| |
| /* update the amp, doing in stereo or mono depending on NID */ |
| static int update_amp(struct hda_codec *codec, hda_nid_t nid, int dir, int idx, |
| unsigned int mask, unsigned int 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 |
| */ |
| static unsigned int get_amp_mask_to_modify(struct hda_codec *codec, |
| hda_nid_t nid, int dir, int idx, |
| unsigned int caps) |
| { |
| unsigned int mask = 0xff; |
| |
| if (caps & (AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) { |
| if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_MUTE_CTL)) |
| mask &= ~0x80; |
| } |
| if (caps & AC_AMPCAP_NUM_STEPS) { |
| if (is_ctl_associated(codec, nid, dir, idx, NID_PATH_VOL_CTL) || |
| is_ctl_associated(codec, nid, dir, idx, NID_PATH_BOOST_CTL)) |
| mask &= ~0x7f; |
| } |
| return mask; |
| } |
| |
| static void activate_amp(struct hda_codec *codec, hda_nid_t nid, int dir, |
| int idx, int idx_to_check, bool enable) |
| { |
| unsigned int caps; |
| unsigned int mask, val; |
| |
| caps = query_amp_caps(codec, nid, dir); |
| val = get_amp_val_to_activate(codec, nid, dir, caps, enable); |
| mask = get_amp_mask_to_modify(codec, nid, dir, idx_to_check, caps); |
| if (!mask) |
| return; |
| |
| val &= mask; |
| update_amp(codec, nid, dir, idx, mask, val); |
| } |
| |
| static void 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); |
| } |
| |
| static void 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); |
| } |
| |
| static void activate_amp_in(struct hda_codec *codec, struct nid_path *path, |
| int i, bool enable, bool add_aamix) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| const hda_nid_t *conn; |
| int n, nums, idx; |
| int type; |
| hda_nid_t nid = path->path[i]; |
| |
| nums = snd_hda_get_conn_list(codec, nid, &conn); |
| if (nums < 0) |
| return; |
| type = get_wcaps_type(get_wcaps(codec, nid)); |
| if (type == AC_WID_PIN || |
| (type == AC_WID_AUD_IN && codec->single_adc_amp)) { |
| nums = 1; |
| idx = 0; |
| } else |
| idx = path->idx[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 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 */ |
| static void 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) */ |
| static void path_power_down_sync(struct hda_codec *codec, struct nid_path *path) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| |
| if (!(spec->power_down_unused || codec->power_save_node) || path->active) |
| return; |
| sync_power_state_change(codec, path_power_update(codec, path, true)); |
| } |
| |
| /* turn on/off EAPD on the given pin */ |
| static void set_pin_eapd(struct hda_codec *codec, hda_nid_t pin, bool enable) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| if (spec->own_eapd_ctl || |
| !(snd_hda_query_pin_caps(codec, pin) & AC_PINCAP_EAPD)) |
| return; |
| if (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 */ |
| static void resume_path_from_idx(struct hda_codec *codec, int path_idx) |
| { |
| struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx); |
| if (path) |
| snd_hda_activate_path(codec, path, path->active, false); |
| } |
| |
| |
| /* |
| * Helper functions for creating mixer ctl elements |
| */ |
| |
| static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol); |
| static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol); |
| static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol); |
| |
| enum { |
| HDA_CTL_WIDGET_VOL, |
| HDA_CTL_WIDGET_MUTE, |
| HDA_CTL_BIND_MUTE, |
| }; |
| static const struct snd_kcontrol_new control_templates[] = { |
| HDA_CODEC_VOLUME(NULL, 0, 0, 0), |
| /* only the put callback is replaced for handling the special mute */ |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .subdevice = HDA_SUBDEV_AMP_FLAG, |
| .info = snd_hda_mixer_amp_switch_info, |
| .get = snd_hda_mixer_amp_switch_get, |
| .put = hda_gen_mixer_mute_put, /* replaced */ |
| .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0), |
| }, |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .info = snd_hda_mixer_amp_switch_info, |
| .get = hda_gen_bind_mute_get, |
| .put = hda_gen_bind_mute_put, /* replaced */ |
| .private_value = HDA_COMPOSE_AMP_VAL(0, 3, 0, 0), |
| }, |
| }; |
| |
| /* add dynamic controls from template */ |
| static struct snd_kcontrol_new * |
| add_control(struct hda_gen_spec *spec, int type, const char *name, |
| int cidx, unsigned long val) |
| { |
| struct snd_kcontrol_new *knew; |
| |
| knew = snd_hda_gen_add_kctl(spec, name, &control_templates[type]); |
| if (!knew) |
| return NULL; |
| knew->index = cidx; |
| if (get_amp_nid_(val)) |
| knew->subdevice = HDA_SUBDEV_AMP_FLAG; |
| knew->private_value = val; |
| return knew; |
| } |
| |
| static int add_control_with_pfx(struct hda_gen_spec *spec, int type, |
| const char *pfx, const char *dir, |
| const char *sfx, int cidx, unsigned long val) |
| { |
| char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; |
| snprintf(name, sizeof(name), "%s %s %s", pfx, dir, sfx); |
| if (!add_control(spec, type, name, cidx, val)) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| #define add_pb_vol_ctrl(spec, type, pfx, val) \ |
| add_control_with_pfx(spec, type, pfx, "Playback", "Volume", 0, val) |
| #define add_pb_sw_ctrl(spec, type, pfx, val) \ |
| add_control_with_pfx(spec, type, pfx, "Playback", "Switch", 0, val) |
| #define __add_pb_vol_ctrl(spec, type, pfx, cidx, val) \ |
| add_control_with_pfx(spec, type, pfx, "Playback", "Volume", cidx, val) |
| #define __add_pb_sw_ctrl(spec, type, pfx, cidx, val) \ |
| add_control_with_pfx(spec, type, pfx, "Playback", "Switch", cidx, val) |
| |
| static int add_vol_ctl(struct hda_codec *codec, const char *pfx, int cidx, |
| unsigned int chs, struct nid_path *path) |
| { |
| unsigned int val; |
| if (!path) |
| return 0; |
| val = path->ctls[NID_PATH_VOL_CTL]; |
| if (!val) |
| return 0; |
| val = amp_val_replace_channels(val, chs); |
| return __add_pb_vol_ctrl(codec->spec, HDA_CTL_WIDGET_VOL, pfx, cidx, val); |
| } |
| |
| /* return the channel bits suitable for the given path->ctls[] */ |
| static int get_default_ch_nums(struct hda_codec *codec, struct nid_path *path, |
| int type) |
| { |
| int chs = 1; /* mono (left only) */ |
| if (path) { |
| hda_nid_t nid = get_amp_nid_(path->ctls[type]); |
| if (nid && (get_wcaps(codec, nid) & AC_WCAP_STEREO)) |
| chs = 3; /* stereo */ |
| } |
| return chs; |
| } |
| |
| static int add_stereo_vol(struct hda_codec *codec, const char *pfx, int cidx, |
| struct nid_path *path) |
| { |
| int chs = get_default_ch_nums(codec, path, NID_PATH_VOL_CTL); |
| return add_vol_ctl(codec, pfx, cidx, chs, path); |
| } |
| |
| /* create a mute-switch for the given mixer widget; |
| * if it has multiple sources (e.g. DAC and loopback), create a bind-mute |
| */ |
| static int add_sw_ctl(struct hda_codec *codec, const char *pfx, int cidx, |
| unsigned int chs, struct nid_path *path) |
| { |
| unsigned int 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); |
| } |
| |
| static int add_stereo_sw(struct hda_codec *codec, const char *pfx, |
| int cidx, struct nid_path *path) |
| { |
| int chs = get_default_ch_nums(codec, path, NID_PATH_MUTE_CTL); |
| return add_sw_ctl(codec, pfx, cidx, chs, path); |
| } |
| |
| /* playback mute control with the software mute bit check */ |
| static void sync_auto_mute_bits(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; |
| |
| if (spec->auto_mute_via_amp) { |
| hda_nid_t nid = get_amp_nid(kcontrol); |
| bool enabled = !((spec->mute_bits >> nid) & 1); |
| ucontrol->value.integer.value[0] &= enabled; |
| ucontrol->value.integer.value[1] &= enabled; |
| } |
| } |
| |
| static int hda_gen_mixer_mute_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| sync_auto_mute_bits(kcontrol, ucontrol); |
| return snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); |
| } |
| |
| /* |
| * Bound mute controls |
| */ |
| #define AMP_VAL_IDX_SHIFT 19 |
| #define AMP_VAL_IDX_MASK (0x0f<<19) |
| |
| static int hda_gen_bind_mute_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| unsigned long pval; |
| int err; |
| |
| mutex_lock(&codec->control_mutex); |
| pval = kcontrol->private_value; |
| kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */ |
| err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol); |
| kcontrol->private_value = pval; |
| mutex_unlock(&codec->control_mutex); |
| return err; |
| } |
| |
| static int hda_gen_bind_mute_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| unsigned long pval; |
| int i, indices, err = 0, change = 0; |
| |
| sync_auto_mute_bits(kcontrol, ucontrol); |
| |
| 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? */ |
| static bool path_has_mixer(struct hda_codec *codec, int path_idx, int ctl_type) |
| { |
| struct nid_path *path = snd_hda_get_path_from_idx(codec, path_idx); |
| return path && path->ctls[ctl_type]; |
| } |
| |
| static const char * const channel_name[4] = { |
| "Front", "Surround", "CLFE", "Side" |
| }; |
| |
| /* give some appropriate ctl name prefix for the given line out channel */ |
| static const char *get_line_out_pfx(struct hda_codec *codec, int ch, |
| int *index, int ctl_type) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| struct auto_pin_cfg *cfg = &spec->autocfg; |
| |
| *index = 0; |
| if (cfg->line_outs == 1 && !spec->multi_ios && |
| !codec->force_pin_prefix && |
| !cfg->hp_outs && !cfg->speaker_outs) |
| return spec->vmaster_mute.hook ? "PCM" : "Master"; |
| |
| /* if there is really a single DAC used in the whole output paths, |
| * 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) |
| return channel_name[ch]; |
| |
| switch (cfg->line_out_type) { |
| case AUTO_PIN_SPEAKER_OUT: |
| /* if the primary channel vol/mute is shared with HP volume, |
| * don't name it as Speaker |
| */ |
| if (!ch && cfg->hp_outs && |
| !path_has_mixer(codec, spec->hp_paths[0], ctl_type)) |
| break; |
| if (cfg->line_outs == 1) |
| return "Speaker"; |
| if (cfg->line_outs == 2) |
| return ch ? "Bass Speaker" : "Speaker"; |
| break; |
| case AUTO_PIN_HP_OUT: |
| /* if the primary channel vol/mute is shared with spk volume, |
| * don't name it as Headphone |
| */ |
| if (!ch && cfg->speaker_outs && |
| !path_has_mixer(codec, spec->speaker_paths[0], ctl_type)) |
| break; |
| /* for multi-io case, only the primary out */ |
| if (ch && spec->multi_ios) |
| break; |
| *index = ch; |
| return "Headphone"; |
| case AUTO_PIN_LINE_OUT: |
| /* This deals with the case where we have two DACs and |
| * one LO, one HP and one Speaker */ |
| if (!ch && cfg->speaker_outs && cfg->hp_outs) { |
| bool hp_lo_shared = !path_has_mixer(codec, spec->hp_paths[0], ctl_type); |
| bool spk_lo_shared = !path_has_mixer(codec, spec->speaker_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"; |
| |
| 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. |
| */ |
| static int assign_out_path_ctls(struct hda_codec *codec, struct nid_path *path) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| hda_nid_t nid; |
| unsigned int val; |
| int badness = 0; |
| |
| if (!path) |
| return BAD_SHARED_VOL * 2; |
| |
| if (path->ctls[NID_PATH_VOL_CTL] || |
| path->ctls[NID_PATH_MUTE_CTL]) |
| return 0; /* already evaluated */ |
| |
| nid = look_for_out_vol_nid(codec, path); |
| if (nid) { |
| val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); |
| 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) { |
| unsigned int 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; |
| } |
| |
| const struct badness_table hda_main_out_badness = { |
| .no_primary_dac = BAD_NO_PRIMARY_DAC, |
| .no_dac = BAD_NO_DAC, |
| .shared_primary = BAD_NO_PRIMARY_DAC, |
| .shared_surr = BAD_SHARED_SURROUND, |
| .shared_clfe = BAD_SHARED_CLFE, |
| .shared_surr_main = BAD_SHARED_SURROUND, |
| }; |
| EXPORT_SYMBOL_GPL(hda_main_out_badness); |
| |
| const struct badness_table hda_extra_out_badness = { |
| .no_primary_dac = BAD_NO_DAC, |
| .no_dac = BAD_NO_DAC, |
| .shared_primary = BAD_NO_EXTRA_DAC, |
| .shared_surr = BAD_SHARED_EXTRA_SURROUND, |
| .shared_clfe = BAD_SHARED_EXTRA_SURROUND, |
| .shared_surr_main = BAD_NO_EXTRA_SURR_DAC, |
| }; |
| EXPORT_SYMBOL_GPL(hda_extra_out_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 */ |
| static inline hda_nid_t try_dac(struct hda_codec *codec, |
| hda_nid_t dac, hda_nid_t pin) |
| { |
| return is_reachable_path(codec, dac, pin) ? dac : 0; |
| } |
| |
| /* try to assign DACs to pins and return the resultant badness */ |
| static int try_assign_dacs(struct hda_codec *codec, int num_outs, |
| const hda_nid_t *pins, hda_nid_t *dacs, |
| int *path_idx, |
| const struct badness_table *bad) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| 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]; |
| |
| path = snd_hda_get_path_from_idx(codec, path_idx[i]); |
| if (path) { |
| badness += assign_out_path_ctls(codec, path); |
| continue; |
| } |
| |
| dacs[i] = get_preferred_dac(codec, pin); |
| if (dacs[i]) { |
| if (is_dac_already_used(codec, dacs[i])) |
| badness += bad->shared_primary; |
| } |
| |
| 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; |
| else if (i == 1) |
| badness += bad->shared_surr; |
| else |
| badness += bad->shared_clfe; |
| } else if (is_reachable_path(codec, spec->private_dac_nids[0], pin)) { |
| dac = spec->private_dac_nids[0]; |
| badness += bad->shared_surr_main; |
| } else if (!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) { |
| dac = 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 */ |
| static bool can_be_multiio_pin(struct hda_codec *codec, |
| unsigned int location, hda_nid_t nid) |
| { |
| unsigned int defcfg, caps; |
| |
| defcfg = snd_hda_codec_get_pincfg(codec, nid); |
| if (get_defcfg_connect(defcfg) != AC_JACK_PORT_COMPLEX) |
| return false; |
| if (location && get_defcfg_location(defcfg) != location) |
| return false; |
| caps = snd_hda_query_pin_caps(codec, nid); |
| if (!(caps & AC_PINCAP_OUT)) |
| return false; |
| return true; |
| } |
| |
| /* count the number of input pins that are capable to be multi-io */ |
| static int count_multiio_pins(struct hda_codec *codec, hda_nid_t reference_pin) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| struct auto_pin_cfg *cfg = &spec->autocfg; |
| unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin); |
| unsigned int location = get_defcfg_location(defcfg); |
| int type, i; |
| int num_pins = 0; |
| |
| for (type = AUTO_PIN_LINE_IN; type >= AUTO_PIN_MIC; type--) { |
| for (i = 0; i < cfg->num_inputs; i++) { |
| if (cfg->inputs[i].type != type) |
| continue; |
| if (can_be_multiio_pin(codec, location, |
| cfg->inputs[i].pin)) |
| num_pins++; |
| } |
| } |
| return num_pins; |
| } |
| |
| /* |
| * multi-io helper |
| * |
| * 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. |
| */ |
| static int 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; |
| unsigned int defcfg = snd_hda_codec_get_pincfg(codec, reference_pin); |
| unsigned int location = get_defcfg_location(defcfg); |
| int badness = 0; |
| struct nid_path *path; |
| |
| old_pins = spec->multi_ios; |
| if (old_pins >= 2) |
| goto end_fill; |
| |
| num_pins = 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); |
| else if (!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 */ |
| static bool 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; |
| } |
| |
| static inline bool has_aamix_out_paths(struct hda_gen_spec *spec) |
| { |
| return spec->aamix_out_paths[0] || spec->aamix_out_paths[1] || |
| spec->aamix_out_paths[2]; |
| } |
| |
| /* create a new path including aamix if available, and return its index */ |
| static int check_aamix_out_path(struct hda_codec *codec, int path_idx) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| struct nid_path *path; |
| hda_nid_t path_dac, dac, pin; |
| |
| path = snd_hda_get_path_from_idx(codec, path_idx); |
| if (!path || !path->depth || |
| is_nid_contained(path, spec->mixer_nid)) |
| return 0; |
| path_dac = path->path[0]; |
| dac = spec->private_dac_nids[0]; |
| pin = path->path[path->depth - 1]; |
| path = snd_hda_add_new_path(codec, dac, pin, spec->mixer_nid); |
| if (!path) { |
| if (dac != path_dac) |
| dac = path_dac; |
| else if (spec->multiout.hp_out_nid[0]) |
| dac = spec->multiout.hp_out_nid[0]; |
| else if (spec->multiout.extra_out_nid[0]) |
| dac = spec->multiout.extra_out_nid[0]; |
| else |
| dac = 0; |
| if (dac) |
| path = snd_hda_add_new_path(codec, dac, pin, |
| spec->mixer_nid); |
| } |
| if (!path) |
| return 0; |
| /* print_nid_path(codec, "output-aamix", path); */ |
| path->active = false; /* unused as default */ |
| path->pin_fixed = true; /* static route */ |
| return snd_hda_get_path_idx(codec, path); |
| } |
| |
| /* check whether the independent HP is available with the current config */ |
| static bool 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) |
| return false; |
| |
| /* assume no path conflicts unless aamix is involved */ |
| if (!spec->mixer_nid || !is_nid_contained(path, spec->mixer_nid)) |
| return true; |
| |
| /* 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)) |
| return false; |
| } |
| 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)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* fill the empty entries in the dac array for speaker/hp with the |
| * shared dac pointed by the paths |
| */ |
| static void refill_shared_dacs(struct hda_codec *codec, int num_outs, |
| hda_nid_t *dacs, int *path_idx) |
| { |
| struct nid_path *path; |
| int i; |
| |
| for (i = 0; i < num_outs; i++) { |
| if (dacs[i]) |
| continue; |
| path = snd_hda_get_path_from_idx(codec, path_idx[i]); |
| if (!path) |
| continue; |
| dacs[i] = path->path[0]; |
| } |
| } |
| |
| /* fill in the dac_nids table from the parsed pin configuration */ |
| static int fill_and_eval_dacs(struct hda_codec *codec, |
| bool fill_hardwired, |
| 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); |
| |
| /* clear path indices */ |
| memset(spec->out_paths, 0, sizeof(spec->out_paths)); |
| memset(spec->hp_paths, 0, sizeof(spec->hp_paths)); |
| memset(spec->speaker_paths, 0, sizeof(spec->speaker_paths)); |
| memset(spec->aamix_out_paths, 0, sizeof(spec->aamix_out_paths)); |
| memset(spec->digout_paths, 0, sizeof(spec->digout_paths)); |
| memset(spec->input_paths, 0, sizeof(spec->input_paths)); |
| memset(spec->loopback_paths, 0, sizeof(spec->loopback_paths)); |
| memset(&spec->digin_path, 0, sizeof(spec->digin_path)); |
| |
| badness = 0; |
| |
| /* fill hard-wired DACs first */ |
| if (fill_hardwired) { |
| bool mapped; |
| do { |
| mapped = map_singles(codec, cfg->line_outs, |
| cfg->line_out_pins, |
| spec->private_dac_nids, |
| spec->out_paths); |
| mapped |= map_singles(codec, cfg->hp_outs, |
| cfg->hp_pins, |
| spec->multiout.hp_out_nid, |
| spec->hp_paths); |
| mapped |= map_singles(codec, cfg->speaker_outs, |
| cfg->speaker_pins, |
| spec->multiout.extra_out_nid, |
| spec->speaker_paths); |
| if (!spec->no_multi_io && |
| fill_mio_first && cfg->line_outs == 1 && |
| cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { |
| err = fill_multi_ios(codec, cfg->line_out_pins[0], true); |
| if (!err) |
| mapped = true; |
| } |
| } while (mapped); |
| } |
| |
| badness += try_assign_dacs(codec, cfg->line_outs, cfg->line_out_pins, |
| spec->private_dac_nids, spec->out_paths, |
| spec->main_out_badness); |
| |
| if (!spec->no_multi_io && fill_mio_first && |
| cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { |
| /* try to fill multi-io first */ |
| err = fill_multi_ios(codec, cfg->line_out_pins[0], false); |
| if (err < 0) |
| return err; |
| /* we don't count badness at this stage yet */ |
| } |
| |
| if (cfg->line_out_type != AUTO_PIN_HP_OUT) { |
| err = try_assign_dacs(codec, cfg->hp_outs, cfg->hp_pins, |
| spec->multiout.hp_out_nid, |
| spec->hp_paths, |
| spec->extra_out_badness); |
| if (err < 0) |
| return err; |
| badness += err; |
| } |
| if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { |
| err = try_assign_dacs(codec, cfg->speaker_outs, |
| cfg->speaker_pins, |
| spec->multiout.extra_out_nid, |
| spec->speaker_paths, |
| spec->extra_out_badness); |
| if (err < 0) |
| return err; |
| badness += err; |
| } |
| if (!spec->no_multi_io && |
| cfg->line_outs == 1 && cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { |
| err = fill_multi_ios(codec, cfg->line_out_pins[0], false); |
| if (err < 0) |
| return err; |
| badness += err; |
| } |
| |
| if (spec->mixer_nid) { |
| spec->aamix_out_paths[0] = |
| check_aamix_out_path(codec, spec->out_paths[0]); |
| if (cfg->line_out_type != AUTO_PIN_HP_OUT) |
| spec->aamix_out_paths[1] = |
| check_aamix_out_path(codec, spec->hp_paths[0]); |
| if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) |
| spec->aamix_out_paths[2] = |
| check_aamix_out_path(codec, spec->speaker_paths[0]); |
| } |
| |
| if (!spec->no_multi_io && |
| cfg->hp_outs && cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) |
| if (count_multiio_pins(codec, cfg->hp_pins[0]) >= 2) |
| spec->multi_ios = 1; /* give badness */ |
| |
| /* re-count num_dacs and squash invalid entries */ |
| spec->multiout.num_dacs = 0; |
| for (i = 0; i < cfg->line_outs; i++) { |
| if (spec->private_dac_nids[i]) |
| spec->multiout.num_dacs++; |
| else { |
| memmove(spec->private_dac_nids + i, |
| spec->private_dac_nids + i + 1, |
| sizeof(hda_nid_t) * (cfg->line_outs - i - 1)); |
| spec->private_dac_nids[cfg->line_outs - 1] = 0; |
| } |
| } |
| |
| spec->ext_channel_count = spec->min_channel_count = |
| spec->multiout.num_dacs * 2; |
| |
| if (spec->multi_ios == 2) { |
| for (i = 0; i < 2; i++) |
| spec->private_dac_nids[spec->multiout.num_dacs++] = |
| spec->multi_io[i].dac; |
| } else if (spec->multi_ios) { |
| spec->multi_ios = 0; |
| badness += BAD_MULTI_IO; |
| } |
| |
| if (spec->indep_hp && !indep_hp_possible(codec)) |
| badness += BAD_NO_INDEP_HP; |
| |
| /* re-fill the shared DAC for speaker / headphone */ |
| if (cfg->line_out_type != AUTO_PIN_HP_OUT) |
| refill_shared_dacs(codec, cfg->hp_outs, |
| spec->multiout.hp_out_nid, |
| spec->hp_paths); |
| if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) |
| refill_shared_dacs(codec, cfg->speaker_outs, |
| spec->multiout.extra_out_nid, |
| spec->speaker_paths); |
| |
| return badness; |
| } |
| |
| #define DEBUG_BADNESS |
| |
| #ifdef DEBUG_BADNESS |
| #define debug_badness(fmt, ...) \ |
| codec_dbg(codec, fmt, ##__VA_ARGS__) |
| #else |
| #define debug_badness(fmt, ...) \ |
| do { if (0) codec_dbg(codec, fmt, ##__VA_ARGS__); } while (0) |
| #endif |
| |
| #ifdef DEBUG_BADNESS |
| static inline void print_nid_path_idx(struct hda_codec *codec, |
| const char *pfx, int idx) |
| { |
| struct nid_path *path; |
| |
| path = snd_hda_get_path_from_idx(codec, idx); |
| if (path) |
| print_nid_path(codec, pfx, path); |
| } |
| |
| static void debug_show_configs(struct hda_codec *codec, |
| struct auto_pin_cfg *cfg) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| static const char * const lo_type[3] = { "LO", "SP", "HP" }; |
| int i; |
| |
| debug_badness("multi_outs = %x/%x/%x/%x : %x/%x/%x/%x (type %s)\n", |
| cfg->line_out_pins[0], cfg->line_out_pins[1], |
| cfg->line_out_pins[2], cfg->line_out_pins[3], |
| spec->multiout.dac_nids[0], |
| spec->multiout.dac_nids[1], |
| spec->multiout.dac_nids[2], |
| spec->multiout.dac_nids[3], |
| lo_type[cfg->line_out_type]); |
| for (i = 0; i < cfg->line_outs; i++) |
| print_nid_path_idx(codec, " out", spec->out_paths[i]); |
| if (spec->multi_ios > 0) |
| debug_badness("multi_ios(%d) = %x/%x : %x/%x\n", |
| spec->multi_ios, |
| spec->multi_io[0].pin, spec->multi_io[1].pin, |
| spec->multi_io[0].dac, spec->multi_io[1].dac); |
| for (i = 0; i < spec->multi_ios; i++) |
| print_nid_path_idx(codec, " mio", |
| spec->out_paths[cfg->line_outs + i]); |
| if (cfg->hp_outs) |
| debug_badness("hp_outs = %x/%x/%x/%x : %x/%x/%x/%x\n", |
| cfg->hp_pins[0], cfg->hp_pins[1], |
| cfg->hp_pins[2], cfg->hp_pins[3], |
| spec->multiout.hp_out_nid[0], |
| spec->multiout.hp_out_nid[1], |
| spec->multiout.hp_out_nid[2], |
| spec->multiout.hp_out_nid[3]); |
| for (i = 0; i < cfg->hp_outs; i++) |
| print_nid_path_idx(codec, " hp ", spec->hp_paths[i]); |
| if (cfg->speaker_outs) |
| debug_badness("spk_outs = %x/%x/%x/%x : %x/%x/%x/%x\n", |
| cfg->speaker_pins[0], cfg->speaker_pins[1], |
| cfg->speaker_pins[2], cfg->speaker_pins[3], |
| spec->multiout.extra_out_nid[0], |
| spec->multiout.extra_out_nid[1], |
| spec->multiout.extra_out_nid[2], |
| spec->multiout.extra_out_nid[3]); |
| for (i = 0; i < cfg->speaker_outs; i++) |
| print_nid_path_idx(codec, " spk", spec->speaker_paths[i]); |
| for (i = 0; i < 3; i++) |
| print_nid_path_idx(codec, " mix", spec->aamix_out_paths[i]); |
| } |
| #else |
| #define debug_show_configs(codec, cfg) /* NOP */ |
| #endif |
| |
| /* find all available DACs of the codec */ |
| static void fill_all_dac_nids(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| hda_nid_t nid; |
| |
| spec->num_all_dacs = 0; |
| memset(spec->all_dacs, 0, sizeof(spec->all_dacs)); |
| for_each_hda_codec_node(nid, codec) { |
| if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_AUD_OUT) |
| continue; |
| if (spec->num_all_dacs >= ARRAY_SIZE(spec->all_dacs)) { |
| codec_err(codec, "Too many DACs!\n"); |
| break; |
| } |
| spec->all_dacs[spec->num_all_dacs++] = nid; |
| } |
| } |
| |
| static int parse_output_paths(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| struct auto_pin_cfg *cfg = &spec->autocfg; |
| struct auto_pin_cfg *best_cfg; |
| unsigned int val; |
| int best_badness = INT_MAX; |
| int badness; |
| bool fill_hardwired = true, fill_mio_first = true; |
| bool best_wired = true, best_mio = true; |
| bool hp_spk_swapped = false; |
| |
| best_cfg = kmalloc(sizeof(*best_cfg), GFP_KERNEL); |
| if (!best_cfg) |
| return -ENOMEM; |
| *best_cfg = *cfg; |
| |
| for (;;) { |
| badness = fill_and_eval_dacs(codec, fill_hardwired, |
| fill_mio_first); |
| if (badness < 0) { |
| kfree(best_cfg); |
| return badness; |
| } |
| debug_badness("==> lo_type=%d, wired=%d, mio=%d, badness=0x%x\n", |
| cfg->line_out_type, fill_hardwired, fill_mio_first, |
| badness); |
| debug_show_configs(codec, cfg); |
| if (badness < best_badness) { |
| best_badness = badness; |
| *best_cfg = *cfg; |
| best_wired = fill_hardwired; |
| best_mio = fill_mio_first; |
| } |
| if (!badness) |
| break; |
| fill_mio_first = !fill_mio_first; |
| if (!fill_mio_first) |
| continue; |
| fill_hardwired = !fill_hardwired; |
| if (!fill_hardwired) |
| continue; |
| if (hp_spk_swapped) |
| break; |
| hp_spk_swapped = true; |
| if (cfg->speaker_outs > 0 && |
| cfg->line_out_type == AUTO_PIN_HP_OUT) { |
| cfg->hp_outs = cfg->line_outs; |
| memcpy(cfg->hp_pins, cfg->line_out_pins, |
| sizeof(cfg->hp_pins)); |
| cfg->line_outs = cfg->speaker_outs; |
| memcpy(cfg->line_out_pins, cfg->speaker_pins, |
| sizeof(cfg->speaker_pins)); |
| cfg->speaker_outs = 0; |
| memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins)); |
| cfg->line_out_type = AUTO_PIN_SPEAKER_OUT; |
| fill_hardwired = true; |
| continue; |
| } |
| if (cfg->hp_outs > 0 && |
| cfg->line_out_type == AUTO_PIN_SPEAKER_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; |
| fill_hardwired = true; |
| continue; |
| } |
| break; |
| } |
| |
| if (badness) { |
| debug_badness("==> restoring best_cfg\n"); |
| *cfg = *best_cfg; |
| fill_and_eval_dacs(codec, best_wired, best_mio); |
| } |
| debug_badness("==> Best config: lo_type=%d, wired=%d, mio=%d\n", |
| cfg->line_out_type, best_wired, best_mio); |
| debug_show_configs(codec, cfg); |
| |
| if (cfg->line_out_pins[0]) { |
| struct nid_path *path; |
| path = snd_hda_get_path_from_idx(codec, spec->out_paths[0]); |
| if (path) |
| spec->vmaster_nid = look_for_out_vol_nid(codec, path); |
| if (spec->vmaster_nid) { |
| snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid, |
| HDA_OUTPUT, spec->vmaster_tlv); |
| if (spec->dac_min_mute) |
| spec->vmaster_tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] |= TLV_DB_SCALE_MUTE; |
| } |
| } |
| |
| /* set initial pinctl targets */ |
| if (spec->prefer_hp_amp || cfg->line_out_type == AUTO_PIN_HP_OUT) |
| val = PIN_HP; |
| else |
| val = PIN_OUT; |
| set_pin_targets(codec, cfg->line_outs, cfg->line_out_pins, val); |
| if (cfg->line_out_type != AUTO_PIN_HP_OUT) |
| set_pin_targets(codec, cfg->hp_outs, cfg->hp_pins, PIN_HP); |
| if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) { |
| val = spec->prefer_hp_amp ? PIN_HP : PIN_OUT; |
| set_pin_targets(codec, cfg->speaker_outs, |
| cfg->speaker_pins, val); |
| } |
| |
| /* clear indep_hp flag if not available */ |
| if (spec->indep_hp && !indep_hp_possible(codec)) |
| spec->indep_hp = 0; |
| |
| kfree(best_cfg); |
| return 0; |
| } |
| |
| /* add playback controls from the parsed DAC table */ |
| static int create_multi_out_ctls(struct hda_codec *codec, |
| const struct auto_pin_cfg *cfg) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| int i, err, noutputs; |
| |
| noutputs = cfg->line_outs; |
| if (spec->multi_ios > 0 && cfg->line_outs < 3) |
| noutputs += spec->multi_ios; |
| |
| for (i = 0; i < noutputs; i++) { |
| const char *name; |
| int index; |
| struct nid_path *path; |
| |
| path = snd_hda_get_path_from_idx(codec, spec->out_paths[i]); |
| if (!path) |
| continue; |
| |
| name = get_line_out_pfx(codec, i, &index, NID_PATH_VOL_CTL); |
| if (!name || !strcmp(name, "CLFE")) { |
| /* Center/LFE */ |
| err = add_vol_ctl(codec, "Center", 0, 1, path); |
| if (err < 0) |
| return err; |
| err = add_vol_ctl(codec, "LFE", 0, 2, path); |
| if (err < 0) |
| return err; |
| } else { |
| err = add_stereo_vol(codec, name, index, path); |
| if (err < 0) |
| return err; |
| } |
| |
| name = get_line_out_pfx(codec, i, &index, NID_PATH_MUTE_CTL); |
| if (!name || !strcmp(name, "CLFE")) { |
| err = add_sw_ctl(codec, "Center", 0, 1, path); |
| if (err < 0) |
| return err; |
| err = add_sw_ctl(codec, "LFE", 0, 2, path); |
| if (err < 0) |
| return err; |
| } else { |
| err = add_stereo_sw(codec, name, index, path); |
| if (err < 0) |
| return err; |
| } |
| } |
| return 0; |
| } |
| |
| static int create_extra_out(struct hda_codec *codec, int path_idx, |
| const char *pfx, int cidx) |
| { |
| struct nid_path *path; |
| int err; |
| |
| path = snd_hda_get_path_from_idx(codec, path_idx); |
| if (!path) |
| return 0; |
| err = add_stereo_vol(codec, pfx, cidx, path); |
| if (err < 0) |
| return err; |
| err = add_stereo_sw(codec, pfx, cidx, path); |
| if (err < 0) |
| return err; |
| return 0; |
| } |
| |
| /* add playback controls for speaker and HP outputs */ |
| static int create_extra_outs(struct hda_codec *codec, int num_pins, |
| const int *paths, const char *pfx) |
| { |
| int i; |
| |
| for (i = 0; i < num_pins; i++) { |
| const char *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"; |
| else if (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; |
| } |
| |
| static int create_hp_out_ctls(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| return create_extra_outs(codec, spec->autocfg.hp_outs, |
| spec->hp_paths, |
| "Headphone"); |
| } |
| |
| static int create_speaker_out_ctls(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| return create_extra_outs(codec, spec->autocfg.speaker_outs, |
| spec->speaker_paths, |
| "Speaker"); |
| } |
| |
| /* |
| * independent HP controls |
| */ |
| |
| static void call_hp_automute(struct hda_codec *codec, |
| struct hda_jack_callback *jack); |
| static int indep_hp_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| return snd_hda_enum_bool_helper_info(kcontrol, uinfo); |
| } |
| |
| static int indep_hp_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct hda_gen_spec *spec = codec->spec; |
| ucontrol->value.enumerated.item[0] = spec->indep_hp_enabled; |
| return 0; |
| } |
| |
| static void update_aamix_paths(struct hda_codec *codec, bool do_mix, |
| int nomix_path_idx, int mix_path_idx, |
| int out_type); |
| |
| static int indep_hp_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct hda_gen_spec *spec = codec->spec; |
| unsigned int select = ucontrol->value.enumerated.item[0]; |
| int ret = 0; |
| |
| mutex_lock(&spec->pcm_mutex); |
| if (spec->active_streams) { |
| ret = -EBUSY; |
| goto unlock; |
| } |
| |
| if (spec->indep_hp_enabled != select) { |
| hda_nid_t *dacp; |
| if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT) |
| dacp = &spec->private_dac_nids[0]; |
| else |
| dacp = &spec->multiout.hp_out_nid[0]; |
| |
| /* update HP aamix paths in case it conflicts with indep HP */ |
| if (spec->have_aamix_ctl) { |
| if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT) |
| update_aamix_paths(codec, spec->aamix_mode, |
| spec->out_paths[0], |
| spec->aamix_out_paths[0], |
| spec->autocfg.line_out_type); |
| else |
| update_aamix_paths(codec, spec->aamix_mode, |
| spec->hp_paths[0], |
| spec->aamix_out_paths[1], |
| AUTO_PIN_HP_OUT); |
| } |
| |
| spec->indep_hp_enabled = select; |
| if (spec->indep_hp_enabled) |
| *dacp = 0; |
| else |
| *dacp = spec->alt_dac_nid; |
| |
| call_hp_automute(codec, NULL); |
| ret = 1; |
| } |
| unlock: |
| mutex_unlock(&spec->pcm_mutex); |
| return ret; |
| } |
| |
| static const struct snd_kcontrol_new indep_hp_ctl = { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "Independent HP", |
| .info = indep_hp_info, |
| .get = indep_hp_get, |
| .put = indep_hp_put, |
| }; |
| |
| |
| static int create_indep_hp_ctls(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| hda_nid_t dac; |
| |
| if (!spec->indep_hp) |
| return 0; |
| if (spec->autocfg.line_out_type == AUTO_PIN_HP_OUT) |
| dac = spec->multiout.dac_nids[0]; |
| else |
| dac = spec->multiout.hp_out_nid[0]; |
| if (!dac) { |
| spec->indep_hp = 0; |
| return 0; |
| } |
| |
| spec->indep_hp_enabled = false; |
| spec->alt_dac_nid = dac; |
| if (!snd_hda_gen_add_kctl(spec, NULL, &indep_hp_ctl)) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| /* |
| * channel mode enum control |
| */ |
| |
| static int ch_mode_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct hda_gen_spec *spec = codec->spec; |
| int chs; |
| |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; |
| uinfo->count = 1; |
| uinfo->value.enumerated.items = spec->multi_ios + 1; |
| if (uinfo->value.enumerated.item > spec->multi_ios) |
| uinfo->value.enumerated.item = spec->multi_ios; |
| chs = uinfo->value.enumerated.item * 2 + spec->min_channel_count; |
| sprintf(uinfo->value.enumerated.name, "%dch", chs); |
| return 0; |
| } |
| |
| static int ch_mode_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct hda_gen_spec *spec = codec->spec; |
| ucontrol->value.enumerated.item[0] = |
| (spec->ext_channel_count - spec->min_channel_count) / 2; |
| return 0; |
| } |
| |
| static inline struct nid_path * |
| get_multiio_path(struct hda_codec *codec, int idx) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| return snd_hda_get_path_from_idx(codec, |
| spec->out_paths[spec->autocfg.line_outs + idx]); |
| } |
| |
| static void update_automute_all(struct hda_codec *codec); |
| |
| /* Default value to be passed as aamix argument for snd_hda_activate_path(); |
| * used for output paths |
| */ |
| static bool aamix_default(struct hda_gen_spec *spec) |
| { |
| return !spec->have_aamix_ctl || spec->aamix_mode; |
| } |
| |
| static int set_multi_io(struct hda_codec *codec, int idx, bool output) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| hda_nid_t nid = spec->multi_io[idx].pin; |
| struct nid_path *path; |
| |
| path = get_multiio_path(codec, idx); |
| if (!path) |
| return -EINVAL; |
| |
| if (path->active == output) |
| return 0; |
| |
| if (output) { |
| set_pin_target(codec, nid, PIN_OUT, true); |
| snd_hda_activate_path(codec, path, true, aamix_default(spec)); |
| set_pin_eapd(codec, nid, true); |
| } else { |
| set_pin_eapd(codec, nid, false); |
| snd_hda_activate_path(codec, path, false, aamix_default(spec)); |
| set_pin_target(codec, nid, spec->multi_io[idx].ctl_in, true); |
| path_power_down_sync(codec, path); |
| } |
| |
| /* update jack retasking in case it modifies any of them */ |
| update_automute_all(codec); |
| |
| return 0; |
| } |
| |
| static int ch_mode_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 i, ch; |
| |
| ch = ucontrol->value.enumerated.item[0]; |
| if (ch < 0 || ch > spec->multi_ios) |
| return -EINVAL; |
| if (ch == (spec->ext_channel_count - spec->min_channel_count) / 2) |
| return 0; |
| spec->ext_channel_count = ch * 2 + spec->min_channel_count; |
| for (i = 0; i < spec->multi_ios; i++) |
| set_multi_io(codec, i, i < ch); |
| spec->multiout.max_channels = max(spec->ext_channel_count, |
| spec->const_channel_count); |
| if (spec->need_dac_fix) |
| spec->multiout.num_dacs = spec->multiout.max_channels / 2; |
| return 1; |
| } |
| |
| static const struct snd_kcontrol_new channel_mode_enum = { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "Channel Mode", |
| .info = ch_mode_info, |
| .get = ch_mode_get, |
| .put = ch_mode_put, |
| }; |
| |
| static int create_multi_channel_mode(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| |
| if (spec->multi_ios > 0) { |
| if (!snd_hda_gen_add_kctl(spec, NULL, &channel_mode_enum)) |
| return -ENOMEM; |
| } |
| return 0; |
| } |
| |
| /* |
| * aamix loopback enable/disable switch |
| */ |
| |
| #define loopback_mixing_info indep_hp_info |
| |
| static int loopback_mixing_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct hda_gen_spec *spec = codec->spec; |
| ucontrol->value.enumerated.item[0] = spec->aamix_mode; |
| return 0; |
| } |
| |
| static void update_aamix_paths(struct hda_codec *codec, bool do_mix, |
| int nomix_path_idx, int mix_path_idx, |
| int out_type) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| struct nid_path *nomix_path, *mix_path; |
| |
| nomix_path = snd_hda_get_path_from_idx(codec, nomix_path_idx); |
| mix_path = snd_hda_get_path_from_idx(codec, mix_path_idx); |
| if (!nomix_path || !mix_path) |
| return; |
| |
| /* if HP aamix path is driven from a different DAC and the |
| * independent HP mode is ON, can't turn on aamix path |
| */ |
| if (out_type == AUTO_PIN_HP_OUT && spec->indep_hp_enabled && |
| mix_path->path[0] != spec->alt_dac_nid) |
| do_mix = false; |
| |
| if (do_mix) { |
| snd_hda_activate_path(codec, nomix_path, false, true); |
| snd_hda_activate_path(codec, mix_path, true, true); |
| path_power_down_sync(codec, nomix_path); |
| } else { |
| snd_hda_activate_path(codec, mix_path, false, false); |
| snd_hda_activate_path(codec, nomix_path, true, false); |
| path_power_down_sync(codec, mix_path); |
| } |
| } |
| |
| /* re-initialize the output paths; only called from loopback_mixing_put() */ |
| static void update_output_paths(struct hda_codec *codec, int num_outs, |
| const int *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); |
| } |
| } |
| |
| static int loopback_mixing_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct hda_gen_spec *spec = codec->spec; |
| const struct auto_pin_cfg *cfg = &spec->autocfg; |
| unsigned int val = ucontrol->value.enumerated.item[0]; |
| |
| if (val == spec->aamix_mode) |
| return 0; |
| spec->aamix_mode = val; |
| if (has_aamix_out_paths(spec)) { |
| update_aamix_paths(codec, val, spec->out_paths[0], |
| spec->aamix_out_paths[0], |
| cfg->line_out_type); |
| update_aamix_paths(codec, val, spec->hp_paths[0], |
| spec->aamix_out_paths[1], |
| AUTO_PIN_HP_OUT); |
| update_aamix_paths(codec, val, spec->speaker_paths[0], |
| spec->aamix_out_paths[2], |
| AUTO_PIN_SPEAKER_OUT); |
| } else { |
| update_output_paths(codec, cfg->line_outs, spec->out_paths); |
| if (cfg->line_out_type != AUTO_PIN_HP_OUT) |
| update_output_paths(codec, cfg->hp_outs, spec->hp_paths); |
| if (cfg->line_out_type != AUTO_PIN_SPEAKER_OUT) |
| update_output_paths(codec, cfg->speaker_outs, |
| spec->speaker_paths); |
| } |
| return 1; |
| } |
| |
| static const struct snd_kcontrol_new loopback_mixing_enum = { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = "Loopback Mixing", |
| .info = loopback_mixing_info, |
| .get = loopback_mixing_get, |
| .put = loopback_mixing_put, |
| }; |
| |
| static int create_loopback_mixing_ctl(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| |
| if (!spec->mixer_nid) |
| return 0; |
| if (!snd_hda_gen_add_kctl(spec, NULL, &loopback_mixing_enum)) |
| return -ENOMEM; |
| spec->have_aamix_ctl = 1; |
| return 0; |
| } |
| |
| /* |
| * shared headphone/mic handling |
| */ |
| |
| static void call_update_outputs(struct hda_codec *codec); |
| |
| /* for shared I/O, change the pin-control accordingly */ |
| static void update_hp_mic(struct hda_codec *codec, int adc_mux, bool force) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| bool as_mic; |
| unsigned int val; |
| hda_nid_t pin; |
| |
| pin = spec->hp_mic_pin; |
| as_mic = spec->cur_mux[adc_mux] == spec->hp_mic_mux_idx; |
| |
| 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; |
| unsigned int 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 */ |
| static int create_hp_mic(struct hda_codec *codec) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| struct auto_pin_cfg *cfg = &spec->autocfg; |
| unsigned int 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; |
| else if (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]; |
| else if (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; |
| } |
| |
| /* |
| * output jack mode |
| */ |
| |
| static int create_hp_mic_jack_mode(struct hda_codec *codec, hda_nid_t pin); |
| |
| static const char * const out_jack_texts[] = { |
| "Line Out", "Headphone Out", |
| }; |
| |
| static int out_jack_mode_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| return snd_hda_enum_helper_info(kcontrol, uinfo, 2, out_jack_texts); |
| } |
| |
| static int out_jack_mode_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| hda_nid_t nid = kcontrol->private_value; |
| if (snd_hda_codec_get_pin_target(codec, nid) == PIN_HP) |
| ucontrol->value.enumerated.item[0] = 1; |
| else |
| ucontrol->value.enumerated.item[0] = 0; |
| return 0; |
| } |
| |
| static int out_jack_mode_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| hda_nid_t nid = kcontrol->private_value; |
| unsigned int val; |
| |
| val = ucontrol->value.enumerated.item[0] ? PIN_HP : PIN_OUT; |
| if (snd_hda_codec_get_pin_target(codec, nid) == val) |
| return 0; |
| snd_hda_set_pin_ctl_cache(codec, nid, val); |
| return 1; |
| } |
| |
| static const struct snd_kcontrol_new out_jack_mode_enum = { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .info = out_jack_mode_info, |
| .get = out_jack_mode_get, |
| .put = out_jack_mode_put, |
| }; |
| |
| static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| const struct snd_kcontrol_new *kctl; |
| int i; |
| |
| snd_array_for_each(&spec->kctls, i, kctl) { |
| if (!strcmp(kctl->name, name) && kctl->index == idx) |
| return true; |
| } |
| return false; |
| } |
| |
| static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin, |
| char *name, size_t name_len) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| int idx = 0; |
| |
| snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx); |
| strlcat(name, " Jack Mode", name_len); |
| |
| for (; find_kctl_name(codec, name, idx); idx++) |
| ; |
| } |
| |
| static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| if (spec->add_jack_modes) { |
| unsigned int pincap = snd_hda_query_pin_caps(codec, pin); |
| if ((pincap & AC_PINCAP_OUT) && (pincap & AC_PINCAP_HP_DRV)) |
| return 2; |
| } |
| return 1; |
| } |
| |
| static int create_out_jack_modes(struct hda_codec *codec, int num_pins, |
| hda_nid_t *pins) |
| { |
| struct hda_gen_spec *spec = codec->spec; |
| int i; |
| |
| for (i = 0; i < num_pins; i++) { |
| hda_nid_t pin = pins[i]; |
| if (pin == spec->hp_mic_pin) |
| continue; |
| if (get_out_jack_num_items(codec, pin) > 1) { |
| struct snd_kcontrol_new *knew; |
| char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; |
| get_jack_mode_name(codec, pin, name, sizeof(name)); |
| knew = snd_hda_gen_add_kctl(spec, name, |
| &out_jack_mode_enum); |
| if (!knew) |
| return -ENOMEM; |
| knew->private_value = pin; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * input jack mode |
| */ |
| |
| /* from AC_PINCTL_VREF_HIZ to AC_PINCTL_VREF_100 */ |
| #define NUM_VREFS 6 |
| |
| static const char * const vref_texts[NUM_VREFS] = { |
| "Line In", "Mic 50pc Bias", "Mic 0V Bias", |
| "", "Mic 80pc Bias", "Mic 100pc Bias" |
| }; |
| |
| static unsigned int get_vref_caps(struct hda_codec *codec, hda_nid_t pin) |
| { |
| unsigned int pincap; |
| |
| pincap = snd_hda_query_pin_caps(codec, pin); |
| pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; |
| /* filter out unusual vrefs */ |
| pincap &= ~(AC_PINCAP_VREF_GRD | AC_PINCAP_VREF_100); |
| return pincap; |
| } |
| |
| /* convert from the enum item index to the vref ctl index (0=HIZ, 1=50%...) */ |
| static int get_vref_idx(unsigned int vref_caps, unsigned int item_idx) |
| { |
| unsigned int i, n = 0; |
| |
| for (i = 0; i < NUM_VREFS; i++) { |
| if (vref_caps & (1 << i)) { |
| if (n == item_idx) |
| return i; |
| n++; |
| } |
| } |
| return 0; |
| } |
| |
| /* convert back from the vref ctl index to the enum item index */ |
| static int cvt_from_vref_idx(unsigned int vref_caps, unsigned int idx) |
| { |
| unsigned int i, n = 0; |
| |
| for (i = 0; i < NUM_VREFS; i++) { |
| if (i == idx) |
| return n; |
| if (vref_caps & (1 << i)) |
| n++; |
| } |
| return 0; |
| } |
| |
| static int in_jack_mode_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
| hda_nid_t nid = kcontrol->private_value; |
| unsigned int vref_caps = get_vref_caps(codec, nid); |
|