Identity
| Part | Rockchip RK3399 I2S0 (the 8-channel I2S/PCM controller) |
| Role | Serializes PCM samples from the FreeBSD sound subsystem onto the I2S bus to the RT5640 codec; sources MCLK/BCLK/LRCK |
| Bus / address | MMIO 0xff880000 |
| GPIO / IRQ | Internal SoC interrupt; clock from clk_i2sout (CRU); pinmux on the i2s0 pad group |
| Datasheet | RK3399 TRM (I2S section) |
| Pine64 wiki | PinePhone Pro hardware |
| Schematic | sheets 6–8 (I2S0 routing to RT5640) |
Status — ● working
I2S0 transmits real audio data on every boot: MCLK 12.288 MHz, BCLK
3.072 MHz, LRCK 48 kHz, 16-bit stereo. The TX FIFO interrupt fires
reliably, the ISR drains the play buffer, and the codec receives valid
samples. Every playback problem encountered during the audio bring-up
arc turned out to be in the codec, the analog rails, or the external
amplifier once 09c23b1 rk_i2s: reset play_ptr + flush TX FIFO on PCMTRIG_START reset play_ptr on
PCMTRIG_START and 4a3b61d rk_i2s: align slot timing with VDW_16 / S16_LE caps aligned slot timing with
VDW_16.
RX capture is now isolated to a lower-level controller/codec boundary:
kernel #186 proved that a live capture has RT5640 ADC power on,
I2S_XFER=2, RX FIFO level nonzero, regular RX interrupts, and still
rx_stats.min_sample=max_sample=0. Linux’s rockchip_snd_rxctrl() starts
both I2S_XFER_TXS_START and I2S_XFER_RXS_START for capture because the
controller wants TX/RX started together. FreeBSD previously started only
RXS_START; the next kernel matches Linux by starting both engines for
one-way streams and only clearing XFER when neither playback nor capture
is active. Kernel #187 confirmed the diagnosis: with I2S_XFER=3,
RX min/max moved off zero and a 256 KiB DMIC capture contained real sample
words instead of an all-zero stream. Kernel #188 then removed the
illegal RT5640 I2C reads from the PCM trigger path; capture closes cleanly
without the “sleeping thread owns a non-sleepable lock” assertion. The next
failure was full-duplex monitor setup: FreeBSD’s START path was clearing
one FIFO while the RK3399 shared serial engine was already being run in the
Linux-style TX+RX mode. That left I2S_CLR_TXC stuck at 1 and made
playback return EIO. Linux only clears both FIFOs after the last stream
has stopped, with a small delay after dropping I2S_XFER; the overlay now
matches that sequencing. Kernel #189 proved the result with playback-only,
capture-only, and dd if=/dev/dsp0 | dd of=/dev/dsp0 full-duplex monitor
runs: CLR=0, clear-timeout counters stayed at zero, and TX/RX frame
counters advanced.
Driver
- Our tree:
src/sys/arm64/rockchip/rk_i2s.c(801 lines) — I2S controller driver, includes the FIFO ISR and FreeBSD-PCM glue (rk_i2s_dai_*). - Linux mainline:
sound/soc/rockchip/rockchip_i2s.c
The driver lives directly under arm64/rockchip rather than
sys/dev/sound/... because it owns the SoC clock + reset + pinctrl
plumbing as well as the I2S protocol layer. Sample movement is PIO
inside the ISR (124 bytes per interrupt at 48 kHz/stereo) — no DMA;
plumbing the PL330 controller would lower CPU overhead and is in the
“moderate” pile in HARDWARE.md.
The original vchan→hardware zero-buffer mystery (essay 13) traced
through this driver because the per-interrupt instrumentation lives
here (rk_i2s.c:410). The eventual root cause was a TLV polarity
bug in the codec, but the visibility into the I2S ready-pointer / FIFO
state is what made the diagnosis falsifiable.
Open work
- Move TX/RX off PIO onto PL330 DMA (improves CPU overhead, also unlocks low-latency audio paths).
- Move RT5640 capture/playback power gating out of the PCM trigger lock
path and into a sleepable taskqueue, so
#188’s safe no-op trigger does not leave ADC/DAC rails powered forever. - The driver still has noisy debug
device_printfs in the ISR; gate behind a debug sysctl before any kind of upstreaming.
Related
- On-device audio — the I2S/codec bring-up.
- Component: RT5640 — codec on the other end of the I2S link.
- Component: AW8737 — external speaker amp.
- Hardware reference — audio block summary.