[PATCH 5.5 071/367] ALSA: hda - Fix DP-MST support for NVIDIA codecs

From: Greg Kroah-Hartman
Date: Mon Feb 10 2020 - 08:06:24 EST


From: Nikhil Mahale <nmahale@xxxxxxxxxx>

commit c7e661a1c2ae98a4754db6a85fc686b4a89322ad upstream.

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@xxxxxxxxx>
Signed-off-by: Nikhil Mahale <nmahale@xxxxxxxxxx>
Reviewed-by: Kai Vehmanen <kai.vehmanen@xxxxxxxxxxxxxxx>
Cc: <stable@xxxxxxxxxxxxxxx>
Link: https://lore.kernel.org/r/20200204102746.1356-1-nmahale@xxxxxxxxxx
Signed-off-by: Takashi Iwai <tiwai@xxxxxxx>
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>

---
sound/pci/hda/patch_hdmi.c | 94 ++++++++++++++++++++++++++++++---------------
1 file changed, 63 insertions(+), 31 deletions(-)

--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -1547,6 +1547,34 @@ static bool update_eld(struct hda_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)
@@ -1568,6 +1596,7 @@ static bool hdmi_present_sense_via_verbs
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);

@@ -1595,10 +1624,19 @@ static bool hdmi_present_sense_via_verbs
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;

@@ -1607,38 +1645,32 @@ static bool hdmi_present_sense_via_verbs
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 */
@@ -1674,10 +1706,10 @@ static void sync_eld_via_acomp(struct hd
/* 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) ?