# X6200 Audio Architecture A detailed technical account of how audio works on the Xiegu X6200, reverse-engineered during Mestre development (May 2026). ## Overview The X6200 has two distinct audio sources and one audio output path. Understanding the distinction between them is essential for any software that wants to produce sound on this radio. ``` ┌─────────────────────────────────────────────────────────┐ │ X6200 Hardware │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ RF Front │ │ Allwinner│ │ │ │ End │ │ R16 │ (SoC / Application CPU) │ │ │ + FPGA │ │ │ │ │ └────┬─────┘ └────┬─────┘ │ │ │ RX audio │ DAC audio │ │ │ (analog) │ (digital → analog via codec) │ │ └──────┬────────┘ │ │ │ │ │ ┌────▼─────┐ │ │ │ Audio │ ← voice_rec bit selects source │ │ │ MUX │ (controlled via I2C to MCU) │ │ └────┬─────┘ │ │ │ │ │ ┌────▼─────┐ │ │ │ PA │ ← Power Amplifier │ │ │ (Class D)│ enabled by rxvol_set > 0 │ │ └────┬─────┘ │ │ │ │ │ ┌────▼─────┐ │ │ │ Speaker │ │ │ └──────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ## The Two Audio Sources ### Source 1: RX-Radio Audio (default) The RF front-end and FPGA process the received RF signal and produce an analog audio output — the classic "radio receiver sound". This is the default audio source at power-on. It is completely independent of the SoC and ALSA; it bypasses the codec entirely and is fed directly into the audio MUX. This is why you hear receiver noise immediately when the X6200 boots, even before Linux has fully started. ### Source 2: SoC DAC Audio The Allwinner R16 SoC contains an integrated audio codec (`sun8i-a33-audio`) which can produce digital audio, converted to analog via the DAC. This is the source used by software running on the SoC — piHPSDR, aplay, PulseAudio, etc. This source goes through the standard Linux ALSA stack: ``` Application (piHPSDR, aplay) → PulseAudio (mixing, resampling) → ALSA (hw:0,0 / pcmC0D0p) → sun8i-a33-audio codec driver → DAC (analog output) → Audio MUX → PA → Speaker ``` ## The Audio MUX — The Critical Control Point A hardware multiplexer inside the MCU/FPGA selects which source reaches the power amplifier and speaker. This MUX is controlled via I2C, **not** via ALSA or any Linux kernel interface. ### I2C Protocol The X6200 MCU sits on I2C bus 0, address `0x72`. All hardware control uses the X6200Control library protocol: a 6-byte write transaction consisting of a 2-byte register address followed by a 4-byte value. The audio MUX is controlled by the `voice_rec` bit in the `sple_atue_trx` register (command index 12, address `0x0030`): | voice_rec bit | Audio source heard in speaker | |---------------|-------------------------------| | 0 (default) | RX-radio (RF receiver) | | 1 | SoC DAC (software audio) | **To switch to SoC DAC audio:** ```bash i2ctransfer -y 0 w6@0x72 0x00 0x30 0x08 0x00 0x00 0x00 ``` **To switch back to RX-radio audio:** ```bash i2ctransfer -y 0 w6@0x72 0x00 0x30 0x00 0x00 0x00 0x00 ``` ## The Power Amplifier — The Other Critical Control Point The speaker PA (power amplifier) is not always on. It is enabled as a side effect of setting the speaker volume via `x6200_control_rxvol_set()`. When `rxvol = 0` (the default after `x6200_control_init()`), the PA is effectively off and nothing is heard regardless of the MUX position or ALSA configuration. This is why setting the correct mixer levels alone is not sufficient — the MCU must be told to activate the PA via the I2C control protocol. ## The ALSA Codec — Clock Dependency The `sun8i-a33-audio` codec requires its master clock (`pll-audio`, 22.5792 MHz) to be running for DAC output to work. The kernel's clock framework only enables this clock when a consumer holds an active reference — i.e., when an application has the PCM device open. **This is the most subtle requirement:** if no process holds `/dev/snd/pcmC0D0p` open, the audio clocks are disabled and the codec produces no output, even if the ALSA mixer is correctly configured and voice_rec is set to DAC mode. PulseAudio, running in system mode, holds the PCM device open continuously. This is what keeps the clocks running at all times and makes reliable audio possible. ``` PulseAudio running → pcmC0D0p open → pll-audio enabled → codec active ``` Without PulseAudio (or equivalent), every `aplay` invocation would need to wait for the codec to stabilise after the clock starts — and the X6200 MCU may reset the MUX during this window. ## The ALSA Mixer — Required Configuration The codec's internal mixer must be configured correctly for audio to flow from the DAC to the analog output. The critical controls are: | numid | Name | Required value | |-------|-----------------------------------|----------------| | 2 | AIF1 DA0 Playback Volume | 160, 160 | | 7 | Headphone Playback Volume | 58 | | 8 | Headphone Playback Switch | on, on | | 20 | AIF1 Data Digital ADC Cap. Switch | on, on | | 26 | AIF1 DA0 Stereo Playback Route | 3, 3 (Mix Mono)| | 28 | AIF1 Slot 0 Digital DAC PB Switch | on, on | | 31 | DAC Playback Switch | off, off | Note that `DAC Playback Switch` (numid=31) must be **off** — this is counterintuitive but correct. The signal path goes through AIF1, not the direct DAC path. These values are saved in `/var/lib/alsa/asound.state` and restored at boot via `alsactl restore` (init script `S50alsa`). ## Complete Initialisation Sequence The following sequence must be performed in this exact order at boot: ### Step 1: ALSA mixer state (S50alsa) ```bash alsactl restore ``` Restores the correct mixer levels from `/var/lib/alsa/asound.state`. ### Step 2: PulseAudio (S50pulseaudio) ```bash pulseaudio --system --daemonize --disallow-exit --exit-idle-time=-1 ``` Starts PulseAudio in system mode. This opens `pcmC0D0p` and keeps the ALSA clocks running continuously. ### Step 3: X6200 hardware init (S70mestre-audio) ```python lib.x6200_control_init() # Establishes I2C state, sends all_cmd lib.x6200_control_spmode_set(False) # Speaker mode (not headphone) lib.x6200_control_rxvol_set(50) # Enables PA, sets volume (0-100) ``` `x6200_control_init()` sends a full command packet to the MCU via I2C, establishing all register values. **It sets rxvol=0** (PA off) as its default. `rxvol_set(50)` must follow immediately to enable the PA. ### Step 4: Switch audio MUX to SoC DAC (S70mestre-audio) ```bash i2ctransfer -y 0 w6@0x72 0x00 0x30 0x08 0x00 0x00 0x00 ``` Sets the `voice_rec` bit, switching the speaker from RX-radio audio to SoC DAC audio. From this point, any audio played via PulseAudio or directly to `hw:0,0` (44100 Hz, stereo, S16_LE) will be heard in the speaker. ## Mestre's Implementation Mestre's init scripts implement this sequence: ``` S50alsa → alsactl restore S50pulseaudio → pulseaudio --system S70mestre-audio → x6200_control_init + rxvol_set + voice_rec switch S96keepalive → I2C PMU keepalive (prevents automatic power-off) ``` The audio MUX switch command (`voice_rec=on`) is permanent until the next `x6200_control_init()` call — it does not need to be repeated. However, the PMU keepalive (separate from audio) must run continuously or the radio will power off after approximately 180 seconds. ## Volume Control Speaker volume is controlled via `x6200_control_rxvol_set(vol)` where `vol` is 0-100. This sends an I2C command to the MCU which adjusts an analog gain stage. **It is not reflected in the ALSA mixer** and cannot be read or set via `amixer`. The ALSA mixer `Headphone Playback Volume` (numid=7) and `AIF1 DA0 Playback Volume` (numid=2) affect the digital gain in the codec but are set to fixed values at boot and not used for runtime volume control in Mestre. ## Known Quirks **rxvol_set defaults to 0 after init.** Every call to `x6200_control_init()` resets the speaker volume to 0 (PA off). Always call `rxvol_set(N)` immediately after `init()`. **voice_rec is reset by x6200_control_init().** If `init()` is called again (e.g., after a crash), the MUX reverts to RX-radio mode and the full init sequence must be repeated. **PulseAudio must start before mestre-audio.** If PulseAudio is not running when the audio MUX is switched, the codec clocks may not be active and the switch may have no audible effect. **44100 Hz stereo S16_LE** is the correct format for direct hardware playback (`hw:0,0`). PulseAudio handles resampling from other rates. ## References - [gdyuldin/X6200Control](https://github.com/gdyuldin/X6200Control) — I2C control library source, including the `sple_atue_trx` register layout and `voice_rec` bit definition. - [gdyuldin/x6200_gui](https://github.com/gdyuldin/x6200_gui) — Reference implementation of the full audio init sequence (`audio_play_en()`, `audio_init()`, `radio_init()`). - [AetherRadio/X6100Control](https://github.com/AetherRadio/X6100Control) — X6100 control library (protocol-compatible with X6200), used to understand the register map before X6200Control source was available. - Allwinner R16 / sun8i-a33 audio driver: `sound/soc/sunxi/` in the Linux kernel tree. The codec is `sun8i-codec` with an AIF1 I2S interface to the SoC's audio PLL.