aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Drake <dsd@laptop.org>2010-01-07 07:47:04 -0500
committerJaroslav Kysela <perex@perex.cz>2010-01-08 03:14:07 -0500
commitc4cfe66c4c2d5a91b3734ffb4e2bad0badd5c874 (patch)
treee9101c86c69abfd22ca5121d49fe596f40476e26
parent75f8991d0e6969407d51501d5a0537f104075c99 (diff)
ALSA: hda - support OLPC XO-1.5 DC input
The XO's audio hardware is wired up to allow DC sensors (e.g. light sensors, thermistors, etc) to be plugged in through the microphone jack. Add sound mixer controls to allow this mode to be enabled and tweaked. Signed-off-by: Daniel Drake <dsd@laptop.org> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
-rw-r--r--sound/pci/hda/patch_conexant.c213
1 files changed, 190 insertions, 23 deletions
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
index 3521f33d43c3..685015a53292 100644
--- a/sound/pci/hda/patch_conexant.c
+++ b/sound/pci/hda/patch_conexant.c
@@ -117,6 +117,16 @@ struct conexant_spec {
117 unsigned int recording; 117 unsigned int recording;
118 void (*capture_prepare)(struct hda_codec *codec); 118 void (*capture_prepare)(struct hda_codec *codec);
119 void (*capture_cleanup)(struct hda_codec *codec); 119 void (*capture_cleanup)(struct hda_codec *codec);
120
121 /* OLPC XO-1.5 supports DC input mode (e.g. for use with analog sensors)
122 * through the microphone jack.
123 * When the user enables this through a mixer switch, both internal and
124 * external microphones are disabled. Gain is fixed at 0dB. In this mode,
125 * we also allow the bias to be configured through a separate mixer
126 * control. */
127 unsigned int dc_enable;
128 unsigned int dc_input_bias; /* offset into cxt5066_olpc_dc_bias */
129 unsigned int mic_boost; /* offset into cxt5066_analog_mic_boost */
120}; 130};
121 131
122static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo, 132static 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,
2024 return 1; 2034 return 1;
2025} 2035}
2026 2036
2037static const struct hda_input_mux cxt5066_olpc_dc_bias = {
2038 .num_items = 3,
2039 .items = {
2040 { "Off", PIN_IN },
2041 { "50%", PIN_VREF50 },
2042 { "80%", PIN_VREF80 },
2043 },
2044};
2045
2046static int cxt5066_set_olpc_dc_bias(struct hda_codec *codec)
2047{
2048 struct conexant_spec *spec = codec->spec;
2049 /* Even though port F is the DC input, the bias is controlled on port B.
2050 * we also leave that port as an active input (but unselected) in DC mode
2051 * just in case that is necessary to make the bias setting take effect. */
2052 return snd_hda_codec_write_cache(codec, 0x1a, 0,
2053 AC_VERB_SET_PIN_WIDGET_CONTROL,
2054 cxt5066_olpc_dc_bias.items[spec->dc_input_bias].index);
2055}
2056
2027/* OLPC defers mic widget control until when capture is started because the 2057/* OLPC defers mic widget control until when capture is started because the
2028 * microphone LED comes on as soon as these settings are put in place. if we 2058 * microphone LED comes on as soon as these settings are put in place. if we
2029 * did this before recording, it would give the false indication that recording 2059 * 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)
2034 if (!spec->recording) 2064 if (!spec->recording)
2035 return; 2065 return;
2036 2066
2067 if (spec->dc_enable) {
2068 /* in DC mode we ignore presence detection and just use the jack
2069 * through our special DC port */
2070 const struct hda_verb enable_dc_mode[] = {
2071 /* disble internal mic, port C */
2072 {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
2073
2074 /* enable DC capture, port F */
2075 {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
2076 {},
2077 };
2078
2079 snd_hda_sequence_write(codec, enable_dc_mode);
2080 /* port B input disabled (and bias set) through the following call */
2081 cxt5066_set_olpc_dc_bias(codec);
2082 return;
2083 }
2084
2085 /* disable DC (port F) */
2086 snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
2087
2037 /* external mic, port B */ 2088 /* external mic, port B */
2038 snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 2089 snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
2039 spec->ext_mic_present ? CXT5066_OLPC_EXT_MIC_BIAS : 0); 2090 spec->ext_mic_present ? CXT5066_OLPC_EXT_MIC_BIAS : 0);
@@ -2049,6 +2100,9 @@ static void cxt5066_olpc_automic(struct hda_codec *codec)
2049 struct conexant_spec *spec = codec->spec; 2100 struct conexant_spec *spec = codec->spec;
2050 unsigned int present; 2101 unsigned int present;
2051 2102
2103 if (spec->dc_enable) /* don't do presence detection in DC mode */
2104 return;
2105
2052 present = snd_hda_codec_read(codec, 0x1a, 0, 2106 present = snd_hda_codec_read(codec, 0x1a, 0,
2053 AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; 2107 AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
2054 if (present) 2108 if (present)
@@ -2123,13 +2177,16 @@ static void cxt5066_hp_automute(struct hda_codec *codec)
2123/* unsolicited event for jack sensing */ 2177/* unsolicited event for jack sensing */
2124static void cxt5066_olpc_unsol_event(struct hda_codec *codec, unsigned int res) 2178static void cxt5066_olpc_unsol_event(struct hda_codec *codec, unsigned int res)
2125{ 2179{
2180 struct conexant_spec *spec = codec->spec;
2126 snd_printdd("CXT5066: unsol event %x (%x)\n", res, res >> 26); 2181 snd_printdd("CXT5066: unsol event %x (%x)\n", res, res >> 26);
2127 switch (res >> 26) { 2182 switch (res >> 26) {
2128 case CONEXANT_HP_EVENT: 2183 case CONEXANT_HP_EVENT:
2129 cxt5066_hp_automute(codec); 2184 cxt5066_hp_automute(codec);
2130 break; 2185 break;
2131 case CONEXANT_MIC_EVENT: 2186 case CONEXANT_MIC_EVENT:
2132 cxt5066_olpc_automic(codec); 2187 /* ignore mic events in DC mode; we're always using the jack */
2188 if (!spec->dc_enable)
2189 cxt5066_olpc_automic(codec);
2133 break; 2190 break;
2134 } 2191 }
2135} 2192}
@@ -2159,6 +2216,15 @@ static const struct hda_input_mux cxt5066_analog_mic_boost = {
2159 }, 2216 },
2160}; 2217};
2161 2218
2219static int cxt5066_set_mic_boost(struct hda_codec *codec)
2220{
2221 struct conexant_spec *spec = codec->spec;
2222 return snd_hda_codec_write_cache(codec, 0x17, 0,
2223 AC_VERB_SET_AMP_GAIN_MUTE,
2224 AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT |
2225 cxt5066_analog_mic_boost.items[spec->mic_boost].index);
2226}
2227
2162static int cxt5066_mic_boost_mux_enum_info(struct snd_kcontrol *kcontrol, 2228static int cxt5066_mic_boost_mux_enum_info(struct snd_kcontrol *kcontrol,
2163 struct snd_ctl_elem_info *uinfo) 2229 struct snd_ctl_elem_info *uinfo)
2164{ 2230{
@@ -2169,15 +2235,8 @@ static int cxt5066_mic_boost_mux_enum_get(struct snd_kcontrol *kcontrol,
2169 struct snd_ctl_elem_value *ucontrol) 2235 struct snd_ctl_elem_value *ucontrol)
2170{ 2236{
2171 struct hda_codec *codec = snd_kcontrol_chip(kcontrol); 2237 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
2172 int val; 2238 struct conexant_spec *spec = codec->spec;
2173 hda_nid_t nid = kcontrol->private_value & 0xff; 2239 ucontrol->value.enumerated.item[0] = spec->mic_boost;
2174 int inout = (kcontrol->private_value & 0x100) ?
2175 AC_AMP_GET_INPUT : AC_AMP_GET_OUTPUT;
2176
2177 val = snd_hda_codec_read(codec, nid, 0,
2178 AC_VERB_GET_AMP_GAIN_MUTE, inout);
2179
2180 ucontrol->value.enumerated.item[0] = val & AC_AMP_GAIN;
2181 return 0; 2240 return 0;
2182} 2241}
2183 2242
@@ -2185,23 +2244,101 @@ static int cxt5066_mic_boost_mux_enum_put(struct snd_kcontrol *kcontrol,
2185 struct snd_ctl_elem_value *ucontrol) 2244 struct snd_ctl_elem_value *ucontrol)
2186{ 2245{
2187 struct hda_codec *codec = snd_kcontrol_chip(kcontrol); 2246 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
2247 struct conexant_spec *spec = codec->spec;
2188 const struct hda_input_mux *imux = &cxt5066_analog_mic_boost; 2248 const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
2189 unsigned int idx; 2249 unsigned int idx;
2190 hda_nid_t nid = kcontrol->private_value & 0xff; 2250 idx = ucontrol->value.enumerated.item[0];
2191 int inout = (kcontrol->private_value & 0x100) ? 2251 if (idx >= imux->num_items)
2192 AC_AMP_SET_INPUT : AC_AMP_SET_OUTPUT; 2252 idx = imux->num_items - 1;
2253
2254 spec->mic_boost = idx;
2255 if (!spec->dc_enable)
2256 cxt5066_set_mic_boost(codec);
2257 return 1;
2258}
2259
2260static void cxt5066_enable_dc(struct hda_codec *codec)
2261{
2262 const struct hda_verb enable_dc_mode[] = {
2263 /* disable gain */
2264 {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
2265
2266 /* switch to DC input */
2267 {0x17, AC_VERB_SET_CONNECT_SEL, 3},
2268 {}
2269 };
2270
2271 /* configure as input source */
2272 snd_hda_sequence_write(codec, enable_dc_mode);
2273 cxt5066_olpc_select_mic(codec); /* also sets configured bias */
2274}
2275
2276static void cxt5066_disable_dc(struct hda_codec *codec)
2277{
2278 /* reconfigure input source */
2279 cxt5066_set_mic_boost(codec);
2280 /* automic also selects the right mic if we're recording */
2281 cxt5066_olpc_automic(codec);
2282}
2283
2284static int cxt5066_olpc_dc_get(struct snd_kcontrol *kcontrol,
2285 struct snd_ctl_elem_value *ucontrol)
2286{
2287 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
2288 struct conexant_spec *spec = codec->spec;
2289 ucontrol->value.integer.value[0] = spec->dc_enable;
2290 return 0;
2291}
2193 2292
2194 if (!imux->num_items) 2293static int cxt5066_olpc_dc_put(struct snd_kcontrol *kcontrol,
2294 struct snd_ctl_elem_value *ucontrol)
2295{
2296 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
2297 struct conexant_spec *spec = codec->spec;
2298 int dc_enable = !!ucontrol->value.integer.value[0];
2299
2300 if (dc_enable == spec->dc_enable)
2195 return 0; 2301 return 0;
2302
2303 spec->dc_enable = dc_enable;
2304 if (dc_enable)
2305 cxt5066_enable_dc(codec);
2306 else
2307 cxt5066_disable_dc(codec);
2308
2309 return 1;
2310}
2311
2312static int cxt5066_olpc_dc_bias_enum_info(struct snd_kcontrol *kcontrol,
2313 struct snd_ctl_elem_info *uinfo)
2314{
2315 return snd_hda_input_mux_info(&cxt5066_olpc_dc_bias, uinfo);
2316}
2317
2318static int cxt5066_olpc_dc_bias_enum_get(struct snd_kcontrol *kcontrol,
2319 struct snd_ctl_elem_value *ucontrol)
2320{
2321 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
2322 struct conexant_spec *spec = codec->spec;
2323 ucontrol->value.enumerated.item[0] = spec->dc_input_bias;
2324 return 0;
2325}
2326
2327static int cxt5066_olpc_dc_bias_enum_put(struct snd_kcontrol *kcontrol,
2328 struct snd_ctl_elem_value *ucontrol)
2329{
2330 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
2331 struct conexant_spec *spec = codec->spec;
2332 const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
2333 unsigned int idx;
2334
2196 idx = ucontrol->value.enumerated.item[0]; 2335 idx = ucontrol->value.enumerated.item[0];
2197 if (idx >= imux->num_items) 2336 if (idx >= imux->num_items)
2198 idx = imux->num_items - 1; 2337 idx = imux->num_items - 1;
2199 2338
2200 snd_hda_codec_write_cache(codec, nid, 0, 2339 spec->dc_input_bias = idx;
2201 AC_VERB_SET_AMP_GAIN_MUTE, 2340 if (spec->dc_enable)
2202 AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | inout | 2341 cxt5066_set_olpc_dc_bias(codec);
2203 imux->items[idx].index);
2204
2205 return 1; 2342 return 1;
2206} 2343}
2207 2344
@@ -2223,6 +2360,9 @@ static void cxt5066_olpc_capture_cleanup(struct hda_codec *codec)
2223 2360
2224 /* disble internal mic, port C */ 2361 /* disble internal mic, port C */
2225 {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, 2362 {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
2363
2364 /* disable DC capture, port F */
2365 {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
2226 {}, 2366 {},
2227 }; 2367 };
2228 2368
@@ -2282,6 +2422,24 @@ static struct snd_kcontrol_new cxt5066_mixer_master_olpc[] = {
2282 {} 2422 {}
2283}; 2423};
2284 2424
2425static struct snd_kcontrol_new cxt5066_mixer_olpc_dc[] = {
2426 {
2427 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
2428 .name = "DC Mode Enable Switch",
2429 .info = snd_ctl_boolean_mono_info,
2430 .get = cxt5066_olpc_dc_get,
2431 .put = cxt5066_olpc_dc_put,
2432 },
2433 {
2434 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
2435 .name = "DC Input Bias Enum",
2436 .info = cxt5066_olpc_dc_bias_enum_info,
2437 .get = cxt5066_olpc_dc_bias_enum_get,
2438 .put = cxt5066_olpc_dc_bias_enum_put,
2439 },
2440 {}
2441};
2442
2285static struct snd_kcontrol_new cxt5066_mixers[] = { 2443static struct snd_kcontrol_new cxt5066_mixers[] = {
2286 { 2444 {
2287 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 2445 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
@@ -2294,11 +2452,10 @@ static struct snd_kcontrol_new cxt5066_mixers[] = {
2294 2452
2295 { 2453 {
2296 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 2454 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
2297 .name = "Ext Mic Boost Capture Enum", 2455 .name = "Analog Mic Boost Capture Enum",
2298 .info = cxt5066_mic_boost_mux_enum_info, 2456 .info = cxt5066_mic_boost_mux_enum_info,
2299 .get = cxt5066_mic_boost_mux_enum_get, 2457 .get = cxt5066_mic_boost_mux_enum_get,
2300 .put = cxt5066_mic_boost_mux_enum_put, 2458 .put = cxt5066_mic_boost_mux_enum_put,
2301 .private_value = 0x17,
2302 }, 2459 },
2303 2460
2304 HDA_BIND_VOL("Capture Volume", &cxt5066_bind_capture_vol_others), 2461 HDA_BIND_VOL("Capture Volume", &cxt5066_bind_capture_vol_others),
@@ -2392,7 +2549,7 @@ static struct hda_verb cxt5066_init_verbs_olpc[] = {
2392 {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, 2549 {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
2393 {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ 2550 {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
2394 2551
2395 /* Port F: unused */ 2552 /* Port F: external DC input through microphone port */
2396 {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, 2553 {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
2397 2554
2398 /* Port G: internal speakers */ 2555 /* Port G: internal speakers */
@@ -2513,15 +2670,22 @@ static int cxt5066_init(struct hda_codec *codec)
2513 if (spec->dell_vostro) 2670 if (spec->dell_vostro)
2514 cxt5066_vostro_automic(codec); 2671 cxt5066_vostro_automic(codec);
2515 } 2672 }
2673 cxt5066_set_mic_boost(codec);
2516 return 0; 2674 return 0;
2517} 2675}
2518 2676
2519static int cxt5066_olpc_init(struct hda_codec *codec) 2677static int cxt5066_olpc_init(struct hda_codec *codec)
2520{ 2678{
2679 struct conexant_spec *spec = codec->spec;
2521 snd_printdd("CXT5066: init\n"); 2680 snd_printdd("CXT5066: init\n");
2522 conexant_init(codec); 2681 conexant_init(codec);
2523 cxt5066_hp_automute(codec); 2682 cxt5066_hp_automute(codec);
2524 cxt5066_olpc_automic(codec); 2683 if (!spec->dc_enable) {
2684 cxt5066_set_mic_boost(codec);
2685 cxt5066_olpc_automic(codec);
2686 } else {
2687 cxt5066_enable_dc(codec);
2688 }
2525 return 0; 2689 return 0;
2526} 2690}
2527 2691
@@ -2604,8 +2768,10 @@ static int patch_cxt5066(struct hda_codec *codec)
2604 codec->patch_ops.unsol_event = cxt5066_olpc_unsol_event; 2768 codec->patch_ops.unsol_event = cxt5066_olpc_unsol_event;
2605 spec->init_verbs[0] = cxt5066_init_verbs_olpc; 2769 spec->init_verbs[0] = cxt5066_init_verbs_olpc;
2606 spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc; 2770 spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
2771 spec->mixers[spec->num_mixers++] = cxt5066_mixer_olpc_dc;
2607 spec->mixers[spec->num_mixers++] = cxt5066_mixers; 2772 spec->mixers[spec->num_mixers++] = cxt5066_mixers;
2608 spec->port_d_mode = 0; 2773 spec->port_d_mode = 0;
2774 spec->mic_boost = 3; /* default 30dB gain */
2609 2775
2610 /* no S/PDIF out */ 2776 /* no S/PDIF out */
2611 spec->multiout.dig_out_nid = 0; 2777 spec->multiout.dig_out_nid = 0;
@@ -2627,6 +2793,7 @@ static int patch_cxt5066(struct hda_codec *codec)
2627 spec->mixers[spec->num_mixers++] = cxt5066_vostro_mixers; 2793 spec->mixers[spec->num_mixers++] = cxt5066_vostro_mixers;
2628 spec->port_d_mode = 0; 2794 spec->port_d_mode = 0;
2629 spec->dell_vostro = 1; 2795 spec->dell_vostro = 1;
2796 spec->mic_boost = 3; /* default 30dB gain */
2630 snd_hda_attach_beep_device(codec, 0x13); 2797 snd_hda_attach_beep_device(codec, 0x13);
2631 2798
2632 /* no S/PDIF out */ 2799 /* no S/PDIF out */