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:
parent
c91925db49
commit
f2e5731dfd
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue