Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 77 additions & 24 deletions sound/soc/sof/ipc4-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
* struct sof_ipc4_timestamp_info - IPC4 timestamp info
* @host_copier: the host copier of the pcm stream
* @dai_copier: the dai copier of the pcm stream
* @stream_start_offset: reported by fw in memory window (converted to frames)
* @stream_end_offset: reported by fw in memory window (converted to frames)
* @stream_start_offset: reported by fw in memory window (converted to
* frames at host_copier sampling rate)
* @stream_end_offset: reported by fw in memory window (converted to
* frames at host_copier sampling rate)
* @llp_offset: llp offset in memory window
* @boundary: wrap boundary should be used for the LLP frame counter
* @delay: Calculated and stored in pointer callback. The stored value is
* returned in the delay callback.
* returned in the delay callback. Expressed in frames at host copier
* sampling rate.
*/
struct sof_ipc4_timestamp_info {
struct sof_ipc4_copier *host_copier;
Expand All @@ -33,7 +35,6 @@ struct sof_ipc4_timestamp_info {
u64 stream_end_offset;
u32 llp_offset;

u64 boundary;
snd_pcm_sframes_t delay;
};

Expand All @@ -48,6 +49,16 @@ struct sof_ipc4_pcm_stream_priv {
bool chain_dma_allocated;
};

/*
* Modulus to use to compare host and link position counters. The sampling
* rates may be different, so the raw hardware counters will wrap
* around at different times. To calculate differences, use
* DELAY_BOUNDARY as a common modulus. This value must be smaller than
* the wrap-around point of any hardware counter, and larger than any
* valid delay measurement.
*/
#define DELAY_BOUNDARY U32_MAX

static inline struct sof_ipc4_timestamp_info *
sof_ipc4_sps_to_time_info(struct snd_sof_pcm_stream *sps)
{
Expand Down Expand Up @@ -1049,6 +1060,35 @@ static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component,
return 0;
}

static u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value)
{
u64 dai_rate, host_rate;

if (!time_info->dai_copier || !time_info->host_copier)
return value;

/*
* copiers do not change sampling rate, so we can use the
* out_format independently of stream direction
*/
dai_rate = time_info->dai_copier->data.out_format.sampling_frequency;
host_rate = time_info->host_copier->data.out_format.sampling_frequency;

if (!dai_rate || !host_rate || dai_rate == host_rate)
return value;

/* take care not to overflow u64, rates can be up to 768000 */
if (value > U32_MAX) {
value = div64_u64(value, dai_rate);
value *= host_rate;
} else {
value *= host_rate;
value = div64_u64(value, dai_rate);
}

return value;
}

static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_sof_pcm_stream *sps,
Expand All @@ -1068,18 +1108,24 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
return -EINVAL;
} else if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_CHAIN_DMA_NODE_ID) {
/*
* While the firmware does not supports time_info reporting for
* While the firmware does not support time_info reporting for
* streams using ChainDMA, it is granted that ChainDMA can only
* be used on Host+Link pairs where the link position is
* accessible from the host side.
*
* Enable delay calculation in case of ChainDMA via host
* accessible registers.
*
* The ChainDMA uses 2x 1ms ping-pong buffer, dai side starts
* when 1ms data is available
* The ChainDMA prefills the link DMA with a preamble
* of zero samples. Set the stream start offset based
* on size of the preamble (driver provided fifo size
* multiplied by 2.5). We add 1ms of margin as the FW
* will align the buffer size to DMA hardware
* alignment that is not known to host.
*/
time_info->stream_start_offset = substream->runtime->rate / MSEC_PER_SEC;
int pre_ms = SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS * 5 / 2 + 1;

time_info->stream_start_offset = pre_ms * substream->runtime->rate / MSEC_PER_SEC;
goto out;
}

Expand All @@ -1099,14 +1145,13 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
time_info->stream_end_offset = ppl_reg.stream_end_offset;
do_div(time_info->stream_end_offset, dai_sample_size);

/* convert to host frame time */
time_info->stream_start_offset =
sof_ipc4_frames_dai_to_host(time_info, time_info->stream_start_offset);
time_info->stream_end_offset =
sof_ipc4_frames_dai_to_host(time_info, time_info->stream_end_offset);

out:
/*
* Calculate the wrap boundary need to be used for delay calculation
* The host counter is in bytes, it will wrap earlier than the frames
* based link counter.
*/
time_info->boundary = div64_u64(~((u64)0),
frames_to_bytes(substream->runtime, 1));
/* Initialize the delay value to 0 (no delay) */
time_info->delay = 0;

Expand Down Expand Up @@ -1149,6 +1194,8 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,

/* For delay calculation we need the host counter */
host_cnt = snd_sof_pcm_get_host_byte_counter(sdev, component, substream);

/* Store the original value to host_ptr */
host_ptr = host_cnt;

/* convert the host_cnt to frames */
Expand All @@ -1167,6 +1214,8 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
sof_mailbox_read(sdev, time_info->llp_offset, &llp, sizeof(llp));
dai_cnt = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l;
}

dai_cnt = sof_ipc4_frames_dai_to_host(time_info, dai_cnt);
dai_cnt += time_info->stream_end_offset;

/* In two cases dai dma counter is not accurate
Expand Down Expand Up @@ -1200,8 +1249,9 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
dai_cnt -= time_info->stream_start_offset;
}

/* Wrap the dai counter at the boundary where the host counter wraps */
div64_u64_rem(dai_cnt, time_info->boundary, &dai_cnt);
/* Convert to a common base before comparisons */
div64_u64_rem(dai_cnt, DELAY_BOUNDARY, &dai_cnt);
div64_u64_rem(host_cnt, DELAY_BOUNDARY, &host_cnt);

if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
head_cnt = host_cnt;
Expand All @@ -1211,14 +1261,17 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
tail_cnt = host_cnt;
}

if (head_cnt < tail_cnt) {
time_info->delay = time_info->boundary - tail_cnt + head_cnt;
goto out;
}
if (unlikely(head_cnt < tail_cnt))
time_info->delay = DELAY_BOUNDARY - tail_cnt + head_cnt;
else
time_info->delay = head_cnt - tail_cnt;

time_info->delay = head_cnt - tail_cnt;
if (time_info->delay > (DELAY_BOUNDARY >> 1)) {
dev_dbg_ratelimited(sdev->dev, "inaccurate delay, host %llu dai_cnt %llu",
host_cnt, dai_cnt);
time_info->delay = 0;
}

out:
/*
* Convert the host byte counter to PCM pointer which wraps in buffer
* and it is in frames
Expand Down
1 change: 0 additions & 1 deletion sound/soc/sof/ipc4-topology.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ MODULE_PARM_DESC(ipc4_ignore_cpc,

#define SOF_IPC4_GAIN_PARAM_ID 0
#define SOF_IPC4_TPLG_ABI_SIZE 6
#define SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS 2

static DEFINE_IDA(alh_group_ida);
static DEFINE_IDA(pipeline_ida);
Expand Down
2 changes: 2 additions & 0 deletions sound/soc/sof/ipc4-topology.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ struct sof_ipc4_dma_stream_ch_map {
#define SOF_IPC4_DMA_METHOD_HDA 1
#define SOF_IPC4_DMA_METHOD_GPDMA 2 /* defined for consistency but not used */

#define SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS 2

/**
* struct sof_ipc4_dma_config: DMA configuration
* @dma_method: HDAudio or GPDMA
Expand Down
Loading