diff --git a/include/sound/asound.h b/include/sound/asound.h
index c2dff5369d33..8c919eb9bb45 100644
--- a/include/sound/asound.h
+++ b/include/sound/asound.h
@@ -37,4 +37,12 @@
 #endif
 
 #include <uapi/sound/asound.h>
+
+
+static inline struct snd_monotonic_timestamp
+timespec64_to_snd_monotonic_timestamp(struct timespec64 ts)
+{
+	return (struct snd_monotonic_timestamp) { (u32)ts.tv_sec, ts.tv_nsec };
+};
+
 #endif /* __SOUND_ASOUND_H */
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 1231f0a943f1..9c61cf77beb8 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -455,10 +455,36 @@ enum {
 	SNDRV_PCM_AUDIO_TSTAMP_TYPE_LAST = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK_SYNCHRONIZED
 };
 
+#if (__BITS_PER_LONG == 32 && defined(__USE_TIME_BITS64)) || defined __KERNEL__
+/*
+ * We used to use 'struct timespec' here, but that no longer works on
+ * 32 bit architectures since the migration to 64-bit time_t in user
+ * space. Rather than updating all ioctls, we change the internal type
+ * to a new one with the same binary layout as before.
+ *
+ * We use a 'unsigned long' as the base type here to give us a little
+ * extra range over the traditional signed type, but this really
+ * should only be used for CLOCK_MONOTONIC timestamps, not CLOCK_REALTIME,
+ * to avoid all issues with y2038 overflow, hence the name.
+ *
+ * alsa-lib 1.1.6 and earlier are incompatible with this definition and
+ * will break either at compile time or at runtime if built against
+ * an older header while using a 64-bit time_t on a 32-bit architecture,
+ * so if you run into a build problem here, please upgrade to the latest
+ * alsa-lib.
+ */
+struct snd_monotonic_timestamp {
+	__kernel_ulong_t tv_sec;
+	__kernel_ulong_t tv_nsec;
+};
+#else
+#define snd_monotonic_timestamp timespec
+#endif
+
 struct snd_pcm_status {
 	snd_pcm_state_t state;		/* stream state */
-	struct timespec trigger_tstamp;	/* time when stream was started/stopped/paused */
-	struct timespec tstamp;		/* reference timestamp */
+	struct snd_monotonic_timestamp trigger_tstamp;/* time when stream was started/stopped/paused */
+	struct snd_monotonic_timestamp tstamp;	/* reference timestamp */
 	snd_pcm_uframes_t appl_ptr;	/* appl ptr */
 	snd_pcm_uframes_t hw_ptr;	/* hw ptr */
 	snd_pcm_sframes_t delay;	/* current delay in frames */
@@ -467,19 +493,19 @@ struct snd_pcm_status {
 	snd_pcm_uframes_t overrange;	/* count of ADC (capture) overrange detections from last status */
 	snd_pcm_state_t suspended_state; /* suspended stream state */
 	__u32 audio_tstamp_data;	 /* needed for 64-bit alignment, used for configs/report to/from userspace */
-	struct timespec audio_tstamp;	/* sample counter, wall clock, PHC or on-demand sync'ed */
-	struct timespec driver_tstamp;	/* useful in case reference system tstamp is reported with delay */
+	struct snd_monotonic_timestamp audio_tstamp;	/* sample counter, wall clock, PHC or on-demand sync'ed */
+	struct snd_monotonic_timestamp driver_tstamp;	/* useful in case reference system tstamp is reported with delay */
 	__u32 audio_tstamp_accuracy;	/* in ns units, only valid if indicated in audio_tstamp_data */
-	unsigned char reserved[52-2*sizeof(struct timespec)]; /* must be filled with zero */
+	unsigned char reserved[52-2*sizeof(struct snd_monotonic_timestamp)]; /* must be filled with zero */
 };
 
 struct snd_pcm_mmap_status {
 	snd_pcm_state_t state;		/* RO: state - SNDRV_PCM_STATE_XXXX */
 	int pad1;			/* Needed for 64 bit alignment */
 	snd_pcm_uframes_t hw_ptr;	/* RO: hw ptr (0...boundary-1) */
-	struct timespec tstamp;		/* Timestamp */
+	struct snd_monotonic_timestamp tstamp; /* Timestamp */
 	snd_pcm_state_t suspended_state; /* RO: suspended stream state */
-	struct timespec audio_tstamp;	/* from sample counter or wall clock */
+	struct snd_monotonic_timestamp audio_tstamp;	/* from sample counter or wall clock */
 };
 
 struct snd_pcm_mmap_control {
@@ -649,7 +675,7 @@ struct snd_rawmidi_params {
 
 struct snd_rawmidi_status {
 	int stream;
-	struct timespec tstamp;		/* Timestamp */
+	struct snd_monotonic_timestamp tstamp; /* Timestamp */
 	size_t avail;			/* available bytes */
 	size_t xruns;			/* count of overruns since last status (in bytes) */
 	unsigned char reserved[16];	/* reserved for future use */
@@ -761,7 +787,7 @@ struct snd_timer_params {
 };
 
 struct snd_timer_status {
-	struct timespec tstamp;		/* Timestamp - last update */
+	struct snd_monotonic_timestamp tstamp;		/* Timestamp - last update */
 	unsigned int resolution;	/* current period resolution in ns */
 	unsigned int lost;		/* counter of master tick lost */
 	unsigned int overrun;		/* count of read queue overruns */
@@ -811,7 +837,7 @@ enum {
 
 struct snd_timer_tread {
 	int event;
-	struct timespec tstamp;
+	struct snd_monotonic_timestamp tstamp;
 	unsigned int val;
 };
 
diff --git a/sound/core/compat.h b/sound/core/compat.h
new file mode 100644
index 000000000000..7a08d6e34955
--- /dev/null
+++ b/sound/core/compat.h
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef SND_CORE_COMPAT_H
+#define SND_CORE_COMPAT_H
+
+struct compat_snd_monotonic_timestamp {
+	__u32	tv_sec;
+	__u32	tv_nsec;
+};
+
+#endif
diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c
index e21d3f35a724..40e9be542322 100644
--- a/sound/core/pcm_compat.c
+++ b/sound/core/pcm_compat.c
@@ -23,6 +23,8 @@
 #include <linux/compat.h>
 #include <linux/slab.h>
 
+#include "compat.h"
+
 static int snd_pcm_ioctl_delay_compat(struct snd_pcm_substream *substream,
 				      s32 __user *src)
 {
@@ -189,8 +191,8 @@ static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream,
 
 struct snd_pcm_status32 {
 	s32 state;
-	struct compat_timespec trigger_tstamp;
-	struct compat_timespec tstamp;
+	struct compat_snd_monotonic_timestamp trigger_tstamp;
+	struct compat_snd_monotonic_timestamp tstamp;
 	u32 appl_ptr;
 	u32 hw_ptr;
 	s32 delay;
@@ -199,10 +201,10 @@ struct snd_pcm_status32 {
 	u32 overrange;
 	s32 suspended_state;
 	u32 audio_tstamp_data;
-	struct compat_timespec audio_tstamp;
-	struct compat_timespec driver_tstamp;
+	struct compat_snd_monotonic_timestamp audio_tstamp;
+	struct compat_snd_monotonic_timestamp driver_tstamp;
 	u32 audio_tstamp_accuracy;
-	unsigned char reserved[52-2*sizeof(struct compat_timespec)];
+	unsigned char reserved[52-2*sizeof(struct compat_snd_monotonic_timestamp)];
 } __attribute__((packed));
 
 
@@ -269,11 +271,9 @@ struct snd_pcm_status_x32 {
 	struct __kernel_timespec audio_tstamp;
 	struct __kernel_timespec driver_tstamp;
 	u32 audio_tstamp_accuracy;
-	unsigned char reserved[52-2*sizeof(struct timespec)];
+	unsigned char reserved[52-2*sizeof(struct __kernel_timespec)];
 } __packed;
 
-#define put_timespec(src, dst) copy_to_user(dst, src, sizeof(*dst))
-
 static int snd_pcm_status_user_x32(struct snd_pcm_substream *substream,
 				   struct snd_pcm_status_x32 __user *src,
 				   bool ext)
@@ -461,14 +461,13 @@ static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream,
 	return err;
 }
 
-
 struct snd_pcm_mmap_status32 {
 	s32 state;
 	s32 pad1;
 	u32 hw_ptr;
-	struct compat_timespec tstamp;
+	struct compat_snd_monotonic_timestamp tstamp;
 	s32 suspended_state;
-	struct compat_timespec audio_tstamp;
+	struct compat_snd_monotonic_timestamp audio_tstamp;
 } __attribute__((packed));
 
 struct snd_pcm_mmap_control32 {
@@ -535,10 +534,11 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream,
 	snd_pcm_stream_unlock_irq(substream);
 	if (put_user(sstatus.state, &src->s.status.state) ||
 	    put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
-	    compat_put_timespec(&sstatus.tstamp, &src->s.status.tstamp) ||
+	    put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) ||
+	    put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) ||
 	    put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
-	    compat_put_timespec(&sstatus.audio_tstamp,
-		    &src->s.status.audio_tstamp) ||
+	    put_user(sstatus.audio_tstamp.tv_sec, &src->s.status.audio_tstamp.tv_sec) ||
+	    put_user(sstatus.audio_tstamp.tv_nsec, &src->s.status.audio_tstamp.tv_nsec) ||
 	    put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
 	    put_user(scontrol.avail_min, &src->c.control.avail_min))
 		return -EFAULT;
@@ -553,10 +553,10 @@ struct snd_pcm_mmap_status_x32 {
 	s32 pad1;
 	u32 hw_ptr;
 	u32 pad2; /* alignment */
-	struct timespec tstamp;
+	struct __kernel_timespec tstamp;
 	s32 suspended_state;
 	s32 pad3;
-	struct timespec audio_tstamp;
+	struct __kernel_timespec audio_tstamp;
 } __packed;
 
 struct snd_pcm_mmap_control_x32 {
@@ -624,9 +624,11 @@ static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream,
 	snd_pcm_stream_unlock_irq(substream);
 	if (put_user(sstatus.state, &src->s.status.state) ||
 	    put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) ||
-	    put_timespec(&sstatus.tstamp, &src->s.status.tstamp) ||
+	    put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp.tv_sec) ||
+	    put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp.tv_nsec) ||
 	    put_user(sstatus.suspended_state, &src->s.status.suspended_state) ||
-	    put_timespec(&sstatus.audio_tstamp, &src->s.status.audio_tstamp) ||
+	    put_user(sstatus.audio_tstamp.tv_sec, &src->s.status.audio_tstamp.tv_sec) ||
+	    put_user(sstatus.audio_tstamp.tv_nsec, &src->s.status.audio_tstamp.tv_nsec) ||
 	    put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) ||
 	    put_user(scontrol.avail_min, &src->c.control.avail_min))
 		return -EFAULT;
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 9278b5ded9a2..e676356bd3be 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -162,7 +162,7 @@ static void xrun(struct snd_pcm_substream *substream)
 		struct timespec64 tstamp;
 
 		snd_pcm_gettime(runtime, &tstamp);
-		runtime->status->tstamp = timespec64_to_timespec(tstamp);
+		runtime->status->tstamp = timespec64_to_snd_monotonic_timestamp(tstamp);
 	}
 	snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
 	if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {
@@ -256,8 +256,8 @@ static void update_audio_tstamp(struct snd_pcm_substream *substream,
 	if (runtime->status->audio_tstamp.tv_sec != audio_tstamp->tv_sec ||
 	    runtime->status->audio_tstamp.tv_nsec != audio_tstamp->tv_nsec) {
 		runtime->status->audio_tstamp =
-			timespec64_to_timespec(*audio_tstamp);
-		runtime->status->tstamp = timespec64_to_timespec(*curr_tstamp);
+			timespec64_to_snd_monotonic_timestamp(*audio_tstamp);
+		runtime->status->tstamp = timespec64_to_snd_monotonic_timestamp(*curr_tstamp);
 	}
 
 
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index c57dd4b30198..d27c6252e14c 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -884,14 +884,13 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
 	status->suspended_state = runtime->status->suspended_state;
 	if (status->state == SNDRV_PCM_STATE_OPEN)
 		goto _end;
-	status->trigger_tstamp = timespec64_to_timespec(runtime->trigger_tstamp);
+	status->trigger_tstamp = timespec64_to_snd_monotonic_timestamp(runtime->trigger_tstamp);
 	if (snd_pcm_running(substream)) {
 		snd_pcm_update_hw_ptr(substream);
 		if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) {
 			status->tstamp = runtime->status->tstamp;
-			status->driver_tstamp = timespec64_to_timespec(runtime->driver_tstamp);
-			status->audio_tstamp =
-				runtime->status->audio_tstamp;
+			status->driver_tstamp = timespec64_to_snd_monotonic_timestamp(runtime->driver_tstamp);
+			status->audio_tstamp = runtime->status->audio_tstamp;
 			if (runtime->audio_tstamp_report.valid == 1)
 				/* backwards compatibility, no report provided in COMPAT mode */
 				snd_pcm_pack_audio_tstamp_report(&status->audio_tstamp_data,
@@ -906,7 +905,7 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
 			struct timespec64 tstamp;
 
 			snd_pcm_gettime(runtime, &tstamp);
-			status->tstamp = timespec64_to_timespec(tstamp);
+			status->tstamp = timespec64_to_snd_monotonic_timestamp(tstamp);
 		}
 	}
  _tstamp_end:
diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c
index f69764d7cdd7..bf8b0c9da690 100644
--- a/sound/core/rawmidi_compat.c
+++ b/sound/core/rawmidi_compat.c
@@ -55,7 +55,7 @@ static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile,
 
 struct snd_rawmidi_status32 {
 	s32 stream;
-	struct compat_timespec tstamp;
+	struct snd_monotonic_timestamp tstamp;
 	u32 avail;
 	u32 xruns;
 	unsigned char reserved[16];
@@ -85,7 +85,8 @@ static int snd_rawmidi_ioctl_status_compat(struct snd_rawmidi_file *rfile,
 	if (err < 0)
 		return err;
 
-	if (compat_put_timespec(&status.tstamp, &src->tstamp) ||
+	if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
+	    put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
 	    put_user(status.avail, &src->avail) ||
 	    put_user(status.xruns, &src->xruns))
 		return -EFAULT;
@@ -98,14 +99,12 @@ static int snd_rawmidi_ioctl_status_compat(struct snd_rawmidi_file *rfile,
 struct snd_rawmidi_status_x32 {
 	s32 stream;
 	u32 rsvd; /* alignment */
-	struct timespec tstamp;
+	struct __kernel_timespec tstamp;
 	u32 avail;
 	u32 xruns;
 	unsigned char reserved[16];
 } __attribute__((packed));
 
-#define put_timespec(src, dst) copy_to_user(dst, src, sizeof(*dst))
-
 static int snd_rawmidi_ioctl_status_x32(struct snd_rawmidi_file *rfile,
 					struct snd_rawmidi_status_x32 __user *src)
 {
@@ -130,7 +129,8 @@ static int snd_rawmidi_ioctl_status_x32(struct snd_rawmidi_file *rfile,
 	if (err < 0)
 		return err;
 
-	if (put_timespec(&status.tstamp, &src->tstamp) ||
+	if (put_user(status.tstamp.tv_sec, &src->tstamp.tv_sec) ||
+	    put_user(status.tstamp.tv_nsec, &src->tstamp.tv_nsec) ||
 	    put_user(status.avail, &src->avail) ||
 	    put_user(status.xruns, &src->xruns))
 		return -EFAULT;
diff --git a/sound/core/timer.c b/sound/core/timer.c
index a77b4619f1b7..b7059a9e9edc 100644
--- a/sound/core/timer.c
+++ b/sound/core/timer.c
@@ -1309,7 +1309,7 @@ static void snd_timer_user_ccallback(struct snd_timer_instance *timeri,
 		return;
 	memset(&r1, 0, sizeof(r1));
 	r1.event = event;
-	r1.tstamp = timespec64_to_timespec(*tstamp);
+	r1.tstamp = timespec64_to_snd_monotonic_timestamp(*tstamp);
 	r1.val = resolution;
 	spin_lock_irqsave(&tu->qlock, flags);
 	snd_timer_user_append_to_tqueue(tu, &r1);
@@ -1352,7 +1352,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
 	if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) &&
 	    tu->last_resolution != resolution) {
 		r1.event = SNDRV_TIMER_EVENT_RESOLUTION;
-		r1.tstamp = timespec64_to_timespec(tstamp);
+		r1.tstamp = timespec64_to_snd_monotonic_timestamp(tstamp);
 		r1.val = resolution;
 		snd_timer_user_append_to_tqueue(tu, &r1);
 		tu->last_resolution = resolution;
@@ -1366,14 +1366,14 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
 		prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1;
 		r = &tu->tqueue[prev];
 		if (r->event == SNDRV_TIMER_EVENT_TICK) {
-			r->tstamp = timespec64_to_timespec(tstamp);
+			r->tstamp = timespec64_to_snd_monotonic_timestamp(tstamp);
 			r->val += ticks;
 			append++;
 			goto __wake;
 		}
 	}
 	r1.event = SNDRV_TIMER_EVENT_TICK;
-	r1.tstamp = timespec64_to_timespec(tstamp);
+	r1.tstamp = timespec64_to_snd_monotonic_timestamp(tstamp);
 	r1.val = ticks;
 	snd_timer_user_append_to_tqueue(tu, &r1);
 	append++;
@@ -1852,7 +1852,7 @@ static int snd_timer_user_status(struct file *file,
 	if (!tu->timeri)
 		return -EBADFD;
 	memset(&status, 0, sizeof(status));
-	status.tstamp = timespec64_to_timespec(tu->tstamp);
+	status.tstamp = timespec64_to_snd_monotonic_timestamp(tu->tstamp);
 	status.resolution = snd_timer_resolution(tu->timeri);
 	status.lost = tu->timeri->lost;
 	status.overrun = tu->overrun;
diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c
index e00f7e399e46..960d1075071f 100644
--- a/sound/core/timer_compat.c
+++ b/sound/core/timer_compat.c
@@ -22,6 +22,8 @@
 
 #include <linux/compat.h>
 
+#include "compat.h"
+
 /*
  * ILP32/LP64 has different size for 'long' type. Additionally, the size
  * of storage alignment differs depending on architectures. Here, '__packed'
@@ -84,7 +86,7 @@ static int snd_timer_user_info_compat(struct file *file,
 }
 
 struct snd_timer_status32 {
-	struct compat_timespec tstamp;
+	struct compat_snd_monotonic_timestamp tstamp;
 	u32 resolution;
 	u32 lost;
 	u32 overrun;
