ASoC: hdac_hdmi: Add jack reporting
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>
diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c
index 6fb44c4c..8391486 100644
--- a/sound/soc/codecs/hdac_hdmi.c
+++ b/sound/soc/codecs/hdac_hdmi.c
@@ -24,11 +24,13 @@
#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 {
struct list_head head;
hda_nid_t nid;
+ const char *name;
struct hdac_hdmi_cvt_params params;
};
@@ -73,6 +76,14 @@
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_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 @@
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 @@
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 @@
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 @@
{
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 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 @@
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 @@
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 @@
.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 @@
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 @@
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);
}