ALSA: hda - hdmi dynamically bind PCM to pin when monitor hotplug
authorLibin Yang <libin.yang@linux.intel.com>
Wed, 16 Dec 2015 08:48:15 +0000 (16:48 +0800)
committerTakashi Iwai <tiwai@suse.de>
Fri, 29 Jan 2016 06:34:56 +0000 (07:34 +0100)
Dynamically bind/unbind the PCM to pin when HDMI/DP monitor hotplug.

When monitor is connected, find a proper PCM for the monitor.
When monitor is disconnected, unbind the PCM from the pin.

The binding policy (use Intel platform as example) is:
1. Try to use the legacy pin-pcm mapping for the device entry 0
   of the pin.
2. If step 1 fails, try to bind pin to the backup PCMs. For example,
   on Intel platform, if DP MST is enabled, 5 PCMs will be created.
   PCM 3, PCM 7, PCM 8 are supposed to be used by device entry 0 of
   pin 5, pin 6 and pin 7. PCM 9 and PCM 10 are the backup PCMs.
3. If step 2 fails, try to find any PCM to bind to the pin.

Signed-off-by: Libin Yang <libin.yang@linux.intel.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_hdmi.c

index 91c7165..6cf25ee 100644 (file)
@@ -75,6 +75,8 @@ struct hdmi_spec_per_cvt {
 
 struct hdmi_spec_per_pin {
        hda_nid_t pin_nid;
+       /* pin idx, different device entries on the same pin use the same idx */
+       int pin_nid_idx;
        int num_mux_nids;
        hda_nid_t mux_nids[HDA_MAX_CONNECTIONS];
        int mux_idx;
@@ -87,6 +89,7 @@ struct hdmi_spec_per_pin {
        struct snd_kcontrol *eld_ctl;
        struct snd_jack *acomp_jack; /* jack via audio component */
        struct hda_pcm *pcm; /* pointer to spec->pcm_rec[n] dynamically*/
+       int pcm_idx; /* which pcm is attached. -1 means no pcm is attached */
        int repoll_count;
        bool setup; /* the stream has been set up by prepare callback */
        int channels; /* current number of channels */
@@ -140,6 +143,8 @@ struct hdmi_spec {
        struct snd_array pins; /* struct hdmi_spec_per_pin */
        struct hda_pcm *pcm_rec[16];
        struct mutex pcm_lock;
+       /* pcm_bitmap means which pcms have been assigned to pins*/
+       unsigned long pcm_bitmap;
        int pcm_used;   /* counter of pcm_rec[] */
        unsigned int channels_max; /* max over all cvts */
 
@@ -1673,6 +1678,60 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
        return 0;
 }
 
+static int hdmi_find_pcm_slot(struct hdmi_spec *spec,
+                               struct hdmi_spec_per_pin *per_pin)
+{
+       int i;
+
+       /* try the prefer PCM */
+       if (!test_bit(per_pin->pin_nid_idx, &spec->pcm_bitmap))
+               return per_pin->pin_nid_idx;
+
+       /* have a second try; check the "reserved area" over num_pins */
+       for (i = spec->num_pins; i < spec->pcm_used; i++) {
+               if (!test_bit(i, &spec->pcm_bitmap))
+                       return i;
+       }
+
+       /* the last try; check the empty slots in pins */
+       for (i = 0; i < spec->num_pins; i++) {
+               if (!test_bit(i, &spec->pcm_bitmap))
+                       return i;
+       }
+       return -EBUSY;
+}
+
+static void hdmi_attach_hda_pcm(struct hdmi_spec *spec,
+                               struct hdmi_spec_per_pin *per_pin)
+{
+       int idx;
+
+       /* pcm already be attached to the pin */
+       if (per_pin->pcm)
+               return;
+       idx = hdmi_find_pcm_slot(spec, per_pin);
+       if (idx == -ENODEV)
+               return;
+       per_pin->pcm_idx = idx;
+       per_pin->pcm = spec->pcm_rec[idx];
+       set_bit(idx, &spec->pcm_bitmap);
+}
+
+static void hdmi_detach_hda_pcm(struct hdmi_spec *spec,
+                               struct hdmi_spec_per_pin *per_pin)
+{
+       int idx;
+
+       /* pcm already be detached from the pin */
+       if (!per_pin->pcm)
+               return;
+       idx = per_pin->pcm_idx;
+       per_pin->pcm_idx = -1;
+       per_pin->pcm = NULL;
+       if (idx >= 0 && idx < spec->pcm_used)
+               clear_bit(idx, &spec->pcm_bitmap);
+}
+
 /* update per_pin ELD from the given new ELD;
  * setup info frame and notification accordingly
  */
@@ -1681,9 +1740,17 @@ static void update_eld(struct hda_codec *codec,
                       struct hdmi_eld *eld)
 {
        struct hdmi_eld *pin_eld = &per_pin->sink_eld;
+       struct hdmi_spec *spec = codec->spec;
        bool old_eld_valid = pin_eld->eld_valid;
        bool eld_changed;
 
+       if (spec->dyn_pcm_assign) {
+               if (eld->eld_valid)
+                       hdmi_attach_hda_pcm(spec, per_pin);
+               else
+                       hdmi_detach_hda_pcm(spec, per_pin);
+       }
+
        if (eld->eld_valid)
                snd_hdmi_show_eld(codec, &eld->info);
 
@@ -1827,13 +1894,19 @@ static void sync_eld_via_acomp(struct hda_codec *codec,
 static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
 {
        struct hda_codec *codec = per_pin->codec;
+       struct hdmi_spec *spec = codec->spec;
+       int ret;
 
+       mutex_lock(&spec->pcm_lock);
        if (codec_has_acomp(codec)) {
                sync_eld_via_acomp(codec, per_pin);
-               return false; /* don't call snd_hda_jack_report_sync() */
+               ret = false; /* don't call snd_hda_jack_report_sync() */
        } else {
-               return hdmi_present_sense_via_verbs(per_pin, repoll);
+               ret = hdmi_present_sense_via_verbs(per_pin, repoll);
        }
+       mutex_unlock(&spec->pcm_lock);
+
+       return ret;
 }
 
 static void hdmi_repoll_eld(struct work_struct *work)
@@ -1877,6 +1950,11 @@ static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid)
 
        per_pin->pin_nid = pin_nid;
        per_pin->non_pcm = false;
+       if (spec->dyn_pcm_assign)
+               per_pin->pcm_idx = -1;
+       else
+               per_pin->pcm_idx = pin_idx;
+       per_pin->pin_nid_idx = pin_idx;
 
        err = hdmi_read_pin_conn(codec, pin_idx);
        if (err < 0)