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 (converted to frames)
23- * @stream_end_offset: reported by fw in memory window (converted to frames)
22+ * @stream_start_offset: reported by fw in memory window (converted to
23+ * frames at host_copier sampling rate)
24+ * @stream_end_offset: reported by fw in memory window (converted to
25+ * frames at host_copier sampling rate)
2426 * @llp_offset: llp offset in memory window
25- * @boundary: wrap boundary should be used for the LLP frame counter
2627 * @delay: Calculated and stored in pointer callback. The stored value is
27- * returned in the delay callback.
28+ * returned in the delay callback. Expressed in frames at host copier
29+ * sampling rate.
2830 */
2931struct sof_ipc4_timestamp_info {
3032 struct sof_ipc4_copier * host_copier ;
@@ -33,7 +35,6 @@ struct sof_ipc4_timestamp_info {
3335 u64 stream_end_offset ;
3436 u32 llp_offset ;
3537
36- u64 boundary ;
3738 snd_pcm_sframes_t delay ;
3839};
3940
@@ -48,6 +49,18 @@ struct sof_ipc4_pcm_stream_priv {
4849 bool chain_dma_allocated ;
4950};
5051
52+ /*
53+ * Modulus to use to compare host and link position counters. The sampling
54+ * rates may be different, so the raw hardware counters will wrap
55+ * around at different times. To calculate differences, use
56+ * DELAY_BOUNDARY as a common modulus. This value must be smaller than
57+ * the wrap-around point of any hardware counter, and larger than any
58+ * valid delay measurement.
59+ */
60+ #define DELAY_BOUNDARY U32_MAX
61+
62+ #define DELAY_MAX (DELAY_BOUNDARY >> 1)
63+
5164static inline struct sof_ipc4_timestamp_info *
5265sof_ipc4_sps_to_time_info (struct snd_sof_pcm_stream * sps )
5366{
@@ -1049,6 +1062,35 @@ static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component,
10491062 return 0 ;
10501063}
10511064
1065+ static u64 sof_ipc4_frames_dai_to_host (struct sof_ipc4_timestamp_info * time_info , u64 value )
1066+ {
1067+ u64 dai_rate , host_rate ;
1068+
1069+ if (!time_info -> dai_copier || !time_info -> host_copier )
1070+ return value ;
1071+
1072+ /*
1073+ * copiers do not change sampling rate, so we can use the
1074+ * out_format independently of stream direction
1075+ */
1076+ dai_rate = time_info -> dai_copier -> data .out_format .sampling_frequency ;
1077+ host_rate = time_info -> host_copier -> data .out_format .sampling_frequency ;
1078+
1079+ if (!dai_rate || !host_rate || dai_rate == host_rate )
1080+ return value ;
1081+
1082+ /* take care not to overflow u64, rates can be up to 768000 */
1083+ if (value > U32_MAX ) {
1084+ value = div64_u64 (value , dai_rate );
1085+ value *= host_rate ;
1086+ } else {
1087+ value *= host_rate ;
1088+ value = div64_u64 (value , dai_rate );
1089+ }
1090+
1091+ return value ;
1092+ }
1093+
10521094static int sof_ipc4_get_stream_start_offset (struct snd_sof_dev * sdev ,
10531095 struct snd_pcm_substream * substream ,
10541096 struct snd_sof_pcm_stream * sps ,
@@ -1068,18 +1110,24 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
10681110 return - EINVAL ;
10691111 } else if (host_copier -> data .gtw_cfg .node_id == SOF_IPC4_CHAIN_DMA_NODE_ID ) {
10701112 /*
1071- * While the firmware does not supports time_info reporting for
1113+ * While the firmware does not support time_info reporting for
10721114 * streams using ChainDMA, it is granted that ChainDMA can only
10731115 * be used on Host+Link pairs where the link position is
10741116 * accessible from the host side.
10751117 *
10761118 * Enable delay calculation in case of ChainDMA via host
10771119 * accessible registers.
10781120 *
1079- * The ChainDMA uses 2x 1ms ping-pong buffer, dai side starts
1080- * when 1ms data is available
1121+ * The ChainDMA prefills the link DMA with a preamble
1122+ * of zero samples. Set the stream start offset based
1123+ * on size of the preamble (driver provided fifo size
1124+ * multiplied by 2.5). We add 1ms of margin as the FW
1125+ * will align the buffer size to DMA hardware
1126+ * alignment that is not known to host.
10811127 */
1082- time_info -> stream_start_offset = substream -> runtime -> rate / MSEC_PER_SEC ;
1128+ int pre_ms = SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS * 5 / 2 + 1 ;
1129+
1130+ time_info -> stream_start_offset = pre_ms * substream -> runtime -> rate / MSEC_PER_SEC ;
10831131 goto out ;
10841132 }
10851133
@@ -1099,14 +1147,13 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
10991147 time_info -> stream_end_offset = ppl_reg .stream_end_offset ;
11001148 do_div (time_info -> stream_end_offset , dai_sample_size );
11011149
1150+ /* convert to host frame time */
1151+ time_info -> stream_start_offset =
1152+ sof_ipc4_frames_dai_to_host (time_info , time_info -> stream_start_offset );
1153+ time_info -> stream_end_offset =
1154+ sof_ipc4_frames_dai_to_host (time_info , time_info -> stream_end_offset );
1155+
11021156out :
1103- /*
1104- * Calculate the wrap boundary need to be used for delay calculation
1105- * The host counter is in bytes, it will wrap earlier than the frames
1106- * based link counter.
1107- */
1108- time_info -> boundary = div64_u64 (~((u64 )0 ),
1109- frames_to_bytes (substream -> runtime , 1 ));
11101157 /* Initialize the delay value to 0 (no delay) */
11111158 time_info -> delay = 0 ;
11121159
@@ -1149,6 +1196,8 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
11491196
11501197 /* For delay calculation we need the host counter */
11511198 host_cnt = snd_sof_pcm_get_host_byte_counter (sdev , component , substream );
1199+
1200+ /* Store the original value to host_ptr */
11521201 host_ptr = host_cnt ;
11531202
11541203 /* convert the host_cnt to frames */
@@ -1167,6 +1216,8 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
11671216 sof_mailbox_read (sdev , time_info -> llp_offset , & llp , sizeof (llp ));
11681217 dai_cnt = ((u64 )llp .reading .llp_u << 32 ) | llp .reading .llp_l ;
11691218 }
1219+
1220+ dai_cnt = sof_ipc4_frames_dai_to_host (time_info , dai_cnt );
11701221 dai_cnt += time_info -> stream_end_offset ;
11711222
11721223 /* In two cases dai dma counter is not accurate
@@ -1200,8 +1251,9 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
12001251 dai_cnt -= time_info -> stream_start_offset ;
12011252 }
12021253
1203- /* Wrap the dai counter at the boundary where the host counter wraps */
1204- div64_u64_rem (dai_cnt , time_info -> boundary , & dai_cnt );
1254+ /* Convert to a common base before comparisons */
1255+ dai_cnt &= DELAY_BOUNDARY ;
1256+ host_cnt &= DELAY_BOUNDARY ;
12051257
12061258 if (substream -> stream == SNDRV_PCM_STREAM_PLAYBACK ) {
12071259 head_cnt = host_cnt ;
@@ -1211,14 +1263,18 @@ static int sof_ipc4_pcm_pointer(struct snd_soc_component *component,
12111263 tail_cnt = host_cnt ;
12121264 }
12131265
1214- if (head_cnt < tail_cnt ) {
1215- time_info -> delay = time_info -> boundary - tail_cnt + head_cnt ;
1216- goto out ;
1217- }
1266+ if (unlikely ( head_cnt < tail_cnt ))
1267+ time_info -> delay = DELAY_BOUNDARY - tail_cnt + head_cnt ;
1268+ else
1269+ time_info -> delay = head_cnt - tail_cnt ;
12181270
1219- time_info -> delay = head_cnt - tail_cnt ;
1271+ if (time_info -> delay > DELAY_MAX ) {
1272+ spcm_dbg_ratelimited (spcm , substream -> stream ,
1273+ "inaccurate delay, host %llu dai_cnt %llu" ,
1274+ host_cnt , dai_cnt );
1275+ time_info -> delay = 0 ;
1276+ }
12201277
1221- out :
12221278 /*
12231279 * Convert the host byte counter to PCM pointer which wraps in buffer
12241280 * and it is in frames
0 commit comments