ALSA: hda - Implement independent HP control
authorTakashi Iwai <tiwai@suse.de>
Fri, 21 Dec 2012 13:09:42 +0000 (14:09 +0100)
committerTakashi Iwai <tiwai@suse.de>
Sat, 12 Jan 2013 07:42:56 +0000 (08:42 +0100)
Similar like the implementation in patch_analog.c and patch_via.c,
the generic parser can provide the independent HP PCM stream now.
It's enabled when spec->indep_hp is set by the caller while parsing.

Currently no dynamic PCM switching as in patch_via.c is implemented
yet.  The control returns -EBUSY when the value is changed during PCM
operations.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/hda_generic.c
sound/pci/hda/hda_generic.h

index f3c6ace..ff15aea 100644 (file)
@@ -41,6 +41,7 @@ int snd_hda_gen_spec_init(struct hda_gen_spec *spec)
        snd_array_init(&spec->kctls, sizeof(struct snd_kcontrol_new), 32);
        snd_array_init(&spec->bind_ctls, sizeof(struct hda_bind_ctls *), 8);
        snd_array_init(&spec->paths, sizeof(struct nid_path), 8);
+       mutex_init(&spec->pcm_mutex);
        return 0;
 }
 EXPORT_SYMBOL_HDA(snd_hda_gen_spec_init);
@@ -1484,6 +1485,79 @@ static int create_speaker_out_ctls(struct hda_codec *codec)
                                 "Speaker");
 }
 
+/*
+ * independent HP controls
+ */
+
+static int indep_hp_info(struct snd_kcontrol *kcontrol,
+                        struct snd_ctl_elem_info *uinfo)
+{
+       return snd_hda_enum_bool_helper_info(kcontrol, uinfo);
+}
+
+static int indep_hp_get(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct hda_gen_spec *spec = codec->spec;
+       ucontrol->value.enumerated.item[0] = spec->indep_hp_enabled;
+       return 0;
+}
+
+static int indep_hp_put(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct hda_gen_spec *spec = codec->spec;
+       unsigned int select = ucontrol->value.enumerated.item[0];
+       int ret = 0;
+
+       mutex_lock(&spec->pcm_mutex);
+       if (spec->active_streams) {
+               ret = -EBUSY;
+               goto unlock;
+       }
+
+       if (spec->indep_hp_enabled != select) {
+               spec->indep_hp_enabled = select;
+               if (spec->indep_hp_enabled)
+                       spec->multiout.hp_out_nid[0] = 0;
+               else
+                       spec->multiout.hp_out_nid[0] = spec->alt_dac_nid;
+               ret = 1;
+       }
+ unlock:
+       mutex_unlock(&spec->pcm_mutex);
+       return ret;
+}
+
+static const struct snd_kcontrol_new indep_hp_ctl = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Independent HP",
+       .info = indep_hp_info,
+       .get = indep_hp_get,
+       .put = indep_hp_put,
+};
+
+
+static int create_indep_hp_ctls(struct hda_codec *codec)
+{
+       struct hda_gen_spec *spec = codec->spec;
+
+       if (!spec->indep_hp)
+               return 0;
+       if (!spec->multiout.hp_out_nid[0]) {
+               spec->indep_hp = 0;
+               return 0;
+       }
+
+       spec->indep_hp_enabled = false;
+       spec->alt_dac_nid = spec->multiout.hp_out_nid[0];
+       if (!snd_hda_gen_add_kctl(spec, NULL, &indep_hp_ctl))
+               return -ENOMEM;
+       return 0;
+}
+
 /*
  * channel mode enum control
  */
@@ -2903,6 +2977,9 @@ int snd_hda_gen_parse_auto_config(struct hda_codec *codec,
        if (err < 0)
                return err;
        err = create_speaker_out_ctls(codec);
+       if (err < 0)
+               return err;
+       err = create_indep_hp_ctls(codec);
        if (err < 0)
                return err;
        err = create_shared_input(codec);
@@ -3057,8 +3134,16 @@ static int playback_pcm_open(struct hda_pcm_stream *hinfo,
                             struct snd_pcm_substream *substream)
 {
        struct hda_gen_spec *spec = codec->spec;
-       return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream,
+       int err;
+
+       mutex_lock(&spec->pcm_mutex);
+       err = snd_hda_multi_out_analog_open(codec,
+                                           &spec->multiout, substream,
                                             hinfo);
+       if (!err)
+               spec->active_streams |= 1 << STREAM_MULTI_OUT;
+       mutex_unlock(&spec->pcm_mutex);
+       return err;
 }
 
 static int playback_pcm_prepare(struct hda_pcm_stream *hinfo,
@@ -3080,6 +3165,44 @@ static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
        return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
 }
 
+static int playback_pcm_close(struct hda_pcm_stream *hinfo,
+                             struct hda_codec *codec,
+                             struct snd_pcm_substream *substream)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       mutex_lock(&spec->pcm_mutex);
+       spec->active_streams &= ~(1 << STREAM_MULTI_OUT);
+       mutex_unlock(&spec->pcm_mutex);
+       return 0;
+}
+
+static int alt_playback_pcm_open(struct hda_pcm_stream *hinfo,
+                                struct hda_codec *codec,
+                                struct snd_pcm_substream *substream)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       int err = 0;
+
+       mutex_lock(&spec->pcm_mutex);
+       if (!spec->indep_hp_enabled)
+               err = -EBUSY;
+       else
+               spec->active_streams |= 1 << STREAM_INDEP_HP;
+       mutex_unlock(&spec->pcm_mutex);
+       return err;
+}
+
+static int alt_playback_pcm_close(struct hda_pcm_stream *hinfo,
+                                 struct hda_codec *codec,
+                                 struct snd_pcm_substream *substream)
+{
+       struct hda_gen_spec *spec = codec->spec;
+       mutex_lock(&spec->pcm_mutex);
+       spec->active_streams &= ~(1 << STREAM_INDEP_HP);
+       mutex_unlock(&spec->pcm_mutex);
+       return 0;
+}
+
 /*
  * Digital out
  */
@@ -3154,6 +3277,7 @@ static const struct hda_pcm_stream pcm_analog_playback = {
        /* NID is set in build_pcms */
        .ops = {
                .open = playback_pcm_open,
+               .close = playback_pcm_close,
                .prepare = playback_pcm_prepare,
                .cleanup = playback_pcm_cleanup
        },
@@ -3171,6 +3295,10 @@ static const struct hda_pcm_stream pcm_analog_alt_playback = {
        .channels_min = 2,
        .channels_max = 2,
        /* NID is set in build_pcms */
+       .ops = {
+               .open = alt_playback_pcm_open,
+               .close = alt_playback_pcm_close
+       },
 };
 
 static const struct hda_pcm_stream pcm_analog_alt_capture = {
index 85d138f..5c1569c 100644 (file)
@@ -65,6 +65,9 @@ struct automic_entry {
        unsigned int attr;      /* pin attribute (INPUT_PIN_ATTR_*) */
 };
 
+/* active stream id */
+enum { STREAM_MULTI_OUT, STREAM_INDEP_HP };
+
 struct hda_gen_spec {
        char stream_name_analog[32];    /* analog PCM stream */
        const struct hda_pcm_stream *stream_analog_playback;
@@ -76,6 +79,10 @@ struct hda_gen_spec {
        const struct hda_pcm_stream *stream_digital_playback;
        const struct hda_pcm_stream *stream_digital_capture;
 
+       /* PCM */
+       unsigned int active_streams;
+       struct mutex pcm_mutex;
+
        /* playback */
        struct hda_multi_out multiout;  /* playback set-up
                                         * max_channels, dacs must be set
@@ -150,6 +157,8 @@ struct hda_gen_spec {
        unsigned int inv_dmic_split:1; /* inverted dmic w/a for conexant */
        unsigned int own_eapd_ctl:1; /* set EAPD by own function */
        unsigned int vmaster_mute_enum:1; /* add vmaster mute mode enum */
+       unsigned int indep_hp:1; /* independent HP supported */
+       unsigned int indep_hp_enabled:1; /* independent HP enabled */
 
        /* for virtual master */
        hda_nid_t vmaster_nid;