Merge tag 'asoc-v5.16' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[linux-2.6-microblaze.git] / sound / soc / sof / intel / hda-dai.c
index 6704dbc..7657938 100644 (file)
 
 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
 
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
+#include "../sof-probes.h"
+#endif
+
 struct hda_pipe_params {
        u8 host_dma_id;
        u8 link_dma_id;
@@ -152,49 +156,68 @@ static int hda_link_dma_params(struct hdac_ext_stream *stream,
        return 0;
 }
 
-/* Send DAI_CONFIG IPC to the DAI that matches the dai_name and direction */
-static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream,
-                              const char *dai_name, int channel, int dir)
+/* Update config for the DAI widget */
+static struct sof_ipc_dai_config *hda_dai_update_config(struct snd_soc_dapm_widget *w,
+                                                       int channel)
 {
+       struct snd_sof_widget *swidget = w->dobj.private;
        struct sof_ipc_dai_config *config;
        struct snd_sof_dai *sof_dai;
-       struct sof_ipc_reply reply;
-       int ret = 0;
 
-       list_for_each_entry(sof_dai, &hda_stream->sdev->dai_list, list) {
-               if (!sof_dai->cpu_dai_name)
-                       continue;
+       if (!swidget)
+               return NULL;
 
-               if (!strcmp(dai_name, sof_dai->cpu_dai_name) &&
-                   dir == sof_dai->comp_dai.direction) {
-                       config = sof_dai->dai_config;
+       sof_dai = swidget->private;
 
-                       if (!config) {
-                               dev_err(hda_stream->sdev->dev,
-                                       "error: no config for DAI %s\n",
-                                       sof_dai->name);
-                               return -EINVAL;
-                       }
+       if (!sof_dai || !sof_dai->dai_config) {
+               dev_err(swidget->scomp->dev, "error: No config for DAI %s\n", w->name);
+               return NULL;
+       }
 
-                       /* update config with stream tag */
-                       config->hda.link_dma_ch = channel;
+       config = &sof_dai->dai_config[sof_dai->current_config];
 
-                       /* send IPC */
-                       ret = sof_ipc_tx_message(hda_stream->sdev->ipc,
-                                                config->hdr.cmd,
-                                                config,
-                                                config->hdr.size,
-                                                &reply, sizeof(reply));
+       /* update config with stream tag */
+       config->hda.link_dma_ch = channel;
 
-                       if (ret < 0)
-                               dev_err(hda_stream->sdev->dev,
-                                       "error: failed to set dai config for %s\n",
-                                       sof_dai->name);
-                       return ret;
-               }
+       return config;
+}
+
+static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream,
+                              struct snd_soc_dapm_widget *w, int channel)
+{
+       struct snd_sof_dev *sdev = hda_stream->sdev;
+       struct sof_ipc_dai_config *config;
+       struct sof_ipc_reply reply;
+
+       config = hda_dai_update_config(w, channel);
+       if (!config) {
+               dev_err(sdev->dev, "error: no config for DAI %s\n", w->name);
+               return -ENOENT;
        }
 
-       return -EINVAL;
+       /* send DAI_CONFIG IPC */
+       return sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
+                                 &reply, sizeof(reply));
+}
+
+static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream,
+                                     struct snd_soc_dapm_widget *w,
+                                     int channel, bool widget_setup)
+{
+       struct snd_sof_dev *sdev = hda_stream->sdev;
+       struct sof_ipc_dai_config *config;
+
+       config = hda_dai_update_config(w, channel);
+       if (!config) {
+               dev_err(sdev->dev, "error: no config for DAI %s\n", w->name);
+               return -ENOENT;
+       }
+
+       /* set up/free DAI widget and send DAI_CONFIG IPC */
+       if (widget_setup)
+               return hda_ctrl_dai_widget_setup(w);
+
+       return hda_ctrl_dai_widget_free(w);
 }
 
 static int hda_link_hw_params(struct snd_pcm_substream *substream,
@@ -208,6 +231,7 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream,
        struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
        struct sof_intel_hda_stream *hda_stream;
        struct hda_pipe_params p_params = {0};
+       struct snd_soc_dapm_widget *w;
        struct hdac_ext_link *link;
        int stream_tag;
        int ret;
@@ -226,9 +250,13 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream,
 
        hda_stream = hstream_to_sof_hda_stream(link_dev);
 
-       /* update the DSP with the new tag */
-       ret = hda_link_config_ipc(hda_stream, dai->name, stream_tag - 1,
-                                 substream->stream);
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               w = dai->playback_widget;
+       else
+               w = dai->capture_widget;
+
+       /* set up the DAI widget and send the DAI_CONFIG with the new tag */
+       ret = hda_link_dai_widget_update(hda_stream, w, stream_tag - 1, true);
        if (ret < 0)
                return ret;
 
@@ -284,6 +312,7 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
                                snd_soc_dai_get_dma_data(dai, substream);
        struct sof_intel_hda_stream *hda_stream;
        struct snd_soc_pcm_runtime *rtd;
+       struct snd_soc_dapm_widget *w;
        struct hdac_ext_link *link;
        struct hdac_stream *hstream;
        struct hdac_bus *bus;
@@ -318,12 +347,16 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
                break;
        case SNDRV_PCM_TRIGGER_SUSPEND:
        case SNDRV_PCM_TRIGGER_STOP:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       w = dai->playback_widget;
+               else
+                       w = dai->capture_widget;
+
                /*
                 * clear link DMA channel. It will be assigned when
                 * hw_params is set up again after resume.
                 */
-               ret = hda_link_config_ipc(hda_stream, dai->name,
-                                         DMA_CHAN_INVALID, substream->stream);
+               ret = hda_link_config_ipc(hda_stream, w, DMA_CHAN_INVALID);
                if (ret < 0)
                        return ret;
 
@@ -354,6 +387,7 @@ static int hda_link_hw_free(struct snd_pcm_substream *substream,
        struct hdac_stream *hstream;
        struct snd_soc_pcm_runtime *rtd;
        struct hdac_ext_stream *link_dev;
+       struct snd_soc_dapm_widget *w;
        int ret;
 
        hstream = substream->runtime->private_data;
@@ -369,9 +403,13 @@ static int hda_link_hw_free(struct snd_pcm_substream *substream,
 
        hda_stream = hstream_to_sof_hda_stream(link_dev);
 
-       /* free the link DMA channel in the FW */
-       ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID,
-                                 substream->stream);
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               w = dai->playback_widget;
+       else
+               w = dai->capture_widget;
+
+       /* free the link DMA channel in the FW and the DAI widget */
+       ret = hda_link_dai_widget_update(hda_stream, w, DMA_CHAN_INVALID, false);
        if (ret < 0)
                return ret;
 
@@ -401,61 +439,131 @@ static const struct snd_soc_dai_ops hda_link_dai_ops = {
        .prepare = hda_link_pcm_prepare,
 };
 
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
-#include "../compress.h"
-
-static struct snd_soc_cdai_ops sof_probe_compr_ops = {
-       .startup        = sof_probe_compr_open,
-       .shutdown       = sof_probe_compr_free,
-       .set_params     = sof_probe_compr_set_params,
-       .trigger        = sof_probe_compr_trigger,
-       .pointer        = sof_probe_compr_pointer,
+#endif
+
+/* only one flag used so far to harden hw_params/hw_free/trigger/prepare */
+struct ssp_dai_dma_data {
+       bool setup;
 };
 
-#endif
-#endif
+static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai,
+                                bool setup)
+{
+       struct snd_soc_component *component;
+       struct snd_sof_widget *swidget;
+       struct snd_soc_dapm_widget *w;
+       struct sof_ipc_fw_version *v;
+       struct snd_sof_dev *sdev;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               w = dai->playback_widget;
+       else
+               w = dai->capture_widget;
+
+       swidget = w->dobj.private;
+       component = swidget->scomp;
+       sdev = snd_soc_component_get_drvdata(component);
+       v = &sdev->fw_ready.version;
+
+       /* DAI_CONFIG IPC during hw_params is not supported in older firmware */
+       if (v->abi_version < SOF_ABI_VER(3, 18, 0))
+               return 0;
+
+       if (setup)
+               return hda_ctrl_dai_widget_setup(w);
+
+       return hda_ctrl_dai_widget_free(w);
+}
+
+static int ssp_dai_startup(struct snd_pcm_substream *substream,
+                          struct snd_soc_dai *dai)
+{
+       struct ssp_dai_dma_data *dma_data;
+
+       dma_data = kzalloc(sizeof(*dma_data), GFP_KERNEL);
+       if (!dma_data)
+               return -ENOMEM;
+
+       snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+       return 0;
+}
+
+static int ssp_dai_setup(struct snd_pcm_substream *substream,
+                        struct snd_soc_dai *dai,
+                        bool setup)
+{
+       struct ssp_dai_dma_data *dma_data;
+       int ret = 0;
+
+       dma_data = snd_soc_dai_get_dma_data(dai, substream);
+       if (!dma_data) {
+               dev_err(dai->dev, "%s: failed to get dma_data\n", __func__);
+               return -EIO;
+       }
+
+       if (dma_data->setup != setup) {
+               ret = ssp_dai_setup_or_free(substream, dai, setup);
+               if (!ret)
+                       dma_data->setup = setup;
+       }
+       return ret;
+}
 
 static int ssp_dai_hw_params(struct snd_pcm_substream *substream,
                             struct snd_pcm_hw_params *params,
                             struct snd_soc_dai *dai)
 {
-       struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
-       struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
-       struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
-       struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
-       struct sof_ipc_dai_config *config;
-       struct snd_sof_dai *sof_dai;
-       struct sof_ipc_reply reply;
-       int ret;
+       /* params are ignored for now */
+       return ssp_dai_setup(substream, dai, true);
+}
 
-       /* DAI_CONFIG IPC during hw_params is not supported in older firmware */
-       if (v->abi_version < SOF_ABI_VER(3, 18, 0))
+static int ssp_dai_prepare(struct snd_pcm_substream *substream,
+                          struct snd_soc_dai *dai)
+{
+       /*
+        * the SSP will only be reconfigured during resume operations and
+        * not in case of xruns
+        */
+       return ssp_dai_setup(substream, dai, true);
+}
+
+static int ssp_dai_trigger(struct snd_pcm_substream *substream,
+                          int cmd, struct snd_soc_dai *dai)
+{
+       if (cmd != SNDRV_PCM_TRIGGER_SUSPEND)
                return 0;
 
-       list_for_each_entry(sof_dai, &sdev->dai_list, list) {
-               if (!sof_dai->cpu_dai_name || !sof_dai->dai_config)
-                       continue;
+       return ssp_dai_setup(substream, dai, false);
+}
 
-               if (!strcmp(dai->name, sof_dai->cpu_dai_name) &&
-                   substream->stream == sof_dai->comp_dai.direction) {
-                       config = &sof_dai->dai_config[sof_dai->current_config];
+static int ssp_dai_hw_free(struct snd_pcm_substream *substream,
+                          struct snd_soc_dai *dai)
+{
+       return ssp_dai_setup(substream, dai, false);
+}
 
-                       /* send IPC */
-                       ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config,
-                                                config->hdr.size, &reply, sizeof(reply));
+static void ssp_dai_shutdown(struct snd_pcm_substream *substream,
+                            struct snd_soc_dai *dai)
+{
+       struct ssp_dai_dma_data *dma_data;
 
-                       if (ret < 0)
-                               dev_err(sdev->dev, "error: failed to set DAI config for %s\n",
-                                       sof_dai->name);
-                       return ret;
-               }
+       dma_data = snd_soc_dai_get_dma_data(dai, substream);
+       if (!dma_data) {
+               dev_err(dai->dev, "%s: failed to get dma_data\n", __func__);
+               return;
        }
-
-       return 0;
+       snd_soc_dai_set_dma_data(dai, substream, NULL);
+       kfree(dma_data);
 }
 
 static const struct snd_soc_dai_ops ssp_dai_ops = {
+       .startup = ssp_dai_startup,
        .hw_params = ssp_dai_hw_params,
+       .prepare = ssp_dai_prepare,
+       .trigger = ssp_dai_trigger,
+       .hw_free = ssp_dai_hw_free,
+       .shutdown = ssp_dai_shutdown,
 };
 
 /*