Radxa ZERO 3 with PCM5122 work together

hello everyone and @MichaIng

I ordered and received a Radxa Zero 3W to pair with my Waveshare PCM5122-based PCB.


Since the chip is a Rockchip RK3566 and the board’s datasheet specifies that the I2C3 and I2S3-M0 buses are accessible via the 40-pin header (on pins 3, 5, 12, 35, and 40),

all that remained was to create a script to set up the DAC. This wasn’t exactly straightforward, as the Zero 3W is quite unique; however, below is a working script for the RCA outputs,


#!/bin/bash
# ==============================================================================
# SCRIPT UNIFIÉ ULTIME v3 : RADXA ZERO 3W + PIFI DAC+ (PIN 16/18 NATIVE FIXED)
# ==============================================================================

mkdir -p /boot/overlay-user
cd /boot/overlay-user

echo "--- [1/5] Réécriture du fichier d'environnement de Boot ---"
sed -i '/overlays=/d' /boot/dietpiEnv.txt
sed -i '/user_overlays=/d' /boot/dietpiEnv.txt

echo "overlays=hk-i2c0" >> /boot/dietpiEnv.txt
echo "user_overlays=pcm5122_i2s" >> /boot/dietpiEnv.txt

echo "--- [2/5] Création du script d'initialisation (I2C PCM5122) ---"
cat << '_EOF_' > /boot/overlay-user/init_dac.sh
#!/bin/bash
sleep 4

# Sélection de la page 0 des registres fondamentaux
i2cset -y 3 0x4d 0x00 0x00
sleep 0.1

# Sortie du mode Standby
i2cset -y 3 0x4d 0x02 0x00
sleep 0.1

# ROUTAGE PLL : Force le PCM5122 à utiliser le BCLK comme source MCLK
i2cset -y 3 0x4d 0x0e 0x08
sleep 0.1

# Ignorer l'absence d'horloge maîtresse externe (MCLK)
i2cset -y 3 0x4d 0x25 0x08
sleep 0.1

# Configuration de la PLL automatique et format I2S standard
i2cset -y 3 0x4d 0x04 0x00
sleep 0.1
i2cset -y 3 0x4d 0x28 0x00
sleep 0.1

# DÉSACTIVATION DE L'AUTO-MUTE
i2cset -y 3 0x4d 0x41 0x00
sleep 0.1

# Configuration et passage à l'état haut des GPIO internes du PCM5122 (RCA)
i2cset -y 3 0x4d 0x08 0x3f  
i2cset -y 3 0x4d 0x09 0x00  
sleep 0.1
i2cset -y 3 0x4d 0x54 0x11  
i2cset -y 3 0x4d 0x55 0x11  
i2cset -y 3 0x4d 0x56 0x11  
i2cset -y 3 0x4d 0x57 0x11  
sleep 0.1

# Configuration volumes (0dB) et retrait des mutes du PCM5122
i2cset -y 3 0x4d 0x3d 0x30  
i2cset -y 3 0x4d 0x3e 0x30  
sleep 0.1
i2cset -y 3 0x4d 0x6c 0x00  
sleep 0.1
i2cset -y 3 0x4d 0x03 0x00  
sleep 0.1

echo "Initialisation des registres I2C terminée."
_EOF_

chmod +x /boot/overlay-user/init_dac.sh

echo "--- [3/5] Réinstallation et activation du service Systemd ---"
cat << '_EOF_' > /etc/systemd/system/init-dac.service
[Unit]
Description=Initialisation et routage PLL du DAC PCM5122 via I2C3
After=sound.target i2c-dev.target
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/boot/overlay-user/init_dac.sh
RemainAfterExit=yes

[Install]
WantedBy=sysinit.target
_EOF_

systemctl daemon-reload
systemctl enable init-dac.service

echo "--- [4/5] Écriture de l'overlay DTS (Libération PWM + Forçage GPIO 3.3V) ---"
cat << '_EOF_' > /boot/overlay-user/pcm5122_i2s.dts
/dts-v1/;
/plugin/;

/* Désactivation des blocs PWM pour libérer les broches 16 et 18 */
&pwm8 {
    status = "disabled";
};

&pwm9 {
    status = "disabled";
};

&{/} {
    pcm5122_sound {
        compatible = "simple-audio-card";
        simple-audio-card,name = "Radxa-PCM5122";
        simple-audio-card,format = "i2s";
        status = "okay";

        simple-audio-card,bitclock-master = <&cpu_dai>;
        simple-audio-card,frame-master = <&cpu_dai>;

        cpu_dai: simple-audio-card,cpu {
            sound-dai = <&i2s3_2ch>;
        };

        simple-audio-card,codec {
            sound-dai = <&pcm5122_codec>;
        };
    };

    pcm5122_codec: pcm5122-codec {
        compatible = "linux,spdif-dit";
        #sound-dai-cells = <0>;
        status = "okay";
    };
};

&hdmi_sound {
    status = "disabled";
};

&i2s3_2ch {
    #sound-dai-cells = <0>;
    rockchip,clk-trcm = <1>;
    pinctrl-names = "default";
    pinctrl-0 = <&i2s3m0_sclk &i2s3m0_lrck &i2s3m0_sdo &jack_pins>;
    status = "okay";
};

&pinctrl {
    pins {
        jack_pins: jack-pins {
            /* Force GPIO3_B1 (pin 16) et GPIO3_B2 (pin 18) en GPIO Sortie Haute (Pull-Up) */
            rockchip,pins = <3 9 0 &pcfg_pull_up>,
                            <3 10 0 &pcfg_pull_up>;
        };
    };
};
_EOF_

# Compilation finale du binaire .dtbo
dtc -I dts -O dtb -@ -o pcm5122_i2s.dtbo pcm5122_i2s.dts

echo "--- [5/5] Création de la configuration ALSA pour le contrôle du volume ---"
cat << '_EOF_' > /etc/asound.conf
pcm.!default {
    type plug
    slave.pcm "softvol"
}

pcm.softvol {
    type softvol
    slave {
        pcm "hw:0,0"
    }
    control {
        name "Master"
        card 0
    }
}

ctl.!default {
    type hw
    card 0
}
_EOF_

echo "=============================================================================="
echo " CONFIGURATION RADXA ZERO 3W PRÊTE ET COMPILÉE !"
echo " Lancez maintenant la commande : reboot"
echo "=============================================================================="


# ==============================================================================
# pour l'activer 
# nano deploy_pcm5122.sh
# ctrl +o et ctrl +x
# puis
# bash deploy_pcm5122.sh
# ==============================================================================

Along with a script to test the PCM5122 registers for automated verification.

cat << '_EOF_' > /usr/local/bin/check_dac.sh
#!/bin/bash
# ==============================================================================
# OUTIL DE DIAGNOSTIC AVANCÉ (V2) - PCM5122 AVEC ROUTAGE HORLOGES ET MUX
# ==============================================================================

VERT='\033[0;32m'
ROUGE='\033[0;31m'
JAUNE='\033[1;33m'
BLEU='\033[0;34m'
NEUTRE='\033[0m'

afficher_diagnostic() {
    clear
    echo -e "${BLEU}======================================================================${NEUTRE}"
    echo -e "${BLEU}          DIAGNOSTIC AVANCÉ - MATÉRIEL DAC PCM5122 & INTERFACES      ${NEUTRE}"
    echo -e "${BLEU}======================================================================${NEUTRE}"

    if ! i2cget -y 3 0x4d 0x00 b >/dev/null 2>&1; then
        echo -e "${ROUGE}[ERREUR] Le PCM5122 ne répond pas sur le bus I2C-3 à l'adresse 0x4d.${NEUTRE}"
        return 1
    fi

    REG_02=$(i2cget -y 3 0x4d 0x02 b)
    REG_6C=$(i2cget -y 3 0x4d 0x6c b)
    REG_72=$(i2cget -y 3 0x4d 0x72 b)
    REG_77=$(i2cget -y 3 0x4d 0x77 b)
    REG_0E=$(i2cget -y 3 0x4d 0x0e b)
    REG_25=$(i2cget -y 3 0x4d 0x25 b)

    VAL_02=$((REG_02))
    BIT_STBY=$(( (VAL_02 >> 4) & 1 ))
    BIT_PDN=$(( VAL_02 & 1 ))

    echo -e "\n${JAUNE}[1/5] ÉTAT DE L'ALIMENTATION GÉNÉRALE (Reg 0x02 : $REG_02)${NEUTRE}"
    if [ "$BIT_PDN" -eq 1 ]; then
        echo -e "  -> Statut global : ${ROUGE}ÉTEINT COMPLET (Powerdown actif)${NEUTRE}"
    elif [ "$BIT_STBY" -eq 1 ]; then
        echo -e "  -> Statut global : ${JAUNE}MODE VEILLE (Standby actif)${NEUTRE}"
    else
        echo -e "  -> Statut global : ${VERT}ACTIF (Prêt à fonctionner)${NEUTRE}"
    fi

    VAL_6C=$((REG_6C))
    MUTE_L=$(( VAL_6C & 1 ))
    MUTE_R=$(( (VAL_6C >> 1) & 1 ))

    echo -e "\n${JAUNE}[2/5] ÉTAT DES AMPLIFICATEURS DE SORTIE (Reg 0x6C : $REG_6C)${NEUTRE}"
    if [ "$MUTE_L" -eq 1 ] && [ "$MUTE_R" -eq 1 ]; then
        echo -e "  -> Amplis Gauche & Droit : ${ROUGE}MUTÉS / COUPÉS${NEUTRE}"
    elif [ "$MUTE_L" -eq 0 ] && [ "$MUTE_R" -eq 0 ]; then
        echo -e "  -> Amplis Gauche & Droit : ${VERT}OUVERTS (Prêts à émettre)${NEUTRE}"
    fi

    VAL_72=$((REG_72))
    BOOST=$(( VAL_72 & 3 ))
    echo -e "\n${JAUNE}[3/5] RÉGLAGE DU BOOST ANALOGIQUE (Reg 0x72 : $REG_72)${NEUTRE}"
    case $BOOST in
        3) echo -e "  -> Niveau de gain : ${VERT}Standard (0 dB) + Auto-Mute Intelligent Actif${NEUTRE}" ;;
        *) echo -e "  -> Niveau de gain : Mode Manuel / Personnalisé (${BOOST})${NEUTRE}" ;;
    esac

    VAL_77=$((REG_77))
    BIT_POMPE=$(( (VAL_77 >> 4) & 1 ))
    echo -e "\n${JAUNE}[4/5] ÉTAT DE LA POMPE DE CHARGE INTERNE (Reg 0x77 : $REG_77)${NEUTRE}"
    if [ "$BIT_POMPE" -eq 1 ]; then
        echo -e "  -> Pompe de charge : ${VERT}ACTIVE & STABLE (Tension négative OK)${NEUTRE}"
    else
        echo -e "  -> Pompe de charge : ${ROUGE}INACTIVE / ÉTEINTE${NEUTRE}"
    fi

    VAL_0E=$((REG_0E))
    VAL_25=$((REG_25))
    echo -e "\n${JAUNE}[5/5] ROUTAGE DE L'HORLOGE SYSTEME / CONFLIT GPIO${NEUTRE}"
    if [ $((VAL_0E & 0x08)) -eq 8 ]; then
        echo -e "  -> Source PLL (Reg 0x0E) : ${JAUNE}Forcée sur BCLK via GPIO4 (Explique le verrouillage du Reg 0x54)${NEUTRE}"
    else
        echo -e "  -> Source PLL (Reg 0x0E) : Standard / Externe (MCLK)"
    fi
    if [ $((VAL_25 & 0x08)) -eq 8 ]; then
        echo -e "  -> Sécurité Horloge (Reg 0x25) : ${VERT}Ignore l'absence de MCLK (Indispensable pour Radxa)${NEUTRE}"
    fi

    echo -e "${BLEU}======================================================================${NEUTRE}"
}

if [ "$1" = "loop" ]; then
    while true; do afficher_diagnostic; sleep 1; done
else
    afficher_diagnostic
fi
_EOF_
chmod +x /usr/local/bin/check_dac.sh

I still have one unresolved issue regarding the headphone jack on my test board (a PiFi DAC+ V2.0); since I’m on vacation, I only have headphones to test the audio output, and the chip handling the jack via the PCM isn’t responding.

It appears to be an issue with managing GPIO3-B1 and GPIO3-B2 (pins 16 and 18 on the 40-pin HAT connector specific to the PiFi DAC+ board).

Do I see it right that, compared to the Orange Pi 3B, the major difference so far is that I2C3 and I2S3 is used (and conflicting GPIO buses disabled), which are accessible on the same pins the HAT expects for I2C and I2S?

In fact the pinout is the same as the hat of the opi 3B and opi zero 2w and also RPI

it’s just a different number of bus I2S and I2C

The secret for the PCM5122 is to drive it by different command with I2C and use the good parameters for the soundcard in front of the clock used by the rk3566 or h618
same things with radxa for a different implementation of the rk3566

after all if a PCM5122 can make music , a PCM5102A works too with the same overlay and same pins

Hello

A little case

Hello everyone

It’s fun designing a case to house the Radxa Zero 3W, the PCM5122, and an interface PCB equipped with a small fan :grinning_face: :wink:.


Subject: Radxa Zero 3W - Peripheral issues (I2C3/I2S3) broken due to mainline kernel 6.18 transition on Debian Trixie

Hi everyone,

I wanted to share a technical summary regarding an issue I am encountering with my Radxa Zero 3W running DietPi (Debian 13 Trixie), which might help the developers or other users facing the same problem.

Current State:

  • OS: DietPi (Debian 13 stable / Trixie)
  • Kernel version: 6.18.45-current-rockchip64 (Mainline)
  • Hardware: Radxa Zero 3W (paired with a Waveshare audio card)

The Problem:
Following a recent package update, the system transitioned from the original Rockchip vendor kernel (branch 6.1) to the generic mainline community kernel (6.18.y-current-rockchip64).

While the system boots cleanly, this mainline kernel migration completely breaks hardware-specific integration for the Radxa Zero 3W:

  1. Missing Peripherals: Important pins and onboard features such as I2S3, I2S3 (needed for external audio DACs/Waveshare cards), the pin 35 as LRCK are completely unavailable or non-functional, stay to ground and flat.
  2. Device Tree Limitations: Mainline kernel 6.18 lacks the proprietary or specific Rockchip/Radxa vendor hardware tables required to initialize these interfaces properly.

Attempts to resolve:
I am currently trying to downgrade back to the stable dts/kernel branch (6.1.x-vendor-rk35xx). However, doing this manually on a pure DietPi Trixie setup is tricky because:

  • The Armbian repository is not enabled by default.
  • Traditional legacy tools like apt-key have been deprecated/removed in Debian 13, making standard third-party repository additions strict and prone to signature decoding failures (sqv parsing errors).

If anyone has a clean, repository-compliant method to pin and force-install the linux-image-vendor-rk35xx and linux-dtb-vendor-rk35xx packages directly on DietPi Trixie without breaking the modern apt GPG keyring framework, I would be very grateful!

Thank you for your amazing work on DietPi.

There is a linux-image-vendor-rk35xx.deb in the DietPi repo, and also linux-dtb-vendor-rk35xx_26.05.0-trunk-dietpi1.deb

Thanks a lot @Jappe
I hope that by downgrading to this older kernel, I’ll get the expected signal on pin 35—specifically, 48 kHz for LRCK—since all my attempts with various drivers using kernel 6.18.37 or 6.18.45 were unsuccessful.

Quick question for an expert: how do I cleanly downgrade to that kernel from the current one via the SSH command line?

You do not need to Armbian repository, in fact it will cause issues/incompatibilities due to some changes, if you are not careful, like the merged linux-dtb and linux-image packages, which is only the case with our builds. Also switching kernels with the vanilla Armbian kernel packages is dangerous, since it can leave you with deleted /boot/dtb-* directories and missing/mixed symlinks, due to some logical bugs in the package maintainer scripts.

However, generally to add 3rd party repos, tools like apt-key are redundant and obsolete since years. Just put the respective key into /etc/apt/trusted.gpg.d, either with .asc extension if it is ASCII-armored, or .gpg extension if it is not. Or better: Put it into the /etc/apt/keyrings directory (extension does not matter), and put a [signed-by=/etc/apt/keyrings/my.key] or Signed-By: /etc/apt/keyrings/my.key entry into the .list or .sources file respectively (depending on whether it is classic or DEB822 list format). The latter, using DEB822 format is the preferred way how 3rd party repos are added nowadays: sources.list(5) — apt — Debian trixie — Debian Manpages

Since we stopped updating the vendor kernel in our RK356x SBC APT components, you need to download and install it manually:

cd /tmp
wget https://dietpi.com/downloads/binaries/linux-image-vendor-rk35xx.deb
sudo dpkg -i linux-image-vendor-rk35xx.deb

If it works as expected after reboot, purge the “current” kernel:

sudo apt autopurge linux-image-current-rockchip64

However, since IIUC it is about I2S buses on certain pins only, it should be possible to achieve that via device tree (overlay). All those are generally available in the mainline device tree, hence just need to be enabled and in case pins adjusted: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi#n1239

I will generally stop maintaining the vendor kernel branch our end shortly, working on getting AP6611S WiFi error free in mainline Linux, and RK356x | Add RKNPU driver to mainline kernel · Issue #7996 · MichaIng/DietPi · GitHub as way to add the RKNPU into mainline Linux for RK356x boards (where Rocket does not work). Then there is no board left which requires the vendor kernel. Hence I recommend any other work to focus on mainline Linux as well, to remain relevant.

EDIT: Moved to troubleshooting for now as requested.

Good evening,
@MichaIng ,
Thanks for moving the thread.

I understand the situation regarding the mainline kernel; I’m going to examine the dtsi file closely—the solution must be in there.

I actually posted a question on the Radxa forum to get a lead.

Obviously, I’d prefer to use the latest kernel from the DietPi distribution, but I just can’t get pin 35 working. It likely comes down to a nuance in the Radxa board’s routing—especially since it works fine on the Orange Pi 3B and the CM4 module, both of which share the same RK3566 chip as the Radxa.

This board has great potential for my use case, so I really want to figure this out, even though there’s almost nothing about it online.

I have a feeling that the dtsi file will help me make some headway :wink:

You need I2S3 M0, which is on A2-A6, usually: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi#n1290

  • A2 for MCLK on pin 13 :white_check_mark:
  • A3 for SCLK on pin 12 :white_check_mark:
  • A4 for LRCK on pin 35 :white_check_mark:
  • A5 for SDO on pin 40 :white_check_mark:
  • A5 for SDI on pin 38 :white_check_mark:

So mainline expectation/wiring for I2S3 matches the Radxa ZERO 3 pin table, with LRCK on pin 35 as you need it. It just needs to be enabled … so it seems. The problem is these /omit-if-no-ref/, so the nodes are not in the base dts as long as they are not referenced in any dts(i) of the build. And mainline does not reference them anywhere (other than I2S1 and I2S2). This is why your overlay does not work => &i2s3m0_lrck etc are not defined, most likely the whole i2s3 node is not.

To keep the impact minimal, best is to redefine them in the overlay:

#include <dt-bindings/pinctrl/rockchip.h>

&{/} {
	i2s3 {
		i2s3m0_lrck: i2s3m0-lrck {
			rockchip,pins = <3 RK_PA4 4 &pcfg_pull_none>;
		};

		i2s3m0_mclk: i2s3m0-mclk {
			rockchip,pins = <3 RK_PA2 4 &pcfg_pull_none>;
		};

		i2s3m0_sclk: i2s3m0-sclk {
			rockchip,pins = <3 RK_PA3 4 &pcfg_pull_none>;
		};

		i2s3m0_sdi: i2s3m0-sdi {
			rockchip,pins = <3 RK_PA6 4 &pcfg_pull_none>;
		};

		i2s3m0_sdo: i2s3m0-sdo {
			rockchip,pins = <3 RK_PA5 4 &pcfg_pull_none>;
		};
	};
};

This dt-bindings include works with pre-processor only. Hence for custom overlays not built into the kernel package, you need to replace those RK_XXX variables with the raw integer values from here: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/dt-bindings/pinctrl/rockchip.h
Like you do for jack_pins already.

@MichaIng

It’s obvious now that it’s written down: the pin description was the missing piece for my overlay to work—just like I had done for the Orange Pi Zero 2W. I guess I’m getting old! :wink:
I’ll test it out this weekend or early next week.

Right now, I’m finishing up the integration of PeppyMeter on my OPI Zero 2W board with the Waveshare 7.9" touchscreen; it’s fully functional—all that’s left is to format a PDF document explaining the various installation steps. :heart_eyes:

Subject: Request to enable CONFIG_SND_SOC_ROCKCHIP_DUMMY_CODEC for Radxa Zero 3W (RK3566)

Hi @MichaIng

I try setting up an external I2S DAC (Waveshare/PiFi based on PCM5122 and PCM5102A) on a Radxa Zero 3W running the latest DietPi with kernel 6.18.45 without success.

To bypass the missing physical MCLK pin on these audio boards, the standard way on Rockchip RK3566 is to pair a simple-audio-card with rockchip,dummy-codec in a custom device tree overlay. This setup works perfectly on my Orange Pi 3B (also an RK3566) with a PiFi DAC+ v2.0 and my personal boardwith PCM5102A.

However, on the Radxa Zero 3W DietPi kernel, the rockchip,dummy-codec driver seems to be missing from the build configuration. As a result:

  • The simple-audio-card fails to instantiate (or if forced with generic dummys like spdif-dit, the clocks freeze).
  • The pinmux debug log shows the I2S3 pins locked in sound (GPIO UNCLAIMED) state because the driver framework cannot bind properly.

Could you please consider enabling CONFIG_SND_SOC_ROCKCHIP_DUMMY_CODEC=m (or =y) in the next kernel compilation for the Radxa Zero 3W / RK35xx family?

This would greatly help anyone trying to use generic or software-less I2S DACs / digital microphones on this board and me first to play music with my waveshare board.

Thank you very much for your amazing work on DietPi!

More info about a week of test
Subject: Technical feedback & kernel request: Enabling CONFIG_SND_SOC_ROCKCHIP_DUMMY_CODEC for Radxa Zero 3W (RK3566) I2S Audio

Hi MichaIng and DietPi team,

I am currently working on implementing an external I2S audio DAC (Waveshare PCM5122 and a masterless PCM5102A board) on a Radxa Zero 3W (RK3566) running DietPi with the latest 6.18.x kernel.

Since these audio boards do not feature or route a physical Master Clock (MCLK) pin from the SoC, the standard and proven approach on Rockchip platforms is to use a simple-audio-card in a Device Tree Overlay tied to the virtual/dummy codec driver (rockchip,dummy-codec). This configuration forces the SoC into a self-contained I2S clock-generation mode (forcing bitclock-master and frame-master on the CPU side) so the DAC can internalize its clocking via its own PLL.

I have performed extensive deep-level diagnostics using an oscilloscope and the kernel’s debug tools, and here is a summary of what was observed:

1. What was attempted

  • Attempt A (Generic Linux Dummies): Trying to bind the sound card to standard available fallbacks like linux,snd-soc-dummy or linux,spdif-dit. While spdif-dit allows the card to register in aplay -l, it fails to properly initialize the full I2S subsystem bus clocks on the RK3566 under Kernel 6.18.
  • Attempt B (Pin-muxing validation): Ensuring i2c3 (for PCM5122 registry configuration at 0x4c) and i2s3_2ch are multiplexed using native kernel labels (&i2s3m0_sclk, &i2s3m0_lrck, etc.).

2. The Root Cause & Blockers Discovered

While the device-tree nodes are successfully loaded as okay, looking into the kernel debugfs for the pin multiplexing reveals a critical issue:

text

root@DietPi:~# cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep -E -i "i2s3"
pin 99 (gpio3-3): sound (GPIO UNCLAIMED) function i2s3 group i2s3m0-sclk
pin 100 (gpio3-4): sound (GPIO UNCLAIMED) function i2s3 group i2s3m0-lrck
pin 101 (gpio3-5): sound (GPIO UNCLAIMED) function i2s3 group i2s3m0-sdo

  • The GPIO UNCLAIMED status: Even when opening an audio stream via aplay -D hw:0,0 /dev/zero, the command freezes instantly. The hardware pins remain dead, and an oscilloscope attached to pins 12, 35, and 40 shows flatlines.
  • Clock Gating Freeze: On recent 6.1x+ kernels, the rockchip-i2s CPU driver enforces strict clock gating and power management. If the driver cannot bind to a recognized, valid audio codec endpoint driver capable of acknowledging master-mode streaming without an explicit physical MCLK, it refuses to finalize the pin mapping and leaves the pins UNCLAIMED.

3. Cross-Platform Validation (Why the driver is mandatory)

To ensure this wasn’t a hardware failure or a logic flaw in my overlay configuration, I cross-tested this setup against an Orange Pi 3B, which shares the exact same RK3566 SoC.
On the Orange Pi 3B, where CONFIG_SND_SOC_ROCKCHIP_DUMMY_CODEC is compiled and active within the kernel, the exact same implementation works flawlessly: the ALSA card instantiates, the pinctrl switches instantly to CLAIMED, aplay streams seamlessly, and the oscilloscope captures clean square wave clocks.

4. The Request

Could you please consider enabling CONFIG_SND_SOC_ROCKCHIP_DUMMY_CODEC=m (or =y) in the defconfig for the Radxa Zero 3W / RK35xx kernel builds?

Restoring this standard Rockchip component will immediately fix audio clock mapping issues for everyone using software-less I2S DACs, digital microphones (Phat DAC, PiFi, Adafruit modules), or custom audio setups on this board.

Orange Pi 3B and Radxa ZERO 3 use the same kernel. Are you maybe still using the vendor kernel on the Orange Pi 3B? CONFIG_SND_SOC_ROCKCHIP_DUMMY_CODEC sounds pretty much like a vendor kernel config.

I guess you mean CONFIG_SND_SOC_DUMMY_CODEC (without the ROCKCHIP part). But even that does not exist in mainline Linux:

Nothing we can do about that.

EDIT: Doesn’t it work to use the actual existing driver for PCM5122?

Hi @MichaIng ,

On the OPi 3B, I use the kernel provided by DietPi, so I initially tried the same script used for the Radxa, but it didn’t work; I couldn’t get a clock signal on pin 35 (LRCK). I also tried the `ti,PCM5122` driver without success, and even manipulating the registers via I2C failed to produce any sound. It was a real struggle to partially enable the pin-mux and the `i2s3`/`i2c3` nodes without locking up the Radxa or failing to create the sound card device; I’ve provided more details—including the various tests I ran—in my technical summary post. What frustrates me is that on the OPi 3B, enabling the I2S node was very simple, and it works with the PCM5122 or PCM5102A by manually driving the I2C bus to wake up the DAC. However, with the Radxa, while the I2C node works perfectly, I’m stuck on enabling the I2S node; it’s a purely software-related issue involving unlocking the clocks and activating I2S.

I just had a look, and Orange Pi 3B systems should have been migrated to mainline Linux with DietPi v10.1.

But that should be solved with the new insights above, that the i2s3m0-lrck node is omitted in the mainline dtb due to lack of reference, and hence needs to be redefined in the overlay, isn’t it?

When you use compatible = "ti,pcm5122", is the pcm512x driver loaded? If so, dmesg maybe gives some info whether probing worked. If not, then it seems the dtb node was never activated, maybe due to some reference or dependency issue.

Yes, the driver loads, and alsamixer allows volume adjustment—even while a `speaker-test` command is running in a loop—without freezing the terminal. However, the i2s3 node—whether using the generic spdif-dit driver, the ti,pcm5122 driver, or snd-soc-dummy—doesn’t physically activate pins 12, 35, and 40; the RK3566 processor isn’t supplying electrical power to the pins. I created a script to test and view the PCM5122 registers, and I connected an oscilloscope to the board, but nothing seems to work. The major breakthrough with my latest deployment script is that the nodes now appear in the pin-mux (but as “unclaimed”), and the i2s3 and i2c3 buses show an “OK” status. I’ll post the script and details this evening; I’m at work right now.

However, I do not want to use the `ti,pcm5122` driver—I want to use the same script for both the PCM5102A and PCM5122—because I2C control works, and the I2S stream, once active, can drive either the PCM5102A or the PCM5122.

At least with mainline Linux (the only one we still support/distribute for RK356x boards), this might be difficult, due to the missing generic dummy driver. But there is a dedicated PCM5102A one: linux/sound/soc/codecs/pcm5102a.c at 786262be6048deab760f68c8acc2c85607165894 · torvalds/linux · GitHub
This is so minimal, without any actual probing, just registering playback controls, that it might even work better/cleaner than the dummy codec from vendor kernel, with its additional capture controls and necessarily large value ranges.

So do I get it right that the lack of the dummy driver now is not an issue anymore, other than that you might need to use two different compatible properties?

Regarding the I2S3 pins: Did you verify that the related device tree nodes and pinctl properties are correctly created/assigned now, and not status = "disabled"? Also makes sense to verify that there is nothing else (internally) attached to and active on those pins, but at least the diagram in your OP does not show any other bus/function on 12/35/40 other than I2S3 and the generic GPIO.

Hello @MichaIng
with this script

#!/bin/bash
# ==============================================================================
# SCRIPT GLOBAL V7 (CORRIGÉ) : RADXA ZERO 3W + I2S3(M0) + I2C3(M0) PCM5122 (0x4c)
# Noyau cible : 6.18.45 | Emplacement unique : /boot/overlay-user/
# Version : Mode Libération d'Horloge Native conforme pinctrl officiel Radxa
# ==============================================================================

set -e

echo "--- [1/6] PURGE ET NETTOYAGE DES ANCIENS RÉSIDUS ---"
apt update && apt install -y i2c-tools device-tree-compiler

if systemctl is-enabled init-dac.service >/dev/null 2>&1; then
    systemctl disable init-dac.service || true
fi
rm -f /etc/systemd/system/init-dac.service
systemctl daemon-reload

mkdir -p /boot/overlay-user
rm -f /boot/pcm5122_i2s.dtbo
rm -f /usr/local/bin/check_dac

echo "--- [2/6] CONFIGURATION DU FICHIER DIETPIENV.TXT ---"
cat << '_EOF_' > /boot/dietpiEnv.txt
rootdev=UUID=ac86f368-e62e-4faf-90ff-58443343ecc7
rootfstype=ext4
consoleargs=console=ttyS2,1500000 console=tty1
extraargs=fsck.repair=yes net.ifnames=0
overlay_path=rockchip
overlay_prefix=rk35xx-radxa-zero-3w rockchip-rk3566 rockchip-rk3568 radxa-zero3 rk3568
overlays=hk-i2c0 ext-ant
user_overlays=pcm5122_i2s
_EOF_

echo "--- [3/6] ÉCRITURE ET COMPILATION DU DTS COMPATIBLE NOYAU ---"
cat << '_EOF_' > /boot/overlay-user/pcm5122_i2s.dts
/dts-v1/;
/plugin/;

/ {
	compatible = "rockchip,rk3566";

	/* Configuration du bus Audio I2S3 (Multiplexage natif M0) */
	fragment@0 {
		target = <&i2s3_2ch>;
		__overlay__ {
			rockchip,clk-trcm = <1>;
			pinctrl-names = "default";
			pinctrl-0 = <&i2s3m0_sclk
				     &i2s3m0_lrck
				     &i2s3m0_sdi
				     &i2s3m0_sdo>;
			status = "okay";
		};
	};

	/* Configuration du bus I2C3 (Multiplexage natif M0 - Pins 3 et 5) */
	fragment@1 {
		target = <&i2c3>;
		__overlay__ {
			pinctrl-names = "default";
			pinctrl-0 = <&i2c3m0_xfer>; /* Assignation officielle M0 */
			status = "okay";
			#address-cells = <1>;
			#size-cells = <0>;

			pcm5122@4c {
				compatible = "ti,pcm5122";
				reg = <0x4c>;
				status = "okay";
			};
		};
	};

	/* Instanciation ALSA simple-audio-card lie au dummy-codec Rockchip */
	fragment@2 {
		target-path = "/";
		__overlay__ {
			sound {
				status = "okay";
				compatible = "simple-audio-card";
				simple-audio-card,name = "PCM5122NoMCLK";
				simple-audio-card,format = "i2s";

				/* Force le CPU en Master pour générer le BCLK et LRCK vers votre oscilloscope */
				simple-audio-card,bitclock-master = <&cpu_dai>;
				simple-audio-card,frame-master = <&cpu_dai>;
				simple-audio-card,mclk-fs = <256>;

				cpu_dai: simple-audio-card,cpu {
					sound-dai = <&i2s3_2ch>;
				};

				codec_dai: simple-audio-card,codec {
					sound-dai = <&i2s3_out>;
				};
			};

			i2s3_out: i2s3-out {
				#sound-dai-cells = <0>;
				compatible = "linux,spdif-dit";
				status = "okay";
			};
		};
	};
};
_EOF_

dtc -I dts -O dtb -@ -o /boot/overlay-user/pcm5122_i2s.dtbo /boot/overlay-user/pcm5122_i2s.dts

echo "--- [4/6] SÉQUENCE SÉCURISÉE 32-FS DU PCM5122 (0x4c) ---"
cat << '_EOF_' > /boot/overlay-user/init_dac.sh
#!/bin/bash
sleep 2

# Reset logiciel du DAC via I2C3
i2cset -y -f 3 0x4c 0x01 0x11 || true
sleep 0.2

# Configuration Transposée pour mode Esclave PLL 32-FS (Sans MCLK)
i2cset -y -f 3 0x4c 0x02 0x10 || true  
i2cset -y -f 3 0x4c 0x25 0x08 || true  

# Ajustement des diviseurs de la PLL interne
i2cset -y -f 3 0x4c 0x0d 0x00 || true  
i2cset -y -f 3 0x4c 0x26 0x00 || true  
i2cset -y -f 3 0x4c 0x41 0x00 || true  

# Passage en mode actif et volume à 100%
i2cset -y -f 3 0x4c 0x02 0x00 || true
i2cset -y -f 3 0x4c 0x03 0x00 || true
i2cset -y -f 3 0x4c 0x3d 0x30 || true  
i2cset -y -f 3 0x4c 0x3e 0x30 || true  
_EOF_

chmod +x /boot/overlay-user/init_dac.sh

cat << '_EOF_' > /etc/systemd/system/init-dac.service
[Unit]
Description=Initialisation manuelle du PCM5122 en mode Esclave
After=sound.target i2c-dev.target
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/boot/overlay-user/init_dac.sh
RemainAfterExit=yes

[Install]
WantedBy=sysinit.target
_EOF_

systemctl daemon-reload
systemctl enable init-dac.service

echo "--- [5/6] CONFIGURATIONS PRIORITÉS ALSA ---"
cat << '_EOF_' > /etc/modprobe.d/alsa-options.conf
options snd_soc_simple_card index=0
options snd_soc_rockchip_hdmi index=1
options snd_soc_hdmi_codec index=2
_EOF_

cat << '_EOF_' > /etc/asound.conf
pcm.!default {
    type plug
    slave.pcm "softvol"
}
pcm.softvol {
    type softvol
    slave {
        pcm "hw:0,0"
        format S16_LE
    }
    control {
        name "Master"
        card 0
    }
}
ctl.!default {
    type hw
    card 0
}
_EOF_

echo "--- [6/6] CRÉATION DE L'OUTIL DE DIAGNOSTIC ---"
cat << '_EOF_' > /boot/overlay-user/check_dac.sh
#!/bin/bash
VERT='\033[0;32m'
ROUGE='\033[0;31m'
JAUNE='\033[1;33m'
BLEU='\033[0;34m'
NEUTRE='\033[0m'

clear
echo -e "${BLEU}======================================================================${NEUTRE}"
echo -e "${BLEU}          DIAGNOSTIC EN MODE PILOTE I2S GÉNERIQUE & LABELS DIRECTS   ${NEUTRE}"
echo -e "${BLEU}======================================================================${NEUTRE}"

echo -e "\n${JAUNE}[1/3] ÉTAT DU BUS DE CONTRÔLE I2C3${NEUTRE}"
if i2cdetect -y 3 | grep -q '4c'; then
    echo -e "  -> Liaison I2C physique : ${VERT}OK (Puce détectée à l'adresse 4c)${NEUTRE}"
else
    echo -e "  -> Liaison I2C physique : ${ROUGE}ÉCHEC (La puce ne répond pas)${NEUTRE}"
fi

echo -e "\n${JAUNE}[2/3] LECTURE INTERNE DES REGISTRES DE LA PLL (VIA I2C)${NEUTRE}"
REG04=$(i2cget -y -f 3 0x4c 0x04 2>/dev/null || echo "ERR")
REG25=$(i2cget -y -f 3 0x4c 0x25 2>/dev/null || echo "ERR")

if [ "$REG04" == "ERR" ]; then
    echo -e "  -> ${ROUGE}Erreur d'accès aux registres.${NEUTRE}"
else
    echo -e "  -> Registre 0x04 (PLL Lock)      : $REG04"
    echo -e "  -> Registre 0x25 (Clock Status)  : $REG25"
    if [ "$REG04" == "0x00" ] || [ "$REG04" == "0x0" ]; then
        echo -e "  -> Statut de la PLL : ${VERT}VERROUILLÉE SUR LE BITCLOCK (SUCCÈS)${NEUTRE}"
    else
        echo -e "  -> Statut de la PLL : ${ROUGE}ATTENTE DE SYNCHRO AUDIO (0x$REG04)${NEUTRE}"
    fi
fi

echo -e "\n${JAUNE}[3/3] RE-APPLICATION EXPÉRIMENTALE DE LA SÉQUENCE MATÉRIELLE${NEUTRE}"
/boot/overlay-user/init_dac.sh
echo -e "  -> Séquence d'initialisation forcée avec succès !"

echo -e "\n${BLEU}======================================================================${NEUTRE}"
_EOF_

chmod +x /boot/overlay-user/check_dac.sh
ln -sf /boot/overlay-user/check_dac.sh /usr/local/bin/check_dac

echo "======================================================================"
echo " DÉPLOIEMENT EFFECTUÉ AVEC SUCCÈS ! REBOOT REQUIS...                 "
echo "======================================================================"


# ==============================================================================
# pour l'activer 
# nano deploy_waveshare.sh
# ctrl +o et ctrl +x
# puis
# bash deploy_waveshare.sh
# ==============================================================================

i obtain this in the console

root@DietPi:~# aplay -l
**** Liste des périphériques matériels PLAYBACK ****
carte 0 : PCM5122NoMCLK [PCM5122NoMCLK], périphérique 0 : fe430000.i2s-dit-hifi dit-hifi-0 [fe430000.i2s-dit-hifi dit-hifi-0]
  Sous-périphériques : 1/1
  Sous-périphérique #0 : subdevice #0
carte 1 : HDMI [HDMI], périphérique 0 : fe400000.i2s-i2s-hifi i2s-hifi-0 [fe400000.i2s-i2s-hifi i2s-hifi-0]
  Sous-périphériques : 1/1
  Sous-périphérique #0 : subdevice #0
root@DietPi:~# cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep -E -i "i2s3|i2c3"
pin 32 (gpio1-0): fe5c0000.i2c (GPIO UNCLAIMED) function i2c3 group i2c3m0-xfer
pin 33 (gpio1-1): fe5c0000.i2c (GPIO UNCLAIMED) function i2c3 group i2c3m0-xfer
pin 99 (gpio3-3): fe430000.i2s (GPIO UNCLAIMED) function i2s3 group i2s3m0-sclk
pin 100 (gpio3-4): fe430000.i2s (GPIO UNCLAIMED) function i2s3 group i2s3m0-lrck
pin 101 (gpio3-5): fe430000.i2s (GPIO UNCLAIMED) function i2s3 group i2s3m0-sdo
pin 102 (gpio3-6): fe430000.i2s (GPIO UNCLAIMED) function i2s3 group i2s3m0-sdi
root@DietPi:~# echo -n "Statut I2S3 : " && cat /proc/device-tree/i2s@fe430000/status && echo "" && echo -n "Statut I2C3 : " && cat /proc/device-tree/i2c@fe5c0000/status && echo ""
Statut I2S3 : okay
Statut I2C3 : okay
root@DietPi:~# 

And for the pilote pcm5122

root@DietPi:~# lsmod | grep pcm512x
snd_soc_pcm512x_spi    12288  0
snd_soc_pcm512x_i2c    12288  0
snd_soc_pcm512x        36864  2 snd_soc_pcm512x_spi,snd_soc_pcm512x_i2c
snd_soc_core          217088  7 snd_soc_pcm512x,snd_soc_spdif_tx,snd_soc_rockchip_i2s_tdm,snd_soc_hdmi_codec,snd_soc_rk817,snd_soc_simple_card_utils,snd_soc_simple_card
snd_pcm               122880  6 snd_soc_pcm512x,snd_soc_hdmi_codec,snd_compress,snd_soc_simple_card_utils,snd_soc_core,snd_pcm_dmaengine
snd                    86016  6 snd_soc_pcm512x,snd_soc_hdmi_codec,snd_timer,snd_compress,snd_soc_core,snd_pcm
root@DietPi:~#