Add an binary mixer 'ELD' to each HDMI PCM device so user space
could read the ELD data of external HDMI display.

Signed-off-by: Brent Lu <brent...@intel.com>
---
 sound/soc/codecs/hdac_hdmi.c | 138 +++++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)

diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c
index f26b77faed59..869d1547ae5d 100644
--- a/sound/soc/codecs/hdac_hdmi.c
+++ b/sound/soc/codecs/hdac_hdmi.c
@@ -9,6 +9,7 @@
  *
  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  */
+
 #include <linux/init.h>
 #include <linux/delay.h>
 #include <linux/module.h>
@@ -107,6 +108,7 @@ struct hdac_hdmi_pcm {
        unsigned char chmap[8]; /* ALSA API channel-map */
        struct mutex lock;
        int jack_event;
+       struct snd_kcontrol *eld_ctl;
 };
 
 struct hdac_hdmi_dai_port_map {
@@ -1248,6 +1250,7 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin 
*pin,
        struct hdac_hdmi_pcm *pcm;
        int size = 0;
        int port_id = -1;
+       bool eld_valid, eld_changed;
 
        if (!hdmi)
                return;
@@ -1273,6 +1276,8 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin 
*pin,
                        size = -EINVAL;
        }
 
+       eld_valid = port->eld.eld_valid;
+
        if (size > 0) {
                port->eld.eld_valid = true;
                port->eld.eld_size = size;
@@ -1281,6 +1286,8 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin 
*pin,
                port->eld.eld_size = 0;
        }
 
+       eld_changed = (eld_valid != port->eld.eld_valid);
+
        pcm = hdac_hdmi_get_pcm(hdev, port);
 
        if (!port->eld.monitor_present || !port->eld.eld_valid) {
@@ -1313,6 +1320,12 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin 
*pin,
 
        }
        mutex_unlock(&hdmi->pin_mutex);
+
+       if (eld_changed && pcm)
+               snd_ctl_notify(hdmi->card,
+                              SNDRV_CTL_EVENT_MASK_VALUE |
+                              SNDRV_CTL_EVENT_MASK_INFO,
+                              &pcm->eld_ctl->id);
 }
 
 static int hdac_hdmi_add_ports(struct hdac_device *hdev,
@@ -1411,6 +1424,122 @@ static void hdac_hdmi_skl_enable_dp12(struct 
hdac_device *hdev)
 
 }
 
+static int hdac_hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_info *uinfo)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct hdac_hdmi_priv *hdmi = snd_soc_component_get_drvdata(component);
+       struct hdac_hdmi_pcm *pcm;
+       struct hdac_hdmi_port *port;
+       struct hdac_hdmi_eld *eld;
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+       uinfo->count = 0;
+
+       pcm = get_hdmi_pcm_from_id(hdmi, kcontrol->id.device);
+       if (!pcm) {
+               dev_dbg(component->dev, "%s: no pcm, device %d\n", __func__,
+                       kcontrol->id.device);
+               return 0;
+       }
+
+       if (list_empty(&pcm->port_list)) {
+               dev_dbg(component->dev, "%s: empty port list, device %d\n",
+                       __func__, kcontrol->id.device);
+               return 0;
+       }
+
+       mutex_lock(&hdmi->pin_mutex);
+
+       list_for_each_entry(port, &pcm->port_list, head) {
+               eld = &port->eld;
+
+               if (eld->eld_valid) {
+                       uinfo->count = eld->eld_size;
+                       break;
+               }
+       }
+
+       mutex_unlock(&hdmi->pin_mutex);
+
+       return 0;
+}
+
+static int hdac_hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+       struct hdac_hdmi_priv *hdmi = snd_soc_component_get_drvdata(component);
+       struct hdac_hdmi_pcm *pcm;
+       struct hdac_hdmi_port *port;
+       struct hdac_hdmi_eld *eld;
+
+       memset(ucontrol->value.bytes.data, 0, 
ARRAY_SIZE(ucontrol->value.bytes.data));
+
+       pcm = get_hdmi_pcm_from_id(hdmi, kcontrol->id.device);
+       if (!pcm) {
+               dev_dbg(component->dev, "%s: no pcm, device %d\n", __func__,
+                       kcontrol->id.device);
+               return 0;
+       }
+
+       if (list_empty(&pcm->port_list)) {
+               dev_dbg(component->dev, "%s: empty port list, device %d\n",
+                       __func__, kcontrol->id.device);
+               return 0;
+       }
+
+       mutex_lock(&hdmi->pin_mutex);
+
+       list_for_each_entry(port, &pcm->port_list, head) {
+               eld = &port->eld;
+
+               if (!eld->eld_valid)
+                       continue;
+
+               if (eld->eld_size > ARRAY_SIZE(ucontrol->value.bytes.data) ||
+                   eld->eld_size > ELD_MAX_SIZE) {
+                       mutex_unlock(&hdmi->pin_mutex);
+
+                       dev_err(component->dev, "%s: buffer too small, device 
%d eld_size %d\n",
+                               __func__, kcontrol->id.device, eld->eld_size);
+                       snd_BUG();
+                       return -EINVAL;
+               }
+
+               memcpy(ucontrol->value.bytes.data, eld->eld_buffer,
+                      eld->eld_size);
+               break;
+       }
+
+       mutex_unlock(&hdmi->pin_mutex);
+
+       return 0;
+}
+
+static int hdac_hdmi_create_eld_ctl(struct snd_soc_component *component, 
struct hdac_hdmi_pcm *pcm)
+{
+       struct snd_kcontrol *kctl;
+       struct snd_kcontrol_new hdmi_eld_ctl = {
+               .access = SNDRV_CTL_ELEM_ACCESS_READ |
+                         SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+               .iface  = SNDRV_CTL_ELEM_IFACE_PCM,
+               .name   = "ELD",
+               .info   = hdac_hdmi_eld_ctl_info,
+               .get    = hdac_hdmi_eld_ctl_get,
+               .device = pcm->pcm_id,
+       };
+
+       /* add ELD ctl with the device number corresponding to the PCM stream */
+       kctl = snd_ctl_new1(&hdmi_eld_ctl, component);
+       if (!kctl)
+               return -ENOMEM;
+
+       pcm->eld_ctl = kctl;
+
+       return snd_ctl_add(component->card->snd_card, kctl);
+}
+
 static const struct snd_soc_dai_ops hdmi_dai_ops = {
        .startup = hdac_hdmi_pcm_open,
        .shutdown = hdac_hdmi_pcm_close,
@@ -1784,6 +1913,15 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int 
device,
                }
        }
 
+       /* add control for ELD Bytes */
+       err = hdac_hdmi_create_eld_ctl(component, pcm);
+       if (err < 0) {
+               dev_err(&hdev->dev,
+                       "eld control add failed with err: %d for pcm: %d\n",
+                       err, device);
+               return err;
+       }
+
        list_add_tail(&pcm->head, &hdmi->pcm_list);
 
        return 0;
-- 
2.17.1

Reply via email to