diff options
author | Jonathan Woithe <jwoithe@physics.adelaide.edu.au> | 2006-02-09 05:53:48 -0500 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2006-03-22 04:28:42 -0500 |
commit | 4c5186ed6b25278df595edf2d355ee87b00c4426 (patch) | |
tree | 1114da4875d4925213e9c99d59dadf7673693900 /sound/pci | |
parent | ba22429d3ea3b9945735b88d4dde74711171ffab (diff) |
[ALSA] hda: add PCM for 2nd ADC on ALC260
Modules: HDA Codec driver
The following patch against alsa 1.0.11rc3 creates a PCM device (pcm1c) for
the second ADC present on the ALC260 codec used by the hda driver. It also
defines a new mixer control allowing the mode of retasking pins to be set;
this means a user can (for example) designate the headphone jack to be a
second input. With this patch in place it is possible to do 4 channel
recording on laptops equipped with an ALC260 codec assuming both a stereo
line-in jack is provided in addition to a headphone jack.
Mixer controls are provided to allow the headphone jack to be switched as
an input. In addition, an (input only) mode control is configured for
the line-in jack to allow a bias voltage to be requested (VREF80 or VREF50)
so headsets based on condensor microphones have a chance of working.
This patch has been tested on a Fujitsu S7020 laptop and as such these
features are currently only configured for the 'fujitsu' model.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/pci')
-rw-r--r-- | sound/pci/hda/patch_realtek.c | 149 |
1 files changed, 119 insertions, 30 deletions
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index b76755264730..c8fc6269b03c 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c | |||
@@ -132,7 +132,7 @@ struct alc_spec { | |||
132 | int num_channel_mode; | 132 | int num_channel_mode; |
133 | 133 | ||
134 | /* PCM information */ | 134 | /* PCM information */ |
135 | struct hda_pcm pcm_rec[2]; /* used in alc_build_pcms() */ | 135 | struct hda_pcm pcm_rec[3]; /* used in alc_build_pcms() */ |
136 | 136 | ||
137 | /* dynamic controls, init_verbs and input_mux */ | 137 | /* dynamic controls, init_verbs and input_mux */ |
138 | struct auto_pin_cfg autocfg; | 138 | struct auto_pin_cfg autocfg; |
@@ -218,56 +218,96 @@ static int alc_ch_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_va | |||
218 | spec->num_channel_mode, &spec->multiout.max_channels); | 218 | spec->num_channel_mode, &spec->multiout.max_channels); |
219 | } | 219 | } |
220 | 220 | ||
221 | |||
222 | /* | 221 | /* |
223 | * Control of pin widget settings via the mixer. Only boolean settings are | 222 | * Control the mode of pin widget settings via the mixer. "pc" is used |
224 | * supported, so VrefEn can't be controlled using these functions as they | 223 | * instead of "%" to avoid consequences of accidently treating the % as |
225 | * stand. | 224 | * being part of a format specifier. Maximum allowed length of a value is |
225 | * 63 characters plus NULL terminator. | ||
226 | */ | ||
227 | static char *alc_pin_mode_names[] = { | ||
228 | "Line in", "Mic 80pc bias", "Mic 50pc bias", | ||
229 | "Line out", "Headphone out", | ||
230 | }; | ||
231 | static unsigned char alc_pin_mode_values[] = { | ||
232 | PIN_IN, PIN_VREF80, PIN_VREF50, PIN_OUT, PIN_HP, | ||
233 | }; | ||
234 | /* The control can present all 5 options, or it can limit the options based | ||
235 | * in the pin being assumed to be exclusively an input or an output pin. | ||
226 | */ | 236 | */ |
227 | static int alc_pinctl_switch_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) | 237 | #define ALC_PIN_DIR_IN 0x00 |
238 | #define ALC_PIN_DIR_OUT 0x01 | ||
239 | #define ALC_PIN_DIR_INOUT 0x02 | ||
240 | |||
241 | /* Info about the pin modes supported by the three different pin directions. | ||
242 | * For each direction the minimum and maximum values are given. | ||
243 | */ | ||
244 | static signed char alc_pin_mode_dir_info[3][2] = { | ||
245 | { 0, 2 }, /* ALC_PIN_DIR_IN */ | ||
246 | { 3, 4 }, /* ALC_PIN_DIR_OUT */ | ||
247 | { 0, 4 }, /* ALC_PIN_DIR_INOUT */ | ||
248 | }; | ||
249 | #define alc_pin_mode_min(_dir) (alc_pin_mode_dir_info[_dir][0]) | ||
250 | #define alc_pin_mode_max(_dir) (alc_pin_mode_dir_info[_dir][1]) | ||
251 | #define alc_pin_mode_n_items(_dir) \ | ||
252 | (alc_pin_mode_max(_dir)-alc_pin_mode_min(_dir)+1) | ||
253 | |||
254 | static int alc_pin_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) | ||
228 | { | 255 | { |
229 | uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; | 256 | unsigned int item_num = uinfo->value.enumerated.item; |
257 | unsigned char dir = (kcontrol->private_value >> 16) & 0xff; | ||
258 | |||
259 | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | ||
230 | uinfo->count = 1; | 260 | uinfo->count = 1; |
231 | uinfo->value.integer.min = 0; | 261 | uinfo->value.enumerated.items = alc_pin_mode_n_items(dir); |
232 | uinfo->value.integer.max = 1; | 262 | |
263 | if (item_num<alc_pin_mode_min(dir) || item_num>alc_pin_mode_max(dir)) | ||
264 | item_num = alc_pin_mode_min(dir); | ||
265 | strcpy(uinfo->value.enumerated.name, alc_pin_mode_names[item_num]); | ||
233 | return 0; | 266 | return 0; |
234 | } | 267 | } |
235 | 268 | ||
236 | static int alc_pinctl_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) | 269 | static int alc_pin_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) |
237 | { | 270 | { |
271 | unsigned int i; | ||
238 | struct hda_codec *codec = snd_kcontrol_chip(kcontrol); | 272 | struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
239 | hda_nid_t nid = kcontrol->private_value & 0xffff; | 273 | hda_nid_t nid = kcontrol->private_value & 0xffff; |
240 | long mask = (kcontrol->private_value >> 16) & 0xff; | 274 | unsigned char dir = (kcontrol->private_value >> 16) & 0xff; |
241 | long *valp = ucontrol->value.integer.value; | 275 | long *valp = ucontrol->value.integer.value; |
276 | unsigned int pinctl = snd_hda_codec_read(codec,nid,0,AC_VERB_GET_PIN_WIDGET_CONTROL,0x00); | ||
242 | 277 | ||
243 | *valp = 0; | 278 | /* Find enumerated value for current pinctl setting */ |
244 | if (snd_hda_codec_read(codec,nid,0,AC_VERB_GET_PIN_WIDGET_CONTROL,0x00) & mask) | 279 | i = alc_pin_mode_min(dir); |
245 | *valp = 1; | 280 | while (alc_pin_mode_values[i]!=pinctl && i<=alc_pin_mode_max(dir)) |
281 | i++; | ||
282 | *valp = i<=alc_pin_mode_max(dir)?i:alc_pin_mode_min(dir); | ||
246 | return 0; | 283 | return 0; |
247 | } | 284 | } |
248 | 285 | ||
249 | static int alc_pinctl_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) | 286 | static int alc_pin_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) |
250 | { | 287 | { |
288 | signed int change; | ||
251 | struct hda_codec *codec = snd_kcontrol_chip(kcontrol); | 289 | struct hda_codec *codec = snd_kcontrol_chip(kcontrol); |
252 | hda_nid_t nid = kcontrol->private_value & 0xffff; | 290 | hda_nid_t nid = kcontrol->private_value & 0xffff; |
253 | long mask = (kcontrol->private_value >> 16) & 0xff; | 291 | unsigned char dir = (kcontrol->private_value >> 16) & 0xff; |
254 | long *valp = ucontrol->value.integer.value; | 292 | long val = *ucontrol->value.integer.value; |
255 | unsigned int pinctl = snd_hda_codec_read(codec,nid,0,AC_VERB_GET_PIN_WIDGET_CONTROL,0x00); | 293 | unsigned int pinctl = snd_hda_codec_read(codec,nid,0,AC_VERB_GET_PIN_WIDGET_CONTROL,0x00); |
256 | int change = ((pinctl & mask)!=0) != *valp; | ||
257 | 294 | ||
295 | if (val<alc_pin_mode_min(dir) || val>alc_pin_mode_max(dir)) | ||
296 | val = alc_pin_mode_min(dir); | ||
297 | |||
298 | change = pinctl != alc_pin_mode_values[val]; | ||
258 | if (change) | 299 | if (change) |
259 | snd_hda_codec_write(codec,nid,0,AC_VERB_SET_PIN_WIDGET_CONTROL, | 300 | snd_hda_codec_write(codec,nid,0,AC_VERB_SET_PIN_WIDGET_CONTROL, |
260 | *valp?(pinctl|mask):(pinctl&~mask)); | 301 | alc_pin_mode_values[val]); |
261 | return change; | 302 | return change; |
262 | } | 303 | } |
263 | 304 | ||
264 | #define ALC_PINCTL_SWITCH(xname, nid, mask) \ | 305 | #define ALC_PIN_MODE(xname, nid, dir) \ |
265 | { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ | 306 | { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ |
266 | .info = alc_pinctl_switch_info, \ | 307 | .info = alc_pin_mode_info, \ |
267 | .get = alc_pinctl_switch_get, \ | 308 | .get = alc_pin_mode_get, \ |
268 | .put = alc_pinctl_switch_put, \ | 309 | .put = alc_pin_mode_put, \ |
269 | .private_value = (nid) | (mask<<16) } | 310 | .private_value = nid | (dir<<16) } |
270 | |||
271 | 311 | ||
272 | /* | 312 | /* |
273 | * set up from the preset table | 313 | * set up from the preset table |
@@ -1250,6 +1290,13 @@ static struct hda_pcm_stream alc880_pcm_digital_capture = { | |||
1250 | /* NID is set in alc_build_pcms */ | 1290 | /* NID is set in alc_build_pcms */ |
1251 | }; | 1291 | }; |
1252 | 1292 | ||
1293 | /* Used by alc_build_pcms to flag that a PCM has no playback stream */ | ||
1294 | static struct hda_pcm_stream alc_pcm_null_playback = { | ||
1295 | .substreams = 0, | ||
1296 | .channels_min = 0, | ||
1297 | .channels_max = 0, | ||
1298 | }; | ||
1299 | |||
1253 | static int alc_build_pcms(struct hda_codec *codec) | 1300 | static int alc_build_pcms(struct hda_codec *codec) |
1254 | { | 1301 | { |
1255 | struct alc_spec *spec = codec->spec; | 1302 | struct alc_spec *spec = codec->spec; |
@@ -1280,6 +1327,23 @@ static int alc_build_pcms(struct hda_codec *codec) | |||
1280 | } | 1327 | } |
1281 | } | 1328 | } |
1282 | 1329 | ||
1330 | /* If the use of more than one ADC is requested for the current | ||
1331 | * model, configure a second analog capture-only PCM. | ||
1332 | */ | ||
1333 | if (spec->num_adc_nids > 1) { | ||
1334 | codec->num_pcms++; | ||
1335 | info++; | ||
1336 | info->name = spec->stream_name_analog; | ||
1337 | /* No playback stream for second PCM */ | ||
1338 | info->stream[SNDRV_PCM_STREAM_PLAYBACK] = alc_pcm_null_playback; | ||
1339 | info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = 0; | ||
1340 | if (spec->stream_analog_capture) { | ||
1341 | snd_assert(spec->adc_nids, return -EINVAL); | ||
1342 | info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_analog_capture); | ||
1343 | info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[1]; | ||
1344 | } | ||
1345 | } | ||
1346 | |||
1283 | if (spec->multiout.dig_out_nid || spec->dig_in_nid) { | 1347 | if (spec->multiout.dig_out_nid || spec->dig_in_nid) { |
1284 | codec->num_pcms++; | 1348 | codec->num_pcms++; |
1285 | info++; | 1349 | info++; |
@@ -2322,6 +2386,11 @@ static hda_nid_t alc260_hp_adc_nids[2] = { | |||
2322 | 0x05, 0x04 | 2386 | 0x05, 0x04 |
2323 | }; | 2387 | }; |
2324 | 2388 | ||
2389 | static hda_nid_t alc260_fujitsu_adc_nids[2] = { | ||
2390 | /* ADC0, ADC1 */ | ||
2391 | 0x04, 0x05 | ||
2392 | }; | ||
2393 | |||
2325 | #define ALC260_DIGOUT_NID 0x03 | 2394 | #define ALC260_DIGOUT_NID 0x03 |
2326 | #define ALC260_DIGIN_NID 0x06 | 2395 | #define ALC260_DIGIN_NID 0x06 |
2327 | 2396 | ||
@@ -2339,10 +2408,11 @@ static struct hda_input_mux alc260_capture_source = { | |||
2339 | * and the internal CD lines. | 2408 | * and the internal CD lines. |
2340 | */ | 2409 | */ |
2341 | static struct hda_input_mux alc260_fujitsu_capture_source = { | 2410 | static struct hda_input_mux alc260_fujitsu_capture_source = { |
2342 | .num_items = 2, | 2411 | .num_items = 3, |
2343 | .items = { | 2412 | .items = { |
2344 | { "Mic/Line", 0x0 }, | 2413 | { "Mic/Line", 0x0 }, |
2345 | { "CD", 0x4 }, | 2414 | { "CD", 0x4 }, |
2415 | { "Headphone", 0x2 }, | ||
2346 | }, | 2416 | }, |
2347 | }; | 2417 | }; |
2348 | 2418 | ||
@@ -2408,11 +2478,12 @@ static struct snd_kcontrol_new alc260_hp_3013_mixer[] = { | |||
2408 | static struct snd_kcontrol_new alc260_fujitsu_mixer[] = { | 2478 | static struct snd_kcontrol_new alc260_fujitsu_mixer[] = { |
2409 | HDA_CODEC_VOLUME("Headphone Playback Volume", 0x08, 0x0, HDA_OUTPUT), | 2479 | HDA_CODEC_VOLUME("Headphone Playback Volume", 0x08, 0x0, HDA_OUTPUT), |
2410 | HDA_BIND_MUTE("Headphone Playback Switch", 0x08, 2, HDA_INPUT), | 2480 | HDA_BIND_MUTE("Headphone Playback Switch", 0x08, 2, HDA_INPUT), |
2411 | ALC_PINCTL_SWITCH("Headphone Amp Switch", 0x14, PIN_HP_AMP), | 2481 | ALC_PIN_MODE("Headphone Jack Mode", 0x14, ALC_PIN_DIR_INOUT), |
2412 | HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT), | 2482 | HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT), |
2413 | HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT), | 2483 | HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT), |
2414 | HDA_CODEC_VOLUME("Mic/Line Playback Volume", 0x07, 0x0, HDA_INPUT), | 2484 | HDA_CODEC_VOLUME("Mic/Line Playback Volume", 0x07, 0x0, HDA_INPUT), |
2415 | HDA_CODEC_MUTE("Mic/Line Playback Switch", 0x07, 0x0, HDA_INPUT), | 2485 | HDA_CODEC_MUTE("Mic/Line Playback Switch", 0x07, 0x0, HDA_INPUT), |
2486 | ALC_PIN_MODE("Mic/Line Jack Mode", 0x12, ALC_PIN_DIR_IN), | ||
2416 | HDA_CODEC_VOLUME("Beep Playback Volume", 0x07, 0x05, HDA_INPUT), | 2487 | HDA_CODEC_VOLUME("Beep Playback Volume", 0x07, 0x05, HDA_INPUT), |
2417 | HDA_CODEC_MUTE("Beep Playback Switch", 0x07, 0x05, HDA_INPUT), | 2488 | HDA_CODEC_MUTE("Beep Playback Switch", 0x07, 0x05, HDA_INPUT), |
2418 | HDA_CODEC_VOLUME("Internal Speaker Playback Volume", 0x09, 0x0, HDA_OUTPUT), | 2489 | HDA_CODEC_VOLUME("Internal Speaker Playback Volume", 0x09, 0x0, HDA_OUTPUT), |
@@ -2645,6 +2716,11 @@ static struct hda_verb alc260_fujitsu_init_verbs[] = { | |||
2645 | {0x03, AC_VERB_SET_DIGI_CONVERT_1, 0}, | 2716 | {0x03, AC_VERB_SET_DIGI_CONVERT_1, 0}, |
2646 | {0x06, AC_VERB_SET_DIGI_CONVERT_1, 0}, | 2717 | {0x06, AC_VERB_SET_DIGI_CONVERT_1, 0}, |
2647 | 2718 | ||
2719 | /* Ensure Line1 pin widget takes its input from the OUT1 sum bus | ||
2720 | * when acting as an output. | ||
2721 | */ | ||
2722 | {0x0d, AC_VERB_SET_CONNECT_SEL, 0}, | ||
2723 | |||
2648 | /* Start with mixer outputs muted */ | 2724 | /* Start with mixer outputs muted */ |
2649 | {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, | 2725 | {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, |
2650 | {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, | 2726 | {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, |
@@ -2654,14 +2730,27 @@ static struct hda_verb alc260_fujitsu_init_verbs[] = { | |||
2654 | {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, | 2730 | {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, |
2655 | /* Unmute Line1 pin widget amp left and right (no equiv mixer ctrl) */ | 2731 | /* Unmute Line1 pin widget amp left and right (no equiv mixer ctrl) */ |
2656 | {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, | 2732 | {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, |
2733 | /* Unmute Line1 pin widget input for when this pin is used as input | ||
2734 | * (no equiv mixer ctrl). Having input and output unmuted doesn't | ||
2735 | * seem to cause a problem. | ||
2736 | */ | ||
2737 | {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, | ||
2657 | /* Unmute pin widget used for Line-in (no equiv mixer ctrl) */ | 2738 | /* Unmute pin widget used for Line-in (no equiv mixer ctrl) */ |
2658 | {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, | 2739 | {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, |
2659 | 2740 | ||
2660 | /* Mute capture amp left and right */ | 2741 | /* Mute capture amp left and right */ |
2661 | {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, | 2742 | {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, |
2662 | /* Set ADC connection select to line in (on mic1 pin) */ | 2743 | /* Set ADC connection select to match default mixer setting - line |
2744 | * in (on mic1 pin) | ||
2745 | */ | ||
2663 | {0x04, AC_VERB_SET_CONNECT_SEL, 0x00}, | 2746 | {0x04, AC_VERB_SET_CONNECT_SEL, 0x00}, |
2664 | 2747 | ||
2748 | /* Do the same for the second ADC: mute capture input amp and | ||
2749 | * set ADC connection to line in | ||
2750 | */ | ||
2751 | {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, | ||
2752 | {0x05, AC_VERB_SET_CONNECT_SEL, 0x00}, | ||
2753 | |||
2665 | /* Mute all inputs to mixer widget (even unconnected ones) */ | 2754 | /* Mute all inputs to mixer widget (even unconnected ones) */ |
2666 | {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */ | 2755 | {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */ |
2667 | {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */ | 2756 | {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */ |
@@ -3009,8 +3098,8 @@ static struct alc_config_preset alc260_presets[] = { | |||
3009 | .init_verbs = { alc260_fujitsu_init_verbs }, | 3098 | .init_verbs = { alc260_fujitsu_init_verbs }, |
3010 | .num_dacs = ARRAY_SIZE(alc260_dac_nids), | 3099 | .num_dacs = ARRAY_SIZE(alc260_dac_nids), |
3011 | .dac_nids = alc260_dac_nids, | 3100 | .dac_nids = alc260_dac_nids, |
3012 | .num_adc_nids = ARRAY_SIZE(alc260_adc_nids), | 3101 | .num_adc_nids = ARRAY_SIZE(alc260_fujitsu_adc_nids), |
3013 | .adc_nids = alc260_adc_nids, | 3102 | .adc_nids = alc260_fujitsu_adc_nids, |
3014 | .num_channel_mode = ARRAY_SIZE(alc260_modes), | 3103 | .num_channel_mode = ARRAY_SIZE(alc260_modes), |
3015 | .channel_mode = alc260_modes, | 3104 | .channel_mode = alc260_modes, |
3016 | .input_mux = &alc260_fujitsu_capture_source, | 3105 | .input_mux = &alc260_fujitsu_capture_source, |