diff options
-rw-r--r-- | sound/pci/hda/patch_conexant.c | 213 |
1 files changed, 190 insertions, 23 deletions
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 3521f33d43c..685015a5329 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -117,6 +117,16 @@ struct conexant_spec { unsigned int recording; void (*capture_prepare)(struct hda_codec *codec); void (*capture_cleanup)(struct hda_codec *codec); + + /* OLPC XO-1.5 supports DC input mode (e.g. for use with analog sensors) + * through the microphone jack. + * When the user enables this through a mixer switch, both internal and + * external microphones are disabled. Gain is fixed at 0dB. In this mode, + * we also allow the bias to be configured through a separate mixer + * control. */ + unsigned int dc_enable; + unsigned int dc_input_bias; /* offset into cxt5066_olpc_dc_bias */ + unsigned int mic_boost; /* offset into cxt5066_analog_mic_boost */ }; static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo, @@ -2024,6 +2034,26 @@ static int cxt5066_hp_master_sw_put(struct snd_kcontrol *kcontrol, return 1; } +static const struct hda_input_mux cxt5066_olpc_dc_bias = { + .num_items = 3, + .items = { + { "Off", PIN_IN }, + { "50%", PIN_VREF50 }, + { "80%", PIN_VREF80 }, + }, +}; + +static int cxt5066_set_olpc_dc_bias(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + /* Even though port F is the DC input, the bias is controlled on port B. + * we also leave that port as an active input (but unselected) in DC mode + * just in case that is necessary to make the bias setting take effect. */ + return snd_hda_codec_write_cache(codec, 0x1a, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + cxt5066_olpc_dc_bias.items[spec->dc_input_bias].index); +} + /* OLPC defers mic widget control until when capture is started because the * microphone LED comes on as soon as these settings are put in place. if we * did this before recording, it would give the false indication that recording @@ -2034,6 +2064,27 @@ static void cxt5066_olpc_select_mic(struct hda_codec *codec) if (!spec->recording) return; + if (spec->dc_enable) { + /* in DC mode we ignore presence detection and just use the jack + * through our special DC port */ + const struct hda_verb enable_dc_mode[] = { + /* disble internal mic, port C */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + + /* enable DC capture, port F */ + {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {}, + }; + + snd_hda_sequence_write(codec, enable_dc_mode); + /* port B input disabled (and bias set) through the following call */ + cxt5066_set_olpc_dc_bias(codec); + return; + } + + /* disable DC (port F) */ + snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0); + /* external mic, port B */ snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, spec->ext_mic_present ? CXT5066_OLPC_EXT_MIC_BIAS : 0); @@ -2049,6 +2100,9 @@ static void cxt5066_olpc_automic(struct hda_codec *codec) struct conexant_spec *spec = codec->spec; unsigned int present; + if (spec->dc_enable) /* don't do presence detection in DC mode */ + return; + present = snd_hda_codec_read(codec, 0x1a, 0, AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; if (present) @@ -2123,13 +2177,16 @@ static void cxt5066_hp_automute(struct hda_codec *codec) /* unsolicited event for jack sensing */ static void cxt5066_olpc_unsol_event(struct hda_codec *codec, unsigned int res) { + struct conexant_spec *spec = codec->spec; snd_printdd("CXT5066: unsol event %x (%x)\n", res, res >> 26); switch (res >> 26) { case CONEXANT_HP_EVENT: cxt5066_hp_automute(codec); break; case CONEXANT_MIC_EVENT: - cxt5066_olpc_automic(codec); + /* ignore mic events in DC mode; we're always using the jack */ + if (!spec->dc_enable) + cxt5066_olpc_automic(codec); break; } } @@ -2159,6 +2216,15 @@ static const struct hda_input_mux cxt5066_analog_mic_boost = { }, }; +static int cxt5066_set_mic_boost(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + return snd_hda_codec_write_cache(codec, 0x17, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT | + cxt5066_analog_mic_boost.items[spec->mic_boost].index); +} + static int cxt5066_mic_boost_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -2169,15 +2235,8 @@ static int cxt5066_mic_boost_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - int val; - hda_nid_t nid = kcontrol->private_value & 0xff; - int inout = (kcontrol->private_value & 0x100) ? - AC_AMP_GET_INPUT : AC_AMP_GET_OUTPUT; - - val = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_AMP_GAIN_MUTE, inout); - - ucontrol->value.enumerated.item[0] = val & AC_AMP_GAIN; + struct conexant_spec *spec = codec->spec; + ucontrol->value.enumerated.item[0] = spec->mic_boost; return 0; } @@ -2185,23 +2244,101 @@ static int cxt5066_mic_boost_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; const struct hda_input_mux *imux = &cxt5066_analog_mic_boost; unsigned int idx; - hda_nid_t nid = kcontrol->private_value & 0xff; - int inout = (kcontrol->private_value & 0x100) ? - AC_AMP_SET_INPUT : AC_AMP_SET_OUTPUT; + idx = ucontrol->value.enumerated.item[0]; + if (idx >= imux->num_items) + idx = imux->num_items - 1; + + spec->mic_boost = idx; + if (!spec->dc_enable) + cxt5066_set_mic_boost(codec); + return 1; +} + +static void cxt5066_enable_dc(struct hda_codec *codec) +{ + const struct hda_verb enable_dc_mode[] = { + /* disable gain */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* switch to DC input */ + {0x17, AC_VERB_SET_CONNECT_SEL, 3}, + {} + }; + + /* configure as input source */ + snd_hda_sequence_write(codec, enable_dc_mode); + cxt5066_olpc_select_mic(codec); /* also sets configured bias */ +} + +static void cxt5066_disable_dc(struct hda_codec *codec) +{ + /* reconfigure input source */ + cxt5066_set_mic_boost(codec); + /* automic also selects the right mic if we're recording */ + cxt5066_olpc_automic(codec); +} + +static int cxt5066_olpc_dc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + ucontrol->value.integer.value[0] = spec->dc_enable; + return 0; +} - if (!imux->num_items) +static int cxt5066_olpc_dc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + int dc_enable = !!ucontrol->value.integer.value[0]; + + if (dc_enable == spec->dc_enable) return 0; + + spec->dc_enable = dc_enable; + if (dc_enable) + cxt5066_enable_dc(codec); + else + cxt5066_disable_dc(codec); + + return 1; +} + +static int cxt5066_olpc_dc_bias_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_hda_input_mux_info(&cxt5066_olpc_dc_bias, uinfo); +} + +static int cxt5066_olpc_dc_bias_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + ucontrol->value.enumerated.item[0] = spec->dc_input_bias; + return 0; +} + +static int cxt5066_olpc_dc_bias_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + const struct hda_input_mux *imux = &cxt5066_analog_mic_boost; + unsigned int idx; + idx = ucontrol->value.enumerated.item[0]; if (idx >= imux->num_items) idx = imux->num_items - 1; - snd_hda_codec_write_cache(codec, nid, 0, - AC_VERB_SET_AMP_GAIN_MUTE, - AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | inout | - imux->items[idx].index); - + spec->dc_input_bias = idx; + if (spec->dc_enable) + cxt5066_set_olpc_dc_bias(codec); return 1; } @@ -2223,6 +2360,9 @@ static void cxt5066_olpc_capture_cleanup(struct hda_codec *codec) /* disble internal mic, port C */ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + + /* disable DC capture, port F */ + {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, {}, }; @@ -2282,6 +2422,24 @@ static struct snd_kcontrol_new cxt5066_mixer_master_olpc[] = { {} }; +static struct snd_kcontrol_new cxt5066_mixer_olpc_dc[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DC Mode Enable Switch", + .info = snd_ctl_boolean_mono_info, + .get = cxt5066_olpc_dc_get, + .put = cxt5066_olpc_dc_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DC Input Bias Enum", + .info = cxt5066_olpc_dc_bias_enum_info, + .get = cxt5066_olpc_dc_bias_enum_get, + .put = cxt5066_olpc_dc_bias_enum_put, + }, + {} +}; + static struct snd_kcontrol_new cxt5066_mixers[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -2294,11 +2452,10 @@ static struct snd_kcontrol_new cxt5066_mixers[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Ext Mic Boost Capture Enum", + .name = "Analog Mic Boost Capture Enum", .info = cxt5066_mic_boost_mux_enum_info, .get = cxt5066_mic_boost_mux_enum_get, .put = cxt5066_mic_boost_mux_enum_put, - .private_value = 0x17, }, HDA_BIND_VOL("Capture Volume", &cxt5066_bind_capture_vol_others), @@ -2392,7 +2549,7 @@ static struct hda_verb cxt5066_init_verbs_olpc[] = { {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ - /* Port F: unused */ + /* Port F: external DC input through microphone port */ {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, /* Port G: internal speakers */ @@ -2513,15 +2670,22 @@ static int cxt5066_init(struct hda_codec *codec) if (spec->dell_vostro) cxt5066_vostro_automic(codec); } + cxt5066_set_mic_boost(codec); return 0; } static int cxt5066_olpc_init(struct hda_codec *codec) { + struct conexant_spec *spec = codec->spec; snd_printdd("CXT5066: init\n"); conexant_init(codec); cxt5066_hp_automute(codec); - cxt5066_olpc_automic(codec); + if (!spec->dc_enable) { + cxt5066_set_mic_boost(codec); + cxt5066_olpc_automic(codec); + } else { + cxt5066_enable_dc(codec); + } return 0; } @@ -2604,8 +2768,10 @@ static int patch_cxt5066(struct hda_codec *codec) codec->patch_ops.unsol_event = cxt5066_olpc_unsol_event; spec->init_verbs[0] = cxt5066_init_verbs_olpc; spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc; + spec->mixers[spec->num_mixers++] = cxt5066_mixer_olpc_dc; spec->mixers[spec->num_mixers++] = cxt5066_mixers; spec->port_d_mode = 0; + spec->mic_boost = 3; /* default 30dB gain */ /* no S/PDIF out */ spec->multiout.dig_out_nid = 0; @@ -2627,6 +2793,7 @@ static int patch_cxt5066(struct hda_codec *codec) spec->mixers[spec->num_mixers++] = cxt5066_vostro_mixers; spec->port_d_mode = 0; spec->dell_vostro = 1; + spec->mic_boost = 3; /* default 30dB gain */ snd_hda_attach_beep_device(codec, 0x13); /* no S/PDIF out */ |