Radxa ZERO 3 with PCM5122 work together

Uhh, please edit this out and edit the individual lines needed in this case, i.e. the last 3. Else this will break every system aside the one where you copied the content from. I suggest using rockchip-rk3568-hk as prefix instead of rockchip-rk3568, and removing hk- from the i2c0 overlay instead. This is an oversight, there are no overlays with rockchip-rk3568 that do not start with rockchip-rk3568-hk, whatever the reason.

The overlay does still not define the pin nodes:

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

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

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

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

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

I was wrong in an earlier post above, where I put that into the root &{/}, but it needs to be in the &pinctrl node, of course. You can also check whether those exist in /proc/device-tree/pinctrl/i2s3. &pinctrl is just /pinctrl.

I found this with my script

root@DietPi:/proc/device-tree/pinctrl/i2s3# ls
i2s3m0-lrck  i2s3m0-mclk  i2s3m0-sclk  i2s3m0-sdi  i2s3m0-sdo  i2s3m1-lrck  i2s3m1-mclk  i2s3m1-sclk  i2s3m1-sdi  i2s3m1-sdo  name
root@DietPi:/proc/device-tree/pinctrl/i2s3# 

for the three lines i modified like this in the script

echo "--- [2/6] Réécriture du fichier d'environnement de Boot ---"
# Nettoyage strict pour éviter les doublons de boot
sed -i '/overlay_prefix=/d' /boot/dietpiEnv.txt
sed -i '/overlays=/d' /boot/dietpiEnv.txt
sed -i '/user_overlays=/d' /boot/dietpiEnv.txt

# Syntaxe technique valide pour Radxa Zero 3W
echo "overlay_prefix=rk35xx-radxa-zero-3w rockchip-rk3566 rockchip-rk3568-hk radxa-zero3 rk3568" >> /boot/dietpiEnv.txt
echo "overlays=i2c0 ext-ant" >> /boot/dietpiEnv.txt
echo "user_overlays=pcm5122_i2s" >> /boot/dietpiEnv.txt

after some test and research it’s just sad that the Radxa zero 3w can’t play music
with the mainline kernel , i be back to my favorite orangepi zero 2w with my waveshare board tomorrow to test and the radxa will stay at the back of the drawer.

conclusion of this day
The diagnosis is conclusive. The generic I2S driver in the Mainline kernel (6.18+) uses a fixed clock table for the RK3566 processor, preventing the RX channel from dropping below 50 MHz. Since Rockchip hardware requires strict synchronization to drive pin 35 (LRCK), it is technically impossible to operate this Waveshare board in Master mode (without an external MCLK) using the Mainline kernel. To obtain the 44.1 kHz square wave on pin 35 and get your audio board working, the only solution is to switch from the Mainline kernel to the official Vendor kernel (6.1.x). This kernel includes Rockchip’s proprietary clock patches, which natively resolve this issue.

Okay, so the pinctrl nodes are there. Then I am also not sure why you do not get a signal on those pins :thinking:.

root@DietPi:~# aplay -v /dev/urandom -t raw -c 2 -r 44100 -f S16_LE > /dev/null 2>&1 &
arecord -D default -t raw -c 2 -r 44100 -f S16_LE > /dev/null 2>&1 &
[1] 846
[2] 847
root@DietPi:~# cat /sys/kernel/debug/clk/clk_summary | grep -i i2s3
       i2s3_mclkout_rx               0       0        0        12000000    0          0     50000      N         deviceless                      no_connection_id         
       i2s3_mclkout_tx               0       0        0        12000000    0          0     50000      N         deviceless                      no_connection_id         
          clk_i2s3_2ch_rx_src        1       1        0        50000000    0          0     50000      Y            deviceless                      no_connection_id         
             clk_i2s3_2ch_rx         1       1        0        50000000    0          0     50000      Y               deviceless                      no_connection_id         
                mclk_i2s3_2ch_rx     2       2        0        50000000    0          0     50000      Y                  fe430000.i2s                    mclk_rx                  
             clk_i2s3_2ch_rx_frac    0       0        0        50000000    0          0     50000      N               deviceless                      no_connection_id         
          clk_i2s3_2ch_tx_src        1       1        0        1000000000  0          0     50000      Y            deviceless                      no_connection_id         
             clk_i2s3_2ch_tx_frac    1       1        0        11289597    0          0     50000      Y               deviceless                      no_connection_id         
                clk_i2s3_2ch_tx      1       1        0        11289597    0          0     50000      Y                  deviceless                      no_connection_id         
                   mclk_i2s3_2ch_tx  2       2        0        11289597    0          0     50000      Y                     i2s@fe430000                    no_connection_id         
                hclk_i2s3_2ch        2       2        0        150000000   0          0     50000      Y                  fe430000.i2s                    hclk                     

I do have a 2.84 MHz signal on pin 12, but not the expected 44.1 kHz signal on LRCK.

I wonder where these clock rates are defined. Here I see MCLK RX/TX and HCLK, but which one is which one? https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi#n1239

Ah, and actually I cannot find &i2c3 defined anywhere in the device tree, is it? Does /proc/device-tree/__symbols__/i2s3 exist?

Hello @MichaIng

root@DietPi:/proc/device-tree/__symbols__# ls -l /proc/device-tree/__symbols__ | grep -E 'i2c3|i2s3'
-r--r--r-- 1 root root 14  2 sept. 06:38 i2c3
-r--r--r-- 1 root root 26  2 sept. 06:38 i2c3m0_xfer
-r--r--r-- 1 root root 26  2 sept. 06:38 i2c3m1_xfer
-r--r--r-- 1 root root 14  2 sept. 06:38 i2s3_2ch
-r--r--r-- 1 root root 26  2 sept. 06:38 i2s3m0_lrck
-r--r--r-- 1 root root 26  2 sept. 06:38 i2s3m0_mclk
-r--r--r-- 1 root root 26  2 sept. 06:38 i2s3m0_sclk
-r--r--r-- 1 root root 25  2 sept. 06:38 i2s3m0_sdi
-r--r--r-- 1 root root 25  2 sept. 06:38 i2s3m0_sdo
-r--r--r-- 1 root root 26  2 sept. 06:38 i2s3m1_lrck
-r--r--r-- 1 root root 26  2 sept. 06:38 i2s3m1_mclk
-r--r--r-- 1 root root 26  2 sept. 06:38 i2s3m1_sclk
-r--r--r-- 1 root root 25  2 sept. 06:38 i2s3m1_sdi
-r--r--r-- 1 root root 25  2 sept. 06:38 i2s3m1_sdo
root@DietPi:/proc/device-tree/__symbols__# 

Ah lol, I mixed up &i2c3 and &i2s3. All good, the prior exists and is the only one used, the latter does not exist but isn’t used, instead &i2s3_2ch. I mean, if any reference symbol wouldn’t exist, the overlay would fail to apply, so this would have caused failure on a much more basic level.

So AFAIU, the LRCK rate is wrong, and I do not see where this is defined. We’d need to further dig into the drivers to see whether it is really hardcoded, or how it can be changed in the device tree. In theory, if we find where the driver sets the 44.1 kHz, we could patch a property into the driver, and add that to the respective device tree node.

I found this explication but it doesn’t work

In the `rockchip_i2s_tdm.c` driver within the mainline Linux kernel, clock calculation does not rely on a fixed value. The driver employs a TDM (Time Division Multiplexing) architecture, in which the bus is divided into time slots. The frequency of the LRCK (Frame Clock) is determined by the interaction between the ALSA configuration, the number of slots, and the Rockchip SoC's clock tree (CCF – Common Clock Framework). Here is how the source code mathematically calculates and configures these ratios. 1. The fundamental mathematical formula: The driver must validate and apply the following equation to ensure the audio stream is neither sped up nor slowed down: \(\text{BCLK} = \text{Sampling Frequency (LRCK)} \times \text{Number of Slots} \times \text{Slot Width}\). LRCK (Frame Frequency): The audio file frequency (e.g., 44,100 Hz). Number of Slots: Set to a default of 8 by the Rockchip TDM driver (to handle 7.1 audio), even if you are only listening in stereo (2 channels)! Slot Width: Generally defaults to 32 bits on modern chips. The default value trap: If ALSA requests 44.1 kHz stereo audio, the driver applies its default TDM values: \(\text{BCLK} = 44,100 \times 8 \times 32 = 11.2896\text{ MHz}\). Your PCM5122 DAC, configured for standard 2-channel I2S mode (2 slots of 32 or 16 bits), expects a ratio of \(64 \times f_s\): \(\text{Expected BCLK} = 44,100 \times 2 \times 32 = 2.8224\text{ MHz}\). If the driver sends 11.2896 MHz, the PCM5122 DAC will detect a discrepancy and perform an incorrect division or interpret the signal at the wrong speed, because the total received frame width does not match its register settings. 2. Analysis of key functions in rockchip_i2s_tdm.c: In the kernel source code, three key ratio-calculation functions need to be examined: A. rockchip_i2s_tdm_set_bclk_ratio() This function is called by ALSA to configure the hardware bit clock (BCLK) divider.
```c
static int rockchip_i2s_tdm_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
{
struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); 

i2s_tdm->bclk_ratio = ratio; 
return 0;
}

What it does: It stores the global ratio requested by the Device Tree (via the simple-audio-card,bclk-ratio property). If you do not force this ratio to 64 (for 2 × 32 bits), the driver uses a dynamic calculation based on TDM.
B. rockchip_i2s_tdm_hw_params() This is the mathematical core when starting audio playback. The driver configures the RK3566 registers.

/* Conceptual snippet of the calculation in hw_params */
unsigned int bclk_rate = params_rate(params) * i2s_tdm->bclk_ratio;

If bclk_ratio is not defined, the code calculates the value using slot functions:

int slots = i2s_tdm->slots ? : 2; // Defaults to 2 if configured, but often forced by TDM
int slot_width = i2s_tdm->slot_width ? : params_width(params);
bclk_rate = params_rate(params) * slots * slot_width;
``` Use the code with caution. The driver then attempts to configure the parent clock (MCLK) as an exact multiple of this `bclk_rate` (typically using a ratio of 1, 2, 4, or 8, depending on the dividers in the `I2S_CKR` register). C. The hardware division function (`rockchip_i2s_tdm_calibrate_mclk`): The driver calculates the binary value to write to the clock configuration register (`I2S_CKR`). The divider is calculated as follows: \(\text{Divider} = \frac{\text{MCLK Frequency}}{\text{BCLK Frequency}} - 1\). If the resulting ratio is not an exact integer, the Rockchip hardware controller generates an unstable clock (jitter), causing the PCM5122 to lose sync or resulting in an incorrect final LRCK frequency when measured on an oscilloscope. 3. How to modify the behavior (Code vs. Device Tree)? Rather than recompiling the kernel, the "mainline" approach involves setting these mathematical variables directly in the Device Tree to force the driver functions to use your specific ratios: Forcing the actual number of channels at the hardware level: In the `&i2s3_2ch` node, adding `rockchip,playback-channels = <2>;` causes the driver to override the 8-slot TDM configuration and use a strict 2-slot divider. Forcing frame geometry: In the `sound` node, the combined use of: `dai-tdm-slot-num = <2>;`
`dai-tdm-slot-width = <32>;` Use the code with caution. It mathematically forces the `bclk_rate` variable to be: \(\text{Frequency} \times 2 \times 32 = 64 \times \text{Frequency}\). The `rockchip_i2s_tdm.c` driver then applies this exact constraint within its `hw_params()` function, configures the hardware register with the correct divider relative to the RK3566's MCLK, and your LRCK frequency will once again be perfectly accurate (44.1 kHz or 48 kHz).

i continue to search :wink:

next step in search

The I2S vs. TDM Format "Bug" in the Mainline Driver: In the legacy Rockchip kernel (Vendor 6.1), the driver forced standard Philips I2S mode (a 50% duty cycle square wave on LRCK) whenever two channels were detected. In the mainline driver (`sound/soc/rockchip/rockchip_i2s_tdm.c`), the logic was rewritten to unify all TDM-type controllers. By default, the driver configures the hardware for TDM pulse mode. In this mode: The LRCK clock is not a square wave (left/right); instead, it is an ultra-short synchronization pulse (Frame Sync) lasting only a single Bit Clock (BCLK) cycle at the start of the data block. If your oscilloscope is set up with a standard trigger or an unsuitable timebase, this single-bit pulse (at ~2.8 MHz) goes completely unnoticed or appears as incoherent noise. Furthermore, the PCM5122 rejects this signal because it requires a true high/low level alternation to distinguish the left channel from the right channel. Mathematical functions to modify in the source code: To force the mainline driver to switch pin 35 to standard I2S mode (square wave), the format configuration registers in the source file `sound/soc/rockchip/rockchip_i2s_tdm.c` must be modified. 1. Forcing the waveform format registers (`I2S_TXCR` and `I2S_RXCR`): In the function that applies hardware parameters (typically `rockchip_i2s_tdm_hw_params` or `rockchip_i2s_tdm_set_fmt`), the driver configures the transmission register (`I2S_TXCR`). By default, the mainline driver applies the `I2S_TXCR_TDM_MODE_TDM` mask. You need to force the bit to switch to I2S mode: c/* In rockchip_i2s_tdm.c */

// Find the section that configures the frame format
val &= ~I2S_TXCR_TDM_MODE_MASK; // Clear the current configuration
val |= I2S_TXCR_TDM_MODE_I2S; // FORCE STANDARD I2S MODE
Use this code with caution. 2. Calculating the frame division ratio (LRCK) In the Rockchip controller, the LRCK frequency is derived by dividing the BCLK clock. The associated register is I2S_CKR. If the driver thinks it is in 8-slot TDM mode, it configures the divider to divide by 256 or 512. For a standard stereo DAC, we must force the divider to divide strictly by the actual stereo frame size. The source code calculates the divider as follows: c/* Native formula from the mainline driver */
unsigned int bclk_rate = params_rate(params) * slots * slot_width;
unsigned int lrck_div = bclk_rate / params_rate(params); // Should equal (slots * slot_width)
Use this code with caution. If your Device Tree properties for masks or slots are ignored by the driver, the `slots` variable defaults to 8. To override this behavior directly in the C code (without relying on the DTS): c/* Raw modification to force 2-channel / 32-bit mode (Ratio 64) */
int forced_slots = 2;
int forced_slot_width = 32;
unsigned int bclk_rate = params_rate(params) * forced_slots * forced_slot_width;

// This forces the variable to be exactly 64
unsigned int lrck_div = forced_slots * forced_slot_width; // Writing the LRCK divider to the CKR register
regmap_update_bits(i2s_tdm->regmap, I2S_CKR, I2S_CKR_LSD_MASK,
I2S_CKR_LSD(lrck_div - 1));
Use this code with caution. How do you implement this change on DietPi? Since you are already using automation scripts, there is no need to recompile the entire Mainline Linux kernel. You can simply compile the audio driver as an external (out-of-tree) module: Download the source code for your current kernel (linux-source-6.18...). Modify the `sound/soc/rockchip/rockchip_i2s_tdm.c` file with the changes mentioned above to force `I2S_TXCR_TDM_MODE_I2S` and the 64 ratio. Compile the module using the DietPi headers (`make -C /lib/modules/$(uname -r)/build M=$(pwd) modules`). Replace the original `.ko` file (`snd-soc-rockchip-i2s-tdm.ko`) in `/lib/modules/...` and run `sudo depmod -a`. Upon the next reboot—regardless of any quirks in the mainline Device Tree—the hardware controller will apply your hard-coded physical instructions: pin 35 will instantly switch to a perfect 44.1 kHz square wave.

:wink:a patch create by IA , if wecan trust it @MichaIng

From: Local Audio Developer <dev@dietpi.com>
Date: Wed, 2 Sep 2026 21:00:00 +0200
Subject: [PATCH] ASoC: rockchip: i2s-tdm: Force standard I2S mode and clean LRCK divider when slots == 2

On mainline kernels, rockchip_i2s_tdm defaults to TDM pulse-mode. When pairing 
with headless or passive stereo DACs (like PCM5102A/PCM5122) over simple-audio-card, 
the driver fails to transition to standard I2S format. This leaves the LRCK pin 
(e.g., Pin 35 / GPIO3_A1 on Radxa Zero 3W) outputting an unreadable 1-bit short TDM pulse, 
or silent due to dynamic clock/handshake isolation.

This patch hooks into hw_params: if 2 slots are defined, it overrides the hardware 
registers (I2S_TXCR/I2S_RXCR) to force I2S mode (50% duty cycle) and enforces a strict 
64x bit-clock division to awake the physical LRCK line.

Signed-off-by: Local Audio Developer <dev@dietpi.com>
---
 sound/soc/rockchip/rockchip_i2s_tdm.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+"/>

diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c
index 5b22b10a2..bc83d42f1 100644
--- a/sound/soc/rockchip/rockchip_i2s_tdm.c
+++ b/sound/soc/rockchip/rockchip_i2s_tdm.c
@@ -263,6 +263,25 @@ static int rockchip_i2s_tdm_hw_params(struct snd_pcm_substream *substream,
 	}
 
 	/*
+	 * Bypassing Mainline TDM pulse-mode restriction for passive stereo setups.
+	 * Force standard I2S Phillips wave format if exactly 2 slots are requested.
+	 */
+	if (slots == 2) {
+		/* Force TX register into standard I2S mode instead of TDM pulse */
+		regmap_update_bits(i2s_tdm->regmap, I2S_TXCR,
+				   I2S_TXCR_TDM_MODE_MASK,
+				   I2S_TXCR_TDM_MODE_I2S);
+
+		/* Force RX register into standard I2S mode just in case */
+		regmap_update_bits(i2s_tdm->regmap, I2S_RXCR,
+				   I2S_RXCR_TDM_MODE_MASK,
+				   I2S_RXCR_TDM_MODE_I2S);
+
+		/* Enforce correct Frame Clock (LRCK) division ratio (2 slots * 32 bits = 64) */
+		lrck_div = slots * slot_width;
+	}
+
+	/*
 	 * Configuration of clock divider register (I2S_CKR)
 	 * Intercept and write the calibrated values to the hardware registers.
 	 */
-- 
2.34.1

Default TDM format: The mainline driver treats I2S as a variant of TDM (Time-Division Multiplexing). Instead of generating a high/low toggle at 44.1 kHz on pin 35, the RK3566 outputs a single-bit pulse (TDM Frame Sync). Oscilloscopes struggle to synchronize with this without a suitable trigger, and the PCM5122 rejects the signal because it requires a square wave to separate the left and right channels. Division locking (lrck_div): Without this fix, the driver calculates lrck_div based on the global TDM structure constants (often defaulting to 8 slots/channels on the SoC). Forcing the value `lrck_div = slots * slot_width;` (i.e., \(2 \times 32 = 64\)) when `slots == 2` ensures that the RK3566 system clock applies the correct fractional divider. Passive multiplexing: The mainline 6.18 kernel incorporates an aggressive power-saving mechanism (CCF). If no slave component responds via a control bus (such as I2C), the subsystem logically isolates the clock GPIO pins. Forcing I2S mode at the hardware register level (`I2S_TXCR_TDM_MODE_I2S`) bypasses this barrier and immediately wakes up pin 35.

No harm to test, but I’d definitely gate this behind a device tree property. I hope this is possible in the driver at this place. Would be bad if we break other I2S use cases where the mainline behavior works as intended and standard RX/TX mode + 64 bit division would be wrong.

Hello @MichaIng ,

Here is my latest script, including an attempt within the DTS that was unfortunately unsuccessful.

/dts-v1/;
/plugin/;

/ {
	compatible = "radxa,zero3", "rockchip,rk3566";

	/* ==========================================
	   FRAGMENT 0 : Libération et affectation I2S3
	   ========================================== */
	fragment@0 {
		target = <&i2s3_2ch>;
		__overlay__ {
			status = "okay";
			#sound-dai-cells = <0>;
			
			/* Correction du mode synchrone (TX maître absolu) */
			rockchip,clk-trcm = <1>; 
			rockchip,playback-channels = <2>;
			rockchip,capture-channels = <2>;
			
			/* Assignation stricte des broches (Pins 12, 35, 40) */
			pinctrl-names = "default";
			pinctrl-0 = <&i2s3m0_sclk
				     &i2s3m0_lrck
				     &i2s3m0_sdi
				     &i2s3m0_sdo>;
		};
	};

	/* ==========================================
	   FRAGMENT 1 : Bus I2C3 (Pins 3 et 5)
	   ========================================== */
	fragment@1 {
		target = <&i2c3>;
		__overlay__ {
			status = "okay";
			pinctrl-names = "default";
			pinctrl-0 = <&i2c3m0_xfer>;
			#address-cells = <1>;
			#size-cells = <0>;
		};
	};

	/* ==========================================
	   FRAGMENT 2 : Configuration de la Carte Son
	   ========================================== */
	fragment@2 {
		target-path = "/";
		__overlay__ {
			sound {
				status = "okay";
				compatible = "simple-audio-card";
				simple-audio-card,name = "PCM5122I2S";
				simple-audio-card,format = "i2s";

				/* Force le SoC en Maître d'horloge */
				simple-audio-card,bitclock-master = <&cpu_dai>;
				simple-audio-card,frame-master = <&cpu_dai>;
				
				/* Mathématiques Mainline : Verrouille le calcul à 64 * Fs */
				simple-audio-card,bclk-ratio = <64>;
				simple-audio-card,mclk-fs = <256>;

				cpu_dai: simple-audio-card,cpu {
					sound-dai = <&i2s3_2ch>;
					/* Force la géométrie pour empêcher le TDM par défaut */
					dai-tdm-slot-num = <2>;
					dai-tdm-slot-width = <32>;
					/* AJOUT MAJEUR : Force le pilote à n'utiliser que les 2 premiers slots */
	                                /* Cela force mathématiquement le registre à basculer en mode onde carrée (I2S standard) */
	                                dai-tdm-slot-tx-mask = <1 1>;
	                                dai-tdm-slot-rx-mask = <1 1>;
				};

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

			/* Codec virtuel générique compatible PCM5102A/PCM5122 sans MCLK */
			pcm5102a_out: pcm5102a-out {
				#sound-dai-cells = <0>;
				compatible = "ti,pcm5102a";
				status = "okay";
			};
		};
	};
};