diff options
Diffstat (limited to 'sound/pci/hda/patch_conexant.c')
| -rw-r--r-- | sound/pci/hda/patch_conexant.c | 592 | 
1 files changed, 494 insertions, 98 deletions
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index c578c28f368..194a28c5499 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -42,10 +42,12 @@  /* Conexant 5051 specific */ -#define CXT5051_SPDIF_OUT	0x1C +#define CXT5051_SPDIF_OUT	0x12  #define CXT5051_PORTB_EVENT	0x38  #define CXT5051_PORTC_EVENT	0x39 +#define AUTO_MIC_PORTB		(1 << 1) +#define AUTO_MIC_PORTC		(1 << 2)  struct conexant_jack { @@ -74,7 +76,7 @@ struct conexant_spec {  					 */  	unsigned int cur_eapd;  	unsigned int hp_present; -	unsigned int no_auto_mic; +	unsigned int auto_mic;  	unsigned int need_dac_fix;  	/* capture */ @@ -111,8 +113,23 @@ struct conexant_spec {  	unsigned int dell_automute;  	unsigned int port_d_mode; -	unsigned char ext_mic_bias; -	unsigned int dell_vostro; +	unsigned int dell_vostro:1; +	unsigned int ideapad: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 */  };  static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo, @@ -185,6 +202,8 @@ 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; @@ -196,6 +215,8 @@ 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;  } @@ -1585,6 +1606,11 @@ static void cxt5051_update_speaker(struct hda_codec *codec)  {  	struct conexant_spec *spec = codec->spec;  	unsigned int pinctl; +	/* headphone pin */ +	pinctl = (spec->hp_present && spec->cur_eapd) ? PIN_HP : 0; +	snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, +			    pinctl); +	/* speaker pin */  	pinctl = (!spec->hp_present && spec->cur_eapd) ? PIN_OUT : 0;  	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,  			    pinctl); @@ -1608,7 +1634,7 @@ static void cxt5051_portb_automic(struct hda_codec *codec)  	struct conexant_spec *spec = codec->spec;  	unsigned int present; -	if (spec->no_auto_mic) +	if (!(spec->auto_mic & AUTO_MIC_PORTB))  		return;  	present = snd_hda_jack_detect(codec, 0x17);  	snd_hda_codec_write(codec, 0x14, 0, @@ -1623,7 +1649,7 @@ static void cxt5051_portc_automic(struct hda_codec *codec)  	unsigned int present;  	hda_nid_t new_adc; -	if (spec->no_auto_mic) +	if (!(spec->auto_mic & AUTO_MIC_PORTC))  		return;  	present = snd_hda_jack_detect(codec, 0x18);  	if (present) @@ -1669,13 +1695,7 @@ static void cxt5051_hp_unsol_event(struct hda_codec *codec,  	conexant_report_jack(codec, nid);  } -static struct snd_kcontrol_new cxt5051_mixers[] = { -	HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT), -	HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT), -	HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT), -	HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT), -	HDA_CODEC_VOLUME("Docking Mic Volume", 0x15, 0x00, HDA_INPUT), -	HDA_CODEC_MUTE("Docking Mic Switch", 0x15, 0x00, HDA_INPUT), +static struct snd_kcontrol_new cxt5051_playback_mixers[] = {  	HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),  	{  		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -1685,7 +1705,16 @@ static struct snd_kcontrol_new cxt5051_mixers[] = {  		.put = cxt5051_hp_master_sw_put,  		.private_value = 0x1a,  	}, +	{} +}; +static struct snd_kcontrol_new cxt5051_capture_mixers[] = { +	HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT), +	HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT), +	HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT), +	HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT), +	HDA_CODEC_VOLUME("Docking Mic Volume", 0x15, 0x00, HDA_INPUT), +	HDA_CODEC_MUTE("Docking Mic Switch", 0x15, 0x00, HDA_INPUT),  	{}  }; @@ -1694,32 +1723,26 @@ static struct snd_kcontrol_new cxt5051_hp_mixers[] = {  	HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT),  	HDA_CODEC_VOLUME("External Mic Volume", 0x15, 0x00, HDA_INPUT),  	HDA_CODEC_MUTE("External Mic Switch", 0x15, 0x00, HDA_INPUT), -	HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT), -	{ -		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, -		.name = "Master Playback Switch", -		.info = cxt_eapd_info, -		.get = cxt_eapd_get, -		.put = cxt5051_hp_master_sw_put, -		.private_value = 0x1a, -	}, -  	{}  };  static struct snd_kcontrol_new cxt5051_hp_dv6736_mixers[] = { -	HDA_CODEC_VOLUME("Mic Volume", 0x14, 0x00, HDA_INPUT), -	HDA_CODEC_MUTE("Mic Switch", 0x14, 0x00, HDA_INPUT), -	HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT), -	{ -		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, -		.name = "Master Playback Switch", -		.info = cxt_eapd_info, -		.get = cxt_eapd_get, -		.put = cxt5051_hp_master_sw_put, -		.private_value = 0x1a, -	}, +	HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x00, HDA_INPUT), +	HDA_CODEC_MUTE("Capture Switch", 0x14, 0x00, HDA_INPUT), +	{} +}; + +static struct snd_kcontrol_new cxt5051_f700_mixers[] = { +	HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x01, HDA_INPUT), +	HDA_CODEC_MUTE("Capture Switch", 0x14, 0x01, HDA_INPUT), +	{} +}; +static struct snd_kcontrol_new cxt5051_toshiba_mixers[] = { +	HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT), +	HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT), +	HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT), +	HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT),  	{}  }; @@ -1748,8 +1771,6 @@ static struct hda_verb cxt5051_init_verbs[] = {  	/* EAPD */  	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */   	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT}, -	{0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT}, -	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTC_EVENT},  	{ } /* end */  }; @@ -1775,7 +1796,6 @@ static struct hda_verb cxt5051_hp_dv6736_init_verbs[] = {  	/* EAPD */  	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */  	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT}, -	{0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT},  	{ } /* end */  }; @@ -1807,17 +1827,60 @@ static struct hda_verb cxt5051_lenovo_x200_init_verbs[] = {  	/* EAPD */  	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */  	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT}, -	{0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT}, -	{0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTC_EVENT},  	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},  	{ } /* end */  }; +static struct hda_verb cxt5051_f700_init_verbs[] = { +	/* Line in, Mic */ +	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03}, +	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, +	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0}, +	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0}, +	/* SPK  */ +	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, +	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00}, +	/* HP, Amp  */ +	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, +	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00}, +	/* DAC1 */ +	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, +	/* Record selector: Int mic */ +	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44}, +	{0x14, AC_VERB_SET_CONNECT_SEL, 0x1}, +	/* SPDIF route: PCM */ +	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x0}, +	/* EAPD */ +	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ +	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT}, +	{ } /* end */ +}; + +static void cxt5051_init_mic_port(struct hda_codec *codec, hda_nid_t nid, +				 unsigned int event) +{ +	snd_hda_codec_write(codec, nid, 0, +			    AC_VERB_SET_UNSOLICITED_ENABLE, +			    AC_USRSP_EN | event); +#ifdef CONFIG_SND_HDA_INPUT_JACK +	conexant_add_jack(codec, nid, SND_JACK_MICROPHONE); +	conexant_report_jack(codec, nid); +#endif +} +  /* initialize jack-sensing, too */  static int cxt5051_init(struct hda_codec *codec)  { +	struct conexant_spec *spec = codec->spec; +  	conexant_init(codec);  	conexant_init_jacks(codec); + +	if (spec->auto_mic & AUTO_MIC_PORTB) +		cxt5051_init_mic_port(codec, 0x17, CXT5051_PORTB_EVENT); +	if (spec->auto_mic & AUTO_MIC_PORTC) +		cxt5051_init_mic_port(codec, 0x18, CXT5051_PORTC_EVENT); +  	if (codec->patch_ops.unsol_event) {  		cxt5051_hp_automute(codec);  		cxt5051_portb_automic(codec); @@ -1832,6 +1895,8 @@ enum {  	CXT5051_HP,	/* no docking */  	CXT5051_HP_DV6736,	/* HP without mic switch */  	CXT5051_LENOVO_X200,	/* Lenovo X200 laptop */ +	CXT5051_F700,       /* HP Compaq Presario F700 */ +	CXT5051_TOSHIBA,	/* Toshiba M300 & co */  	CXT5051_MODELS  }; @@ -1840,11 +1905,15 @@ static const char *cxt5051_models[CXT5051_MODELS] = {  	[CXT5051_HP]		= "hp",  	[CXT5051_HP_DV6736]	= "hp-dv6736",  	[CXT5051_LENOVO_X200]	= "lenovo-x200", +	[CXT5051_F700]          = "hp-700", +	[CXT5051_TOSHIBA]	= "toshiba",  };  static struct snd_pci_quirk cxt5051_cfg_tbl[] = {  	SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV6736", CXT5051_HP_DV6736),  	SND_PCI_QUIRK(0x103c, 0x360b, "Compaq Presario CQ60", CXT5051_HP), +	SND_PCI_QUIRK(0x103c, 0x30ea, "Compaq Presario F700", CXT5051_F700), +	SND_PCI_QUIRK(0x1179, 0xff50, "Toshiba M30x", CXT5051_TOSHIBA),  	SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board",  		      CXT5051_LAPTOP),  	SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP), @@ -1872,8 +1941,9 @@ static int patch_cxt5051(struct hda_codec *codec)  	spec->multiout.dig_out_nid = CXT5051_SPDIF_OUT;  	spec->num_adc_nids = 1; /* not 2; via auto-mic switch */  	spec->adc_nids = cxt5051_adc_nids; -	spec->num_mixers = 1; -	spec->mixers[0] = cxt5051_mixers; +	spec->num_mixers = 2; +	spec->mixers[0] = cxt5051_capture_mixers; +	spec->mixers[1] = cxt5051_playback_mixers;  	spec->num_init_verbs = 1;  	spec->init_verbs[0] = cxt5051_init_verbs;  	spec->spdif_route = 0; @@ -1887,6 +1957,7 @@ static int patch_cxt5051(struct hda_codec *codec)  	board_config = snd_hda_check_board_config(codec, CXT5051_MODELS,  						  cxt5051_models,  						  cxt5051_cfg_tbl); +	spec->auto_mic = AUTO_MIC_PORTB | AUTO_MIC_PORTC;  	switch (board_config) {  	case CXT5051_HP:  		spec->mixers[0] = cxt5051_hp_mixers; @@ -1894,11 +1965,20 @@ static int patch_cxt5051(struct hda_codec *codec)  	case CXT5051_HP_DV6736:  		spec->init_verbs[0] = cxt5051_hp_dv6736_init_verbs;  		spec->mixers[0] = cxt5051_hp_dv6736_mixers; -		spec->no_auto_mic = 1; +		spec->auto_mic = 0;  		break;  	case CXT5051_LENOVO_X200:  		spec->init_verbs[0] = cxt5051_lenovo_x200_init_verbs;  		break; +	case CXT5051_F700: +		spec->init_verbs[0] = cxt5051_f700_init_verbs; +		spec->mixers[0] = cxt5051_f700_mixers; +		spec->auto_mic = 0; +		break; +	case CXT5051_TOSHIBA: +		spec->mixers[0] = cxt5051_toshiba_mixers; +		spec->auto_mic = AUTO_MIC_PORTB; +		break;  	}  	return 0; @@ -1966,33 +2046,117 @@ 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 + * 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_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); + +	/* internal mic, port C */ +	snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, +		spec->ext_mic_present ? 0 : PIN_VREF80); +} +  /* toggle input of built-in and mic jack appropriately */ -static void cxt5066_automic(struct hda_codec *codec) +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) +{ +	unsigned int present; +  	struct hda_verb ext_mic_present[] = {  		/* enable external mic, port B */ -		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, spec->ext_mic_bias}, +		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},  		/* switch to external mic input */  		{0x17, AC_VERB_SET_CONNECT_SEL, 0}, +		{0x14, AC_VERB_SET_CONNECT_SEL, 0}, -		/* disable internal mic, port C */ -		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, +		/* disable internal digital mic */ +		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},  		{}  	};  	static struct hda_verb ext_mic_absent[] = {  		/* enable internal mic, port C */ -		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, +		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},  		/* switch to internal mic input */ -		{0x17, AC_VERB_SET_CONNECT_SEL, 1}, +		{0x14, AC_VERB_SET_CONNECT_SEL, 2},  		/* disable external mic, port B */  		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},  		{}  	}; -	unsigned int present;  	present = snd_hda_jack_detect(codec, 0x1a);  	if (present) { @@ -2005,36 +2169,24 @@ static void cxt5066_automic(struct hda_codec *codec)  }  /* toggle input of built-in digital mic and mic jack appropriately */ -static void cxt5066_vostro_automic(struct hda_codec *codec) +static void cxt5066_ideapad_automic(struct hda_codec *codec)  { -	struct conexant_spec *spec = codec->spec;  	unsigned int present;  	struct hda_verb ext_mic_present[] = { -		/* enable external mic, port B */ -		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, spec->ext_mic_bias}, - -		/* switch to external mic input */ -		{0x17, AC_VERB_SET_CONNECT_SEL, 0},  		{0x14, AC_VERB_SET_CONNECT_SEL, 0}, - -		/* disable internal digital mic */ +		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},  		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},  		{}  	};  	static struct hda_verb ext_mic_absent[] = { -		/* enable internal mic, port C */ -		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, - -		/* switch to internal mic input */  		{0x14, AC_VERB_SET_CONNECT_SEL, 2}, - -		/* disable external mic, port B */ -		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, +		{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, +		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},  		{}  	}; -	present = snd_hda_jack_detect(codec, 0x1a); +	present = snd_hda_jack_detect(codec, 0x1b);  	if (present) {  		snd_printdd("CXT5066: external microphone detected\n");  		snd_hda_sequence_write(codec, ext_mic_present); @@ -2063,15 +2215,18 @@ static void cxt5066_hp_automute(struct hda_codec *codec)  }  /* unsolicited event for jack sensing */ -static void cxt5066_unsol_event(struct hda_codec *codec, unsigned int res) +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_automic(codec); +		/* ignore mic events in DC mode; we're always using the jack */ +		if (!spec->dc_enable) +			cxt5066_olpc_automic(codec);  		break;  	}  } @@ -2090,6 +2245,20 @@ static void cxt5066_vostro_event(struct hda_codec *codec, unsigned int res)  	}  } +/* unsolicited event for jack sensing */ +static void cxt5066_ideapad_event(struct hda_codec *codec, unsigned int res) +{ +	snd_printdd("CXT5066_ideapad: 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_ideapad_automic(codec); +		break; +	} +} +  static const struct hda_input_mux cxt5066_analog_mic_boost = {  	.num_items = 5,  	.items = { @@ -2101,6 +2270,23 @@ static const struct hda_input_mux cxt5066_analog_mic_boost = {  	},  }; +static void cxt5066_set_mic_boost(struct hda_codec *codec) +{ +	struct conexant_spec *spec = codec->spec; +	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); +	if (spec->ideapad) { +		/* adjust the internal mic as well...it is not through 0x17 */ +		snd_hda_codec_write_cache(codec, 0x23, 0, +			AC_VERB_SET_AMP_GAIN_MUTE, +			AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_INPUT | +				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)  { @@ -2111,15 +2297,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;  } @@ -2127,26 +2306,132 @@ 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); +} -	if (!imux->num_items) +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; -	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;  } +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 struct hda_input_mux cxt5066_capture_source = {  	.num_items = 4,  	.items = { @@ -2187,6 +2472,7 @@ static struct snd_kcontrol_new cxt5066_mixer_master_olpc[] = {  		.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, @@ -2198,6 +2484,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, @@ -2210,11 +2514,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), @@ -2296,10 +2599,10 @@ static struct hda_verb cxt5066_init_verbs_olpc[] = {  	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */  	/* Port B: external microphone */ -	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, CXT5066_OLPC_EXT_MIC_BIAS}, +	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},  	/* Port C: internal microphone */ -	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, +	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},  	/* Port D: unused */  	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, @@ -2308,7 +2611,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 */ @@ -2412,6 +2715,56 @@ static struct hda_verb cxt5066_init_verbs_vostro[] = {  	{ } /* end */  }; +static struct hda_verb cxt5066_init_verbs_ideapad[] = { +	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port B */ +	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port C */ +	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */ +	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */ + +	/* Speakers  */ +	{0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, +	{0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + +	/* HP, Amp  */ +	{0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, +	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + +	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, +	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + +	/* DAC1 */ +	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + +	/* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */ +	{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_UNMUTE(2) | 0x50}, +	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, +	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, +	{0x14, AC_VERB_SET_CONNECT_SEL, 2},	/* default to internal mic */ + +	/* Audio input selector */ +	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x2}, +	{0x17, AC_VERB_SET_CONNECT_SEL, 1},	/* route ext mic */ + +	/* SPDIF route: PCM */ +	{0x20, AC_VERB_SET_CONNECT_SEL, 0x0}, +	{0x22, AC_VERB_SET_CONNECT_SEL, 0x0}, + +	{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, +	{0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + +	/* internal microphone */ +	{0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* enable int mic */ + +	/* EAPD */ +	{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ + +	{0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, +	{0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT}, +	{ } /* end */ +}; +  static struct hda_verb cxt5066_init_verbs_portd_lo[] = {  	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},  	{ } /* end */ @@ -2428,8 +2781,24 @@ static int cxt5066_init(struct hda_codec *codec)  		cxt5066_hp_automute(codec);  		if (spec->dell_vostro)  			cxt5066_vostro_automic(codec); -		else -			cxt5066_automic(codec); +		else if (spec->ideapad) +			cxt5066_ideapad_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); +	if (!spec->dc_enable) { +		cxt5066_set_mic_boost(codec); +		cxt5066_olpc_automic(codec); +	} else { +		cxt5066_enable_dc(codec);  	}  	return 0;  } @@ -2439,6 +2808,7 @@ enum {  	CXT5066_DELL_LAPTOP,	/* Dell Laptop */  	CXT5066_OLPC_XO_1_5,	/* OLPC XO 1.5 */  	CXT5066_DELL_VOSTO,	/* Dell Vostro 1015i */ +	CXT5066_IDEAPAD,	/* Lenovo IdeaPad U150 */  	CXT5066_MODELS  }; @@ -2446,7 +2816,8 @@ static const char *cxt5066_models[CXT5066_MODELS] = {  	[CXT5066_LAPTOP]		= "laptop",  	[CXT5066_DELL_LAPTOP]	= "dell-laptop",  	[CXT5066_OLPC_XO_1_5]	= "olpc-xo-1_5", -	[CXT5066_DELL_VOSTO]    = "dell-vostro" +	[CXT5066_DELL_VOSTO]    = "dell-vostro", +	[CXT5066_IDEAPAD]	= "ideapad",  };  static struct snd_pci_quirk cxt5066_cfg_tbl[] = { @@ -2456,6 +2827,7 @@ static struct snd_pci_quirk cxt5066_cfg_tbl[] = {  		      CXT5066_DELL_LAPTOP),  	SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT5066_OLPC_XO_1_5),  	SND_PCI_QUIRK(0x1028, 0x0402, "Dell Vostro", CXT5066_DELL_VOSTO), +	SND_PCI_QUIRK(0x17aa, 0x3a0d, "ideapad", CXT5066_IDEAPAD),  	{}  }; @@ -2470,7 +2842,7 @@ static int patch_cxt5066(struct hda_codec *codec)  	codec->spec = spec;  	codec->patch_ops = conexant_patch_ops; -	codec->patch_ops.init = cxt5066_init; +	codec->patch_ops.init = conexant_init;  	spec->dell_automute = 0;  	spec->multiout.max_channels = 2; @@ -2483,7 +2855,6 @@ static int patch_cxt5066(struct hda_codec *codec)  	spec->input_mux = &cxt5066_capture_source;  	spec->port_d_mode = PIN_HP; -	spec->ext_mic_bias = PIN_VREF80;  	spec->num_init_verbs = 1;  	spec->init_verbs[0] = cxt5066_init_verbs; @@ -2510,20 +2881,28 @@ static int patch_cxt5066(struct hda_codec *codec)  		spec->dell_automute = 1;  		break;  	case CXT5066_OLPC_XO_1_5: -		codec->patch_ops.unsol_event = cxt5066_unsol_event; +		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->ext_mic_bias = CXT5066_OLPC_EXT_MIC_BIAS; +		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_VOSTO: +		codec->patch_ops.init = cxt5066_init;  		codec->patch_ops.unsol_event = cxt5066_vostro_event;  		spec->init_verbs[0] = cxt5066_init_verbs_vostro;  		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc; @@ -2531,6 +2910,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 */ @@ -2539,6 +2919,22 @@ static int patch_cxt5066(struct hda_codec *codec)  		/* input source automatically selected */  		spec->input_mux = NULL;  		break; +	case CXT5066_IDEAPAD: +		codec->patch_ops.init = cxt5066_init; +		codec->patch_ops.unsol_event = cxt5066_ideapad_event; +		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master; +		spec->mixers[spec->num_mixers++] = cxt5066_mixers; +		spec->init_verbs[0] = cxt5066_init_verbs_ideapad; +		spec->port_d_mode = 0; +		spec->ideapad = 1; +		spec->mic_boost = 2;	/* default 20dB gain */ + +		/* no S/PDIF out */ +		spec->multiout.dig_out_nid = 0; + +		/* input source automatically selected */ +		spec->input_mux = NULL; +		break;  	}  	return 0;  |