Skip to content

USB audio kernel (CM5)

On a CM5 with the stock kernel the ESP32-S3 USB sound card enumerates, shows up in aplay -l, PipeWire picks it as the default output and playback “runs” - but the speakers and the headphone jack stay completely silent. The keyboard and other USB devices work.

  • The ESP32-S3 sound card is a full-speed (12 Mbit/s) USB Audio Class device behind the board’s high-speed FE2.1 hub. The hub’s Transaction Translator (TT) converts, so the host has to send each audio packet as a series of split transactions (start-split / complete-split) timed to USB microframes.
  • On the CM5 the only controller wired to the module’s USB pins is dwc2, and dwc2 leaves that microframe timing to the driver.
  • When the TT schedule is empty, dwc2_uframe_schedule_split() places the isochronous-OUT split at relative microframe 0, which puts the first, data-carrying start-split on the wire in microframe 7 of the previous frame. For packets larger than 188 bytes the remaining start-splits fall into the next frame, so one full-speed transaction straddles a high-speed frame boundary.
  • The Linux EHCI driver forbids exactly this (commit 8c05dc598e5b, “USB: EHCI: No SSPLIT allowed in uframe 7”) and never starts an isochronous full-speed transaction later than microframe 5. The FE2.1’s TT silently discards such transactions: every URB completes without an error bit, and nothing reaches the full-speed bus.

A 24-line change in drivers/usb/dwc2/hcd_queue.c: dwc2_uframe_schedule_split() no longer picks relative microframe 0 for isochronous OUT splits.

Upstream pull request raspberrypi/linux#7554 - “usb: dwc2: host: No ISOC OUT start-split allowed in uframe 7” (open at the time of writing)
Related, already merged #6936 and #7290 fix interrupt splits (keyboards and mice behind a hub) - they do not cover isochronous audio
Our build Raspberry Pi rpi-6.18.y (6.18.44, base ce0873cb5e0c) + the patch, bcm2712_defconfig, CONFIG_LOCALVERSION="-dwc2fix", make bindeb-pkg
Download release kernel-dwc2fix-6.18.44-4 - linux-image + linux-headers debs and SHA256SUMS

Once the pull request is merged and released in Raspberry Pi’s kernel packages, the stock kernel will play audio and this page will no longer be needed.

The .deb from make bindeb-pkg has none of Raspberry Pi OS’s boot hooks, so the boot files are placed by hand - the same steps the image build runs:

Terminal window
REL=https://github.com/TypixNode/pi-gen/releases/download/kernel-dwc2fix-6.18.44-4
mkdir -p ~/dwc2fix && cd ~/dwc2fix
curl -fLO "$REL/SHA256SUMS"
for f in $(awk '{print $2}' SHA256SUMS); do curl -fLO "$REL/$f"; done
sha256sum -c SHA256SUMS
sudo dpkg -i linux-image-*.deb linux-headers-*.deb
KVER=$(ls /lib/modules | grep dwc2fix | head -n1)
sudo update-initramfs -c -k "$KVER"
# boot files: uncompressed kernel image + initramfs under our own names
if [ "$(od -An -tx1 -N2 /boot/vmlinuz-$KVER | tr -d ' ')" = 1f8b ]; then
zcat "/boot/vmlinuz-$KVER" | sudo tee /boot/firmware/kernel-dwc2fix.img >/dev/null
else
sudo cp "/boot/vmlinuz-$KVER" /boot/firmware/kernel-dwc2fix.img
fi
sudo cp "/boot/initrd.img-$KVER" /boot/firmware/initramfs-dwc2fix
# device trees and overlays built with this kernel
sudo cp /usr/lib/linux-image-$KVER/broadcom/bcm2712*.dtb /boot/firmware/
sudo cp /usr/lib/linux-image-$KVER/overlays/* /boot/firmware/overlays/

Then boot it on the CM5 only - append to /boot/firmware/config.txt:

[cm5]
kernel=kernel-dwc2fix.img
initramfs initramfs-dwc2fix followkernel
[all]

Reboot and check:

Terminal window
uname -r # 6.18.44-dwc2fix+
speaker-test -c 2 -t sine -l 1
  • Fall back to the stock kernel by commenting out the kernel= line; the stock kernel_2712.img stays installed and keeps being updated by apt.
  • An apt upgrade of the stock kernel writes its own device trees back over the ones copied above. That is harmless for the stock kernel; repeat the two cp lines if the dwc2fix kernel misbehaves after an upgrade.
  • If the battery driver is installed through DKMS, it is built for this kernel automatically, because the headers package is installed alongside.
  • Why not force the whole USB bus to full speed instead? That avoids splits, but low-speed devices (many mice) then fail to enumerate behind the hub, and every USB device drops to 12 Mbit/s. We tried it; the patched kernel is the better trade.