|
@@ -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 */
|