Adding CMX655D codec to Jetson Nano 2gb

Hello,

We are using external codec chip (CMX655D) connected via I2C for configuration and I2S for sound transfer. CMX development board connected to Jetson board has one speaker and two microphones (MicL and MicR) on it.
I initially added it to the device tree in the following way:
i2c@7000c400 {
cmx_codec: cmx655@54 {
#sound-dai-cells = <0>;
compatible = “cml,cmx655D”;
reg = <0x54>;
vdd-supply = <&p3448_vdd_3v3_sys>;
reset-gpios = <&gpio TEGRA_GPIO(B, 7) GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio>;
interrupts = <TEGRA_GPIO(B, 5) 0x01>;
cmx655,classd-oc-reset-attempts = <5>;
status = “okay”;
};
};
sound {
nvidia,num-codec-link = <5>;
nvidia,dai-link-5 {
link-name = “cmx655-0”;
cpu-dai = <&tegra_i2s1>;
codec-dai = <&cmx_codec>;
cpu-dai-name = “I2S1”;
codec-dai-name = “cmx655”;
format = “i2s”;
bitclock-master;
frame-master;
srate = <48000>;
bit-format = “s16_le”;
num-channel = <2>;
name-prefix = “c”;
status = “okay”;
};
};
Codec is successfully added, but no inputs or outputs from it are available from the user space.
Then I tried to add audio routes to codec-provided microphone and speaker:
sound {
nvidia,audio-routing =
“x Headphone”, “x OUT”,
“x IN”, “x Mic”,
“y Headphone”, “y OUT”,
“y IN”, “y Mic”,
“a IN”, “a Mic”,
“b IN”, “b Mic”,
“c IN”, “c MICL”,
“c SPKR”, “c OUT”;
};
This didn’t work, with the error:
[ 4.425966] tegra-asoc: sound: ASoC: no sink widget found for c IN
[ 4.432188] tegra-asoc: sound: ASoC: Failed to add route c MICL → direct → c IN
[ 4.439698] tegra-asoc: sound: ASoC: no source widget found for c OUT
[ 4.446142] tegra-asoc: sound: ASoC: Failed to add route c OUT → direct → c SPKR

My questions:

  1. Is it at all possible to use external codecs with the built-in nvidia audio card?
  2. If so, how to make the proper routes to the sinks and sources provided by a codec? Do I need to add the widgets in the device tree or in nvidia soundcard driver, or in both?

Thanks in advance,
Nikola

Hi Nikola,

  1. Is it at all possible to use external codecs with the built-in nvidia audio card?
    Yes, It is possible to add external codec.

It seems some issue with the dts changes made for the CMX655D codec support.

  1. sound node entry
    sound {
    nvidia,num-codec-link = <5>;
    nvidia,dai-link-5 {

Can you attach the whole dts file which you have made changes. Seems like dai-link-5 is newly added by you which might not be needed. We might to alter the existing dai-link entry to include codec dai based on the Tegra I2Sx used.

on (2), Only routing change would be needed on dts file, no need to add widgets in any files. The routing entry made above was wrong. I will update you on the required changes once I get the full dts file changes info from you.

I’m sending several items:

  • final dts used for the tatget
  • dtsi with the original definition of the sound node
  • sources for the CMX codec, as it is an out-of-tree driver

Thanks in advance for your time.

cmx655.c (46.7 KB)
cmx655.h (4.9 KB)
tegra210-p3448-0003-p3542-0000.dts (11.1 KB)
tegra210-porg-p3448-common.dtsi (22.0 KB)

Hi Nikola,
Thanks!. Please correct the below issues in your target dts file changes.

  1. I2S1 is used for your codec which is not exposed on P3448+P3542 board. You need to use I2S4 for codec connection from 40pin header.

    In the file tegra210-p3448-0003-p3542-0000.dts

    Remove the below changes
        nvidia,num-codec-link = <5>;
		nvidia,dai-link-5 {
			link-name = "cmx655-0";
			cpu-dai = <&tegra_i2s1>;
			codec-dai = <&cmx_codec>;
			cpu-dai-name = "I2S1";
			codec-dai-name = "cmx655";
			format = "i2s";
			bitclock-master;
			frame-master;
			srate = <48000>;
			bit-format = "s16_le";
			num-channel = <2>;
			//name-prefix = "o";
			status = "okay";
		};
    Add below lines after removing above changes.
		nvidia,dai-link-1 {
			link-name = "cmx655-0";
			cpu-dai = <&tegra_i2s4>;
			codec-dai = <&cmx_codec>;
			cpu-dai-name = "I2S4";
			codec-dai-name = "cmx655";
			format = "i2s";
			bitclock-master;
			frame-master;
			srate = <48000>;
			bit-format = "s16_le";
			num-channel = <2>;
			name-prefix = "x";
			status = "okay";
		};
Also correct the routing entry like below
			"x Headphone",	"x SPKR",
			"x MICL",		"x Mic",
			"y Headphone",	"y OUT",
			"y IN",		"y Mic",
			"a IN",		"a Mic",
			"b IN",		"b Mic";

Also make sure the I2S4 pinmux is set to SFIO using jetson-io tool as by default the I2S4 is GPIO on 40 pin header.

Let’s give the try with this change for now.

1 Like

Thanks, this actually registered the codec successfully.
Now, when I try aplay some audio sample, I have some underrun messages, but no sound played at the speaker:
Playing WAVE ‘file_example_WAV_1MG.wav’ : Signed 16 bit Little Endian, Rate 8000 Hz, Stereo
underrun!!! (at least 16.663 ms long)
underrun!!! (at least 0.031 ms long)
underrun!!! (at least 38.418 ms long)
Could you give me some hints how to play (or record) the sound with this setup?
Thank you in advance, and thank you again for the help so far.

Hi Nikola,
Can you confirm whether I2S4 signals are probed fine before it goes to the codec. I hope the SFIO settings for I2S4 on 40pin header is done.

Also does the codec requires external MCLK clock or only I2S4 signal should be fine for the codec playback.

To play with aplay below reference command should work considering the APE soundcard is registered as card 1

amixer -c 1 cset name=“ADMAIF1 Mux” I2S4
amixer -c 1 cset name=“I2S4 Mux” ADMAIF1
aplay -D hw:1,0

Please try once with 48KHz wavefile content for playback.

Pinmux for i2s4 is:
dap4_din_pj5 {
nvidia,pins = “dap4_din_pj5”;
nvidia,function = “i2s4b”;
nvidia,pull = <TEGRA_PIN_PULL_UP>;
nvidia,tristate = <TEGRA_PIN_DISABLE>;
nvidia,enable-input = <TEGRA_PIN_ENABLE>;
};

                    dap4_dout_pj6 {
                            nvidia,pins = "dap4_dout_pj6";
                            nvidia,function = "i2s4b";
                            nvidia,pull = <TEGRA_PIN_PULL_NONE>;
                            nvidia,tristate = <TEGRA_PIN_DISABLE>;
                            nvidia,enable-input = <TEGRA_PIN_DISABLE>;
                    };

                    dap4_fs_pj4 {
                            nvidia,pins = "dap4_fs_pj4";
                            nvidia,function = "i2s4b";
                            nvidia,pull = <TEGRA_PIN_PULL_NONE>;
                            nvidia,tristate = <TEGRA_PIN_DISABLE>;
                            nvidia,enable-input = <TEGRA_PIN_DISABLE>;
                    };

                    dap4_sclk_pj7 {
                            nvidia,pins = "dap4_sclk_pj7";
                            nvidia,function = "i2s4b";
                            nvidia,pull = <TEGRA_PIN_PULL_NONE>;
                            nvidia,tristate = <TEGRA_PIN_DISABLE>;
                            nvidia,enable-input = <TEGRA_PIN_DISABLE>;
                    };

Codec devkit has an internal oscillator of 24.576MHz, so no external clock is necessary.
I’ll check the behaviour of the pins with oscilloscope today and share the results.

I tried both amixer commands, they both fail with the message:
amixer: Cannot find the given element from control hw:1
I tried to list and grep card 1 for these two resources:
root@jetson-nano-2gb-devkit:~# amixer -c 1 | grep -i “ADMAIF1 MUX”
Simple mixer control ‘ADMAIF1 Mux’,0
root@jetson-nano-2gb-devkit:~# amixer -c 1 | grep -i “I2S4 MUX”
Simple mixer control ‘I2S4 Mux’,0
It seems like they are there, but setting them fails.

Oscilloscope shows:

  • dout is zero, with or without playback
  • sclk is 1, with or without playback
  • fs has some low voltage, but above zero
    Thing that comes to my mind: should I change the direction of sclk and fs so that they are generated by codec?

Looks like codec supports both Master and Slave mode. So any direction should work considering pinmux is set properly.

Please refer below link for jetson-io tool to set i2s4 pinmux on 40 pin header.
https://docs.nvidia.com/jetson/archives/l4t-archived/l4t-3271/index.html#page/Tegra%20Linux%20Driver%20Package%20Development%20Guide/hw_setup_jetson_io.html#

Also as no signal is coming out, Can you check is DAPM path is completed during aplay command. Please add debug prints in the hw_params function (cmx655HwParams) in the codec file to confirm is dapm path is completed or not.

Please go through the documentation on various debugging info present @ https://docs.nvidia.com/jetson/archives/l4t-archived/l4t-3271/index.html#page/Tegra%20Linux%20Driver%20Package%20Development%20Guide/asoc_driver.19.2.html#

1 Like

Question regarding pinmux: if codec is set to bitclock master (which it is in our case), should then i2s clk and fs lines be set as inputs? My logic is that codec should be the one driving these clocks, so then Jetson should treat them as inputs.
I added some debug prints to hw_params functions (both mandatory and conditional); if none of them gets printed, does that mean that the path is incomplete?

if codec is set to bitclock master (which it is in our case), should then i2s clk and fs lines be set as inputs?
Yes, that’s right the I2S clk and fs should be set as inputs.

if none of them gets printed, does that mean that the path is incomplete?
Yes, if debug prints are not seen, then dapm path is incomplete which needs fix in the routing entry made in dts file.

The document pointed has some debug info on how to find DAPM issues, let me know if it helps.

You were correct, hw_params is never invoked.
I noticed that tegra_machine_dai_init from tegra_machine_driver_mobile.c explicitly tries to find specific link names, such as:
rtd = snd_soc_get_pcm_runtime(card, “rt565x-playback”);
It will then only set the sysclock for the given codec. Should I modify tegra_machine_driver_mobile.c to react also on cmx655d as well?

Yes, you need to call set_sysclk codec callback from machine driver as we do for rt565x-playback. you need to use link-name “cmx655-0”

As DAPM path not completed, can you dump the codec mixer controls to check if any controls/switch needs to be set before playback?.

I introduced the set_sysclk for codec in the machine driver, but I found out that bitclock-master and frame-master device tree properties are completely ignored by the driver, so setting the codec as bitclock master must be done manually. Isn’t this a slight anti-pattern, or am I missing something?

Hi Nikola,
Please use the mixer control setting as below to set codec as master.

amixer -c 1 cset name=“I2S4 codec master mode” “cbm-cfm”

1 Like

With some debugging hooks, I get this during booting:
[ 4.428778] cmx655 1-0054: cmx655I2cProbe entered
[ 4.429881] cmx655 1-0054: devm_snd_soc_register_codec returns 0
[ 4.429884] cmx655 1-0054: cmx655I2cProbe returns 0
[ 4.440021] OPE platform probe
[ 4.440107] OPE platform probe successful
[ 4.440132] OPE platform probe
[ 4.440202] OPE platform probe successful
[ 4.443239] input: tegra-hda HDMI/DP,pcm=3 as /devices/70030000.hda/sound/card0/input0
[ 4.458610] mmc0: SDHCI controller on sdhci-tegra.3 [sdhci-tegra.3] using ADMA 64-bit with 64 bit addr
[ 4.466330] cmx655 1-0054: entered CMX655 component probe
[ 4.467295] cmx655 1-0054: starting the system clock
[ 4.470147] tegra-asoc: sound: ADMAIF1 <-> ADMAIF1 mapping ok
[ 4.470272] tegra-asoc: sound: ADMAIF2 <-> ADMAIF2 mapping ok
[ 4.470393] tegra-asoc: sound: ADMAIF3 <-> ADMAIF3 mapping ok
[ 4.470516] tegra-asoc: sound: ADMAIF4 <-> ADMAIF4 mapping ok
[ 4.470655] tegra-asoc: sound: ADMAIF5 <-> ADMAIF5 mapping ok
[ 4.470774] tegra-asoc: sound: ADMAIF6 <-> ADMAIF6 mapping ok
[ 4.470894] tegra-asoc: sound: ADMAIF7 <-> ADMAIF7 mapping ok
[ 4.471011] tegra-asoc: sound: ADMAIF8 <-> ADMAIF8 mapping ok
[ 4.471132] tegra-asoc: sound: ADMAIF9 <-> ADMAIF9 mapping ok
[ 4.471252] tegra-asoc: sound: ADMAIF10 <-> ADMAIF10 mapping ok
[ 4.473326] cmx655 1-0054: set dai fmt
[ 4.473328] cmx655 1-0054: bitclock and frame slave
[ 7.512834] cmx655 1-0054: set dai fmt
[ 7.516600] cmx655 1-0054: bitclock and frame slave
[ 7.522179] cmx655 1-0054: set dai fmt
[ 7.526008] cmx655 1-0054: bitclock and frame master
[ 7.557452] eth0: 0xffffff800a64c000, 48:b0:2d:5b:28:16, IRQ 403
[ 7.630992] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready
[ 7.656496] tegradc tegradc.0: blank - powerdown
[ 7.845660] tegradc tegradc.1: unblank
[ 7.849624] tegradc tegradc.0: blank - powerdown
[ 8.380654] tegra-asoc: sound: format 4
[ 8.380687] tegra-asoc: sound: trying to initialise cmx655 clock
[ 8.380690] set day system clock 1
[ 8.551118] tegra-asoc: sound: format 4
[ 8.555005] tegra-asoc: sound: trying to initialise cmx655 clock
[ 8.561028] set dai system clock 1
[ 8.685543] tegra-asoc: sound: format 4
[ 8.692420] tegra-asoc: sound: trying to initialise cmx655 clock
[ 8.701658] set dai system clock 1
[ 8.714692] tegra-asoc: sound: format 4
[ 8.718588] tegra-asoc: sound: trying to initialise cmx655 clock
[ 8.724606] set dai system clock 1
[ 8.838172] tegra-asoc: sound: format 4
[ 8.842055] tegra-asoc: sound: trying to initialise cmx655 clock
[ 8.848223] set dai system clock 1
[ 9.950515] random: crng init done
[ 29.053507] tegra-asoc: sound: format 4
[ 29.057438] tegra-asoc: sound: trying to initialise cmx655 clock
[ 29.063587] set dai system clock 1
[ 29.073868] tegra-asoc: sound: format 4
[ 29.078316] tegra-asoc: sound: trying to initialise cmx655 clock
[ 29.084764] set dai system clock 1
The results of amixer commands are in the logs. Any help would be welcome in understanding why the hw_params are not invoked.
contents.txt (146.2 KB)
controls.txt (30.5 KB)
scontents.txt (139.5 KB)
scontrols.txt (26.0 KB)

Hi Nikola,
The issue of hw_params not getting called will be due to not having proper routing entry in dts related to codec and if any codec mixer controls not set before playback.

Did you get chance to contact codec vendor for right steps here?

I see few codec mixer controls with switch may be we might need to set these controls based on whether the playback is on speaker/headset etc…

@mkumard I like the device tree overlay approach, and I’m trying to configuring a device tree for the cmx655D codec such that we can provide alsa-controls via the i2c codec driver (the codec is functional when using a user-space API to control). In addition to the i2c control path there is the reset pin and the interrupt pin (GPIO 13 and GPIO 15). How would we create a device tree overlay to accomplish this task? I see the jetson provides device tree overlay examples but they are almost entirely focused on i2s, which is working.

I’m able to compile and boot this device tree overlay:

/dts-v1/;
/plugin/;
/ {
	overlay-name = "cmx655d overlay";
	jetson-header-name = "Jetson 40pin Header";
	compatible = "nvidia,p3542-0000+p3448-0003";

	// define sound card
	fragment@0 {
		target = <&tegra_sound>;
		__overlay__ {
			status="okay";
			
			nvidia,dai-link-1 {
				link-name = "cmx655-0";
				cpu-dai = <&tegra_i2s4>;
				codec-dai = <&cmx_codec>;
				cpu-dai-name = "I2S4";
				codec-dai-name = "cmx655";
				format = "i2s";
				bitclock-master;
				frame-master;				
				srate = <48000>;
				bit-format = "s16_le";
				num-channels = <2>;
				name-prefix = "x";
				status = "okay";
			};
		};
	};
	
	// Define drivers for codec (one input and one output) 
	fragment@1 {
		target = <&hdr40_i2c1>;
		__overlay__ {
			#address-cells = <1>;
			#size-cells = <0>;

			cmx_codec: cmx655@54 {
				#sound-dai-cells = <0>;
				compatible = "cml,cmx655D";
				status = "okay";
				reg = <0x54>;
				vdd-supply = <&hdr40_vdd_3v3>;
				reset-gpios = <0x5a 0x0f 0x00>;
				interrupt-parent = <0x5a>;
				interrupts = <0x0d 0x01>;
				cmx655,classd-oc-reset-attempts = <5>;
			};
		};
	};


	fragment@2 {
		target = <&pinmux>;
		__overlay__ {
			pinctrl-names = "default";
			pinctrl-0 = <&hdr40_pinmux>;

			hdr40_pinmux: header-40pin-pinmux {
				hdr40-pin12 {
					nvidia,pins = "dap4_sclk_pj7";
					nvidia,function = "i2s4b";
					nvidia,pull = <0x1>;
					nvidia,tristate = <0x0>;
					nvidia,enable-input = <0x1>;
				};

				hdr40-pin35 {
					nvidia,pins = "dap4_fs_pj4";
					nvidia,function = "i2s4b";
					nvidia,pull = <0x1>;
					nvidia,tristate = <0x0>;
					nvidia,enable-input = <0x1>;
				};

				hdr40-pin38 {
					nvidia,pins = "dap4_din_pj5";
					nvidia,function = "i2s4b";
					nvidia,pull = <0x1>;
					nvidia,tristate = <0x1>;
					nvidia,enable-input = <0x1>;
				};

				hdr40-pin40 {
					nvidia,pins = "dap4_dout_pj6";
					nvidia,function = "i2s4b";
					nvidia,pull = <0x1>;
					nvidia,tristate = <0x0>;
					nvidia,enable-input = <0x0>;
				};
				
			};
		};
	};
};

but i’m getting these warnings which I suppose are interfering with the configuration:

dtc -@ -I dts -O dtb -o cmx655d.dtbo cmx655d.dts
cmx655d.dtbo: Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property
cmx655d.dtbo: Warning (unit_address_vs_reg): Node /fragment@1 has a unit name, but no reg property
cmx655d.dtbo: Warning (unit_address_vs_reg): Node /fragment@2 has a unit name, but no reg property
cmx655d.dtbo: Warning (gpios_property): Could not get phandle node for /fragment@1/__overlay__/cmx655@54:reset-gpios(cell 0)
cmx655d.dtbo: Warning (interrupts_property): Bad interrupt-parent phandle for /fragment@1/__overlay__/cmx655@54

This is how it was done on the raspberry pi:

/dts-v1/;
/plugin/;

/*
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+   Last modified: $Date: 2020-04-09 10:12:39 +0100 (Thu, 09 Apr 2020) $
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+   Device Tree Overlay for generic I2S audio output on Raspberry Pi
*/

/ {
    compatible = "brcm,bcm2708";
    // Define sound card 
    fragment@0 {
        target = <&sound>;
        __overlay__ {

            compatible = "simple-audio-card";
            simple-audio-card,name = "CMX655D";
            
            status="okay";

            simple-audio-card,dai-link@0 {
                format = "i2s";
                
                bitclock-master = <&codec_dai>;
                frame-master = <&codec_dai>;

                cpu_dai: cpu {
                    sound-dai = <&i2s>;
                };

                codec_dai: codec {
                    sound-dai = <&codec>;
                };
            };
        };
    };
    // Define drivers for codec (one input and one output) 
    fragment@1 {
        target = <&i2c>;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";
            codec: cmx655 {
                reg = <0x54>;
                #sound-dai-cells = <0>;
                compatible = "cml,cmx655D";
                reset-gpios = <&gpio 24 1>;
                interrupt-parent = <&gpio>;
                interrupts = <25 0x2>; /* falling edge */
                interrupt-names = "irq";
                pinctrl-names = "default";
                pinctrl-0 = <&ev6550DHAT_pins>;
                cmx655,classd-oc-reset-attempts = <5>;
            };
        };
    };

    // Enable I2S
    fragment@2 {
        target = <&i2s>;
        __overlay__ {
            #sound-dai-cells = <0>;
            status = "okay";
        };
    };
    
    // Setup pins used by EV6550DHAT
    fragment@3 {
        target = <&gpio>;
        __overlay__ {
            ev6550DHAT_pins: cmx655_pins {
                // Pins resetN, IRQn
                brcm,pins = <24 25>;
                // Out, In
                brcm,function = <1 0>; 
                // No pull, pull up
                brcm,pull = <0 2>; 
            };
        };
    };
};

What would be the equivalent to this for the jetson nano 2GB? Attached is the generated
kernel_tegra210-p3448-0003-p3542-0000-user-custom.dts (308.0 KB)
device tree source.

Thanks,
Abdo

Hi Abdo,
There are multiple overlay dtsi files exist @ hardware\nvidia\platform\tegra\common\kernel-dts\overlays which has support for various codecs. May be this might help you on overlay changes. Let me know if still need any help on this.

the reset pin and the interrupt pin (GPIO 13 and GPIO 15). How would we create a device tree overlay to accomplish this task?
I believe these entries are part of i2c codec node, so no extra changes required for reset and interrupt with overlay as it will be part of codec overlay node.

reset-gpios = <0x5a 0x0f 0x00>; interrupt-parent = <0x5a>; interrupts = <0x0d 0x01>;
The above entry in your file looks not correct, for reset and codec jack interrupt entries in dts, below is an example to add entries in codec node.
/* Codec IRQ output /
interrupt-parent = <&gpio>;
interrupts = <TEGRA_GPIO(B, 6) GPIO_ACTIVE_HIGH>;
/
gpio reset */
reset-gpios = <&gpio TEGRA_GPIO(B, 6) 0>;
Please find right GPIO map (i.e B,6) in pinmux sheet based on the GPIO pin used for your codec for reset and Interrupt.