@@ -14,12 +14,80 @@
#include <sound/hda-mlink.h>
#include <sound/hda-sdw-bpt.h>
#include <sound/sof.h>
+#include <sound/sof/ipc4/header.h>
#include "../ops.h"
#include "../sof-priv.h"
+#include "../ipc4-priv.h"
#include "hda.h"
#define BPT_FREQUENCY 192000 /* The max rate defined in rate_bits[] hdac_device.c */
#define BPT_MULTIPLIER ((BPT_FREQUENCY / 48000) - 1)
+#define BPT_CHAIN_DMA_FIFO_MS 10
+/*
+ * This routine is directly inspired by sof_ipc4_chain_dma_trigger(),
+ * with major simplifications since there are no pipelines defined
+ * and no dependency on ALSA hw_params
+ */
+static int chain_dma_trigger(struct snd_sof_dev *sdev, unsigned int stream_tag,
+ int direction, int state)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ bool allocate, enable, set_fifo_size;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ int dma_id;
+
+ if (sdev->pdata->ipc_type != SOF_IPC_TYPE_4)
+ return -EOPNOTSUPP;
+
+ switch (state) {
+ case SOF_IPC4_PIPE_RUNNING: /* Allocate and start the chain */
+ allocate = true;
+ enable = true;
+ set_fifo_size = true;
+ break;
+ case SOF_IPC4_PIPE_PAUSED: /* Stop the chain */
+ allocate = true;
+ enable = false;
+ set_fifo_size = false;
+ break;
+ case SOF_IPC4_PIPE_RESET: /* Deallocate chain resources and remove the chain */
+ allocate = false;
+ enable = false;
+ set_fifo_size = false;
+ break;
+ default:
+ dev_err(sdev->dev, "Unexpected state %d", state);
+ return -EINVAL;
+ }
+
+ msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ /* for BPT/BRA we can use the same stream tag for host and link */
+ dma_id = stream_tag - 1;
+ if (direction == SNDRV_PCM_STREAM_CAPTURE)
+ dma_id += ipc4_data->num_playback_streams;
+
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(dma_id);
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(dma_id);
+
+ /* For BPT/BRA we use 32 bits so SCS is not set */
+
+ /* CHAIN DMA needs at least 2ms */
+ if (set_fifo_size)
+ msg.extension |= SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(BPT_FREQUENCY / 1000 *
+ BPT_CHAIN_DMA_FIFO_MS *
+ sizeof(u32));
+
+ if (allocate)
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK;
+
+ if (enable)
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;
+
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+}
static int hda_sdw_bpt_dma_prepare(struct device *dev, struct hdac_ext_stream **sdw_bpt_stream,
struct snd_dma_buffer *dmab_bdl, u32 bpt_num_bytes,
@@ -46,6 +114,21 @@ static int hda_sdw_bpt_dma_prepare(struct device *dev, struct hdac_ext_stream **
}
*sdw_bpt_stream = bpt_stream;
+ if (!sdev->dspless_mode_selected) {
+ struct hdac_stream *hstream;
+ u32 mask;
+
+ /* decouple host and link DMA if the DSP is used */
+ hstream = &bpt_stream->hstream;
+ mask = BIT(hstream->index);
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, mask);
+
+ snd_hdac_ext_stream_reset(bpt_stream);
+
+ snd_hdac_ext_stream_setup(bpt_stream, format);
+ }
+
if (hdac_stream(bpt_stream)->direction == SNDRV_PCM_STREAM_PLAYBACK) {
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_ext_link *hlink;
@@ -63,6 +146,8 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream
struct snd_dma_buffer *dmab_bdl)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ struct hdac_stream *hstream;
+ u32 mask;
int ret;
ret = hda_cl_cleanup(sdev->dev, dmab_bdl, true, sdw_bpt_stream);
@@ -83,6 +168,22 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream
snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag);
}
+ if (!sdev->dspless_mode_selected) {
+ /* Release CHAIN_DMA resources */
+ ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
+ hdac_stream(sdw_bpt_stream)->direction,
+ SOF_IPC4_PIPE_RESET);
+ if (ret < 0)
+ dev_err(sdev->dev, "%s: chain_dma_trigger PIPE_RESET failed: %d\n",
+ __func__, ret);
+
+ /* couple host and link DMA */
+ hstream = &sdw_bpt_stream->hstream;
+ mask = BIT(hstream->index);
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, 0);
+ }
+
return 0;
}
@@ -95,6 +196,20 @@ static int hda_sdw_bpt_dma_enable(struct device *dev, struct hdac_ext_stream *sd
if (ret < 0)
dev_err(sdev->dev, "%s: SDW BPT DMA trigger start failed\n", __func__);
+ if (!sdev->dspless_mode_selected) {
+ /* the chain DMA needs to be programmed before the DMAs */
+ ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
+ hdac_stream(sdw_bpt_stream)->direction,
+ SOF_IPC4_PIPE_RUNNING);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: chain_dma_trigger failed: %d\n",
+ __func__, ret);
+ hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_STOP);
+ return ret;
+ }
+ snd_hdac_ext_stream_start(sdw_bpt_stream);
+ }
+
return ret;
}
@@ -103,6 +218,17 @@ static int hda_sdw_bpt_dma_disable(struct device *dev, struct hdac_ext_stream *s
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
int ret;
+ if (!sdev->dspless_mode_selected) {
+ snd_hdac_ext_stream_clear(sdw_bpt_stream);
+
+ ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
+ hdac_stream(sdw_bpt_stream)->direction,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ dev_err(sdev->dev, "%s: chain_dma_trigger PIPE_PAUSED failed: %d\n",
+ __func__, ret);
+ }
+
ret = hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_STOP);
if (ret < 0)
dev_err(sdev->dev, "%s: SDW BPT DMA trigger stop failed\n", __func__);