#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/module.h>
+#include <linux/sort.h>
#include <sound/core.h>
#include <sound/jack.h>
#include "hda_codec.h"
#define ALC_FIXUP_ACT_INIT HDA_FIXUP_ACT_INIT
#define ALC_FIXUP_ACT_BUILD HDA_FIXUP_ACT_BUILD
+#define MAX_AUTO_MIC_PINS 3
+
+struct alc_automic_entry {
+ hda_nid_t pin; /* pin */
+ int idx; /* imux index, -1 = invalid */
+ unsigned int attr; /* pin attribute (INPUT_PIN_ATTR_*) */
+};
#define MAX_NID_PATH_DEPTH 5
/* capture source */
struct hda_input_mux input_mux;
unsigned int cur_mux[3];
- hda_nid_t ext_mic_pin;
- hda_nid_t dock_mic_pin;
- hda_nid_t int_mic_pin;
/* channel model */
const struct hda_channel_mode *channel_mode;
hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
hda_nid_t imux_pins[HDA_MAX_NUM_INPUTS];
unsigned int dyn_adc_idx[HDA_MAX_NUM_INPUTS];
- int int_mic_idx, ext_mic_idx, dock_mic_idx; /* for auto-mic */
hda_nid_t inv_dmic_pin;
hda_nid_t shared_mic_vref_pin;
/* path list */
struct snd_array paths;
+ /* auto-mic stuff */
+ int am_num_entries;
+ struct alc_automic_entry am_entry[MAX_AUTO_MIC_PINS];
+
/* hooks */
void (*init_hook)(struct hda_codec *codec);
#ifdef CONFIG_PM
unsigned int automute_speaker_possible:1; /* there are speakers and either LO or HP */
unsigned int automute_lo_possible:1; /* there are line outs and HP */
unsigned int keep_vref_in_automute:1; /* Don't clear VREF in automute */
+ unsigned int line_in_auto_switch:1; /* allow line-in auto switch */
/* other flags */
unsigned int need_dac_fix:1; /* need to limit DACs for multi channels */
static void alc_mic_automute(struct hda_codec *codec, struct hda_jack_tbl *jack)
{
struct alc_spec *spec = codec->spec;
- hda_nid_t *pins = spec->imux_pins;
+ int i;
if (!spec->auto_mic)
return;
- if (snd_BUG_ON(spec->int_mic_idx < 0 || spec->ext_mic_idx < 0))
- return;
- if (snd_hda_jack_detect(codec, pins[spec->ext_mic_idx]))
- alc_mux_select(codec, 0, spec->ext_mic_idx, false);
- else if (spec->dock_mic_idx >= 0 &&
- snd_hda_jack_detect(codec, pins[spec->dock_mic_idx]))
- alc_mux_select(codec, 0, spec->dock_mic_idx, false);
- else
- alc_mux_select(codec, 0, spec->int_mic_idx, false);
+ for (i = spec->am_num_entries - 1; i > 0; i--) {
+ if (snd_hda_jack_detect(codec, spec->am_entry[i].pin)) {
+ alc_mux_select(codec, 0, spec->am_entry[i].idx, false);
+ return;
+ }
+ }
+ alc_mux_select(codec, 0, spec->am_entry[0].idx, false);
}
/* update the master volume per volume-knob's unsol event */
{
struct alc_spec *spec = codec->spec;
const struct hda_input_mux *imux;
+ int i;
imux = &spec->input_mux;
- spec->ext_mic_idx = find_idx_in_nid_list(spec->ext_mic_pin,
- spec->imux_pins, imux->num_items);
- spec->int_mic_idx = find_idx_in_nid_list(spec->int_mic_pin,
- spec->imux_pins, imux->num_items);
- spec->dock_mic_idx = find_idx_in_nid_list(spec->dock_mic_pin,
- spec->imux_pins, imux->num_items);
- if (spec->ext_mic_idx < 0 || spec->int_mic_idx < 0)
- return false; /* no corresponding imux */
-
- snd_hda_jack_detect_enable_callback(codec, spec->ext_mic_pin,
- ALC_MIC_EVENT, alc_mic_automute);
- if (spec->dock_mic_pin)
- snd_hda_jack_detect_enable_callback(codec, spec->dock_mic_pin,
+ for (i = 0; i < spec->am_num_entries; i++) {
+ spec->am_entry[i].idx =
+ find_idx_in_nid_list(spec->am_entry[i].pin,
+ spec->imux_pins, imux->num_items);
+ if (spec->am_entry[i].idx < 0)
+ return false; /* no corresponding imux */
+ }
+
+ /* we don't need the jack detection for the first pin */
+ for (i = 1; i < spec->am_num_entries; i++)
+ snd_hda_jack_detect_enable_callback(codec,
+ spec->am_entry[i].pin,
ALC_MIC_EVENT,
alc_mic_automute);
return true;
}
+static int compare_attr(const void *ap, const void *bp)
+{
+ const struct alc_automic_entry *a = ap;
+ const struct alc_automic_entry *b = bp;
+ return (int)(a->attr - b->attr);
+}
+
/*
* Check the availability of auto-mic switch;
* Set up if really supported
{
struct alc_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
- hda_nid_t fixed, ext, dock;
- int i;
+ unsigned int types;
+ int i, num_pins;
- spec->ext_mic_idx = spec->int_mic_idx = spec->dock_mic_idx = -1;
-
- fixed = ext = dock = 0;
+ types = 0;
+ num_pins = 0;
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t nid = cfg->inputs[i].pin;
- unsigned int defcfg;
- defcfg = snd_hda_codec_get_pincfg(codec, nid);
- switch (snd_hda_get_input_pin_attr(defcfg)) {
+ unsigned int attr;
+ attr = snd_hda_codec_get_pincfg(codec, nid);
+ attr = snd_hda_get_input_pin_attr(attr);
+ if (types & (1 << attr))
+ return 0; /* already occupied */
+ switch (attr) {
case INPUT_PIN_ATTR_INT:
- if (fixed)
- return 0; /* already occupied */
if (cfg->inputs[i].type != AUTO_PIN_MIC)
return 0; /* invalid type */
- fixed = nid;
break;
case INPUT_PIN_ATTR_UNUSED:
return 0; /* invalid entry */
- case INPUT_PIN_ATTR_DOCK:
- if (dock)
- return 0; /* already occupied */
- if (cfg->inputs[i].type > AUTO_PIN_LINE_IN)
- return 0; /* invalid type */
- dock = nid;
- break;
default:
- if (ext)
- return 0; /* already occupied */
- if (cfg->inputs[i].type != AUTO_PIN_MIC)
+ if (cfg->inputs[i].type > AUTO_PIN_LINE_IN)
return 0; /* invalid type */
- ext = nid;
+ if (!spec->line_in_auto_switch &&
+ cfg->inputs[i].type != AUTO_PIN_MIC)
+ return 0; /* only mic is allowed */
+ if (!is_jack_detectable(codec, nid))
+ return 0; /* no unsol support */
break;
}
+ if (num_pins >= MAX_AUTO_MIC_PINS)
+ return 0;
+ types |= (1 << attr);
+ spec->am_entry[num_pins].pin = nid;
+ spec->am_entry[num_pins].attr = attr;
+ num_pins++;
}
- if (!ext && dock) {
- ext = dock;
- dock = 0;
- }
- if (!ext || !fixed)
+
+ if (num_pins < 2)
return 0;
- if (!is_jack_detectable(codec, ext))
- return 0; /* no unsol support */
- if (dock && !is_jack_detectable(codec, dock))
- return 0; /* no unsol support */
- /* check imux indices */
- spec->ext_mic_pin = ext;
- spec->int_mic_pin = fixed;
- spec->dock_mic_pin = dock;
+ spec->am_num_entries = num_pins;
+ /* sort the am_entry in the order of attr so that the pin with a
+ * higher attr will be selected when the jack is plugged.
+ */
+ sort(spec->am_entry, num_pins, sizeof(spec->am_entry[0]),
+ compare_attr, NULL);
if (!alc_auto_mic_check_imux(codec))
return 0;
spec->auto_mic = 1;
spec->num_adc_nids = 1;
- spec->cur_mux[0] = spec->int_mic_idx;
+ spec->cur_mux[0] = spec->am_entry[0].idx;
snd_printdd("realtek: Enable auto-mic switch on NID 0x%x/0x%x/0x%x\n",
- ext, fixed, dock);
+ spec->am_entry[0].pin,
+ spec->am_entry[1].pin,
+ spec->am_entry[2].pin);
return 0;
}
{
struct alc_spec *spec = codec->spec;
+ if (snd_BUG_ON(!spec->am_entry[1].pin || !spec->autocfg.hp_pins[0]))
+ return;
if (action == ALC_FIXUP_ACT_PROBE)
- snd_hda_jack_set_gating_jack(codec, spec->ext_mic_pin,
+ snd_hda_jack_set_gating_jack(codec, spec->am_entry[1].pin,
spec->autocfg.hp_pins[0]);
}