Appendix · reference

RK3399 I2S0 (controller)

8-channel I2S transmitter feeding the RT5640 codec at 48 kHz / 16-bit stereo

Identity

PartRockchip RK3399 I2S0 (the 8-channel I2S/PCM controller)
RoleSerializes PCM samples from the FreeBSD sound subsystem onto the I2S bus to the RT5640 codec; sources MCLK/BCLK/LRCK
Bus / addressMMIO 0xff880000
GPIO / IRQInternal SoC interrupt; clock from clk_i2sout (CRU); pinmux on the i2s0 pad group
DatasheetRK3399 TRM (I2S section)
Pine64 wikiPinePhone Pro hardware
Schematicsheets 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

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