diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c index 8e049bc400535e..e237582912d6e2 100644 --- a/sound/soc/sof/intel/hda-sdw-bpt.c +++ b/sound/soc/sof/intel/hda-sdw-bpt.c @@ -14,12 +14,82 @@ #include #include #include +#include #include "../ops.h" #include "../sof-priv.h" +#include "../ipc4-priv.h" #include "hda.h" #define BPT_FREQUENCY 192000 #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 -ENOTSUPP; + + 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, @@ -54,6 +124,23 @@ static int hda_sdw_bpt_dma_prepare(struct device *dev, } *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; @@ -72,6 +159,8 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, 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); @@ -92,6 +181,25 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, 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; } @@ -106,6 +214,22 @@ static int hda_sdw_bpt_dma_enable(struct device *dev, dev_err(sdev->dev, "%s: SDW BPT DMA trigger start failed\n", __func__); return ret; } + + 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 0; } @@ -115,6 +239,18 @@ static int hda_sdw_bpt_dma_disable(struct device *dev, 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__); @@ -341,12 +477,12 @@ int hda_sdw_bpt_close(struct device *dev, int ret1; ret = hda_sdw_bpt_dma_deprepare(dev, - bpt_tx_stream, - dmab_tx_bdl); + bpt_rx_stream, + dmab_rx_bdl); ret1 = hda_sdw_bpt_dma_deprepare(dev, - bpt_rx_stream, - dmab_rx_bdl); + bpt_tx_stream, + dmab_tx_bdl); if (!ret) ret = ret1;