Fix clicking speakers after suspend on the Surface Go 2

Posted at — Oct 31, 2026

Distorted sound after suspend

My Microsoft System specs:
CPU: Intel Core m3-8100Y,
Audio: Realtek ALC298 (snd_hda_intel),
OS: Fedora 44, kernel 7.2.9,
PipeWire 1.6.9, chrony 4.9
runs Fedora without problems, except for one thing: after waking up from suspend, the internal speakers sometimes click every one to two seconds. It happens with YouTube in Firefox as well as with local videos. It doesn’t happen after every resume, and a reboot always fixes it.

What it wasn’t

The obvious suspects all looked fine:

  1. No errors from snd_hda_intel or PipeWire in the journal around the resumes.
  2. The TSC clocksource was stable, no “unstable” warnings after resume.
  3. The codec’s power saving (snd_hda_intel power_save) suspended and resumed normally.
  4. The CPU wasn’t throttled.

As it turned out, bigger PipeWire buffers (default.clock.min-quantum) can’t fix this either.

Measuring while it clicks

The next time the clicking started, I measured while a YouTube video was playing. pw-top showed the ERR (xrun) counter of the speaker sink going up by one or two every second, while Firefox had none and PipeWire needed only a fraction of a millisecond per 42 ms cycle:

S   ID  QUANT   RATE    WAIT    BUSY   W/Q   B/Q  ERR FORMAT           NAME
R   48   2048  48000 100,3us  33,8us  0,00  0,00  10599    S32LE 2 48000 alsa_output.pci-0000_00_1f.3.analog-stereo
R  104   3600  48000  39,9us  45,6us  0,00  0,00    0    F32LE 2 48000  + Firefox
...                                                     ## 6 seconds later
R   48   2048  48000 250,3us  73,9us  0,01  0,00  10609    S32LE 2 48000 alsa_output.pci-0000_00_1f.3.analog-stereo

So it was not a CPU problem. To see what the driver does, I sampled the ALSA status of the speaker stream every few milliseconds:

while sleep 0.005; do grep -E "trigger_time|tstamp|hw_ptr" /proc/asound/card0/pcm0p/sub0/status; done

trigger_time changes whenever the stream is (re)started, and hw_ptr counts the frames the sound card has played at time tstamp. The stream was restarted every 0.587 s, and in between, the card played about 52,400 frames per second instead of 48,000:

# stream start   runs for   frames per second (measured against CLOCK_MONOTONIC)
16081.751        0.441 s    52398
16082.338        0.582 s    52390
16082.925        0.580 s    52386
16083.512        0.583 s    52332

PipeWire’s buffer ran dry, PipeWire restarted the stream, and every restart was an audible click.

A slow clock, not a fast sound card

A sound card doesn’t suddenly run 9 % fast. But 52,365 / 48,000 is almost exactly 12/11, which is what you get when the reference clock runs 1/12 (8.33 %) slow. And 8.33 % is the fastest rate at which chrony’s maxslewrate defaults to 83,333 ppm, which is 1/12. The kernel slews the clock by changing its tick length, here 9,167 instead of the nominal 10,000 microseconds. This changes the speed of CLOCK_REALTIME and CLOCK_MONOTONIC alike; only CLOCK_MONOTONIC_RAW is unaffected. corrects the system clock gradually. PipeWire times its writes to the sound card with CLOCK_MONOTONIC. With that clock slowed down by 8.33 %, the card seems to play 9 % too fast, more than PipeWire can compensate.

chrony confirmed it:

$ chronyc tracking
...
System time     : 6399.871582031 seconds fast of NTP time
...

The clock was almost two hours ahead, and chrony was slowly slewing it back. Its log showed when it started:

$ journalctl -u chronyd -o short-iso | grep "System clock wrong"
2026-10-17T12:19:12+02:00 fedora-2.fritz.box chronyd[738]: System clock wrong by -7200.344686 seconds
2026-10-17T18:19:02+02:00 fedora-2.fritz.box chronyd[738]: System clock wrong by -6709.801330 seconds
2026-10-17T20:10:32+02:00 fedora-2.fritz.box chronyd[738]: System clock wrong by -6566.457939 seconds

The first entry is exactly two hours, right after a resume, and CEST is UTC+2. timedatectl showed why:

$ timedatectl
...
          RTC in local TZ: yes

Why the clock jumps

The Surface’s hardware clock (RTC) was set to local time, most likely a leftover from the Windows it shipped with. On most resumes, the kernel adds the time spent asleep and everything is fine. But after some s2idle resumes, the kernel sets the system clock straight to the RTC value and reads it as On x86, the kernel normally adds the sleep time in timekeeping_resume(), based on the CMOS RTC. If the RTC’s seconds didn’t change during the last s2idle cycle, nothing is added, and the RTC driver’s rtc_resume() steps in, using a snapshot it never took. The result is: system clock = RTC value. With the RTC in UTC this moves the clock by a second at most, with the RTC in local time by the UTC offset. , which puts it two hours ahead. Within one week this happened three times, each time when waking up after a long suspend.

chrony would normally fix that quickly. But Fedora’s default makestep 1.0 3 only allows chrony to step the clock during its first three updates after starting. After that it can only slew, at most 8.33 % faster or slower. Correcting two hours that way takes about a day of uptime, and the speakers click the whole time. A reboot restarts chronyd, which then steps the clock right away. That’s why rebooting always fixed it.

The proof: while the video was clicking, sudo chronyc makestep stepped the clock to the correct time. The clicks stopped at once. The stream then ran through the whole 10 s measurement without a single restart, and the card played 47,998 frames per second.

Fix

When the clicks are there, step the clock right away (no reboot needed):

sudo chronyc makestep

For a permanent fix, keep the RTC in UTC and allow chrony to step large offsets at any time:

# check whether you are affected
timedatectl | grep "RTC in local TZ"
journalctl -u chronyd | grep "System clock wrong"

# keep the RTC in UTC; this copies the current system time into the RTC,
# so check first that "chronyc tracking" shows a small offset
sudo timedatectl set-local-rtc 0

# step offsets above 1 s at any time, instead of slewing for hours
sudo sed -i 's/^makestep 1.0 3$/makestep 1.0 -1/' /etc/chrony.conf
sudo systemctl restart chronyd

# reboot once, otherwise the kernel keeps writing local time to the RTC until the next boot
sudo reboot

With the RTC in UTC, the kernel can only move the clock by about a second on resume. If anything else ever puts the clock off by more than a second, chrony now steps it back within seconds. A step doesn’t change the speed of CLOCK_MONOTONIC, so PipeWire doesn’t notice it at all.

If you still dual-boot Windows, either make Windows keep the RTC in UTC as well (registry value HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation\RealTimeIsUniversal, DWORD 1), or apply only the chrony change.

Upstream fix

The clock jump itself is a kernel bug, and it’s already known. Fedora bug rhbz#2543517 describes the same jump on a Lenovo ThinkBook at UTC+3, where chrony’s slew made the desktop laggy for about 36 hours. Alperen Kılıç has posted a It makes rtc_resume() return early when rtc_suspend() didn’t take a snapshot. The bug has been in the kernel since version 4.1 from 2015. to the Linux RTC mailing list: rtc: class: Do not inject sleep time without a suspend snapshot. At the end of October 2026 it isn’t merged yet, and the Fedora bug is still open. Until a kernel with the patch arrives, the steps above are needed.