ALSA: hda/conexant - Re-implement OLPC XO workarounds via fixup

OLPC XO needs a few special handling.  Now these are implemented as a
fixup to the generic parser.

Obviously, the DC BIAS mode had to be added manually.  This is mainly
implemented in the mic_autoswitch hook, where the mic pins are
overwritten depending on the DC bias mode.  This also required the
override of the mic boost control, since the mic boost is applied only
when the DC mode is disabled.

In addition, the mic pins must be set dynamically at recording time
because these also control the LED.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2014-01-27 16:28:02 +01:00
parent 8f0972dfa9
commit 3a00c6605b
1 changed files with 269 additions and 374 deletions

View File

@ -68,6 +68,12 @@ struct conexant_spec {
unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */
/* OPLC XO specific */
bool recording;
bool dc_enable;
unsigned int dc_input_bias; /* offset into olpc_xo_dc_bias */
struct nid_path *dc_mode_path;
#ifdef ENABLE_CXT_STATIC_QUIRKS
const struct snd_kcontrol_new *mixers[5];
int num_mixers;
@ -123,19 +129,6 @@ struct conexant_spec {
unsigned int hp_laptop:1;
unsigned int asus:1;
unsigned int ext_mic_present;
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 */
#endif /* ENABLE_CXT_STATIC_QUIRKS */
};
@ -253,8 +246,6 @@ static int conexant_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
struct snd_pcm_substream *substream)
{
struct conexant_spec *spec = codec->spec;
if (spec->capture_prepare)
spec->capture_prepare(codec);
snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
stream_tag, 0, format);
return 0;
@ -266,8 +257,6 @@ static int conexant_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
{
struct conexant_spec *spec = codec->spec;
snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
if (spec->capture_cleanup)
spec->capture_cleanup(codec);
return 0;
}
@ -1940,11 +1929,6 @@ static const hda_nid_t cxt5066_adc_nids[3] = { 0x14, 0x15, 0x16 };
static const hda_nid_t cxt5066_capsrc_nids[1] = { 0x17 };
static const hda_nid_t cxt5066_digout_pin_nids[2] = { 0x20, 0x22 };
/* OLPC's microphone port is DC coupled for use with external sensors,
* therefore we use a 50% mic bias in order to center the input signal with
* the DC input range of the codec. */
#define CXT5066_OLPC_EXT_MIC_BIAS PIN_VREF50
static const struct hda_channel_mode cxt5066_modes[1] = {
{ 2, NULL },
};
@ -1997,88 +1981,6 @@ 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_set_pin_ctl_cache(codec, 0x1a,
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
* is happening when it is not. */
static void cxt5066_olpc_select_mic(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
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_set_pin_ctl(codec, 0x1e, 0);
/* external mic, port B */
snd_hda_set_pin_ctl(codec, 0x1a,
spec->ext_mic_present ? CXT5066_OLPC_EXT_MIC_BIAS : 0);
/* internal mic, port C */
snd_hda_set_pin_ctl(codec, 0x1b,
spec->ext_mic_present ? 0 : PIN_VREF80);
}
/* toggle input of built-in and mic jack appropriately */
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)
snd_printdd("CXT5066: external microphone detected\n");
else
snd_printdd("CXT5066: external microphone absent\n");
snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL,
present ? 0 : 1);
spec->ext_mic_present = !!present;
cxt5066_olpc_select_mic(codec);
}
/* toggle input of built-in digital mic and mic jack appropriately */
static void cxt5066_vostro_automic(struct hda_codec *codec)
{
@ -2251,23 +2153,6 @@ static void cxt5066_automic(struct hda_codec *codec)
cxt5066_asus_automic(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:
/* ignore mic events in DC mode; we're always using the jack */
if (!spec->dc_enable)
cxt5066_olpc_automic(codec);
break;
}
}
/* unsolicited event for jack sensing */
static void cxt5066_unsol_event(struct hda_codec *codec, unsigned int res)
{
@ -2338,124 +2223,10 @@ static int cxt5066_mic_boost_mux_enum_put(struct snd_kcontrol *kcontrol,
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;
}
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;
spec->dc_input_bias = idx;
if (spec->dc_enable)
cxt5066_set_olpc_dc_bias(codec);
return 1;
}
static void cxt5066_olpc_capture_prepare(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
/* mark as recording and configure the microphone widget so that the
* recording LED comes on. */
spec->recording = 1;
cxt5066_olpc_select_mic(codec);
}
static void cxt5066_olpc_capture_cleanup(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
const struct hda_verb disable_mics[] = {
/* disable external mic, port B */
{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
/* 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},
{},
};
snd_hda_sequence_write(codec, disable_mics);
spec->recording = 0;
}
static void conexant_check_dig_outs(struct hda_codec *codec,
const hda_nid_t *dig_pins,
int num_pins)
@ -2506,43 +2277,6 @@ static const struct snd_kcontrol_new cxt5066_mixer_master[] = {
{}
};
static const struct snd_kcontrol_new cxt5066_mixer_master_olpc[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
.subdevice = HDA_SUBDEV_AMP_FLAG,
.info = snd_hda_mixer_amp_volume_info,
.get = snd_hda_mixer_amp_volume_get,
.put = snd_hda_mixer_amp_volume_put,
.tlv = { .c = snd_hda_mixer_amp_tlv },
/* offset by 28 volume steps to limit minimum gain to -46dB */
.private_value =
HDA_COMPOSE_AMP_VAL_OFS(0x10, 3, 0, HDA_OUTPUT, 28),
},
{}
};
static const 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 const struct snd_kcontrol_new cxt5066_mixers[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
@ -2633,67 +2367,6 @@ static const struct hda_verb cxt5066_init_verbs[] = {
{ } /* end */
};
static const struct hda_verb cxt5066_init_verbs_olpc[] = {
/* Port A: headphones */
{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
/* Port B: external microphone */
{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
/* Port C: internal microphone */
{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
/* Port D: unused */
{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
/* Port E: unused, but has primary EAPD */
{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
/* Port F: external DC input through microphone port */
{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
/* Port G: internal speakers */
{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
{0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
/* DAC1 */
{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
/* DAC2: unused */
{0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50},
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
{0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)},
{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},
{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)},
{0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)},
/* Disable digital microphone port */
{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
/* Audio input selectors */
{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x3},
{0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE },
/* Disable SPDIF */
{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
/* enable unsolicited events for Port A and B */
{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT},
{0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT},
{ } /* end */
};
static const struct hda_verb cxt5066_init_verbs_vostro[] = {
/* Port A: headphones */
{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
@ -2889,25 +2562,9 @@ static int cxt5066_init(struct hda_codec *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);
if (!spec->dc_enable) {
cxt5066_set_mic_boost(codec);
cxt5066_olpc_automic(codec);
} else {
cxt5066_enable_dc(codec);
}
return 0;
}
enum {
CXT5066_LAPTOP, /* Laptops w/ EAPD support */
CXT5066_DELL_LAPTOP, /* Dell Laptop */
CXT5066_OLPC_XO_1_5, /* OLPC XO 1.5 */
CXT5066_DELL_VOSTRO, /* Dell Vostro 1015i */
CXT5066_IDEAPAD, /* Lenovo IdeaPad U150 */
CXT5066_THINKPAD, /* Lenovo ThinkPad T410s, others? */
@ -2920,7 +2577,6 @@ enum {
static const char * const cxt5066_models[CXT5066_MODELS] = {
[CXT5066_LAPTOP] = "laptop",
[CXT5066_DELL_LAPTOP] = "dell-laptop",
[CXT5066_OLPC_XO_1_5] = "olpc-xo-1_5",
[CXT5066_DELL_VOSTRO] = "dell-vostro",
[CXT5066_IDEAPAD] = "ideapad",
[CXT5066_THINKPAD] = "thinkpad",
@ -2941,10 +2597,8 @@ static const struct snd_pci_quirk cxt5066_cfg_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x1643, "Asus K52JU", CXT5066_ASUS),
SND_PCI_QUIRK(0x1043, 0x1993, "Asus U50F", CXT5066_ASUS),
SND_PCI_QUIRK(0x1179, 0xff1e, "Toshiba Satellite C650D", CXT5066_IDEAPAD),
SND_PCI_QUIRK(0x1179, 0xff50, "Toshiba Satellite P500-PSPGSC-01800T", CXT5066_OLPC_XO_1_5),
SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board",
CXT5066_LAPTOP),
SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT5066_OLPC_XO_1_5),
SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400s", CXT5066_THINKPAD),
SND_PCI_QUIRK(0x17aa, 0x21c5, "Thinkpad Edge 13", CXT5066_THINKPAD),
SND_PCI_QUIRK(0x17aa, 0x21c6, "Thinkpad Edge 13", CXT5066_ASUS),
@ -3030,32 +2684,11 @@ static int patch_cxt5066(struct hda_codec *codec)
spec->mic_boost = 3; /* default 30dB gain */
break;
case CXT5066_OLPC_XO_1_5:
codec->patch_ops.init = cxt5066_olpc_init;
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;
/* input source automatically selected */
spec->input_mux = NULL;
/* our capture hooks which allow us to turn on the microphone LED
* at the right time */
spec->capture_prepare = cxt5066_olpc_capture_prepare;
spec->capture_cleanup = cxt5066_olpc_capture_cleanup;
break;
case CXT5066_DELL_VOSTRO:
codec->patch_ops.init = cxt5066_init;
codec->patch_ops.unsol_event = cxt5066_unsol_event;
spec->init_verbs[0] = cxt5066_init_verbs_vostro;
spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
spec->mixers[spec->num_mixers++] = cxt5066_mixer_master;
spec->mixers[spec->num_mixers++] = cxt5066_mixers;
spec->mixers[spec->num_mixers++] = cxt5066_vostro_mixers;
spec->port_d_mode = 0;
@ -3238,6 +2871,7 @@ enum {
CXT_FIXUP_HEADPHONE_MIC,
CXT_FIXUP_GPIO1,
CXT_FIXUP_THINKPAD_ACPI,
CXT_FIXUP_OLPC_XO,
};
/* for hda_fixup_thinkpad_acpi() */
@ -3315,6 +2949,261 @@ static void cxt_fixup_headphone_mic(struct hda_codec *codec,
}
}
/* OPLC XO 1.5 fixup */
/* 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. */
#define update_mic_pin(codec, nid, val) \
snd_hda_codec_update_cache(codec, nid, 0, \
AC_VERB_SET_PIN_WIDGET_CONTROL, val)
static const struct hda_input_mux olpc_xo_dc_bias = {
.num_items = 3,
.items = {
{ "Off", PIN_IN },
{ "50%", PIN_VREF50 },
{ "80%", PIN_VREF80 },
},
};
static void olpc_xo_update_mic_boost(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
int ch, val;
for (ch = 0; ch < 2; ch++) {
val = AC_AMP_SET_OUTPUT |
(ch ? AC_AMP_SET_RIGHT : AC_AMP_SET_LEFT);
if (!spec->dc_enable)
val |= snd_hda_codec_amp_read(codec, 0x17, ch, HDA_OUTPUT, 0);
snd_hda_codec_write(codec, 0x17, 0,
AC_VERB_SET_AMP_GAIN_MUTE, val);
}
}
static void olpc_xo_update_mic_pins(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
int cur_input, val;
struct nid_path *path;
cur_input = spec->gen.input_paths[0][spec->gen.cur_mux[0]];
/* Set up mic pins for port-B, C and F dynamically as the recording
* LED is turned on/off by these pin controls
*/
if (!spec->dc_enable) {
/* disable DC bias path and pin for port F */
update_mic_pin(codec, 0x1e, 0);
snd_hda_activate_path(codec, spec->dc_mode_path, false, false);
/* update port B (ext mic) and C (int mic) */
/* 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 is happening when it is not.
*/
update_mic_pin(codec, 0x1a, spec->recording ?
snd_hda_codec_get_pin_target(codec, 0x1a) : 0);
update_mic_pin(codec, 0x1b, spec->recording ?
snd_hda_codec_get_pin_target(codec, 0x1b) : 0);
/* enable normal mic path */
path = snd_hda_get_path_from_idx(codec, cur_input);
if (path)
snd_hda_activate_path(codec, path, true, false);
} else {
/* disable normal mic path */
path = snd_hda_get_path_from_idx(codec, cur_input);
if (path)
snd_hda_activate_path(codec, path, false, false);
/* 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.
*/
if (spec->recording)
val = olpc_xo_dc_bias.items[spec->dc_input_bias].index;
else
val = 0;
update_mic_pin(codec, 0x1a, val);
update_mic_pin(codec, 0x1b, 0);
/* enable DC bias path and pin */
update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0);
snd_hda_activate_path(codec, spec->dc_mode_path, true, false);
}
}
/* mic_autoswitch hook */
static void olpc_xo_automic(struct hda_codec *codec, struct hda_jack_tbl *jack)
{
struct conexant_spec *spec = codec->spec;
int saved_cached_write = codec->cached_write;
codec->cached_write = 1;
/* in DC mode, we don't handle automic */
if (!spec->dc_enable)
snd_hda_gen_mic_autoswitch(codec, jack);
olpc_xo_update_mic_pins(codec);
snd_hda_codec_flush_cache(codec);
codec->cached_write = saved_cached_write;
if (spec->dc_enable)
olpc_xo_update_mic_boost(codec);
}
/* pcm_capture hook */
static void olpc_xo_capture_hook(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
struct snd_pcm_substream *substream,
int action)
{
struct conexant_spec *spec = codec->spec;
/* toggle spec->recording flag and update mic pins accordingly
* for turning on/off LED
*/
switch (action) {
case HDA_GEN_PCM_ACT_PREPARE:
spec->recording = 1;
olpc_xo_update_mic_pins(codec);
break;
case HDA_GEN_PCM_ACT_CLEANUP:
spec->recording = 0;
olpc_xo_update_mic_pins(codec);
break;
}
}
static int olpc_xo_dc_mode_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;
}
static int olpc_xo_dc_mode_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;
olpc_xo_update_mic_pins(codec);
olpc_xo_update_mic_boost(codec);
return 1;
}
static int olpc_xo_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 olpc_xo_dc_bias_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
return snd_hda_input_mux_info(&olpc_xo_dc_bias, uinfo);
}
static int olpc_xo_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 = &olpc_xo_dc_bias;
unsigned int idx;
idx = ucontrol->value.enumerated.item[0];
if (idx >= imux->num_items)
idx = imux->num_items - 1;
if (spec->dc_input_bias == idx)
return 0;
spec->dc_input_bias = idx;
if (spec->dc_enable)
olpc_xo_update_mic_pins(codec);
return 1;
}
static const struct snd_kcontrol_new olpc_xo_mixers[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "DC Mode Enable Switch",
.info = snd_ctl_boolean_mono_info,
.get = olpc_xo_dc_mode_get,
.put = olpc_xo_dc_mode_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "DC Input Bias Enum",
.info = olpc_xo_dc_bias_enum_info,
.get = olpc_xo_dc_bias_enum_get,
.put = olpc_xo_dc_bias_enum_put,
},
{}
};
/* overriding mic boost put callback; update mic boost volume only when
* DC mode is disabled
*/
static int olpc_xo_mic_boost_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 ret = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol);
if (ret > 0 && spec->dc_enable)
olpc_xo_update_mic_boost(codec);
return ret;
}
static void cxt_fixup_olpc_xo(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
struct conexant_spec *spec = codec->spec;
int i;
if (action != HDA_FIXUP_ACT_PROBE)
return;
spec->gen.mic_autoswitch_hook = olpc_xo_automic;
spec->gen.pcm_capture_hook = olpc_xo_capture_hook;
spec->dc_mode_path = snd_hda_add_new_path(codec, 0x1e, 0x14, 0);
snd_hda_add_new_ctls(codec, olpc_xo_mixers);
/* OLPC's microphone port is DC coupled for use with external sensors,
* therefore we use a 50% mic bias in order to center the input signal
* with the DC input range of the codec.
*/
snd_hda_codec_set_pin_target(codec, 0x1a, PIN_VREF50);
/* override mic boost control */
for (i = 0; i < spec->gen.kctls.used; i++) {
struct snd_kcontrol_new *kctl =
snd_array_elem(&spec->gen.kctls, i);
if (!strcmp(kctl->name, "Mic Boost Volume")) {
kctl->put = olpc_xo_mic_boost_put;
break;
}
}
}
/* ThinkPad X200 & co with cxt5051 */
static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = {
@ -3400,6 +3289,10 @@ static const struct hda_fixup cxt_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = hda_fixup_thinkpad_acpi,
},
[CXT_FIXUP_OLPC_XO] = {
.type = HDA_FIXUP_FUNC,
.v.func = cxt_fixup_olpc_xo,
},
};
static const struct snd_pci_quirk cxt5051_fixups[] = {
@ -3416,6 +3309,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = {
SND_PCI_QUIRK(0x1025, 0x0543, "Acer Aspire One 522", CXT_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_GPIO1),
SND_PCI_QUIRK(0x1043, 0x138d, "Asus", CXT_FIXUP_HEADPHONE_MIC_PIN),
SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT_FIXUP_OLPC_XO),
SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo T400", CXT_PINCFG_LENOVO_TP410),
SND_PCI_QUIRK(0x17aa, 0x215e, "Lenovo T410", CXT_PINCFG_LENOVO_TP410),
SND_PCI_QUIRK(0x17aa, 0x215f, "Lenovo T510", CXT_PINCFG_LENOVO_TP410),
@ -3439,6 +3333,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = {
{ .id = CXT_PINCFG_LENOVO_TP410, .name = "tp410" },
{ .id = CXT_FIXUP_THINKPAD_ACPI, .name = "thinkpad" },
{ .id = CXT_PINCFG_LEMOTE_A1004, .name = "lemote-a1004" },
{ .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" },
{}
};