ALSA: hda - Fix DP-MST support for NVIDIA codecs
authorNikhil Mahale <nmahale@nvidia.com>
Tue, 4 Feb 2020 10:27:46 +0000 (15:57 +0530)
committerTakashi Iwai <tiwai@suse.de>
Tue, 4 Feb 2020 16:16:08 +0000 (17:16 +0100)
If dyn_pcm_assign is set, different jack objects are being created
for pcm and pins.

If dyn_pcm_assign is set, generic_hdmi_build_jack() calls into
add_hdmi_jack_kctl() to create and track separate jack object for
pcm. Like sync_eld_via_acomp(), hdmi_present_sense_via_verbs() also
need to report status change of the pcm jack.

Rename pin_idx_to_jack() to pin_idx_to_pcm_jack(). Update
hdmi_present_sense_via_verbs() to report plug state of pcm jack
object. Unlike sync_eld_via_acomp(), for !acomp drivers the pcm
jack's plug state must be consistent with plug state
of pin's jack.

Fixes: 5398e94fb753 ("ALSA: hda - Add DP-MST support for NVIDIA codecs")
Reported-and-tested-by: Martin Regner <martin@larkos.de>
Signed-off-by: Nikhil Mahale <nmahale@nvidia.com>
Reviewed-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/20200204102746.1356-1-nmahale@nvidia.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_hdmi.c

index 7c006f9..5119a9a 100644 (file)
@@ -1550,6 +1550,34 @@ static bool update_eld(struct hda_codec *codec,
        return eld_changed;
 }
 
+static struct snd_jack *pin_idx_to_pcm_jack(struct hda_codec *codec,
+                                           struct hdmi_spec_per_pin *per_pin)
+{
+       struct hdmi_spec *spec = codec->spec;
+       struct snd_jack *jack = NULL;
+       struct hda_jack_tbl *jack_tbl;
+
+       /* if !dyn_pcm_assign, get jack from hda_jack_tbl
+        * in !dyn_pcm_assign case, spec->pcm_rec[].jack is not
+        * NULL even after snd_hda_jack_tbl_clear() is called to
+        * free snd_jack. This may cause access invalid memory
+        * when calling snd_jack_report
+        */
+       if (per_pin->pcm_idx >= 0 && spec->dyn_pcm_assign) {
+               jack = spec->pcm_rec[per_pin->pcm_idx].jack;
+       } else if (!spec->dyn_pcm_assign) {
+               /*
+                * jack tbl doesn't support DP MST
+                * DP MST will use dyn_pcm_assign,
+                * so DP MST will never come here
+                */
+               jack_tbl = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid,
+                                                   per_pin->dev_id);
+               if (jack_tbl)
+                       jack = jack_tbl->jack;
+       }
+       return jack;
+}
 /* update ELD and jack state via HD-audio verbs */
 static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
                                         int repoll)
@@ -1571,6 +1599,7 @@ static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
        int present;
        bool ret;
        bool do_repoll = false;
+       struct snd_jack *pcm_jack = NULL;
 
        present = snd_hda_jack_pin_sense(codec, pin_nid, dev_id);
 
@@ -1598,10 +1627,19 @@ static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
                        do_repoll = true;
        }
 
-       if (do_repoll)
+       if (do_repoll) {
                schedule_delayed_work(&per_pin->work, msecs_to_jiffies(300));
-       else
+       } else {
+               /*
+                * pcm_idx >=0 before update_eld() means it is in monitor
+                * disconnected event. Jack must be fetched before
+                * update_eld().
+                */
+               pcm_jack = pin_idx_to_pcm_jack(codec, per_pin);
                update_eld(codec, per_pin, eld);
+               if (!pcm_jack)
+                       pcm_jack = pin_idx_to_pcm_jack(codec, per_pin);
+       }
 
        ret = !repoll || !eld->monitor_present || eld->eld_valid;
 
@@ -1610,38 +1648,32 @@ static bool hdmi_present_sense_via_verbs(struct hdmi_spec_per_pin *per_pin,
                jack->block_report = !ret;
                jack->pin_sense = (eld->monitor_present && eld->eld_valid) ?
                        AC_PINSENSE_PRESENCE : 0;
-       }
-       mutex_unlock(&per_pin->lock);
-       return ret;
-}
 
-static struct snd_jack *pin_idx_to_jack(struct hda_codec *codec,
-                                struct hdmi_spec_per_pin *per_pin)
-{
-       struct hdmi_spec *spec = codec->spec;
-       struct snd_jack *jack = NULL;
-       struct hda_jack_tbl *jack_tbl;
+               if (spec->dyn_pcm_assign && pcm_jack && !do_repoll) {
+                       int state = 0;
+
+                       if (jack->pin_sense & AC_PINSENSE_PRESENCE)
+                               state = SND_JACK_AVOUT;
+                       snd_jack_report(pcm_jack, state);
+               }
 
-       /* if !dyn_pcm_assign, get jack from hda_jack_tbl
-        * in !dyn_pcm_assign case, spec->pcm_rec[].jack is not
-        * NULL even after snd_hda_jack_tbl_clear() is called to
-        * free snd_jack. This may cause access invalid memory
-        * when calling snd_jack_report
-        */
-       if (per_pin->pcm_idx >= 0 && spec->dyn_pcm_assign)
-               jack = spec->pcm_rec[per_pin->pcm_idx].jack;
-       else if (!spec->dyn_pcm_assign) {
                /*
-                * jack tbl doesn't support DP MST
-                * DP MST will use dyn_pcm_assign,
-                * so DP MST will never come here
+                * snd_hda_jack_pin_sense() call at the beginning of this
+                * function, updates jack->pins_sense and clears
+                * jack->jack_dirty, therefore snd_hda_jack_report_sync() will
+                * not override the jack->pin_sense.
+                *
+                * snd_hda_jack_report_sync() is superfluous for dyn_pcm_assign
+                * case. The jack->pin_sense update was already performed, and
+                * hda_jack->jack is NULL for dyn_pcm_assign.
+                *
+                * Don't call snd_hda_jack_report_sync() for
+                * dyn_pcm_assign.
                 */
-               jack_tbl = snd_hda_jack_tbl_get_mst(codec, per_pin->pin_nid,
-                                                   per_pin->dev_id);
-               if (jack_tbl)
-                       jack = jack_tbl->jack;
+               ret = ret && !spec->dyn_pcm_assign;
        }
-       return jack;
+       mutex_unlock(&per_pin->lock);
+       return ret;
 }
 
 /* update ELD and jack state via audio component */
@@ -1677,10 +1709,10 @@ static void sync_eld_via_acomp(struct hda_codec *codec,
        /* pcm_idx >=0 before update_eld() means it is in monitor
         * disconnected event. Jack must be fetched before update_eld()
         */
-       jack = pin_idx_to_jack(codec, per_pin);
+       jack = pin_idx_to_pcm_jack(codec, per_pin);
        changed = update_eld(codec, per_pin, eld);
        if (jack == NULL)
-               jack = pin_idx_to_jack(codec, per_pin);
+               jack = pin_idx_to_pcm_jack(codec, per_pin);
        if (changed && jack)
                snd_jack_report(jack,
                                (eld->monitor_present && eld->eld_valid) ?