@@ -30,62 +30,84 @@ struct hda_pipe_params {
};
/*
- * Unlike GP dma, there is a set of stream registers in hda controller
- * to control the link dma channels. Each register controls one link
- * dma channel and the relation is fixed. To make sure FW uses correct
- * link dma channels, host allocates stream registers and sends the
- * corresponding link dma channels to FW to allocate link dma channel
- *
- * FIXME: this API is abused in the sense that tx_num and rx_num are
- * passed as arguments, not returned. We need to find a better way to
- * retrieve the stream tag allocated for the link DMA
+ * This function checks if the host dma channel corresponding
+ * to the link DMA stream_tag argument is assigned to one
+ * of the FEs connected to the BE DAI.
*/
-static int hda_link_dma_get_channels(struct snd_soc_dai *dai,
- unsigned int *tx_num,
- unsigned int *tx_slot,
- unsigned int *rx_num,
- unsigned int *rx_slot)
+static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
+ int dir, int stream_tag)
{
- struct hdac_bus *bus;
- struct hdac_ext_stream *stream;
- struct snd_pcm_substream substream;
- struct snd_sof_dev *sdev =
- snd_soc_component_get_drvdata(dai->component);
-
- bus = sof_to_bus(sdev);
-
- memset(&substream, 0, sizeof(substream));
- if (*tx_num == 1) {
- substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
- stream = snd_hdac_ext_stream_assign(bus, &substream,
- HDAC_EXT_STREAM_TYPE_LINK);
- if (!stream) {
- dev_err(bus->dev, "error: failed to find a free hda ext stream for playback");
- return -EBUSY;
- }
+ struct snd_pcm_substream *fe_substream;
+ struct hdac_stream *fe_hstream;
+ struct snd_soc_dpcm *dpcm;
+
+ for_each_dpcm_fe(rtd, dir, dpcm) {
+ fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
+ fe_hstream = fe_substream->runtime->private_data;
+ if (fe_hstream->stream_tag == stream_tag)
+ return true;
+ }
- snd_soc_dai_set_dma_data(dai, &substream, stream);
- *tx_slot = hdac_stream(stream)->stream_tag - 1;
+ return false;
+}
+
+static struct hdac_ext_stream *
+ hda_link_stream_assign(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sof_intel_hda_stream *hda_stream;
+ struct hdac_ext_stream *res = NULL;
+ struct hdac_stream *stream = NULL;
- dev_dbg(bus->dev, "link dma channel %d for playback", *tx_slot);
+ int stream_dir = substream->stream;
+
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "stream type not supported\n");
+ return NULL;
}
- if (*rx_num == 1) {
- substream.stream = SNDRV_PCM_STREAM_CAPTURE;
- stream = snd_hdac_ext_stream_assign(bus, &substream,
- HDAC_EXT_STREAM_TYPE_LINK);
- if (!stream) {
- dev_err(bus->dev, "error: failed to find a free hda ext stream for capture");
- return -EBUSY;
+ list_for_each_entry(stream, &bus->stream_list, list) {
+ struct hdac_ext_stream *hstream =
+ stream_to_hdac_ext_stream(stream);
+ if (stream->direction != substream->stream)
+ continue;
+
+ hda_stream = hstream_to_sof_hda_stream(hstream);
+
+ /* check if available */
+ if (!hstream->link_locked) {
+ if (stream->opened) {
+ /*
+ * check if the stream tag matches the stream
+ * tag of one of the connected FEs
+ */
+ if (hda_check_fes(rtd, stream_dir,
+ stream->stream_tag)) {
+ res = hstream;
+ break;
+ }
+ } else {
+ res = hstream;
+ break;
+ }
}
+ }
- snd_soc_dai_set_dma_data(dai, &substream, stream);
- *rx_slot = hdac_stream(stream)->stream_tag - 1;
-
- dev_dbg(bus->dev, "link dma channel %d for capture", *rx_slot);
+ if (res) {
+ /*
+ * Decouple host and link DMA. The decoupled flag
+ * is updated in snd_hdac_ext_stream_decouple().
+ */
+ if (!res->decoupled)
+ snd_hdac_ext_stream_decouple(bus, res, true);
+ spin_lock_irq(&bus->reg_lock);
+ res->link_locked = 1;
+ res->link_substream = substream;
+ spin_unlock_irq(&bus->reg_lock);
}
- return 0;
+ return res;
}
static int hda_link_dma_params(struct hdac_ext_stream *stream,
@@ -122,6 +144,51 @@ 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)
+{
+ 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 (!strcmp(dai_name, sof_dai->cpu_dai_name) &&
+ dir == sof_dai->comp_dai.direction) {
+ config = sof_dai->dai_config;
+
+ if (!config) {
+ dev_err(hda_stream->sdev->dev,
+ "error: no config for DAI %s\n",
+ sof_dai->name);
+ return -EINVAL;
+ }
+
+ /* update config with stream tag */
+ config->hda.link_dma_ch = channel;
+
+ /* send IPC */
+ ret = sof_ipc_tx_message(hda_stream->sdev->ipc,
+ config->hdr.cmd,
+ config,
+ config->hdr.size,
+ &reply, sizeof(reply));
+
+ if (ret < 0)
+ dev_err(hda_stream->sdev->dev,
+ "error: failed to set dai config for %s\n",
+ sof_dai->name);
+ return ret;
+ }
+ }
+
+ return -EINVAL;
+}
+
static int hda_link_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
@@ -135,20 +202,31 @@ static int hda_link_hw_params(struct snd_pcm_substream *substream,
struct hda_pipe_params p_params = {0};
struct hdac_ext_link *link;
int stream_tag;
+ int ret;
- link_dev = snd_soc_dai_get_dma_data(dai, substream);
+ link_dev = hda_link_stream_assign(bus, substream);
+ if (!link_dev)
+ return -EBUSY;
+
+ stream_tag = hdac_stream(link_dev)->stream_tag;
+
+ 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 (ret < 0)
+ return ret;
+
+ snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev);
- hda_stream = container_of(link_dev, struct sof_intel_hda_stream,
- hda_stream);
hda_stream->hw_params_upon_resume = 0;
link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name);
if (!link)
return -EINVAL;
- stream_tag = hdac_stream(link_dev)->stream_tag;
-
- /* set the stream tag in the codec dai dma params */
+ /* set the stream tag in the codec dai dma params */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0);
else
@@ -181,8 +259,7 @@ static int hda_link_pcm_prepare(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
int stream = substream->stream;
- hda_stream = container_of(link_dev, struct sof_intel_hda_stream,
- hda_stream);
+ hda_stream = hstream_to_sof_hda_stream(link_dev);
/* setup hw_params again only if resuming from system suspend */
if (!hda_stream->hw_params_upon_resume)
@@ -199,8 +276,24 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
{
struct hdac_ext_stream *link_dev =
snd_soc_dai_get_dma_data(dai, substream);
+ struct sof_intel_hda_stream *hda_stream;
+ struct snd_soc_pcm_runtime *rtd;
+ struct hdac_ext_link *link;
+ struct hdac_stream *hstream;
+ struct hdac_bus *bus;
+ int stream_tag;
int ret;
+ hstream = substream->runtime->private_data;
+ bus = hstream->bus;
+ rtd = snd_pcm_substream_chip(substream);
+
+ link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
+ if (!link)
+ return -EINVAL;
+
+ hda_stream = hstream_to_sof_hda_stream(link_dev);
+
dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
@@ -217,8 +310,22 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_hdac_ext_link_stream_start(link_dev);
break;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
+ /*
+ * clear and release 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);
+ if (ret < 0)
+ return ret;
+ stream_tag = hdac_stream(link_dev)->stream_tag;
+ snd_hdac_ext_link_clear_stream_id(link, stream_tag);
+ snd_hdac_ext_stream_release(link_dev,
+ HDAC_EXT_STREAM_TYPE_LINK);
+
+ /* fallthrough */
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_STOP:
snd_hdac_ext_link_stream_clear(link_dev);
break;
@@ -228,62 +335,38 @@ static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
return 0;
}
-/*
- * FIXME: This API is also abused since it's used for two purposes.
- * when the substream argument is NULL this function is used for cleanups
- * that aren't necessarily required, and called explicitly by handling
- * ASoC core structures, which is not recommended.
- * This part will be reworked in follow-up patches.
- */
static int hda_link_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
- const char *name;
unsigned int stream_tag;
+ struct sof_intel_hda_stream *hda_stream;
struct hdac_bus *bus;
struct hdac_ext_link *link;
struct hdac_stream *hstream;
- struct hdac_ext_stream *stream;
struct snd_soc_pcm_runtime *rtd;
struct hdac_ext_stream *link_dev;
- struct snd_pcm_substream pcm_substream;
-
- memset(&pcm_substream, 0, sizeof(pcm_substream));
- if (substream) {
- hstream = substream->runtime->private_data;
- bus = hstream->bus;
- rtd = snd_pcm_substream_chip(substream);
- link_dev = snd_soc_dai_get_dma_data(dai, substream);
- snd_hdac_ext_stream_decouple(bus, link_dev, false);
- name = rtd->codec_dai->component->name;
- link = snd_hdac_ext_bus_get_link(bus, name);
- if (!link)
- return -EINVAL;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- stream_tag = hdac_stream(link_dev)->stream_tag;
- snd_hdac_ext_link_clear_stream_id(link, stream_tag);
- }
+ int ret;
- link_dev->link_prepared = 0;
- } else {
- /* release all hda streams when dai link is unloaded */
- pcm_substream.stream = SNDRV_PCM_STREAM_PLAYBACK;
- stream = snd_soc_dai_get_dma_data(dai, &pcm_substream);
- if (stream) {
- snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL);
- snd_hdac_ext_stream_release(stream,
- HDAC_EXT_STREAM_TYPE_LINK);
- }
+ hstream = substream->runtime->private_data;
+ bus = hstream->bus;
+ rtd = snd_pcm_substream_chip(substream);
+ link_dev = snd_soc_dai_get_dma_data(dai, substream);
+ hda_stream = hstream_to_sof_hda_stream(link_dev);
- pcm_substream.stream = SNDRV_PCM_STREAM_CAPTURE;
- stream = snd_soc_dai_get_dma_data(dai, &pcm_substream);
- if (stream) {
- snd_soc_dai_set_dma_data(dai, &pcm_substream, NULL);
- snd_hdac_ext_stream_release(stream,
- HDAC_EXT_STREAM_TYPE_LINK);
- }
- }
+ /* free the link DMA channel in the FW */
+ ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID,
+ substream->stream);
+ if (ret < 0)
+ return ret;
+
+ link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
+ if (!link)
+ return -EINVAL;
+
+ stream_tag = hdac_stream(link_dev)->stream_tag;
+ snd_hdac_ext_link_clear_stream_id(link, stream_tag);
+ snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK);
+ link_dev->link_prepared = 0;
return 0;
}
@@ -293,7 +376,6 @@ static const struct snd_soc_dai_ops hda_link_dai_ops = {
.hw_free = hda_link_hw_free,
.trigger = hda_link_pcm_trigger,
.prepare = hda_link_pcm_prepare,
- .get_channel_map = hda_link_dma_get_channels,
};
#endif
@@ -60,6 +60,8 @@
(IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \
IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST))
+#define DMA_CHAN_INVALID 0xFFFFFFFF
+
struct snd_sof_dev;
struct snd_sof_ipc_msg;
struct snd_sof_ipc;
@@ -2591,9 +2591,7 @@ static int sof_link_dmic_load(struct snd_soc_component *scomp, int index,
*/
static int sof_link_hda_process(struct snd_sof_dev *sdev,
struct snd_soc_dai_link *link,
- struct sof_ipc_dai_config *config,
- int tx_slot,
- int rx_slot)
+ struct sof_ipc_dai_config *config)
{
struct sof_ipc_reply reply;
u32 size = sizeof(*config);
@@ -2606,22 +2604,11 @@ static int sof_link_hda_process(struct snd_sof_dev *sdev,
continue;
if (strcmp(link->name, sof_dai->name) == 0) {
- if (sof_dai->comp_dai.direction ==
- SNDRV_PCM_STREAM_PLAYBACK) {
- if (!link->dpcm_playback)
- return -EINVAL;
-
- config->hda.link_dma_ch = tx_slot;
- } else {
- if (!link->dpcm_capture)
- return -EINVAL;
-
- config->hda.link_dma_ch = rx_slot;
- }
-
config->dai_index = sof_dai->comp_dai.dai_index;
found = 1;
+ config->hda.link_dma_ch = DMA_CHAN_INVALID;
+
/* save config in dai component */
sof_dai->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!sof_dai->dai_config)
@@ -2667,10 +2654,6 @@ static int sof_link_hda_load(struct snd_soc_component *scomp, int index,
struct snd_soc_tplg_private *private = &cfg->priv;
struct snd_soc_dai *dai;
u32 size = sizeof(*config);
- u32 tx_num = 0;
- u32 tx_slot = 0;
- u32 rx_num = 0;
- u32 rx_slot = 0;
int ret;
/* init IPC */
@@ -2694,22 +2677,7 @@ static int sof_link_hda_load(struct snd_soc_component *scomp, int index,
return -EINVAL;
}
- if (link->dpcm_playback)
- tx_num = 1;
-
- if (link->dpcm_capture)
- rx_num = 1;
-
- ret = snd_soc_dai_get_channel_map(dai, &tx_num, &tx_slot,
- &rx_num, &rx_slot);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to get dma channel for HDA%d\n",
- config->dai_index);
-
- return ret;
- }
-
- ret = sof_link_hda_process(sdev, link, config, tx_slot, rx_slot);
+ ret = sof_link_hda_process(sdev, link, config);
if (ret < 0)
dev_err(sdev->dev, "error: failed to process hda dai link %s",
link->name);
@@ -2837,17 +2805,6 @@ static int sof_link_hda_unload(struct snd_sof_dev *sdev,
return -EINVAL;
}
- /*
- * FIXME: this call to hw_free is mainly to release the link DMA ID.
- * This is abusing the API and handling SOC internals is not
- * recommended. This part will be reworked.
- */
- if (dai->driver->ops->hw_free)
- ret = dai->driver->ops->hw_free(NULL, dai);
- if (ret < 0)
- dev_err(sdev->dev, "error: failed to free hda resource for %s\n",
- link->name);
-
return ret;
}