ASoC: hdac_hdmi: Add jack reporting
authorJeeja KP <jeeja.kp@intel.com>
Fri, 12 Feb 2016 02:16:06 +0000 (07:46 +0530)
committerMark Brown <broonie@kernel.org>
Mon, 15 Feb 2016 20:54:03 +0000 (20:54 +0000)
Jack is created based on pcm devices enumerated, so we will
create Jack as "HDMI/DP, pcm=x Jack". This style is expected by
current usermode like PulseAudio and CRAS.

This patch exports an API which can be used to register Jack
based on PCM. This API also establishes the map between PCM and
cvt. Further cvt to pin mapping is established with the help of
usermode selection based on the board topology.

During device probe as the PCMs may not be registered, initial
pin sense don't report jack events. So, first time jack reporting
is done during user selection of mux control.

Signed-off-by: Jeeja KP <jeeja.kp@intel.com>
Signed-off-by: Subhransu S. Prusty <subhransu.s.prusty@intel.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/hdac_hdmi.c
sound/soc/codecs/hdac_hdmi.h [new file with mode: 0644]

index 6fb44c4cc8c780a1b8c400c291bc43f40c0ce418..83914868874490cc944e488139e289ec8ff7b634 100644 (file)
 #include <linux/hdmi.h>
 #include <drm/drm_edid.h>
 #include <sound/pcm_params.h>
+#include <sound/jack.h>
 #include <sound/soc.h>
 #include <sound/hdaudio_ext.h>
 #include <sound/hda_i915.h>
 #include <sound/pcm_drm_eld.h>
 #include "../../hda/local.h"
+#include "hdac_hdmi.h"
 
 #define NAME_SIZE      32
 
@@ -52,6 +54,7 @@ struct hdac_hdmi_cvt_params {
 struct hdac_hdmi_cvt {
        struct list_head head;
        hda_nid_t nid;
+       const char *name;
        struct hdac_hdmi_cvt_params params;
 };
 
@@ -73,6 +76,14 @@ struct hdac_hdmi_pin {
        struct delayed_work work;
 };
 
+struct hdac_hdmi_pcm {
+       struct list_head head;
+       int pcm_id;
+       struct hdac_hdmi_pin *pin;
+       struct hdac_hdmi_cvt *cvt;
+       struct snd_jack *jack;
+};
+
 struct hdac_hdmi_dai_pin_map {
        int dai_id;
        struct hdac_hdmi_pin *pin;
@@ -83,8 +94,10 @@ struct hdac_hdmi_priv {
        struct hdac_hdmi_dai_pin_map dai_map[3];
        struct list_head pin_list;
        struct list_head cvt_list;
+       struct list_head pcm_list;
        int num_pin;
        int num_cvt;
+       struct mutex pin_mutex;
 };
 
 static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev)
@@ -478,6 +491,67 @@ static void hdac_hdmi_fill_route(struct snd_soc_dapm_route *route,
        route->connected = handler;
 }
 
+static struct hdac_hdmi_pcm *hdac_hdmi_get_pcm(struct hdac_ext_device *edev,
+                                       struct hdac_hdmi_pin *pin)
+{
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm = NULL;
+
+       list_for_each_entry(pcm, &hdmi->pcm_list, head) {
+               if (pcm->pin == pin)
+                       return pcm;
+       }
+
+       return NULL;
+}
+
+/*
+ * Based on user selection, map the PINs with the PCMs.
+ */
+static int hdac_hdmi_set_pin_mux(struct snd_kcontrol *kcontrol,
+               struct snd_ctl_elem_value *ucontrol)
+{
+       int ret;
+       struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+       struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+       struct snd_soc_dapm_context *dapm = w->dapm;
+       struct hdac_hdmi_pin *pin = w->priv;
+       struct hdac_ext_device *edev = to_hda_ext_device(dapm->dev);
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm = NULL;
+       const char *cvt_name =  e->texts[ucontrol->value.enumerated.item[0]];
+
+       ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+       if (ret < 0)
+               return ret;
+
+       mutex_lock(&hdmi->pin_mutex);
+       list_for_each_entry(pcm, &hdmi->pcm_list, head) {
+               if (pcm->pin == pin)
+                       pcm->pin = NULL;
+
+               /*
+                * Jack status is not reported during device probe as the
+                * PCMs are not registered by then. So report it here.
+                */
+               if (!strcmp(cvt_name, pcm->cvt->name) && !pcm->pin) {
+                       pcm->pin = pin;
+                       if (pin->eld.monitor_present && pin->eld.eld_valid) {
+                               dev_dbg(&edev->hdac.dev,
+                                       "jack report for pcm=%d\n",
+                                       pcm->pcm_id);
+
+                               snd_jack_report(pcm->jack, SND_JACK_AVOUT);
+                       }
+                       mutex_unlock(&hdmi->pin_mutex);
+                       return ret;
+               }
+       }
+       mutex_unlock(&hdmi->pin_mutex);
+
+       return ret;
+}
+
 /*
  * Ideally the Mux inputs should be based on the num_muxs enumerated, but
  * the display driver seem to be programming the connection list for the pin
@@ -520,7 +594,7 @@ static int hdac_hdmi_create_pin_muxs(struct hdac_ext_device *edev,
        kc->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
        kc->access = 0;
        kc->info = snd_soc_info_enum_double;
-       kc->put = snd_soc_dapm_put_enum_double;
+       kc->put = hdac_hdmi_set_pin_mux;
        kc->get = snd_soc_dapm_get_enum_double;
 
        se->reg = SND_SOC_NOPM;
@@ -548,8 +622,7 @@ static int hdac_hdmi_create_pin_muxs(struct hdac_ext_device *edev,
                return -ENOMEM;
 
        return hdac_hdmi_fill_widget_info(&edev->hdac.dev, widget,
-                       snd_soc_dapm_mux, &pin->nid, widget_name,
-                       NULL, kc, 1);
+                       snd_soc_dapm_mux, pin, widget_name, NULL, kc, 1);
 }
 
 /* Add cvt <- input <- mux route map */
@@ -728,12 +801,15 @@ static int hdac_hdmi_add_cvt(struct hdac_ext_device *edev, hda_nid_t nid)
 {
        struct hdac_hdmi_priv *hdmi = edev->private_data;
        struct hdac_hdmi_cvt *cvt;
+       char name[NAME_SIZE];
 
        cvt = kzalloc(sizeof(*cvt), GFP_KERNEL);
        if (!cvt)
                return -ENOMEM;
 
        cvt->nid = nid;
+       sprintf(name, "cvt %d", cvt->nid);
+       cvt->name = kstrdup(name, GFP_KERNEL);
 
        list_add_tail(&cvt->head, &hdmi->cvt_list);
        hdmi->num_cvt++;
@@ -744,6 +820,8 @@ static int hdac_hdmi_add_cvt(struct hdac_ext_device *edev, hda_nid_t nid)
 static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
 {
        struct hdac_ext_device *edev = pin->edev;
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm;
        int val;
 
        if (!edev)
@@ -758,13 +836,31 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
        dev_dbg(&edev->hdac.dev, "Pin sense val %x for pin: %d\n",
                                                val, pin->nid);
 
+
+       mutex_lock(&hdmi->pin_mutex);
        pin->eld.monitor_present = !!(val & AC_PINSENSE_PRESENCE);
        pin->eld.eld_valid = !!(val & AC_PINSENSE_ELDV);
 
+       pcm = hdac_hdmi_get_pcm(edev, pin);
+
        if (!pin->eld.monitor_present || !pin->eld.eld_valid) {
 
                dev_dbg(&edev->hdac.dev, "%s: disconnect for pin %d\n",
                                                __func__, pin->nid);
+
+               /*
+                * PCMs are not registered during device probe, so don't
+                * report jack here. It will be done in usermode mux
+                * control select.
+                */
+               if (pcm) {
+                       dev_dbg(&edev->hdac.dev,
+                               "jack report for pcm=%d\n", pcm->pcm_id);
+
+                       snd_jack_report(pcm->jack, 0);
+               }
+
+               mutex_unlock(&hdmi->pin_mutex);
                goto put_hdac_device;
        }
 
@@ -774,14 +870,32 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
                                pin->eld.eld_buffer,
                                &pin->eld.eld_size) == 0) {
 
+                       if (pcm) {
+                               dev_dbg(&edev->hdac.dev,
+                                       "jack report for pcm=%d\n",
+                                       pcm->pcm_id);
+
+                               snd_jack_report(pcm->jack, SND_JACK_AVOUT);
+                       }
+
                        print_hex_dump_bytes("ELD: ", DUMP_PREFIX_OFFSET,
                                        pin->eld.eld_buffer, pin->eld.eld_size);
                } else {
                        pin->eld.monitor_present = false;
                        pin->eld.eld_valid = false;
+
+                       if (pcm) {
+                               dev_dbg(&edev->hdac.dev,
+                                       "jack report for pcm=%d\n",
+                                       pcm->pcm_id);
+
+                               snd_jack_report(pcm->jack, 0);
+                       }
                }
        }
 
+       mutex_unlock(&hdmi->pin_mutex);
+
        /*
         * Sometimes the pin_sense may present invalid monitor
         * present and eld_valid. If ELD data is not valid, loop few
@@ -1039,6 +1153,35 @@ static struct i915_audio_component_audio_ops aops = {
        .pin_eld_notify = hdac_hdmi_eld_notify_cb,
 };
 
+int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
+{
+       char jack_name[NAME_SIZE];
+       struct snd_soc_codec *codec = dai->codec;
+       struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec);
+       struct snd_soc_dapm_context *dapm =
+               snd_soc_component_get_dapm(&codec->component);
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm;
+
+       /*
+        * this is a new PCM device, create new pcm and
+        * add to the pcm list
+        */
+       pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
+       pcm->pcm_id = device;
+       pcm->cvt = hdmi->dai_map[dai->id].cvt;
+
+       list_add_tail(&pcm->head, &hdmi->pcm_list);
+
+       sprintf(jack_name, "HDMI/DP, pcm=%d Jack", device);
+
+       return snd_jack_new(dapm->card->snd_card, jack_name,
+               SND_JACK_AVOUT, &pcm->jack, true, false);
+}
+EXPORT_SYMBOL_GPL(hdac_hdmi_jack_init);
+
 static int hdmi_codec_probe(struct snd_soc_codec *codec)
 {
        struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec);
@@ -1111,6 +1254,8 @@ static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
 
        INIT_LIST_HEAD(&hdmi_priv->pin_list);
        INIT_LIST_HEAD(&hdmi_priv->cvt_list);
+       INIT_LIST_HEAD(&hdmi_priv->pcm_list);
+       mutex_init(&hdmi_priv->pin_mutex);
 
        ret = hdac_hdmi_parse_and_map_nid(edev, &hdmi_dais, &num_dais);
        if (ret < 0) {
@@ -1129,11 +1274,20 @@ static int hdac_hdmi_dev_remove(struct hdac_ext_device *edev)
        struct hdac_hdmi_priv *hdmi = edev->private_data;
        struct hdac_hdmi_pin *pin, *pin_next;
        struct hdac_hdmi_cvt *cvt, *cvt_next;
+       struct hdac_hdmi_pcm *pcm, *pcm_next;
 
        snd_soc_unregister_codec(&edev->hdac.dev);
 
+       list_for_each_entry_safe(pcm, pcm_next, &hdmi->pcm_list, head) {
+               pcm->cvt = NULL;
+               pcm->pin = NULL;
+               list_del(&pcm->head);
+               kfree(pcm);
+       }
+
        list_for_each_entry_safe(cvt, cvt_next, &hdmi->cvt_list, head) {
                list_del(&cvt->head);
+               kfree(cvt->name);
                kfree(cvt);
        }
 
diff --git a/sound/soc/codecs/hdac_hdmi.h b/sound/soc/codecs/hdac_hdmi.h
new file mode 100644 (file)
index 0000000..8dfd1e0
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef __HDAC_HDMI_H__
+#define __HDAC_HDMI_H__
+
+int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int pcm);
+
+#endif /* __HDAC_HDMI_H__ */