ALSA: hda - Preliminary support for new Conexant audio codecs

This patch adds the preliminary support for new Conexant audio codecs with
14f1:5097, 14f1:5098, 14f1:50a1, 14f1:50a2, 14f1:50ab, 14f1:50ac,
14f1:50b8 and 14f1:50b9.

Unlike other Conexant parsers, this is designed to be mostly automatic,
parsing from BIOS pin configurations.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2010-09-15 10:07:08 +02:00
parent c91925db49
commit f2e5731dfd
1 changed files with 639 additions and 1 deletions

View File

@ -57,6 +57,12 @@ struct conexant_jack {
};
struct pin_dac_pair {
hda_nid_t pin;
hda_nid_t dac;
int type;
};
struct conexant_spec {
struct snd_kcontrol_new *mixers[5];
@ -77,6 +83,7 @@ struct conexant_spec {
unsigned int cur_eapd;
unsigned int hp_present;
unsigned int auto_mic;
int auto_mic_ext; /* autocfg.inputs[] index for ext mic */
unsigned int need_dac_fix;
/* capture */
@ -110,9 +117,12 @@ struct conexant_spec {
struct auto_pin_cfg autocfg;
struct hda_input_mux private_imux;
hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
struct pin_dac_pair dac_info[8];
int dac_info_filled;
unsigned int dell_automute;
unsigned int port_d_mode;
unsigned int auto_mute:1; /* used in auto-parser */
unsigned int dell_automute:1;
unsigned int dell_vostro:1;
unsigned int ideapad:1;
unsigned int thinkpad:1;
@ -3253,6 +3263,610 @@ static int patch_cxt5066(struct hda_codec *codec)
return 0;
}
/*
* Automatic parser for CX20641 & co
*/
static hda_nid_t cx_auto_adc_nids[] = { 0x14 };
/* get the connection index of @nid in the widget @mux */
static int get_connection_index(struct hda_codec *codec, hda_nid_t mux,
hda_nid_t nid)
{
hda_nid_t conn[HDA_MAX_NUM_INPUTS];
int i, nums;
nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
for (i = 0; i < nums; i++)
if (conn[i] == nid)
return i;
return -1;
}
/* get an unassigned DAC from the given list.
* Return the nid if found and reduce the DAC list, or return zero if
* not found
*/
static hda_nid_t get_unassigned_dac(struct hda_codec *codec, hda_nid_t pin,
hda_nid_t *dacs, int *num_dacs)
{
int i, nums = *num_dacs;
hda_nid_t ret = 0;
for (i = 0; i < nums; i++) {
if (get_connection_index(codec, pin, dacs[i]) >= 0) {
ret = dacs[i];
break;
}
}
if (!ret)
return 0;
if (--nums > 0)
memmove(dacs, dacs + 1, nums * sizeof(hda_nid_t));
*num_dacs = nums;
return ret;
}
#define MAX_AUTO_DACS 5
/* fill analog DAC list from the widget tree */
static int fill_cx_auto_dacs(struct hda_codec *codec, hda_nid_t *dacs)
{
hda_nid_t nid, end_nid;
int nums = 0;
end_nid = codec->start_nid + codec->num_nodes;
for (nid = codec->start_nid; nid < end_nid; nid++) {
unsigned int wcaps = get_wcaps(codec, nid);
unsigned int type = get_wcaps_type(wcaps);
if (type == AC_WID_AUD_OUT && !(wcaps & AC_WCAP_DIGITAL)) {
dacs[nums++] = nid;
if (nums >= MAX_AUTO_DACS)
break;
}
}
return nums;
}
/* fill pin_dac_pair list from the pin and dac list */
static int fill_dacs_for_pins(struct hda_codec *codec, hda_nid_t *pins,
int num_pins, hda_nid_t *dacs, int *rest,
struct pin_dac_pair *filled, int type)
{
int i, nums;
nums = 0;
for (i = 0; i < num_pins; i++) {
filled[nums].pin = pins[i];
filled[nums].type = type;
filled[nums].dac = get_unassigned_dac(codec, pins[i], dacs, rest);
nums++;
}
return nums;
}
/* parse analog output paths */
static void cx_auto_parse_output(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
hda_nid_t dacs[MAX_AUTO_DACS];
int i, j, nums, rest;
rest = fill_cx_auto_dacs(codec, dacs);
/* parse all analog output pins */
nums = fill_dacs_for_pins(codec, cfg->line_out_pins, cfg->line_outs,
dacs, &rest, spec->dac_info,
AUTO_PIN_LINE_OUT);
nums += fill_dacs_for_pins(codec, cfg->hp_pins, cfg->hp_outs,
dacs, &rest, spec->dac_info + nums,
AUTO_PIN_HP_OUT);
nums += fill_dacs_for_pins(codec, cfg->speaker_pins, cfg->speaker_outs,
dacs, &rest, spec->dac_info + nums,
AUTO_PIN_SPEAKER_OUT);
spec->dac_info_filled = nums;
/* fill multiout struct */
for (i = 0; i < nums; i++) {
hda_nid_t dac = spec->dac_info[i].dac;
if (!dac)
continue;
switch (spec->dac_info[i].type) {
case AUTO_PIN_LINE_OUT:
spec->private_dac_nids[spec->multiout.num_dacs] = dac;
spec->multiout.num_dacs++;
break;
case AUTO_PIN_HP_OUT:
case AUTO_PIN_SPEAKER_OUT:
if (!spec->multiout.hp_nid) {
spec->multiout.hp_nid = dac;
break;
}
for (j = 0; j < ARRAY_SIZE(spec->multiout.extra_out_nid); j++)
if (!spec->multiout.extra_out_nid[j]) {
spec->multiout.extra_out_nid[j] = dac;
break;
}
break;
}
}
spec->multiout.dac_nids = spec->private_dac_nids;
spec->multiout.max_channels = nums * 2;
if (cfg->hp_outs > 0)
spec->auto_mute = 1;
spec->vmaster_nid = spec->private_dac_nids[0];
}
/* auto-mute/unmute speaker and line outs according to headphone jack */
static void cx_auto_hp_automute(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
int i, present;
if (!spec->auto_mute)
return;
present = 0;
for (i = 0; i < cfg->hp_outs; i++) {
if (snd_hda_jack_detect(codec, cfg->hp_pins[i])) {
present = 1;
break;
}
}
for (i = 0; i < cfg->line_outs; i++) {
snd_hda_codec_write(codec, cfg->line_out_pins[i], 0,
AC_VERB_SET_PIN_WIDGET_CONTROL,
present ? 0 : PIN_OUT);
}
for (i = 0; i < cfg->speaker_outs; i++) {
snd_hda_codec_write(codec, cfg->speaker_pins[i], 0,
AC_VERB_SET_PIN_WIDGET_CONTROL,
present ? 0 : PIN_OUT);
}
}
/* automatic switch internal and external mic */
static void cx_auto_automic(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
struct hda_input_mux *imux = &spec->private_imux;
int ext_idx = spec->auto_mic_ext;
if (!spec->auto_mic)
return;
if (snd_hda_jack_detect(codec, cfg->inputs[ext_idx].pin)) {
snd_hda_codec_write(codec, spec->adc_nids[0], 0,
AC_VERB_SET_CONNECT_SEL,
imux->items[ext_idx].index);
} else {
snd_hda_codec_write(codec, spec->adc_nids[0], 0,
AC_VERB_SET_CONNECT_SEL,
imux->items[!ext_idx].index);
}
}
static void cx_auto_unsol_event(struct hda_codec *codec, unsigned int res)
{
int nid = (res & AC_UNSOL_RES_SUBTAG) >> 20;
switch (res >> 26) {
case CONEXANT_HP_EVENT:
cx_auto_hp_automute(codec);
conexant_report_jack(codec, nid);
break;
case CONEXANT_MIC_EVENT:
cx_auto_automic(codec);
conexant_report_jack(codec, nid);
break;
}
}
static int is_int_mic_conn(unsigned int def_conf)
{
unsigned int loc = get_defcfg_location(def_conf);
return get_defcfg_connect(def_conf) == AC_JACK_PORT_FIXED ||
(loc & 0x30) == AC_JACK_LOC_INTERNAL;
}
/* return true if it's an internal-mic pin */
static int is_int_mic(struct hda_codec *codec, hda_nid_t pin)
{
unsigned int def_conf = snd_hda_codec_get_pincfg(codec, pin);
return get_defcfg_device(def_conf) == AC_JACK_MIC_IN &&
is_int_mic_conn(def_conf);
}
/* return true if it's an external-mic pin */
static int is_ext_mic(struct hda_codec *codec, hda_nid_t pin)
{
unsigned int def_conf = snd_hda_codec_get_pincfg(codec, pin);
return get_defcfg_device(def_conf) == AC_JACK_MIC_IN &&
!is_int_mic_conn(def_conf);
}
/* check whether the pin config is suitable for auto-mic switching;
* auto-mic is enabled only when one int-mic and one-ext mic exist
*/
static void cx_auto_check_auto_mic(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
if (is_ext_mic(codec, cfg->inputs[0].pin) &&
is_int_mic(codec, cfg->inputs[1].pin)) {
spec->auto_mic = 1;
spec->auto_mic_ext = 1;
return;
}
if (is_int_mic(codec, cfg->inputs[1].pin) &&
is_ext_mic(codec, cfg->inputs[0].pin)) {
spec->auto_mic = 1;
spec->auto_mic_ext = 0;
return;
}
}
static void cx_auto_parse_input(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
struct hda_input_mux *imux;
int i;
imux = &spec->private_imux;
for (i = 0; i < cfg->num_inputs; i++) {
int idx = get_connection_index(codec, spec->adc_nids[0],
cfg->inputs[i].pin);
if (idx >= 0) {
const char *label;
label = hda_get_autocfg_input_label(codec, cfg, i);
snd_hda_add_imux_item(imux, label, idx, NULL);
}
}
if (imux->num_items == 2 && cfg->num_inputs == 2)
cx_auto_check_auto_mic(codec);
if (imux->num_items > 1 && !spec->auto_mic)
spec->input_mux = imux;
}
/* get digital-input audio widget corresponding to the given pin */
static hda_nid_t cx_auto_get_dig_in(struct hda_codec *codec, hda_nid_t pin)
{
hda_nid_t nid, end_nid;
end_nid = codec->start_nid + codec->num_nodes;
for (nid = codec->start_nid; nid < end_nid; nid++) {
unsigned int wcaps = get_wcaps(codec, nid);
unsigned int type = get_wcaps_type(wcaps);
if (type == AC_WID_AUD_IN && (wcaps & AC_WCAP_DIGITAL)) {
if (get_connection_index(codec, nid, pin) >= 0)
return nid;
}
}
return 0;
}
static void cx_auto_parse_digital(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
hda_nid_t nid;
if (cfg->dig_outs &&
snd_hda_get_connections(codec, cfg->dig_out_pins[0], &nid, 1) == 1)
spec->multiout.dig_out_nid = nid;
if (cfg->dig_in_pin)
spec->dig_in_nid = cx_auto_get_dig_in(codec, cfg->dig_in_pin);
}
#ifdef CONFIG_SND_HDA_INPUT_BEEP
static void cx_auto_parse_beep(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
hda_nid_t nid, end_nid;
end_nid = codec->start_nid + codec->num_nodes;
for (nid = codec->start_nid; nid < end_nid; nid++)
if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) {
set_beep_amp(spec, nid, 0, HDA_OUTPUT);
break;
}
}
#else
#define cx_auto_parse_beep(codec)
#endif
static int cx_auto_parse_auto_config(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
int err;
err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
if (err < 0)
return err;
cx_auto_parse_output(codec);
cx_auto_parse_input(codec);
cx_auto_parse_digital(codec);
cx_auto_parse_beep(codec);
return 0;
}
static void cx_auto_turn_on_eapd(struct hda_codec *codec, int num_pins,
hda_nid_t *pins)
{
int i;
for (i = 0; i < num_pins; i++) {
if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD)
snd_hda_codec_write(codec, pins[i], 0,
AC_VERB_SET_EAPD_BTLENABLE, 0x02);
}
}
static void select_connection(struct hda_codec *codec, hda_nid_t pin,
hda_nid_t src)
{
int idx = get_connection_index(codec, pin, src);
if (idx >= 0)
snd_hda_codec_write(codec, pin, 0,
AC_VERB_SET_CONNECT_SEL, idx);
}
static void cx_auto_init_output(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
hda_nid_t nid;
int i;
for (i = 0; i < spec->multiout.num_dacs; i++)
snd_hda_codec_write(codec, spec->multiout.dac_nids[i], 0,
AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
for (i = 0; i < cfg->hp_outs; i++)
snd_hda_codec_write(codec, cfg->hp_pins[i], 0,
AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP);
if (spec->auto_mute) {
for (i = 0; i < cfg->hp_outs; i++) {
snd_hda_codec_write(codec, cfg->hp_pins[i], 0,
AC_VERB_SET_UNSOLICITED_ENABLE,
AC_USRSP_EN | CONEXANT_HP_EVENT);
}
cx_auto_hp_automute(codec);
} else {
for (i = 0; i < cfg->line_outs; i++)
snd_hda_codec_write(codec, cfg->line_out_pins[i], 0,
AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
for (i = 0; i < cfg->speaker_outs; i++)
snd_hda_codec_write(codec, cfg->speaker_pins[i], 0,
AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
}
for (i = 0; i < spec->dac_info_filled; i++) {
nid = spec->dac_info[i].dac;
if (!nid)
nid = spec->multiout.dac_nids[0];
select_connection(codec, spec->dac_info[i].pin, nid);
}
/* turn on EAPD */
cx_auto_turn_on_eapd(codec, cfg->line_outs, cfg->line_out_pins);
cx_auto_turn_on_eapd(codec, cfg->hp_outs, cfg->hp_pins);
cx_auto_turn_on_eapd(codec, cfg->speaker_outs, cfg->speaker_pins);
}
static void cx_auto_init_input(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
int i;
for (i = 0; i < spec->num_adc_nids; i++)
snd_hda_codec_write(codec, spec->adc_nids[i], 0,
AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0));
for (i = 0; i < cfg->num_inputs; i++) {
unsigned int type;
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);
}
if (spec->auto_mic) {
int ext_idx = spec->auto_mic_ext;
snd_hda_codec_write(codec, cfg->inputs[ext_idx].pin, 0,
AC_VERB_SET_UNSOLICITED_ENABLE,
AC_USRSP_EN | CONEXANT_MIC_EVENT);
cx_auto_automic(codec);
} else {
for (i = 0; i < spec->num_adc_nids; i++) {
snd_hda_codec_write(codec, spec->adc_nids[i], 0,
AC_VERB_SET_CONNECT_SEL,
spec->private_imux.items[0].index);
}
}
}
static void cx_auto_init_digital(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
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);
if (spec->dig_in_nid)
snd_hda_codec_write(codec, cfg->dig_in_pin, 0,
AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN);
}
static int cx_auto_init(struct hda_codec *codec)
{
/*snd_hda_sequence_write(codec, cx_auto_init_verbs);*/
cx_auto_init_output(codec);
cx_auto_init_input(codec);
cx_auto_init_digital(codec);
return 0;
}
static int cx_auto_add_volume(struct hda_codec *codec, const char *basename,
const char *dir, int cidx,
hda_nid_t nid, int hda_dir)
{
static char name[32];
static struct snd_kcontrol_new knew[] = {
HDA_CODEC_VOLUME(name, 0, 0, 0),
HDA_CODEC_MUTE(name, 0, 0, 0),
};
static char *sfx[2] = { "Volume", "Switch" };
int i, err;
for (i = 0; i < 2; i++) {
struct snd_kcontrol *kctl;
knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, 3, 0, hda_dir);
knew[i].subdevice = HDA_SUBDEV_AMP_FLAG;
knew[i].index = cidx;
snprintf(name, sizeof(name), "%s%s %s", basename, dir, sfx[i]);
kctl = snd_ctl_new1(&knew[i], codec);
if (!kctl)
return -ENOMEM;
err = snd_hda_ctl_add(codec, nid, kctl);
if (err < 0)
return err;
if (!(query_amp_caps(codec, nid, hda_dir) & AC_AMPCAP_MUTE))
break;
}
return 0;
}
#define cx_auto_add_pb_volume(codec, nid, str, idx) \
cx_auto_add_volume(codec, str, " Playback", idx, nid, HDA_OUTPUT)
static int cx_auto_build_output_controls(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
int i, err;
int num_line = 0, num_hp = 0, num_spk = 0;
static const char *texts[3] = { "Front", "Surround", "CLFE" };
if (spec->dac_info_filled == 1)
return cx_auto_add_pb_volume(codec, spec->dac_info[0].dac,
"Master", 0);
for (i = 0; i < spec->dac_info_filled; i++) {
const char *label;
int idx, type;
if (!spec->dac_info[i].dac)
continue;
type = spec->dac_info[i].type;
if (type == AUTO_PIN_LINE_OUT)
type = spec->autocfg.line_out_type;
switch (type) {
case AUTO_PIN_LINE_OUT:
default:
label = texts[num_line++];
idx = 0;
break;
case AUTO_PIN_HP_OUT:
label = "Headphone";
idx = num_hp++;
break;
case AUTO_PIN_SPEAKER_OUT:
label = "Speaker";
idx = num_spk++;
break;
}
err = cx_auto_add_pb_volume(codec, spec->dac_info[i].dac,
label, idx);
if (err < 0)
return err;
}
return 0;
}
static int cx_auto_build_input_controls(struct hda_codec *codec)
{
struct conexant_spec *spec = codec->spec;
struct auto_pin_cfg *cfg = &spec->autocfg;
static const char *prev_label;
int i, err, cidx;
err = cx_auto_add_volume(codec, "Capture", "", 0, spec->adc_nids[0],
HDA_INPUT);
if (err < 0)
return err;
prev_label = NULL;
cidx = 0;
for (i = 0; i < cfg->num_inputs; i++) {
hda_nid_t nid = cfg->inputs[i].pin;
const char *label;
if (!(get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
continue;
label = hda_get_autocfg_input_label(codec, cfg, i);
if (label == prev_label)
cidx++;
else
cidx = 0;
prev_label = label;
err = cx_auto_add_volume(codec, label, " Capture", cidx,
nid, HDA_INPUT);
if (err < 0)
return err;
}
return 0;
}
static int cx_auto_build_controls(struct hda_codec *codec)
{
int err;
err = cx_auto_build_output_controls(codec);
if (err < 0)
return err;
err = cx_auto_build_input_controls(codec);
if (err < 0)
return err;
return conexant_build_controls(codec);
}
static struct hda_codec_ops cx_auto_patch_ops = {
.build_controls = cx_auto_build_controls,
.build_pcms = conexant_build_pcms,
.init = cx_auto_init,
.free = conexant_free,
.unsol_event = cx_auto_unsol_event,
#ifdef CONFIG_SND_HDA_POWER_SAVE
.suspend = conexant_suspend,
#endif
.reboot_notify = snd_hda_shutup_pins,
};
static int patch_conexant_auto(struct hda_codec *codec)
{
struct conexant_spec *spec;
int err;
spec = kzalloc(sizeof(*spec), GFP_KERNEL);
if (!spec)
return -ENOMEM;
codec->spec = spec;
spec->adc_nids = cx_auto_adc_nids;
spec->num_adc_nids = ARRAY_SIZE(cx_auto_adc_nids);
spec->capsrc_nids = spec->adc_nids;
err = cx_auto_parse_auto_config(codec);
if (err < 0) {
kfree(codec->spec);
codec->spec = NULL;
return err;
}
codec->patch_ops = cx_auto_patch_ops;
if (spec->beep_amp)
snd_hda_attach_beep_device(codec, spec->beep_amp);
return 0;
}
/*
*/
@ -3271,6 +3885,22 @@ static struct hda_codec_preset snd_hda_preset_conexant[] = {
.patch = patch_cxt5066 },
{ .id = 0x14f15069, .name = "CX20585",
.patch = patch_cxt5066 },
{ .id = 0x14f15097, .name = "CX20631",
.patch = patch_conexant_auto },
{ .id = 0x14f15098, .name = "CX20632",
.patch = patch_conexant_auto },
{ .id = 0x14f150a1, .name = "CX20641",
.patch = patch_conexant_auto },
{ .id = 0x14f150a2, .name = "CX20642",
.patch = patch_conexant_auto },
{ .id = 0x14f150ab, .name = "CX20651",
.patch = patch_conexant_auto },
{ .id = 0x14f150ac, .name = "CX20652",
.patch = patch_conexant_auto },
{ .id = 0x14f150b8, .name = "CX20664",
.patch = patch_conexant_auto },
{ .id = 0x14f150b9, .name = "CX20665",
.patch = patch_conexant_auto },
{} /* terminator */
};
@ -3281,6 +3911,14 @@ MODULE_ALIAS("snd-hda-codec-id:14f15066");
MODULE_ALIAS("snd-hda-codec-id:14f15067");
MODULE_ALIAS("snd-hda-codec-id:14f15068");
MODULE_ALIAS("snd-hda-codec-id:14f15069");
MODULE_ALIAS("snd-hda-codec-id:14f15097");
MODULE_ALIAS("snd-hda-codec-id:14f15098");
MODULE_ALIAS("snd-hda-codec-id:14f150a1");
MODULE_ALIAS("snd-hda-codec-id:14f150a2");
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_LICENSE("GPL");
MODULE_DESCRIPTION("Conexant HD-audio codec");