* 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;
        u64 stream_end_offset;
        u32 llp_offset;
 
-       u64 boundary;
        snd_pcm_sframes_t delay;
 };
 
        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)
 {
        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,
        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;
 
 
        /* 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 */
                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
                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 */
+       dai_cnt &= DELAY_BOUNDARY;
+       host_cnt &= DELAY_BOUNDARY;
 
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
                head_cnt = host_cnt;
                tail_cnt = host_cnt;
        }
 
-       if (head_cnt < tail_cnt) {
-               time_info->delay = time_info->boundary - tail_cnt + head_cnt;
-               goto out;
-       }
-
-       time_info->delay =  head_cnt - tail_cnt;
+       if (unlikely(head_cnt < tail_cnt))
+               time_info->delay = DELAY_BOUNDARY - tail_cnt + head_cnt;
+       else
+               time_info->delay = head_cnt - tail_cnt;
 
-out:
        /*
         * Convert the host byte counter to PCM pointer which wraps in buffer
         * and it is in frames