diff options
Diffstat (limited to 'sound/pci/hda/patch_conexant.c')
| -rw-r--r-- | sound/pci/hda/patch_conexant.c | 186 | 
1 files changed, 116 insertions, 70 deletions
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index d906c5b74cf..3acb5824ad3 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -30,6 +30,7 @@  #include "hda_codec.h"  #include "hda_local.h" +#include "hda_auto_parser.h"  #include "hda_beep.h"  #include "hda_jack.h" @@ -66,6 +67,7 @@ struct imux_info {  };  struct conexant_spec { +	struct hda_gen_spec gen;  	const struct snd_kcontrol_new *mixers[5];  	int num_mixers; @@ -141,6 +143,7 @@ struct conexant_spec {  	unsigned int hp_laptop:1;  	unsigned int asus:1;  	unsigned int pin_eapd_ctrls:1; +	unsigned int fixup_stereo_dmic:1;  	unsigned int adc_switching:1; @@ -1601,17 +1604,13 @@ static void cxt5051_update_speaker(struct hda_codec *codec)  	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); +	snd_hda_set_pin_ctl(codec, 0x16, 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); +	snd_hda_set_pin_ctl(codec, 0x1a, pinctl);  	/* on ideapad there is an additional speaker (subwoofer) to mute */  	if (spec->ideapad) -		snd_hda_codec_write(codec, 0x1b, 0, -				    AC_VERB_SET_PIN_WIDGET_CONTROL, -				    pinctl); +		snd_hda_set_pin_ctl(codec, 0x1b, pinctl);  }  /* turn on/off EAPD (+ mute HP) as a master switch */ @@ -1996,8 +1995,7 @@ static void cxt5066_update_speaker(struct hda_codec *codec)  	/* Port A (HP) */  	pinctl = (hp_port_a_present(spec) && spec->cur_eapd) ? PIN_HP : 0; -	snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, -			pinctl); +	snd_hda_set_pin_ctl(codec, 0x19, pinctl);  	/* Port D (HP/LO) */  	pinctl = spec->cur_eapd ? spec->port_d_mode : 0; @@ -2010,13 +2008,11 @@ static void cxt5066_update_speaker(struct hda_codec *codec)  		if (!hp_port_d_present(spec))  			pinctl = 0;  	} -	snd_hda_codec_write(codec, 0x1c, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, -			pinctl); +	snd_hda_set_pin_ctl(codec, 0x1c, pinctl);  	/* CLASS_D AMP */  	pinctl = (!spec->hp_present && spec->cur_eapd) ? PIN_OUT : 0; -	snd_hda_codec_write(codec, 0x1f, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, -			pinctl); +	snd_hda_set_pin_ctl(codec, 0x1f, pinctl);  }  /* turn on/off EAPD (+ mute HP) as a master switch */ @@ -2047,8 +2043,7 @@ static int cxt5066_set_olpc_dc_bias(struct hda_codec *codec)  	/* 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, +	return snd_hda_set_pin_ctl_cache(codec, 0x1a,  		cxt5066_olpc_dc_bias.items[spec->dc_input_bias].index);  } @@ -2081,14 +2076,14 @@ static void cxt5066_olpc_select_mic(struct hda_codec *codec)  	}  	/* disable DC (port F) */ -	snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0); +	snd_hda_set_pin_ctl(codec, 0x1e, 0);  	/* external mic, port B */ -	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, +	snd_hda_set_pin_ctl(codec, 0x1a,  		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, +	snd_hda_set_pin_ctl(codec, 0x1b,  		spec->ext_mic_present ? 0 : PIN_VREF80);  } @@ -3357,9 +3352,7 @@ static void do_automute(struct hda_codec *codec, int num_pins,  	struct conexant_spec *spec = codec->spec;  	int i;  	for (i = 0; i < num_pins; i++) -		snd_hda_codec_write(codec, pins[i], 0, -				    AC_VERB_SET_PIN_WIDGET_CONTROL, -				    on ? PIN_OUT : 0); +		snd_hda_set_pin_ctl(codec, pins[i], on ? PIN_OUT : 0);  	if (spec->pin_eapd_ctrls)  		cx_auto_turn_eapd(codec, num_pins, pins, on);  } @@ -3976,8 +3969,7 @@ static void cx_auto_init_output(struct hda_codec *codec)  		if (snd_hda_query_pin_caps(codec, cfg->hp_pins[i]) &  		    AC_PINCAP_HP_DRV)  			val |= AC_PINCTL_HP_EN; -		snd_hda_codec_write(codec, cfg->hp_pins[i], 0, -				    AC_VERB_SET_PIN_WIDGET_CONTROL, val); +		snd_hda_set_pin_ctl(codec, cfg->hp_pins[i], val);  	}  	mute_outputs(codec, cfg->hp_outs, cfg->hp_pins);  	mute_outputs(codec, cfg->line_outs, cfg->line_out_pins); @@ -4030,13 +4022,11 @@ static void cx_auto_init_input(struct hda_codec *codec)  	}  	for (i = 0; i < cfg->num_inputs; i++) { -		unsigned int type; +		hda_nid_t pin = cfg->inputs[i].pin; +		unsigned int type = PIN_IN;  		if (cfg->inputs[i].type == AUTO_PIN_MIC) -			type = PIN_VREF80; -		else -			type = PIN_IN; -		snd_hda_codec_write(codec, cfg->inputs[i].pin, 0, -				    AC_VERB_SET_PIN_WIDGET_CONTROL, type); +			type |= snd_hda_get_default_vref(codec, pin); +		snd_hda_set_pin_ctl(codec, pin, type);  	}  	if (spec->auto_mic) { @@ -4063,11 +4053,9 @@ static void cx_auto_init_digital(struct hda_codec *codec)  	struct auto_pin_cfg *cfg = &spec->autocfg;  	if (spec->multiout.dig_out_nid) -		snd_hda_codec_write(codec, cfg->dig_out_pins[0], 0, -				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); +		snd_hda_set_pin_ctl(codec, cfg->dig_out_pins[0], PIN_OUT);  	if (spec->dig_in_nid) -		snd_hda_codec_write(codec, cfg->dig_in_pin, 0, -				    AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN); +		snd_hda_set_pin_ctl(codec, cfg->dig_in_pin, PIN_IN);  }  static int cx_auto_init(struct hda_codec *codec) @@ -4084,9 +4072,9 @@ static int cx_auto_init(struct hda_codec *codec)  static int cx_auto_add_volume_idx(struct hda_codec *codec, const char *basename,  			      const char *dir, int cidx, -			      hda_nid_t nid, int hda_dir, int amp_idx) +			      hda_nid_t nid, int hda_dir, int amp_idx, int chs)  { -	static char name[32]; +	static char name[44];  	static struct snd_kcontrol_new knew[] = {  		HDA_CODEC_VOLUME(name, 0, 0, 0),  		HDA_CODEC_MUTE(name, 0, 0, 0), @@ -4096,7 +4084,7 @@ static int cx_auto_add_volume_idx(struct hda_codec *codec, const char *basename,  	for (i = 0; i < 2; i++) {  		struct snd_kcontrol *kctl; -		knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, 3, amp_idx, +		knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, chs, amp_idx,  							    hda_dir);  		knew[i].subdevice = HDA_SUBDEV_AMP_FLAG;  		knew[i].index = cidx; @@ -4115,7 +4103,7 @@ static int cx_auto_add_volume_idx(struct hda_codec *codec, const char *basename,  }  #define cx_auto_add_volume(codec, str, dir, cidx, nid, hda_dir)		\ -	cx_auto_add_volume_idx(codec, str, dir, cidx, nid, hda_dir, 0) +	cx_auto_add_volume_idx(codec, str, dir, cidx, nid, hda_dir, 0, 3)  #define cx_auto_add_pb_volume(codec, nid, str, idx)			\  	cx_auto_add_volume(codec, str, " Playback", idx, nid, HDA_OUTPUT) @@ -4185,6 +4173,36 @@ static int cx_auto_build_output_controls(struct hda_codec *codec)  	return 0;  } +/* Returns zero if this is a normal stereo channel, and non-zero if it should +   be split in two independent channels. +   dest_label must be at least 44 characters. */ +static int cx_auto_get_rightch_label(struct hda_codec *codec, const char *label, +				     char *dest_label, int nid) +{ +	struct conexant_spec *spec = codec->spec; +	int i; + +	if (!spec->fixup_stereo_dmic) +		return 0; + +	for (i = 0; i < AUTO_CFG_MAX_INS; i++) { +		int def_conf; +		if (spec->autocfg.inputs[i].pin != nid) +			continue; + +		if (spec->autocfg.inputs[i].type != AUTO_PIN_MIC) +			return 0; +		def_conf = snd_hda_codec_get_pincfg(codec, nid); +		if (snd_hda_get_input_pin_attr(def_conf) != INPUT_PIN_ATTR_INT) +			return 0; + +		/* Finally found the inverted internal mic! */ +		snprintf(dest_label, 44, "Inverted %s", label); +		return 1; +	} +	return 0; +} +  static int cx_auto_add_capture_volume(struct hda_codec *codec, hda_nid_t nid,  				      const char *label, const char *pfx,  				      int cidx) @@ -4193,14 +4211,25 @@ static int cx_auto_add_capture_volume(struct hda_codec *codec, hda_nid_t nid,  	int i;  	for (i = 0; i < spec->num_adc_nids; i++) { +		char rightch_label[44];  		hda_nid_t adc_nid = spec->adc_nids[i];  		int idx = get_input_connection(codec, adc_nid, nid);  		if (idx < 0)  			continue;  		if (codec->single_adc_amp)  			idx = 0; + +		if (cx_auto_get_rightch_label(codec, label, rightch_label, nid)) { +			/* Make two independent kcontrols for left and right */ +			int err = cx_auto_add_volume_idx(codec, label, pfx, +					      cidx, adc_nid, HDA_INPUT, idx, 1); +			if (err < 0) +				return err; +			return cx_auto_add_volume_idx(codec, rightch_label, pfx, +						      cidx, adc_nid, HDA_INPUT, idx, 2); +		}  		return cx_auto_add_volume_idx(codec, label, pfx, -					      cidx, adc_nid, HDA_INPUT, idx); +					      cidx, adc_nid, HDA_INPUT, idx, 3);  	}  	return 0;  } @@ -4213,9 +4242,19 @@ static int cx_auto_add_boost_volume(struct hda_codec *codec, int idx,  	int i, con;  	nid = spec->imux_info[idx].pin; -	if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP) +	if (get_wcaps(codec, nid) & AC_WCAP_IN_AMP) { +		char rightch_label[44]; +		if (cx_auto_get_rightch_label(codec, label, rightch_label, nid)) { +			int err = cx_auto_add_volume_idx(codec, label, " Boost", +							 cidx, nid, HDA_INPUT, 0, 1); +			if (err < 0) +				return err; +			return cx_auto_add_volume_idx(codec, rightch_label, " Boost", +						      cidx, nid, HDA_INPUT, 0, 2); +		}  		return cx_auto_add_volume(codec, label, " Boost", cidx,  					  nid, HDA_INPUT); +	}  	con = __select_input_connection(codec, spec->imux_info[idx].adc, nid,  					&mux, false, 0);  	if (con < 0) @@ -4370,37 +4409,21 @@ static const struct hda_codec_ops cx_auto_patch_ops = {  /*   * pin fix-up   */ -struct cxt_pincfg { -	hda_nid_t nid; -	u32 val; -}; - -static void apply_pincfg(struct hda_codec *codec, const struct cxt_pincfg *cfg) -{ -	for (; cfg->nid; cfg++) -		snd_hda_codec_set_pincfg(codec, cfg->nid, cfg->val); - -} - -static void apply_pin_fixup(struct hda_codec *codec, -			    const struct snd_pci_quirk *quirk, -			    const struct cxt_pincfg **table) -{ -	quirk = snd_pci_quirk_lookup(codec->bus->pci, quirk); -	if (quirk) { -		snd_printdd(KERN_INFO "hda_codec: applying pincfg for %s\n", -			    quirk->name); -		apply_pincfg(codec, table[quirk->value]); -	} -} -  enum {  	CXT_PINCFG_LENOVO_X200,  	CXT_PINCFG_LENOVO_TP410, +	CXT_FIXUP_STEREO_DMIC,  }; +static void cxt_fixup_stereo_dmic(struct hda_codec *codec, +				  const struct hda_fixup *fix, int action) +{ +	struct conexant_spec *spec = codec->spec; +	spec->fixup_stereo_dmic = 1; +} +  /* ThinkPad X200 & co with cxt5051 */ -static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = { +static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = {  	{ 0x16, 0x042140ff }, /* HP (seq# overridden) */  	{ 0x17, 0x21a11000 }, /* dock-mic */  	{ 0x19, 0x2121103f }, /* dock-HP */ @@ -4409,16 +4432,26 @@ static const struct cxt_pincfg cxt_pincfg_lenovo_x200[] = {  };  /* ThinkPad 410/420/510/520, X201 & co with cxt5066 */ -static const struct cxt_pincfg cxt_pincfg_lenovo_tp410[] = { +static const struct hda_pintbl cxt_pincfg_lenovo_tp410[] = {  	{ 0x19, 0x042110ff }, /* HP (seq# overridden) */  	{ 0x1a, 0x21a190f0 }, /* dock-mic */  	{ 0x1c, 0x212140ff }, /* dock-HP */  	{}  }; -static const struct cxt_pincfg *cxt_pincfg_tbl[] = { -	[CXT_PINCFG_LENOVO_X200] = cxt_pincfg_lenovo_x200, -	[CXT_PINCFG_LENOVO_TP410] = cxt_pincfg_lenovo_tp410, +static const struct hda_fixup cxt_fixups[] = { +	[CXT_PINCFG_LENOVO_X200] = { +		.type = HDA_FIXUP_PINS, +		.v.pins = cxt_pincfg_lenovo_x200, +	}, +	[CXT_PINCFG_LENOVO_TP410] = { +		.type = HDA_FIXUP_PINS, +		.v.pins = cxt_pincfg_lenovo_tp410, +	}, +	[CXT_FIXUP_STEREO_DMIC] = { +		.type = HDA_FIXUP_FUNC, +		.v.func = cxt_fixup_stereo_dmic, +	},  };  static const struct snd_pci_quirk cxt5051_fixups[] = { @@ -4432,6 +4465,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = {  	SND_PCI_QUIRK(0x17aa, 0x215f, "Lenovo T510", CXT_PINCFG_LENOVO_TP410),  	SND_PCI_QUIRK(0x17aa, 0x21ce, "Lenovo T420", CXT_PINCFG_LENOVO_TP410),  	SND_PCI_QUIRK(0x17aa, 0x21cf, "Lenovo T520", CXT_PINCFG_LENOVO_TP410), +	SND_PCI_QUIRK(0x17aa, 0x3975, "Lenovo U300s", CXT_FIXUP_STEREO_DMIC),  	{}  }; @@ -4471,13 +4505,16 @@ static int patch_conexant_auto(struct hda_codec *codec)  	case 0x14f15051:  		add_cx5051_fake_mutes(codec);  		codec->pin_amp_workaround = 1; -		apply_pin_fixup(codec, cxt5051_fixups, cxt_pincfg_tbl); +		snd_hda_pick_fixup(codec, NULL, cxt5051_fixups, cxt_fixups);  		break;  	default:  		codec->pin_amp_workaround = 1; -		apply_pin_fixup(codec, cxt5066_fixups, cxt_pincfg_tbl); +		snd_hda_pick_fixup(codec, NULL, cxt5066_fixups, cxt_fixups); +		break;  	} +	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); +  	/* Show mute-led control only on HP laptops  	 * This is a sort of white-list: on HP laptops, EAPD corresponds  	 * only to the mute-LED without actualy amp function.  Meanwhile, @@ -4556,6 +4593,12 @@ static const struct hda_codec_preset snd_hda_preset_conexant[] = {  	  .patch = patch_conexant_auto },  	{ .id = 0x14f150b9, .name = "CX20665",  	  .patch = patch_conexant_auto }, +	{ .id = 0x14f1510f, .name = "CX20751/2", +	  .patch = patch_conexant_auto }, +	{ .id = 0x14f15110, .name = "CX20751/2", +	  .patch = patch_conexant_auto }, +	{ .id = 0x14f15111, .name = "CX20753/4", +	  .patch = patch_conexant_auto },  	{} /* terminator */  }; @@ -4576,6 +4619,9 @@ MODULE_ALIAS("snd-hda-codec-id:14f150ab");  MODULE_ALIAS("snd-hda-codec-id:14f150ac");  MODULE_ALIAS("snd-hda-codec-id:14f150b8");  MODULE_ALIAS("snd-hda-codec-id:14f150b9"); +MODULE_ALIAS("snd-hda-codec-id:14f1510f"); +MODULE_ALIAS("snd-hda-codec-id:14f15110"); +MODULE_ALIAS("snd-hda-codec-id:14f15111");  MODULE_LICENSE("GPL");  MODULE_DESCRIPTION("Conexant HD-audio codec");  |