Enable I2S_2 (SAI5)

Hi. I’m trying to use an ADC driver that was developed for the sound subsystem. The ADC I have is a ti ads127x, which is very similar to the ads117x driver (available on mainline), but the ads117x is a 16 bit adc, while the ads127x is a 24 bit adc.

I’m using the following dts, which follows the simple-card bindings documentation

&pinctrl_sai5 {
        fsl,pins = <
                MX8MM_IOMUXC_SAI5_RXD0_SAI5_RX_DATA0            0x6     /* SODIMM 48 */
                MX8MM_IOMUXC_SAI5_RXD1_SAI5_TX_SYNC             0x6     /* SODIMM 44 */
                MX8MM_IOMUXC_SAI5_RXD2_SAI5_TX_BCLK             0x6     /* SODIMM 42 */
                MX8MM_IOMUXC_SAI5_RXD3_SAI5_TX_DATA0            0x6     /* SODIMM 46 */
        >;
};

&sai5 {
        #sound-dai-cells = <0>;
        assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
        assigned-clock-rates = <24576000>;
        assigned-clocks = <&clk IMX8MM_CLK_SAI5>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_sai5>;
        status = "okay";
};

/ {
        adc: adc {
                compatible = "simple-audio-card";
                simple-audio-card,name = "ti-ads1278";
                bitclock-master = <&ads1278_codec>;
                frame-master = <&ads1278_codec>;
                simple-audio-card,routing =
                        "Input1", "Capture",
                        "Input2", "Capture",
                        "Input3", "Capture",
                        "Input4", "Capture",
                        "Input5", "Capture",
                        "Input6", "Capture",
                        "Input7", "Capture",
                        "Input8", "Capture";

                simple-audio-card,dai-link {
                        format = "left_j";
                        bitclock-master = <&ads1278_codec>;
                        frame-master = <&ads1278_codec>;

                        cpu {
                                sound-dai = <&sai5>;
                                dai-tdm-slot-num = <8>;
                                dai-tdm-slot-width = <24>;
                        };

                        ads1278_codec: codec {
                                sound-dai = <&ads1278 0>;
                                clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
                        };
                };
        };

        ads1278: codec-ads1278@0 {
                compatible = "ti,ads1278";
                #sound-dai-cells = <0>;
                status = "okay";
        };
};


&wm8904_1a {
        status = "disabled";
}

I’ve connected a logic analyzer to the I2S_1_BCLK, I2S_1_MCLK, I2S_2_BCLK, I2S_2_SYNC pins on header X2/X3 (and disconnected them from X4). With the logic analyzer connected, and after running arecord, I’d expected to see the bit clock signal on I2S_2_BCLK, but it doesn’t change a bit. None of the I2S_2 signals change.

This is the output of several commands:

root@verdin-imx8mm-06894967:~# ./stest 
### Devices (/proc/asound/devices)
  2: [ 0- 0]: digital audio capture
  3: [ 0]   : control
 33:        : timer

### Cards (/proc/asound/cards)
 0 [tiads1278      ]: ti-ads1278 - ti-ads1278
                      ti-ads1278

### Modules
snd_soc_ads127x        20480  1

### dmesg
[    4.796126] ads127x-codec codec-ads1278@0: > ti,ads127x_probe
[    4.797902] debugfs: Directory '30050000.sai' with parent 'ti-ads1278' already present!
[    4.798415] asoc-simple-card adc: ads127x-hifi <-> 30050000.sai mapping ok

### test
arecord -D hw:0,0 -V mono -c 1 -f S24_LE -r 48000 -t wav line.wav
Recording WAVE 'line.wav' : Signed 24 bit Little Endian, Rate 48000 Hz, Mono

It can be seen that the “card” is detected, the codec is enabled, that the adc is recognized and connected to sai5 (30050000.sai).

If I change the “card”'s codec to use the SAI2_ROOT clock (instead of SAI5_ROOT) and the cpu to use sai2 (instead of sai5), the card gets connected to sai2 (30020000.sai), and I get a 25 MHz signal on I2S_1_MCLK, but nothing else.

The DTS I’m using is device-trees/dts-arm64/imx8mm-verdin-nonwifi-dev.dts, which includes:

  • imx8mm-verdin.dtsi
  • imx8mm-verdin-nonwifi.dtsi
  • imx8mm-verdin-dev.dtsi

And the DTS I’ve copied above is a modified version of imx8mm-verdin-dev.dtsi (the last on of the list).

Can someone see what I’m doing wrong or what I’m missing?

Thanks!

Dear @pato,

Can you please share with us more details on your setup?

  • Which exact Verdin Module and Version are you using?
  • It seems that you’re using the Verdin Development Board but can you confirm it with the version?
  • Which Image are you running on your module and version?
  • How do you compile and deploy this Device Tree to your module?
  • You talked about the driver. Did you check that it’s enabled on your image with something like zcat /proc/config.gz | grep -i ads?

Thanks

  • Which exact Verdin Module and Version are you using?

Verdin imx8m-mini

  • It seems that you’re using the Verdin Development Board but can you confirm it with the version?

Verdin Development Board v1.1B

  • Which Image are you running on your module and version?

Torizon Core RT Tezi 5.6.0+build.13

  • How do you compile and deploy this Device Tree to your module?

Using the torizoncore-builder. The following lines compile the kernel module, the device tree and upload everything:

torizoncore-builder kernel build_module linux/sound/soc/codecs/ --autoload
torizoncore-builder dt apply device-trees/dts-arm64/imx8mm-verdin-nonwifi-dev.dts
torizoncore-builder dto apply device-trees/overlays/verdin-imx8mm_lt8912_overlay.dts
torizoncore-builder union ti_ads1278
torizoncore-builder deploy ti_ads1278 --remote-host IP <etc>
  • You talked about the driver. Did you check that it’s enabled on your image with something like zcat /proc/config.gz | grep -i ads?

I did not check using that specific command, but the first line of the dmesg part of my previous post says: ads127x-codec codec-ads1278@0: > ti,ads127x_probe, which is printed when the module is probed.

The dmesg command is dmesg | grep ads127, and the output is:

### dmesg
[    4.796126] ads127x-codec codec-ads1278@0: > ti,ads127x_probe
[    4.797902] debugfs: Directory '30050000.sai' with parent 'ti-ads1278' already present!
[    4.798415] asoc-simple-card adc: ads127x-hifi <-> 30050000.sai mapping ok

Thank you!

Hi @pato,

Could you please share the your entire custom imx8mm-verdin-dev.dtsi file? From the device tree section you’ve shared, I believe your ads1278: codec-ads1278@0 codec node should be inside a I2C node like &i2c4, as seen in the original imx8mm-verdin-dev.dtsi file.

Also, are you using a custom ads1278 driver? I did not find it in the Mainline kernel.

Best regards,
André

Sure, this is the content of the custom dtsi (imx8mm-verdin-dev.dtsi (1.6 KB)):

// SPDX-License-Identifier: GPL-2.0+ OR MIT
/*
 * Copyright 2020 Toradex
 */

#include "imx8mm-verdin-dahlia.dtsi"

&pinctrl_sai5 {
        fsl,pins = <
                MX8MM_IOMUXC_SAI5_RXD0_SAI5_RX_DATA0            0x6     /* SODIMM 48 */
                MX8MM_IOMUXC_SAI5_RXD1_SAI5_TX_SYNC             0x6     /* SODIMM 44 */
                MX8MM_IOMUXC_SAI5_RXD2_SAI5_TX_BCLK             0x6     /* SODIMM 42 */
                MX8MM_IOMUXC_SAI5_RXD3_SAI5_TX_DATA0            0x6     /* SODIMM 46 */
        >;
};

&sai5 {
        #sound-dai-cells = <0>;
        assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
        assigned-clock-rates = <24576000>;
        assigned-clocks = <&clk IMX8MM_CLK_SAI5>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_sai5>;
        status = "okay";
};

/ {
        adc: adc {
                compatible = "simple-audio-card";
                simple-audio-card,name = "ti-ads1278";
                bitclock-master = <&ads1278_codec>;
                frame-master = <&ads1278_codec>;
                simple-audio-card,routing =
                        "Input1", "Capture",
                        "Input2", "Capture",
                        "Input3", "Capture",
                        "Input4", "Capture",
                        "Input5", "Capture",
                        "Input6", "Capture",
                        "Input7", "Capture",
                        "Input8", "Capture";

                simple-audio-card,dai-link {
                        format = "left_j";
                        bitclock-master = <&ads1278_codec>;
                        frame-master = <&ads1278_codec>;

                        cpu {
                                sound-dai = <&sai5>;
                                dai-tdm-slot-num = <8>;
                                dai-tdm-slot-width = <24>;
                        };

                        ads1278_codec: codec {
                                sound-dai = <&ads1278 0>;
                                clocks = <&clk IMX8MM_CLK_SAI5_ROOT>;
                        };
                };
        };

        ads1278: codec-ads1278@0 {
                compatible = "ti,ads1278";
                #sound-dai-cells = <0>;
                status = "okay";
        };
};

&gpio_expander_21 {
        status = "okay";
};

/* Limit frequency on dev board due to long traces and bad signal integrity */
&usdhc2 {
        max-frequency = <100000000>;
};

&wm8904_1a {
        status = "disabled";
};

The driver from the mainline kernel is for the ads117x (here). I’ve changed it a littlebit, but basically one could change line 22 of that file by replacing SNDRV_PCM_FMTBIT_S16_LE with SNDRV_PCM_FMTBIT_S24_LE and would obtain the driver for the ads127x (here’s my modified ads127x.c version (2.7 KB))

Here you can find the dt-binding documentation of the ads117x driver. Or all files from TI.

Bests!

@andre_m.tx Please, also note that the ads1278 does not provide an i2c interface for controlling, everything is managed using the SAI interface.
Here you can see the product/datasheet.

Hi @pato,

Thanks for the update. We are internally discussing your issue. We will return to you as soon as possible.

Best regards,
André

Hi @pato,

After a long investigation, we’ve found the following:

Therefore, the ADS1278 does not support the SAI interface of the Verdin iMX8M Mini. This is probably the reason why you are not getting any signal on the I2S pins.

Although it still should be possible to use the ADS1278 as an audio ADC, it will require a custom, manual solution that uses interfaces which are not meant for audio signals.

Hence, in order to use the SAI interface, we recommend that you use an ADC that explicitly supports I2S.

Best regards,
André

Hi @andre_m.tx ,

Although the SAI interface is not explicitly mentioned, the datasheet says the following:

image
That wording is very similar to that used for the I2S, including the left/right clock, the bit clock, and the left-justified configuration.

On the other hand, this is an official driver from Texas for the ads1178, another ADC. The ads1278 is a drop-in replacement for the ads1178. It’s datasheet shows the same signals and wording as that for the ads1278.

What we are expecting now is to generate the SAI signals, as the ADC works in slave mode and expects the master to generate those for it to send. As far as I understand, regardless of it the ADC is connected or not, I should see the SAI signals.

This are signals of the Verdin iMX8MM we need:


Actually, just sai5.TX_SYNC, sai5.TX_BCLK and sai5.RX_DATA, which are mapped to the development board X2, X3 and X4 headers as shown in it’s datasheet:

If we could get those signals, we could test the official TI driver.

regards,

Hello @pato,
I just read through the thread, and I would like to give you some suggestions of things to look into:
it seems to me that the problem could be related to the fact that the SAI interface is not being configured to be a master for the clock and the frame sync (on the RX side). I think that the code that makes this setup is here: fsl_sai.c « fsl « soc « sound - linux-toradex.git - Linux kernel for Apalis and Colibri modules

        /* DAI clock master masks */                                                                                                                           
        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {                                                                                                            
        case SND_SOC_DAIFMT_CBS_CFS:                                                                                                                           
                val_cr2 |= FSL_SAI_CR2_BCD_MSTR;                                                                                                               
                val_cr4 |= FSL_SAI_CR4_FSD_MSTR;                                                                                                               
                break;                                                                                                                                         
        case SND_SOC_DAIFMT_CBM_CFM:                                                                                                                           
                sai->slave_mode[tx] = true;                                                                                                                    
                break;                                                                                                                                         
        case SND_SOC_DAIFMT_CBS_CFM:                                                                                                                           
                val_cr2 |= FSL_SAI_CR2_BCD_MSTR;                                                                                                               
                break;                                                                                                                                         
        case SND_SOC_DAIFMT_CBM_CFS:                                                                                                                           
                val_cr4 |= FSL_SAI_CR4_FSD_MSTR;                                                                                                               
                sai->slave_mode[tx] = true;                                                                                                                    
                break;                                                                                                                                         
        default:                                                                                                                                               
                return -EINVAL;                                                                                                                                
        } 

My feeling is that what you are looking for is to have the SAI configured with SND_SOC_DAIFMT_CBM_CFM in soc-dai.h:

/*                                                                                                                                                             
 * DAI hardware clock masters.                                                                                                                                 
 *                                                                                                                                                             
 * This is wrt the codec, the inverse is true for the interface                                                                                                
 * i.e. if the codec is clk and FRM master then the interface is                                                                                               
 * clk and frame slave.                                                                                                                                        
 */                                                                                                                                                            
#define SND_SOC_DAIFMT_CBM_CFM          (1 << 12) /* codec clk & FRM master */                                                                                 
#define SND_SOC_DAIFMT_CBS_CFM          (2 << 12) /* codec clk slave & FRM master */                                                                           
#define SND_SOC_DAIFMT_CBM_CFS          (3 << 12) /* codec clk master & frame slave */                                                                         
#define SND_SOC_DAIFMT_CBS_CFS          (4 << 12) /* codec clk & FRM slave */

To check what’s actually being configured, you could enable the debug on the simple-card driver in sound/soc/generic/simple-card.c by applying the following patch:

diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c
index 58c200c27ba1..75d8beb451fa 100644
--- a/sound/soc/generic/simple-card.c
+++ b/sound/soc/generic/simple-card.c
@@ -5,6 +5,9 @@
 // Copyright (C) 2012 Renesas Solutions Corp.
 // Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
 
+#define DEBUG
+
+
 #include <linux/clk.h>
 #include <linux/device.h>
 #include <linux/module.h>

After recompiling / installing the kernel, you should get the following output on the kernel boot (please note that I tested this in another module, but you should have a similar output on yours):

root@verdin-imx8mm-06827736:~# dmesg|grep simple
[    1.312682] usbcore: registered new interface driver usb_serial_simple
[    1.734071] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    1.734151] asoc-simple-card sound-card: link_of (/sound-card)
[    2.542609] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    2.542688] asoc-simple-card sound-card: link_of (/sound-card)
[    2.598530] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    2.598609] asoc-simple-card sound-card: link_of (/sound-card)
[    2.679071] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    2.679137] asoc-simple-card sound-card: link_of (/sound-card)
[    2.690458] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    2.690522] asoc-simple-card sound-card: link_of (/sound-card)
[    4.642577] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    4.642644] asoc-simple-card sound-card: link_of (/sound-card)
[    4.664298] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    4.664372] asoc-simple-card sound-card: link_of (/sound-card)
[    4.673068] asoc-simple-card sound-card: link 1, dais 2, ccnf 0
[    4.673136] asoc-simple-card sound-card: link_of (/sound-card)
[    4.673201] asoc-simple-card sound-card: Card Name: imx8mm-nau8822
[    4.673205] asoc-simple-card sound-card: DAI0
[    4.673210] asoc-simple-card sound-card: cpu sysclk = 400000000
[    4.673213] asoc-simple-card sound-card: cpu direction = IN
[    4.673217] asoc-simple-card sound-card: codec sysclk = 24576000
[    4.673220] asoc-simple-card sound-card: codec direction = IN
[    4.673224] asoc-simple-card sound-card: codec clk 24576000Hz
[    4.673228] asoc-simple-card sound-card: dai name = 30020000.sai-nau8822-hifi
[    4.673231] asoc-simple-card sound-card: dai format = 1001
[    4.700210] asoc-simple-card sound-card: nau8822-hifi <-> 30020000.sai mapping ok
[    4.707830] asoc-simple-card sound-card: ASoC: no DMI vendor name!

You can see that there is a format bitmask that I assume is what we need to change:

[    4.673231] asoc-simple-card sound-card: dai format = 1001

Also, there are these device tree flags that could be related to master / slave clock configurations:

        if ((of_find_property(np, "fsl,i2s-xtor", NULL) != NULL) ||                                                                                            
            (of_find_property(np, "fsl,txm-rxs", NULL) != NULL))                                                                                               
        {                                                                                                                                                      
                sai->masterflag[FSL_FMT_TRANSMITTER] = SND_SOC_DAIFMT_CBS_CFS;                                                                                 
                sai->masterflag[FSL_FMT_RECEIVER] = SND_SOC_DAIFMT_CBM_CFM;                                                                                    
        } else {                                                                                                                                               
                if (!of_property_read_u32(np, "fsl,txmasterflag",                                                                                              
                        &sai->masterflag[FSL_FMT_TRANSMITTER]))                                                                                                
                        sai->masterflag[FSL_FMT_TRANSMITTER] <<= 12;                                                                                           
                if (!of_property_read_u32(np, "fsl,rxmasterflag",                                                                                              
                        &sai->masterflag[FSL_FMT_RECEIVER]))                                                                                                   
                        sai->masterflag[FSL_FMT_RECEIVER] <<= 12;                                                                                              
        }

Could you try to check if some of this helps you solve the issue?

Best regards,
Rafael Beims