ASoC: hdac_hda: add support for HDMI/DP as a HDA codec
authorKai Vehmanen <kai.vehmanen@linux.intel.com>
Tue, 29 Oct 2019 13:40:10 +0000 (15:40 +0200)
committerMark Brown <broonie@kernel.org>
Tue, 29 Oct 2019 17:31:50 +0000 (17:31 +0000)
Handle all HDA codecs using same logic, including HDMI/DP.

Call to snd_hda_codec_build_controls() is delayed for HDMI/DP HDA
devices. This is needed to discover the PCM device numbers as
defined in topology.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/20191029134017.18901-3-kai.vehmanen@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/hdac_hda.c
sound/soc/codecs/hdac_hda.h

index 91242b6..298761a 100644 (file)
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
 #include <sound/hdaudio_ext.h>
+#include <sound/hda_i915.h>
 #include <sound/hda_codec.h>
 #include <sound/hda_register.h>
-#include "hdac_hda.h"
 
-#define HDAC_ANALOG_DAI_ID             0
-#define HDAC_DIGITAL_DAI_ID            1
-#define HDAC_ALT_ANALOG_DAI_ID         2
+#include "hdac_hda.h"
 
 #define STUB_FORMATS   (SNDRV_PCM_FMTBIT_S8 | \
                        SNDRV_PCM_FMTBIT_U8 | \
                        SNDRV_PCM_FMTBIT_U32_LE | \
                        SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE)
 
+#define STUB_HDMI_RATES        (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+                                SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
+                                SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\
+                                SNDRV_PCM_RATE_192000)
+
 static int hdac_hda_dai_open(struct snd_pcm_substream *substream,
                             struct snd_soc_dai *dai);
 static void hdac_hda_dai_close(struct snd_pcm_substream *substream,
@@ -121,7 +124,46 @@ static struct snd_soc_dai_driver hdac_hda_dais[] = {
                .formats = STUB_FORMATS,
                .sig_bits = 24,
        },
-}
+},
+{
+       .id = HDAC_HDMI_0_DAI_ID,
+       .name = "intel-hdmi-hifi1",
+       .ops = &hdac_hda_dai_ops,
+       .playback = {
+               .stream_name    = "hifi1",
+               .channels_min   = 1,
+               .channels_max   = 32,
+               .rates          = STUB_HDMI_RATES,
+               .formats        = STUB_FORMATS,
+               .sig_bits = 24,
+       },
+},
+{
+       .id = HDAC_HDMI_1_DAI_ID,
+       .name = "intel-hdmi-hifi2",
+       .ops = &hdac_hda_dai_ops,
+       .playback = {
+               .stream_name    = "hifi2",
+               .channels_min   = 1,
+               .channels_max   = 32,
+               .rates          = STUB_HDMI_RATES,
+               .formats        = STUB_FORMATS,
+               .sig_bits = 24,
+       },
+},
+{
+       .id = HDAC_HDMI_2_DAI_ID,
+       .name = "intel-hdmi-hifi3",
+       .ops = &hdac_hda_dai_ops,
+       .playback = {
+               .stream_name    = "hifi3",
+               .channels_min   = 1,
+               .channels_max   = 32,
+               .rates          = STUB_HDMI_RATES,
+               .formats        = STUB_FORMATS,
+               .sig_bits = 24,
+       },
+},
 
 };
 
@@ -135,10 +177,11 @@ static int hdac_hda_dai_set_tdm_slot(struct snd_soc_dai *dai,
 
        hda_pvt = snd_soc_component_get_drvdata(component);
        pcm = &hda_pvt->pcm[dai->id];
+
        if (tx_mask)
-               pcm[dai->id].stream_tag[SNDRV_PCM_STREAM_PLAYBACK] = tx_mask;
+               pcm->stream_tag[SNDRV_PCM_STREAM_PLAYBACK] = tx_mask;
        else
-               pcm[dai->id].stream_tag[SNDRV_PCM_STREAM_CAPTURE] = rx_mask;
+               pcm->stream_tag[SNDRV_PCM_STREAM_CAPTURE] = rx_mask;
 
        return 0;
 }
@@ -278,6 +321,12 @@ static struct hda_pcm *snd_soc_find_pcm_from_dai(struct hdac_hda_priv *hda_pvt,
        struct hda_pcm *cpcm;
        const char *pcm_name;
 
+       /*
+        * map DAI ID to the closest matching PCM name, using the naming
+        * scheme used by hda-codec snd_hda_gen_build_pcms() and for
+        * HDMI in hda_codec patch_hdmi.c)
+        */
+
        switch (dai->id) {
        case HDAC_ANALOG_DAI_ID:
                pcm_name = "Analog";
@@ -288,13 +337,22 @@ static struct hda_pcm *snd_soc_find_pcm_from_dai(struct hdac_hda_priv *hda_pvt,
        case HDAC_ALT_ANALOG_DAI_ID:
                pcm_name = "Alt Analog";
                break;
+       case HDAC_HDMI_0_DAI_ID:
+               pcm_name = "HDMI 0";
+               break;
+       case HDAC_HDMI_1_DAI_ID:
+               pcm_name = "HDMI 1";
+               break;
+       case HDAC_HDMI_2_DAI_ID:
+               pcm_name = "HDMI 2";
+               break;
        default:
                dev_err(&hcodec->core.dev, "invalid dai id %d\n", dai->id);
                return NULL;
        }
 
        list_for_each_entry(cpcm, &hcodec->pcm_list_head, list) {
-               if (strpbrk(cpcm->name, pcm_name))
+               if (strstr(cpcm->name, pcm_name))
                        return cpcm;
        }
 
@@ -302,6 +360,18 @@ static struct hda_pcm *snd_soc_find_pcm_from_dai(struct hdac_hda_priv *hda_pvt,
        return NULL;
 }
 
+static bool is_hdmi_codec(struct hda_codec *hcodec)
+{
+       struct hda_pcm *cpcm;
+
+       list_for_each_entry(cpcm, &hcodec->pcm_list_head, list) {
+               if (cpcm->pcm_type == HDA_PCM_TYPE_HDMI)
+                       return true;
+       }
+
+       return false;
+}
+
 static int hdac_hda_codec_probe(struct snd_soc_component *component)
 {
        struct hdac_hda_priv *hda_pvt =
@@ -322,6 +392,15 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component)
 
        snd_hdac_ext_bus_link_get(hdev->bus, hlink);
 
+       /*
+        * Ensure any HDA display is powered at codec probe.
+        * After snd_hda_codec_device_new(), display power is
+        * managed by runtime PM.
+        */
+       if (hda_pvt->need_display_power)
+               snd_hdac_display_power(hdev->bus,
+                                      HDA_CODEC_IDX_CONTROLLER, true);
+
        ret = snd_hda_codec_device_new(hcodec->bus, component->card->snd_card,
                                       hdev->addr, hcodec);
        if (ret < 0) {
@@ -366,20 +445,31 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component)
                dev_dbg(&hdev->dev, "no patch file found\n");
        }
 
+       /* configure codec for 1:1 PCM:DAI mapping */
+       hcodec->mst_no_extra_pcms = 1;
+
        ret = snd_hda_codec_parse_pcms(hcodec);
        if (ret < 0) {
                dev_err(&hdev->dev, "unable to map pcms to dai %d\n", ret);
                goto error;
        }
 
-       ret = snd_hda_codec_build_controls(hcodec);
-       if (ret < 0) {
-               dev_err(&hdev->dev, "unable to create controls %d\n", ret);
-               goto error;
+       /* HDMI controls need to be created in machine drivers */
+       if (!is_hdmi_codec(hcodec)) {
+               ret = snd_hda_codec_build_controls(hcodec);
+               if (ret < 0) {
+                       dev_err(&hdev->dev, "unable to create controls %d\n",
+                               ret);
+                       goto error;
+               }
        }
 
        hcodec->core.lazy_cache = true;
 
+       if (hda_pvt->need_display_power)
+               snd_hdac_display_power(hdev->bus,
+                                      HDA_CODEC_IDX_CONTROLLER, false);
+
        /*
         * hdac_device core already sets the state to active and calls
         * get_noresume. So enable runtime and set the device to suspend.
index 6b1bd4f..e145cec 100644 (file)
@@ -6,6 +6,16 @@
 #ifndef __HDAC_HDA_H__
 #define __HDAC_HDA_H__
 
+enum {
+       HDAC_ANALOG_DAI_ID = 0,
+       HDAC_DIGITAL_DAI_ID,
+       HDAC_ALT_ANALOG_DAI_ID,
+       HDAC_HDMI_0_DAI_ID,
+       HDAC_HDMI_1_DAI_ID,
+       HDAC_HDMI_2_DAI_ID,
+       HDAC_LAST_DAI_ID = HDAC_HDMI_2_DAI_ID,
+};
+
 struct hdac_hda_pcm {
        int stream_tag[2];
        unsigned int format_val[2];
@@ -13,7 +23,8 @@ struct hdac_hda_pcm {
 
 struct hdac_hda_priv {
        struct hda_codec codec;
-       struct hdac_hda_pcm pcm[2];
+       struct hdac_hda_pcm pcm[HDAC_LAST_DAI_ID];
+       bool need_display_power;
 };
 
 #define hdac_to_hda_priv(_hdac) \