Skip to content

Commit 0ea0668

Browse files
ujfalusibroonie
authored andcommitted
ASoC: SOF: ipc4-pcm: Correct the delay calculation
This patch improves the delay calculation by relying on the LLP (Linear Link Position) on the DAI side and the LDP (Linear Data Pointer) on the host side. The LDP provides the same DMA position as LPIB, but with a linear count instead of a position in the ALSA ring buffer. The LDP values are provided in bytes and must be converted to frames. The difference in units means that the host counter will wrap earlier than the LLP. We need to wrap the LLP at the same boundary as the host counter. The ASoC framework relies on separate pointer and delay callback. Measurement errors can be reduced by processing all the counter values in the pointer callback. The delay value is stored, and will be reported to higher levels in the delay callback. For playback, the firmware provides a stream_start offset to handle mixing/pause usages, where the DAI might have started earlier than the PCM device. The delay calculation must be special-cased when the link counter has not reached the start offset value, i.e. no valid audio has left the DSP. Cc: [email protected] # 6.8 Signed-off-by: Peter Ujfalusi <[email protected]> Reviewed-by: Kai Vehmanen <[email protected]> Reviewed-by: Pierre-Louis Bossart <[email protected]> Link: https://msgid.link/r/[email protected] Signed-off-by: Mark Brown <[email protected]>
1 parent 77165bd commit 0ea0668

File tree

1 file changed

+127
-32
lines changed

1 file changed

+127
-32
lines changed

sound/soc/sof/ipc4-pcm.c

Lines changed: 127 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,22 @@
1919
* struct sof_ipc4_timestamp_info - IPC4 timestamp info
2020
* @host_copier: the host copier of the pcm stream
2121
* @dai_copier: the dai copier of the pcm stream
22-
* @stream_start_offset: reported by fw in memory window
22+
* @stream_start_offset: reported by fw in memory window (converted to frames)
23+
* @stream_end_offset: reported by fw in memory window (converted to frames)
2324
* @llp_offset: llp offset in memory window
25+
* @boundary: wrap boundary should be used for the LLP frame counter
26+
* @delay: Calculated and stored in pointer callback. The stored value is
27+
* returned in the delay callback.
2428
*/
2529
struct sof_ipc4_timestamp_info {
2630
struct sof_ipc4_copier *host_copier;
2731
struct sof_ipc4_copier *dai_copier;
2832
u64 stream_start_offset;
33+
u64 stream_end_offset;
2934
u32 llp_offset;
35+
36+
u64 boundary;
37+
snd_pcm_sframes_t delay;
3038
};
3139

3240
static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state,
@@ -726,6 +734,10 @@ static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm
726734
if (abi_version < SOF_IPC4_FW_REGS_ABI_VER)
727735
support_info = false;
728736

737+
/* For delay reporting the get_host_byte_counter callback is needed */
738+
if (!sof_ops(sdev) || !sof_ops(sdev)->get_host_byte_counter)
739+
support_info = false;
740+
729741
for_each_pcm_streams(stream) {
730742
pipeline_list = &spcm->stream[stream].pipeline_list;
731743

@@ -858,7 +870,6 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
858870
struct sof_ipc4_copier *host_copier = time_info->host_copier;
859871
struct sof_ipc4_copier *dai_copier = time_info->dai_copier;
860872
struct sof_ipc4_pipeline_registers ppl_reg;
861-
u64 stream_start_position;
862873
u32 dai_sample_size;
863874
u32 ch, node_index;
864875
u32 offset;
@@ -875,38 +886,51 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
875886
if (ppl_reg.stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION)
876887
return -EINVAL;
877888

878-
stream_start_position = ppl_reg.stream_start_offset;
879889
ch = dai_copier->data.out_format.fmt_cfg;
880890
ch = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(ch);
881891
dai_sample_size = (dai_copier->data.out_format.bit_depth >> 3) * ch;
882-
/* convert offset to sample count */
883-
do_div(stream_start_position, dai_sample_size);
884-
time_info->stream_start_offset = stream_start_position;
892+
893+
/* convert offsets to frame count */
894+
time_info->stream_start_offset = ppl_reg.stream_start_offset;
895+
do_div(time_info->stream_start_offset, dai_sample_size);
896+
time_info->stream_end_offset = ppl_reg.stream_end_offset;
897+
do_div(time_info->stream_end_offset, dai_sample_size);
898+
899+
/*
900+
* Calculate the wrap boundary need to be used for delay calculation
901+
* The host counter is in bytes, it will wrap earlier than the frames
902+
* based link counter.
903+
*/
904+
time_info->boundary = div64_u64(~((u64)0),
905+
frames_to_bytes(substream->runtime, 1));
906+
/* Initialize the delay value to 0 (no delay) */
907+
time_info->delay = 0;
885908

886909
return 0;
887910
}
888911

889-
static snd_pcm_sframes_t sof_ipc4_pcm_delay(struct snd_soc_component *component,
890-
struct snd_pcm_substream *substream)
912+
static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
913+
struct snd_pcm_substream *substream,
914+
snd_pcm_uframes_t *pointer)
891915
{
892916
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
893917
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
894918
struct sof_ipc4_timestamp_info *time_info;
895919
struct sof_ipc4_llp_reading_slot llp;
896-
snd_pcm_uframes_t head_ptr, tail_ptr;
920+
snd_pcm_uframes_t head_cnt, tail_cnt;
897921
struct snd_sof_pcm_stream *stream;
922+
u64 dai_cnt, host_cnt, host_ptr;
898923
struct snd_sof_pcm *spcm;
899-
u64 tmp_ptr;
900924
int ret;
901925

902926
spcm = snd_sof_find_spcm_dai(component, rtd);
903927
if (!spcm)
904-
return 0;
928+
return -EOPNOTSUPP;
905929

906930
stream = &spcm->stream[substream->stream];
907931
time_info = stream->private;
908932
if (!time_info)
909-
return 0;
933+
return -EOPNOTSUPP;
910934

911935
/*
912936
* stream_start_offset is updated to memory window by FW based on
@@ -916,46 +940,116 @@ static snd_pcm_sframes_t sof_ipc4_pcm_delay(struct snd_soc_component *component,
916940
if (time_info->stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION) {
917941
ret = sof_ipc4_get_stream_start_offset(sdev, substream, stream, time_info);
918942
if (ret < 0)
919-
return 0;
943+
return -EOPNOTSUPP;
920944
}
921945

946+
/* For delay calculation we need the host counter */
947+
host_cnt = snd_sof_pcm_get_host_byte_counter(sdev, component, substream);
948+
host_ptr = host_cnt;
949+
950+
/* convert the host_cnt to frames */
951+
host_cnt = div64_u64(host_cnt, frames_to_bytes(substream->runtime, 1));
952+
922953
/*
923954
* If the LLP counter is not reported by firmware in the SRAM window
924-
* then read the dai (link) position via host accessible means if
955+
* then read the dai (link) counter via host accessible means if
925956
* available.
926957
*/
927958
if (!time_info->llp_offset) {
928-
tmp_ptr = snd_sof_pcm_get_dai_frame_counter(sdev, component, substream);
929-
if (!tmp_ptr)
930-
return 0;
959+
dai_cnt = snd_sof_pcm_get_dai_frame_counter(sdev, component, substream);
960+
if (!dai_cnt)
961+
return -EOPNOTSUPP;
931962
} else {
932963
sof_mailbox_read(sdev, time_info->llp_offset, &llp, sizeof(llp));
933-
tmp_ptr = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l;
964+
dai_cnt = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l;
934965
}
966+
dai_cnt += time_info->stream_end_offset;
935967

936-
/* In two cases dai dma position is not accurate
968+
/* In two cases dai dma counter is not accurate
937969
* (1) dai pipeline is started before host pipeline
938-
* (2) multiple streams mixed into one. Each stream has the same dai dma position
970+
* (2) multiple streams mixed into one. Each stream has the same dai dma
971+
* counter
972+
*
973+
* Firmware calculates correct stream_start_offset for all cases
974+
* including above two.
975+
* Driver subtracts stream_start_offset from dai dma counter to get
976+
* accurate one
977+
*/
978+
979+
/*
980+
* On stream start the dai counter might not yet have reached the
981+
* stream_start_offset value which means that no frames have left the
982+
* DSP yet from the audio stream (on playback, capture streams have
983+
* offset of 0 as we start capturing right away).
984+
* In this case we need to adjust the distance between the counters by
985+
* increasing the host counter by (offset - dai_counter).
986+
* Otherwise the dai_counter needs to be adjusted to reflect the number
987+
* of valid frames passed on the DAI side.
939988
*
940-
* Firmware calculates correct stream_start_offset for all cases including above two.
941-
* Driver subtracts stream_start_offset from dai dma position to get accurate one
989+
* The delay is the difference between the counters on the two
990+
* sides of the DSP.
942991
*/
943-
tmp_ptr -= time_info->stream_start_offset;
992+
if (dai_cnt < time_info->stream_start_offset) {
993+
host_cnt += time_info->stream_start_offset - dai_cnt;
994+
dai_cnt = 0;
995+
} else {
996+
dai_cnt -= time_info->stream_start_offset;
997+
}
998+
999+
/* Wrap the dai counter at the boundary where the host counter wraps */
1000+
div64_u64_rem(dai_cnt, time_info->boundary, &dai_cnt);
9441001

945-
/* Calculate the delay taking into account that both pointer can wrap */
946-
div64_u64_rem(tmp_ptr, substream->runtime->boundary, &tmp_ptr);
9471002
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
948-
head_ptr = substream->runtime->status->hw_ptr;
949-
tail_ptr = tmp_ptr;
1003+
head_cnt = host_cnt;
1004+
tail_cnt = dai_cnt;
9501005
} else {
951-
head_ptr = tmp_ptr;
952-
tail_ptr = substream->runtime->status->hw_ptr;
1006+
head_cnt = dai_cnt;
1007+
tail_cnt = host_cnt;
1008+
}
1009+
1010+
if (head_cnt < tail_cnt) {
1011+
time_info->delay = time_info->boundary - tail_cnt + head_cnt;
1012+
goto out;
9531013
}
9541014

955-
if (head_ptr < tail_ptr)
956-
return substream->runtime->boundary - tail_ptr + head_ptr;
1015+
time_info->delay = head_cnt - tail_cnt;
1016+
1017+
out:
1018+
/*
1019+
* Convert the host byte counter to PCM pointer which wraps in buffer
1020+
* and it is in frames
1021+
*/
1022+
div64_u64_rem(host_ptr, snd_pcm_lib_buffer_bytes(substream), &host_ptr);
1023+
*pointer = bytes_to_frames(substream->runtime, host_ptr);
1024+
1025+
return 0;
1026+
}
1027+
1028+
static snd_pcm_sframes_t sof_ipc4_pcm_delay(struct snd_soc_component *component,
1029+
struct snd_pcm_substream *substream)
1030+
{
1031+
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
1032+
struct sof_ipc4_timestamp_info *time_info;
1033+
struct snd_sof_pcm_stream *stream;
1034+
struct snd_sof_pcm *spcm;
1035+
1036+
spcm = snd_sof_find_spcm_dai(component, rtd);
1037+
if (!spcm)
1038+
return 0;
1039+
1040+
stream = &spcm->stream[substream->stream];
1041+
time_info = stream->private;
1042+
/*
1043+
* Report the stored delay value calculated in the pointer callback.
1044+
* In the unlikely event that the calculation was skipped/aborted, the
1045+
* default 0 delay returned.
1046+
*/
1047+
if (time_info)
1048+
return time_info->delay;
1049+
1050+
/* No delay information available, report 0 as delay */
1051+
return 0;
9571052

958-
return head_ptr - tail_ptr;
9591053
}
9601054

9611055
const struct sof_ipc_pcm_ops ipc4_pcm_ops = {
@@ -965,6 +1059,7 @@ const struct sof_ipc_pcm_ops ipc4_pcm_ops = {
9651059
.dai_link_fixup = sof_ipc4_pcm_dai_link_fixup,
9661060
.pcm_setup = sof_ipc4_pcm_setup,
9671061
.pcm_free = sof_ipc4_pcm_free,
1062+
.pointer = sof_ipc4_pcm_pointer,
9681063
.delay = sof_ipc4_pcm_delay,
9691064
.ipc_first_on_start = true,
9701065
.platform_stop_during_hw_free = true,

0 commit comments

Comments
 (0)