summaryrefslogtreecommitdiff
path: root/sound/pci/hda/patch_conexant.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/hda/patch_conexant.c')
-rw-r--r--sound/pci/hda/patch_conexant.c186
1 files changed, 116 insertions, 70 deletions
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
index d906c5b74cf0..3acb5824ad39 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");