From 23c09b00900c3fa6672148738cad29d6fc6ded7c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 19 Aug 2011 09:05:35 +0200 Subject: ALSA: hda - Support multiple speakers by Realtek auto-parser Add the support of multiple speakers by Realtek auto-parser. When all speaker pins have individual DACs, create each speaker volume control. Otherwise, create a bind-volume control for all speaker outs. Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 196 +++++++++++++++++++++++++++++++++++------- 1 file changed, 164 insertions(+), 32 deletions(-) (limited to 'sound/pci/hda/patch_realtek.c') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index d330e971743..e0ecf5a5b09 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -203,6 +203,9 @@ struct alc_spec { /* multi-io */ int multi_ios; struct alc_multi_io multi_io[4]; + + /* bind volumes */ + struct snd_array bind_ctls; }; #define ALC_MODEL_AUTO 0 /* common for all chips */ @@ -2369,6 +2372,18 @@ static void alc_free_kctls(struct hda_codec *codec) snd_array_free(&spec->kctls); } +static void alc_free_bind_ctls(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + if (spec->bind_ctls.list) { + struct hda_bind_ctls **ctl = spec->bind_ctls.list; + int i; + for (i = 0; i < spec->bind_ctls.used; i++) + kfree(ctl[i]); + } + snd_array_free(&spec->bind_ctls); +} + static void alc_free(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; @@ -2379,6 +2394,7 @@ static void alc_free(struct hda_codec *codec) alc_shutup(codec); snd_hda_input_jack_free(codec); alc_free_kctls(codec); + alc_free_bind_ctls(codec); kfree(spec); snd_hda_detach_beep_device(codec); } @@ -2449,11 +2465,15 @@ enum { ALC_CTL_WIDGET_VOL, ALC_CTL_WIDGET_MUTE, ALC_CTL_BIND_MUTE, + ALC_CTL_BIND_VOL, + ALC_CTL_BIND_SW, }; static const struct snd_kcontrol_new alc_control_templates[] = { HDA_CODEC_VOLUME(NULL, 0, 0, 0), HDA_CODEC_MUTE(NULL, 0, 0, 0), HDA_BIND_MUTE(NULL, 0, 0, 0), + HDA_BIND_VOL(NULL, 0), + HDA_BIND_SW(NULL, 0), }; /* add dynamic controls */ @@ -2494,13 +2514,14 @@ static int add_control_with_pfx(struct alc_spec *spec, int type, #define __add_pb_sw_ctrl(spec, type, pfx, cidx, val) \ add_control_with_pfx(spec, type, pfx, "Playback", "Switch", cidx, val) +static const char * const channel_name[4] = { + "Front", "Surround", "CLFE", "Side" +}; + static const char *alc_get_line_out_pfx(struct alc_spec *spec, int ch, bool can_be_master, int *index) { struct auto_pin_cfg *cfg = &spec->autocfg; - static const char * const chname[4] = { - "Front", "Surround", NULL /*CLFE*/, "Side" - }; *index = 0; if (cfg->line_outs == 1 && !spec->multi_ios && @@ -2523,7 +2544,10 @@ static const char *alc_get_line_out_pfx(struct alc_spec *spec, int ch, return "PCM"; break; } - return chname[ch]; + if (snd_BUG_ON(ch >= ARRAY_SIZE(channel_name))) + return "PCM"; + + return channel_name[ch]; } /* create input playback/capture controls for the given pin */ @@ -2869,6 +2893,28 @@ static int alc_auto_fill_dac_nids(struct hda_codec *codec) return 0; } +/* fill in the dac_nids table for surround speakers, etc */ +static int alc_auto_fill_extra_dacs(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + const struct auto_pin_cfg *cfg = &spec->autocfg; + int i; + + if (cfg->speaker_outs < 2 || !spec->multiout.extra_out_nid[0]) + return 0; + + for (i = 1; i < cfg->speaker_outs; i++) + spec->multiout.extra_out_nid[i] = + get_dac_if_single(codec, cfg->speaker_pins[i]); + for (i = 1; i < cfg->speaker_outs; i++) { + if (spec->multiout.extra_out_nid[i]) + continue; + spec->multiout.extra_out_nid[i] = + alc_auto_look_for_dac(codec, cfg->speaker_pins[0]); + } + return 0; +} + static int alc_auto_add_vol_ctl(struct hda_codec *codec, const char *pfx, int cidx, hda_nid_t nid, unsigned int chs) @@ -2991,16 +3037,13 @@ static int alc_auto_create_multi_out_ctls(struct hda_codec *codec, return 0; } -/* add playback controls for speaker and HP outputs */ static int alc_auto_create_extra_out(struct hda_codec *codec, hda_nid_t pin, - hda_nid_t dac, const char *pfx) + hda_nid_t dac, const char *pfx) { struct alc_spec *spec = codec->spec; hda_nid_t sw, vol; int err; - if (!pin) - return 0; if (!dac) { /* the corresponding DAC is already occupied */ if (!(get_wcaps(codec, pin) & AC_WCAP_OUT_AMP)) @@ -3021,6 +3064,92 @@ static int alc_auto_create_extra_out(struct hda_codec *codec, hda_nid_t pin, return 0; } +static struct hda_bind_ctls *new_bind_ctl(struct hda_codec *codec, + unsigned int nums, + struct hda_ctl_ops *ops) +{ + struct alc_spec *spec = codec->spec; + struct hda_bind_ctls **ctlp, *ctl; + snd_array_init(&spec->bind_ctls, sizeof(ctl), 8); + ctlp = snd_array_new(&spec->bind_ctls); + if (!ctlp) + return NULL; + ctl = kzalloc(sizeof(*ctl) + sizeof(long) * (nums + 1), GFP_KERNEL); + *ctlp = ctl; + if (ctl) + ctl->ops = ops; + return ctl; +} + +/* add playback controls for speaker and HP outputs */ +static int alc_auto_create_extra_outs(struct hda_codec *codec, int num_pins, + const hda_nid_t *pins, + const hda_nid_t *dacs, + const char *pfx) +{ + struct alc_spec *spec = codec->spec; + struct hda_bind_ctls *ctl; + char name[32]; + int i, n, err; + + if (!num_pins || !pins[0]) + return 0; + + if (num_pins == 1) + return alc_auto_create_extra_out(codec, *pins, *dacs, pfx); + + if (dacs[num_pins - 1]) { + /* OK, we have a multi-output system with individual volumes */ + for (i = 0; i < num_pins; i++) { + snprintf(name, sizeof(name), "%s %s", + pfx, channel_name[i]); + err = alc_auto_create_extra_out(codec, pins[i], dacs[i], + name); + if (err < 0) + return err; + } + return 0; + } + + /* Let's create a bind-controls */ + ctl = new_bind_ctl(codec, num_pins, &snd_hda_bind_sw); + if (!ctl) + return -ENOMEM; + n = 0; + for (i = 0; i < num_pins; i++) { + if (get_wcaps(codec, pins[i]) & AC_WCAP_OUT_AMP) + ctl->values[n++] = + HDA_COMPOSE_AMP_VAL(pins[i], 3, 0, HDA_OUTPUT); + } + if (n) { + snprintf(name, sizeof(name), "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_BIND_SW, name, 0, (long)ctl); + if (err < 0) + return err; + } + + ctl = new_bind_ctl(codec, num_pins, &snd_hda_bind_vol); + if (!ctl) + return -ENOMEM; + n = 0; + for (i = 0; i < num_pins; i++) { + hda_nid_t vol; + if (!pins[i] || !dacs[i]) + continue; + vol = alc_look_for_out_vol_nid(codec, pins[i], dacs[i]); + if (vol) + ctl->values[n++] = + HDA_COMPOSE_AMP_VAL(vol, 3, 0, HDA_OUTPUT); + } + if (n) { + snprintf(name, sizeof(name), "%s Playback Volume", pfx); + err = add_control(spec, ALC_CTL_BIND_VOL, name, 0, (long)ctl); + if (err < 0) + return err; + } + return 0; +} + static int alc_auto_create_hp_out(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; @@ -3032,9 +3161,10 @@ static int alc_auto_create_hp_out(struct hda_codec *codec) static int alc_auto_create_speaker_out(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; - return alc_auto_create_extra_out(codec, spec->autocfg.speaker_pins[0], - spec->multiout.extra_out_nid[0], - "Speaker"); + return alc_auto_create_extra_outs(codec, spec->autocfg.speaker_outs, + spec->autocfg.speaker_pins, + spec->multiout.extra_out_nid, + "Speaker"); } static void alc_auto_set_output_and_unmute(struct hda_codec *codec, @@ -3225,27 +3355,13 @@ static const struct snd_kcontrol_new alc_auto_channel_mode_enum = { .put = alc_auto_ch_mode_put, }; -static int alc_auto_add_multi_channel_mode(struct hda_codec *codec, - int (*fill_dac)(struct hda_codec *)) +static int alc_auto_add_multi_channel_mode(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; unsigned int location, defcfg; int num_pins; - if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT && cfg->hp_outs == 1) { - /* use HP as primary out */ - cfg->speaker_outs = cfg->line_outs; - memcpy(cfg->speaker_pins, cfg->line_out_pins, - sizeof(cfg->speaker_pins)); - cfg->line_outs = cfg->hp_outs; - memcpy(cfg->line_out_pins, cfg->hp_pins, sizeof(cfg->hp_pins)); - cfg->hp_outs = 0; - memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins)); - cfg->line_out_type = AUTO_PIN_HP_OUT; - if (fill_dac) - fill_dac(codec); - } if (cfg->line_outs != 1 || cfg->line_out_type == AUTO_PIN_SPEAKER_OUT) return 0; @@ -3550,27 +3666,43 @@ static int alc_parse_auto_config(struct hda_codec *codec, const hda_nid_t *ssid_nids) { struct alc_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; int err; - err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, - ignore_nids); + err = snd_hda_parse_pin_def_config(codec, cfg, ignore_nids); if (err < 0) return err; - if (!spec->autocfg.line_outs) { - if (spec->autocfg.dig_outs || spec->autocfg.dig_in_pin) { + if (!cfg->line_outs) { + if (cfg->dig_outs || cfg->dig_in_pin) { spec->multiout.max_channels = 2; spec->no_analog = 1; goto dig_only; } return 0; /* can't find valid BIOS pin config */ } + + if (cfg->line_out_type == AUTO_PIN_SPEAKER_OUT && cfg->hp_outs == 1) { + /* use HP as primary out */ + cfg->speaker_outs = cfg->line_outs; + memcpy(cfg->speaker_pins, cfg->line_out_pins, + sizeof(cfg->speaker_pins)); + cfg->line_outs = cfg->hp_outs; + memcpy(cfg->line_out_pins, cfg->hp_pins, sizeof(cfg->hp_pins)); + cfg->hp_outs = 0; + memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins)); + cfg->line_out_type = AUTO_PIN_HP_OUT; + } + err = alc_auto_fill_dac_nids(codec); if (err < 0) return err; - err = alc_auto_add_multi_channel_mode(codec, alc_auto_fill_dac_nids); + err = alc_auto_add_multi_channel_mode(codec); + if (err < 0) + return err; + err = alc_auto_fill_extra_dacs(codec); if (err < 0) return err; - err = alc_auto_create_multi_out_ctls(codec, &spec->autocfg); + err = alc_auto_create_multi_out_ctls(codec, cfg); if (err < 0) return err; err = alc_auto_create_hp_out(codec); -- cgit v1.2.3