[PATCH v2 6/6] ASoC: qcom: q6apm-dai: add push-pull and watermark event support
From: Srinivas Kandagatla
Date: Thu May 28 2026 - 15:03:36 EST
Wire q6apm-dai to use push-pull shared memory graphs.
For push-pull graphs, configure the circular buffer and position buffer,
register watermark events, and use watermark notifications to report PCM
period elapsed. Skip legacy fragment queueing and ACK handling because the
DSP reads/writes directly from the shared circular buffer.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxxxxxxxx>
---
sound/soc/qcom/qdsp6/q6apm-dai.c | 144 ++++++++++++++++++++++++-------
1 file changed, 114 insertions(+), 30 deletions(-)
diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c
index 3a1be41df096..bf1f872a09f4 100644
--- a/sound/soc/qcom/qdsp6/q6apm-dai.c
+++ b/sound/soc/qcom/qdsp6/q6apm-dai.c
@@ -18,6 +18,7 @@
#include "q6apm.h"
#define DRV_NAME "q6apm-dai"
+#define POS_BUFFER_BYTES 4096
#define PLAYBACK_MIN_NUM_PERIODS 2
#define PLAYBACK_MAX_NUM_PERIODS 8
@@ -62,8 +63,12 @@ struct q6apm_dai_rtd {
struct snd_codec codec;
struct snd_compr_params codec_param;
struct snd_dma_buffer dma_buffer;
+ struct sh_mem_pull_push_mode_position_buffer *pos_buffer;
+ uint32_t last_pos_index;
phys_addr_t phys;
+ phys_addr_t pos_phys;
unsigned int pcm_size;
+ unsigned int push_pull_size;
unsigned int pcm_count;
unsigned int periods;
uint64_t bytes_sent;
@@ -128,6 +133,9 @@ static void event_handler(uint32_t opcode, uint32_t token, void *payload, void *
struct snd_pcm_substream *substream = prtd->substream;
switch (opcode) {
+ case APM_CLIENT_EVENT_WATERMARK_EVENT:
+ snd_pcm_period_elapsed(substream);
+ break;
case APM_CLIENT_EVENT_CMD_EOS_DONE:
prtd->state = Q6APM_STREAM_STOPPED;
break;
@@ -234,24 +242,47 @@ static int q6apm_dai_prepare(struct snd_soc_component *component,
q6apm_free_fragments(prtd->graph, substream->stream);
}
+ prtd->last_pos_index = 0;
prtd->pcm_count = snd_pcm_lib_period_bytes(substream);
- /* rate and channels are sent to audio driver */
- ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg);
- if (ret < 0) {
- dev_err(dev, "%s: q6apm_open_write failed\n", __func__);
- return ret;
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ if (prtd->pcm_size != prtd->push_pull_size) {
+ ret = q6apm_push_pull_config(prtd->graph, prtd->phys, prtd->pos_phys,
+ prtd->pcm_size);
+ if (ret < 0) {
+ dev_err(dev, "Push/Pull config failed rc = %d\n", ret);
+ return ret;
+ }
+
+ ret = q6apm_register_watermark_event(prtd->graph,
+ prtd->pcm_size / prtd->periods,
+ prtd->periods);
+ if (ret < 0) {
+ dev_err(dev, "WaterMark event config failed rc = %d\n", ret);
+ return ret;
+ }
+ prtd->push_pull_size = prtd->pcm_size;
+ }
+ } else {
+ ret = q6apm_alloc_fragments(prtd->graph, substream->stream, prtd->phys,
+ (prtd->pcm_size / prtd->periods), prtd->periods);
+ if (ret < 0) {
+ dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
+ return ret;
+ }
+
}
ret = q6apm_graph_media_format_pcm(prtd->graph, &cfg);
- if (ret < 0)
+ if (ret < 0) {
dev_err(dev, "%s: CMD Format block failed\n", __func__);
+ return ret;
+ }
- ret = q6apm_alloc_fragments(prtd->graph, substream->stream, prtd->phys,
- (prtd->pcm_size / prtd->periods), prtd->periods);
-
+ /* rate and channels are sent to audio driver */
+ ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg);
if (ret < 0) {
- dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
- return -ENOMEM;
+ dev_err(dev, "Failed to set media format %d\n", ret);
+ return ret;
}
ret = q6apm_graph_prepare(prtd->graph);
@@ -265,13 +296,13 @@ static int q6apm_dai_prepare(struct snd_soc_component *component,
dev_err(dev, "Failed to Start Graph %d\n", ret);
return ret;
}
-
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
- int i;
- /* Queue the buffers for Capture ONLY after graph is started */
- for (i = 0; i < runtime->periods; i++)
- q6apm_read(prtd->graph);
-
+ if (!q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ int i;
+ /* Queue the buffers for Capture ONLY after graph is started */
+ for (i = 0; i < runtime->periods; i++)
+ q6apm_read(prtd->graph);
+ }
}
/* Now that graph as been prepared and started update the internal state accordingly */
@@ -286,6 +317,9 @@ static int q6apm_dai_ack(struct snd_soc_component *component, struct snd_pcm_sub
struct q6apm_dai_rtd *prtd = runtime->private_data;
int i, ret = 0, avail_periods;
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph))
+ return 0;
+
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
avail_periods = (runtime->control->appl_ptr - prtd->queue_ptr)/runtime->period_size;
for (i = 0; i < avail_periods; i++) {
@@ -317,6 +351,7 @@ static int q6apm_dai_trigger(struct snd_soc_component *component,
/* TODO support be handled via SoftPause Module */
prtd->state = Q6APM_STREAM_STOPPED;
prtd->queue_ptr = 0;
+ prtd->last_pos_index = 0;
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
@@ -402,6 +437,14 @@ static int q6apm_dai_open(struct snd_soc_component *component,
else
prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32);
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ void *pos_buffer;
+
+ prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX;
+ pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX);
+ prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer);
+ }
+
return 0;
err:
kfree(prtd);
@@ -436,6 +479,25 @@ static snd_pcm_uframes_t q6apm_dai_pointer(struct snd_soc_component *component,
struct q6apm_dai_rtd *prtd = runtime->private_data;
snd_pcm_uframes_t ptr;
+ if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) {
+ int retries = 10;
+ uint32_t index, fc1, fc2;
+
+ /* index is valid if frame_counter does not change while reading. */
+ do {
+ fc1 = READ_ONCE(prtd->pos_buffer->frame_counter);
+ index = READ_ONCE(prtd->pos_buffer->index);
+ fc2 = READ_ONCE(prtd->pos_buffer->frame_counter);
+ } while (fc1 != fc2 && --retries);
+
+ if (fc1 != fc2)
+ index = prtd->last_pos_index;
+ else
+ prtd->last_pos_index = index;
+
+ ptr = bytes_to_frames(runtime, index);
+ return ptr;
+ }
ptr = q6apm_get_hw_pointer(prtd->graph, substream->stream) * runtime->period_size;
if (ptr)
return ptr - 1;
@@ -468,7 +530,8 @@ static int q6apm_dai_hw_params(struct snd_soc_component *component,
}
static int q6apm_dai_memory_map(struct snd_soc_component *component,
- struct snd_pcm_substream *substream, int graph_id)
+ struct snd_pcm_substream *substream,
+ int graph_id, bool is_push_pull)
{
struct q6apm_dai_data *pdata;
struct device *dev = component->dev;
@@ -490,6 +553,19 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component,
if (ret < 0)
dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
+ if (is_push_pull) {
+ if (pdata->sid < 0)
+ phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX;
+ else
+ phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32);
+
+ ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES);
+ if (ret < 0)
+ dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret);
+ } else {
+
+ }
+
return ret;
}
@@ -504,25 +580,30 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc
*/
int size = BUFFER_BYTES_MAX + PAGE_SIZE;
int graph_id, ret;
- struct snd_pcm_substream *substream;
+ bool is_push_pull;
+ struct snd_pcm_substream *substream = NULL;
graph_id = cpu_dai->driver->id;
- ret = snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev, size);
- if (ret)
- return ret;
-
/* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */
- if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
- ret = q6apm_dai_memory_map(component, substream, graph_id);
+ else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
+ substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+
+
+ if (substream) {
+ is_push_pull = q6apm_is_graph_in_push_pull_mode_from_id(component->dev,
+ graph_id,
+ substream->stream);
+ if (is_push_pull)
+ size += POS_BUFFER_BYTES;
+
+ ret = snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev, size);
if (ret)
return ret;
- }
- if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
- substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
- ret = q6apm_dai_memory_map(component, substream, graph_id);
+ ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull);
if (ret)
return ret;
}
@@ -547,6 +628,9 @@ static void q6apm_dai_memory_unmap(struct snd_soc_component *component,
graph_id = cpu_dai->driver->id;
q6apm_unmap_memory_fixed_region(component->dev, graph_id);
+
+ if (q6apm_is_graph_in_push_pull_mode_from_id(component->dev, graph_id, substream->stream))
+ q6apm_unmap_pos_buffer(component->dev, graph_id);
}
static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm)
--
2.53.0