This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM3352: When McASP is set to I2S slave and audio playback is stopped, noise gets outputted to the left audio channel

Part Number: AM3352

Tool/software:

Hello everyone.

We are using an AM3352 SoC on a TQMA335x SoM, and we have successfully set up one McASP serializer as I2S slave by using simple-audio-card in the devicetree and a minimal snd_soc_dai_driver in the kernel (Linux 5.4).

This is the relevant part of our devicetree:

/ {
    wssdsp1: wssdsp1 {
		#sound-dai-cells = <0>;
		compatible = "dwe,wssdsp1";
		status = "okay";
	};

	sound {
		compatible = "simple-audio-card";
		simple-audio-card,name = "WSSDSP1";
		simple-audio-card,format = "i2s";
		simple-audio-card,bitclock-master = <&sound_master>;
		simple-audio-card,frame-master = <&sound_master>;
		simple-audio-card,cpu {
			sound-dai = <&mcasp0>;
		};

		sound_master: simple-audio-card,codec {
			#sound-dai-cells = <0>;
			sound-dai = <&wssdsp1>; /* codec name */
			system-clock-frequency = <24000000>;
		};
};

&am33xx_pinmux {
mcasp0_pins_adsp: mcasp0_pins_adsp {
		pinctrl-single,pins = <
			0x194 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsx.mcasp0_fsx */
			0x190 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx.mcasp0_aclkx */
			0x198 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mcasp0_axr0.mcasp0_axr0 */
			0x1a8 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_axr1.mcasp0_axr1 */
			0x19c (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mcasp0_ahclkr.mcasp0_axr2 */
			0x1ac (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mcasp0_ahclkx.mcasp0_axr3 */
			0x1a0 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkr.mcasp0_aclkr */
			0x1a4 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsr.mcasp0_fsr */
		>;
	};

};

&mcasp0 {
	#sound-dai-cells = <0>;
	pinctrl-names = "default";
	pinctrl-0 = <&mcasp0_pins_adsp>;
	status = "okay";
	op-mode = <0>;		/* MCASP_IIS_MODE */
	tdm-slots = <2>;
	/* 4 serializers */
	serial-dir = <  /* 0: INACTIVE, 1: TX, 2: RX */
		1 0 0 0
	>;
	/* bit depth for tx and rx */
	tx-num-evt = <32>;
	rx-num-evt = <32>;
};

This is the driver:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include <sound/soc.h>

static struct snd_soc_dai_driver wssdsp1_dai = {
	.name = "wssdsp1-hifi",
	.playback = {
		.stream_name	= "Playback",
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_384000,
		.formats = SNDRV_PCM_FMTBIT_S32_LE
	},
};

static struct snd_soc_component_driver soc_component_dev_wssdsp1 = {
};

static int wssdsp1_probe(struct platform_device *pdev)
{
	int ret;

	ret = devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_wssdsp1,
										  &wssdsp1_dai, 1);

	if (ret) {
		printk("failed to register component wssdsp1: %d\n", ret);
		return ret;
	}
	else {
		printk("successfully registered component wssdsp1: %d\n", ret);
	}

	return 0;
}

static const struct of_device_id wssdsp1_of_match[] = {
	{ .compatible = "dwe,wssdsp1", },
	{ }
};
MODULE_DEVICE_TABLE(of, wssdsp1_of_match);

static struct platform_driver wssdsp1_codec_driver = {
	.probe		= wssdsp1_probe,
	.driver		= {
		.name	= "wssdsp1-codec",
		.of_match_table = wssdsp1_of_match,
	},
};

module_platform_driver(wssdsp1_codec_driver);

MODULE_DESCRIPTION("ASoC WSSDSP1 codec driver");
MODULE_AUTHOR("Rolf Anderegg <rolf.anderegg@weiss.ch>");
MODULE_LICENSE("GPL");

There is no noise during playback. However, whenever playback stops, there is random noise on the left I2S channel!

It's as if the I2S data pin (mcasp0_axr0) was not pulled down anymore.

When trying to measure the I2S data pin with the scope, the noise goes away, which suggests that the pin is indeed floating.

The noise changes based on the CPU load of the AM3352, suggesting crosstalk.

We have the same exact hardware running fine on kernel 3.x with no noise whatsoever, so this has to be a software issue.

Any help will be greatly appreciated.

Kind regards,

Michele

  • Hi Michele,

    As you mentioned that there is no noise on Kernel 3.x and has noise in kernel 5.4. Do you see any changes in the McASP driver or audio related changes that you think might have caused the issue to pop up?

    Also, as its a custom board is this reproducible on our EVM?

    Best Regards,

    Suren

  • Hi Suren, thanks for your reply. Before answering your question, I just wanted to post an updated version of the relevant devicetree part, where I removed some unnecessary properties and pinmux.

    / {
    	wssdsp1: wssdsp1 {
    		#sound-dai-cells = <0>;
    		compatible = "dwe,wssdsp1";
    		status = "okay";
    	};
    
    	sound {
    		compatible = "simple-audio-card";
    		simple-audio-card,name = "WSSDSP1";
    		simple-audio-card,format = "i2s";
    		simple-audio-card,bitclock-master = <&sound_master>;
    		simple-audio-card,frame-master = <&sound_master>;
    
    		simple-audio-card,cpu {
    			sound-dai = <&mcasp0>;
    		};
    
    		sound_master: simple-audio-card,codec {
    			#sound-dai-cells = <0>;
    			sound-dai = <&wssdsp1>; /* codec name */
    		};
    };
    
    &am33xx_pinmux {
    	mcasp0_pins_adsp: mcasp0_pins_adsp {
    		pinctrl-single,pins = <
    			0x198 (PIN_OUTPUT_PULLDOWN | MUX_MODE0)  /* (D12) mcasp0_axr0.mcasp0_axr0    - I2S data tx/rx 0 */
    			0x194 (PIN_INPUT_PULLDOWN | MUX_MODE0)   /* (B13) mcasp0_fsx.mcasp0_fsx      - WCLK rx          */
    			0x190 (PIN_INPUT_PULLDOWN | MUX_MODE0)   /* (A13) mcasp0_aclkx.mcasp0_aclkx  - BCLK rx          */
    		>;
    	};
    
    };
    
    &mcasp0 {
    	#sound-dai-cells = <0>;
    	pinctrl-names = "default";
    	pinctrl-0 = <&mcasp0_pins_adsp>;
    	status = "okay";
    	op-mode = <0>;		/* MCASP_IIS_MODE */
    	tdm-slots = <2>;
    	/* 4 serializers */
    	serial-dir = <  /* 0: INACTIVE, 1: TX, 2: RX */
    		1 0 0 0
    	>;
    	/* bit depth for tx and rx */
    	tx-num-evt = <32>;
    	rx-num-evt = <32>;
    };

    Now back to your question -- in the Linux 3.14 days, we didn't use simple-audio-card (I think it wasn't there yet), and there were three ingredients:

    • devicetree
    • A SOC codec in sound/soc/codecs/
    • A DAI link in a patched sound/soc/davinci/davinci-evm.c

    In the devicetree, we had this:

    / {
    	am33xx_pinmux: pinmux@44e10800 {
    		mcasp0_pins_adsp: mcasp0_pins_adsp {
    			pinctrl-single,pins = <
    				0x194 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsx.mcasp0_fsx */
    				0x190 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx.mcasp0_aclkx */
    				0x198 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_axr0.mcasp0_axr0 */
    				0x1a8 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_axr1.mcasp0_axr1 */
    				0x19c (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mcasp0_ahclkr.mcasp0_axr2 */
    				0x1ac (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mcasp0_ahclkx.mcasp0_axr3 */
    				0x1a0 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkr.mcasp0_aclkr */
    				0x1a4 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsr.mcasp0_fsr */
    			>;
    		};
    	};
    	
    	wh17_adsp_dit: wh17_adsp_dit {
    		compatible = "linux,wh17-adsp-dit";
        };
    	sound {
    		compatible = "ti,am335x-wh17-adsp-audio";
    		ti,model = "Weiss WH17 ADSP";
    		ti,audio-codec = <&wh17_adsp_dit>;
    		ti,mcasp-controller = <&mcasp0>;
    		ti,codec-clock-rate = <24000000>;
    	};
    };
    
    &mcasp0 {
    	pinctrl-names = "default";
    	pinctrl-0 = <&mcasp0_pins_adsp>;
    
    	status = "okay";
    
    	op-mode = <0>;		/* MCASP_IIS_MODE */
    	tdm-slots = <2>;
    	/* 16 serializer */
    	serial-dir = <  /* 0: INACTIVE, 1: TX, 2: RX */
    		1 2 0 0
    		0 0 0 0
    		0 0 0 0
    		0 0 0 0
    	>;
    	tx-num-evt = <32>;
    	rx-num-evt = <32>;
    };

    This is how the SOC codec looked:

    #define DRV_NAME "wh17-adsp-dit" 
    #define STUB_RATES  SNDRV_PCM_RATE_8000_384000 
    #define STUB_FORMATS    (SNDRV_PCM_FMTBIT_S32_LE) 
    
    static struct snd_soc_codec_driver soc_codec_wh17_adsp_dit = { 
    }; 
    
    static struct snd_soc_dai_driver dit_stub_dai = { 
        .name       = "wh17-adsp-tx-hifi", 
        .playback   = { 
            .stream_name    = "Playback", 
            .channels_min   = 2, 
            .channels_max   = 2, 
            .rates      = STUB_RATES, 
            .formats    = STUB_FORMATS, 
        }, 
    }; 
    
    static int wh17_adsp_dit_probe(struct platform_device *pdev) 
    { 
        return snd_soc_register_codec(&pdev->dev, &soc_codec_wh17_adsp_dit, 
        &dit_stub_dai, 1); 
    } 
    
    static int wh17_adsp_dit_remove(struct platform_device *pdev) 
    { 
        snd_soc_unregister_codec(&pdev->dev); 
        return 0; 
    } 
    
    #ifdef CONFIG_OF 
        static const struct of_device_id wh17_adsp_dit_dt_ids[] = { 
        { .compatible = "linux,wh17-adsp-dit", }, 
        { } 
        }; 
        MODULE_DEVICE_TABLE(of, wh17_adsp_dit_dt_ids); 
    #endif 
    
    static struct platform_driver wh17_adsp_dit_driver = { 
        .probe      = wh17_adsp_dit_probe, 
        .remove     = wh17_adsp_dit_remove, 
        .driver     = { 
            .name   = DRV_NAME, 
            .owner  = THIS_MODULE, 
            .of_match_table = of_match_ptr(wh17_adsp_dit_dt_ids), 
        }, 
    }; 
    
    module_platform_driver(wh17_adsp_dit_driver);

    And as for the DAI link:

    static int wh17_adsp_hw_params(struct snd_pcm_substream *substream, 
                                    struct snd_pcm_hw_params *params) 
    { 
        struct snd_soc_pcm_runtime *rtd = substream->private_data; 
        struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 
        struct snd_soc_card *soc_card = rtd->card; 
        int ret = 0; 
        unsigned sysclk = ((struct snd_soc_card_drvdata_davinci *) 
                            snd_soc_card_get_drvdata(soc_card))->sysclk; 
    
        /* set the CPU system clock */ 
        printk("WH17 ADSP hw params: sysclk:%d\n", sysclk); 
        ret = snd_soc_dai_set_sysclk(cpu_dai, 0, sysclk, SND_SOC_CLOCK_OUT); 
        if (ret < 0) { 
            return ret; 
        }
    
        return 0; 
    } 
    
    static struct snd_soc_ops wh17_adsp_ops = { 
        .hw_params = wh17_adsp_hw_params, 
    }; 
    
    
    static struct snd_soc_dai_link evm_dai_wh17_adsp = { 
        .name       = "Weiss Engineering WH17 ADSP",
        .stream_name    = "Playback",
        .codec_dai_name = "wh17-adsp-tx-hifi",
        .ops        = &wh17_adsp_ops, 
        .dai_fmt    = ( SND_SOC_DAIFMT_CBM_CFM | /* This indicates to the McASP that the WH17 ADSP will 
                                                    be a clock master for both the bit clock and the frame 
                                                    sync. In other words, the McASP is slaved to the bit clock 
                                                    and frame sync, which are generated by the WH17 ADSP. */ 
        SND_SOC_DAIFMT_I2S | /* I2S mode */ 
        SND_SOC_DAIFMT_NB_NF), /* normal bit clock + normal frame sync */ 
    }; 
     	
    static const struct of_device_id davinci_evm_dt_ids[] = { 
        .compatible = "ti,am335x-wh17-adsp-audio", 
        .data = &evm_dai_wh17_adsp, 
    }, 
        { /* sentinel */ } 
    };
    
    MODULE_DEVICE_TABLE(of, davinci_evm_dt_ids); 

    About your last question, I don't have the TI EVM unfortunately. This is the board we're using: TQMA335x.

    Let me know if you need further information.

    Thanks again,

    Michele

  • Hi Michele, 

    Have you tried to tune the ALSA mixer controls related to Playback and adjust the gains to see if the playback works fine without noise.

    Best Regards,

    Suren

  • Hi Suren,

    as mentioned, the noise only occurs when playback is stopped. So for example when we do

    speaker-test -D hw:CARD=WSSDSP1,DEV=0 -c 2 -F S32_LE -r 44100 -t sine -f 440

    there is no noise, only the test sine signal, alternating from left to right.

    When we stop the speaker-test there is crackling noise on the left channel.

    Best regards,

    Rolf

  • Hi Michele/Rolf,

    Can you test the same with the SDK released software with Linux kernel (6.1)

    https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-1BUptXj3op/09.01.00.001/tisdk-default-image-am335x-evm.wic.xz

    And do you observe the issue with this released software?

    Best Regards,

    Suren

  • Hi Suren,

    Unfortunately we cannot test any other Linux kernel than 5.4, because that's what our BSP provides.

    We tried looking closer at the davinci-mcasp.c which comes with our kernel, and compare it again what we had in kernel 3.14. We couldn't find something there that could explain this behavior.

    However one thing that caught my attention was the DISMOD property. It is documented here:

    Optional properties:
    
    - dismod : Specify the drive on TX pin during inactive slots
    	0 : 3-state
    	2 : logic low
    	3 : logic high
    	Defaults to 'logic low' when the property is not present

    Do I understand correctly that this should define the state of the serializer pins when the alsa playback is stopped? If so, it's clearly not working!

    Kind regards,

    Michele

  • Hi Michele,

    I am on travel this week and will have to take a look at it when I return early next week. 
    In the meantime, could you refer to the below thread and see if it helps:

    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/737730/am3352-mcasp-dismod-not-working

    Best Regards,

    Suren

  • Hello Suren,

    we took a look at the topic that you suggested and we ran a series of tests by reading and writing to the McASP 0 serializer control register (SRCTL0 -- screenshot from the datasheet below).

    At first we couldn't read anything from SRCTL0 (we got a bus error from devmem) unless ALSA playback was running. But we needed exactly that -- otherwise how could we check whether the serializer was becoming inactive, what was the value od dismod, etc.? So we added the ti,no-idle; property to our &mcasp0 node in the devicetree.

    At this point, we could read from SRCTL0 all the time and this is what we have:

    1. When the system is booted up and the ALSA device hasn't been accessed yet:

    root@Livebox-0004:~# devmem 0x48038180 32
    0x00000000

    2. While playing:

    root@Livebox-0004:~# devmem 0x48038180 32
    0x00000009

    • XRDY is 0: Transmit buffer (XBUF) contains data
    • DISMOD is 2h: Drive on pin is logic low
    • SRMOD is 1h: Serializer is transmitter

    3. After playback stops:

    root@Livebox-0004:~# devmem 0x48038180 32
    0x00000019

    • XRDY is 1: Transmit buffer (XBUF) is empty and needs to be written before the start of the next time slot or a transmit underrun occurs
    • DISMOD is 2h: Drive on pin is logic low
    • SRMOD is 1h: Serializer is transmitter

    This is where things get weird:

    1. Why does SRMOD show that serializer is trasmitter and XRDY that the buffer is empty after the device gets closed? Shouldn't SRMOD be 0 (serializer inactive)?
    2. Why is the serializer pin not pulled down after I set SRMOD to 0, XRDY to 0, and DISMOD to 2? As you can see below, the value is set successfully. But the noise is still there, so the pin is clearly not pulled down. 
      root@Livebox-0004:~# devmem 0x48038180 32 0x8
      root@Livebox-0004:~# devmem 0x48038180
      0x00000008

    We repeated the same experiments on our old Linux 3.x setup:

    1. Right after system startup:

    root@DAC501-4ch-0001:~ devmem 0x48038180 32
    0x00000000

    2. During McASP playback:

    root@DAC501-4ch-0001:~ devmem 0x48038180 32
    0x00000001

    3. After playback stops:

    root@DAC501-4ch-0001:~ devmem 0x48038180 32
    0x00000011

    So it's almost the same like on Linux 5.4, except DISMOD seems to be always on tri-state, but despite this, the pin is actually pulled down and there is no noise whatsoever coming from the serializer.

    We would really appreciate if you could tell us which part of this behavior is expected and which isn't.

    Thank you,

    Michele

  • Hi Michele,

    Happy New year! 

    Apologies for the delay. I was on business travel last month.

    Please allow me a couple of days to analyze the data you have shared and come back to you.

    Best Regards,

    Suren

  • Hi Michele,

    I ran on my AM335x board with v07x version of software (Linux kernel 5.4) 

    https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-1BUptXj3op/07.03.00.005/am335x-evm-linux-tisdk-default-image-07.03.00.005.wic.xz

    And didn't see any issues of audio after playback stopped.

    Best Regards,

    Suren

  • Hi Suren

    Thanks for checking with Linux kernel 5.4. It seems that this is an issue especially related to the McASP being configured as I2S slave. Have you tried this setup?

    In the meantime we may have discovered the root of the problem:

    since we're now able to reliably read the McASP registers, we extended our search to all kinds of McASP registers. We then noticed that the Pin Direction register (PDIR) is doing strange things depending on the start/stop state:

    On playback:

    root@Livebox-0001:~# devmem 0x48038014 32
    0x00000001


    On playback stopped:
    root@Livebox-0001:~# devmem 0x48038014 32
    0x00000000


    Meaning that the AXR0 pin is being correctly set as output during playback, but being reset to input when stopped.
    This can't be right. When we then manually set the AXR0 to output during stop state the noise disappears!

    root@Livebox-0001:~# devmem 0x48038014 32 1
    root@Livebox-0001:~# devmem 0x48038014 32
    0x00000001


    So apparently having the AXR0 being set as input essentially has the effect that it is prone to signal injection by neighboring signals.

    So we tried to find the mechanism in the McASP driver that is causing this behavior and came across the following function in davinci-mcasp.c:
    static void mcasp_stop_tx(struct davinci_mcasp *mcasp)
    {
    ...
        mcasp_set_axr_pdir(mcasp, false);
    }

    This mechanism was introduced by Peter Ujfalusi's commit in 2018, which clears all the PDIR bits when playback stops, regardless whether McASP is configured as clock master or slave. Our view is that this mechanism makes sense in a clock master setup, but in a slave setup as ours it fails to

    configure the pins back to input which makes them to obey the global pin configuration regarding to pull up/down

    as is suggested in the commit message by Peter Ujfalusi.

    We have thus applied the following patch, which would probably require further tinkering and take into account the master/slave clocking context:

    diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c
    index c9dbda233951..65d47bdb16ec 100644
    --- a/sound/soc/ti/davinci-mcasp.c
    +++ b/sound/soc/ti/davinci-mcasp.c
    @@ -346,7 +346,8 @@ static void mcasp_stop_tx(struct davinci_mcasp *mcasp)
     		mcasp_clr_bits(mcasp, reg, FIFO_ENABLE);
     	}
     
    -	mcasp_set_axr_pdir(mcasp, false);
    +	if (false /*todo: disable if codec is clock slave*/)
    +		mcasp_set_axr_pdir(mcasp, false);
     }
     
     static void davinci_mcasp_stop(struct davinci_mcasp *mcasp, int stream)
    


    Maybe we could invite Peter Ujfalusi to comment on this issue here in this thread?

    Best regards,
    Rolf Anderegg

  • Hi Rolf, 

    As you are able to resolve the issue, I am going to close this thread. 

    Also, you can reach out to Peter on the opensource community for clarification. 

    Best Regards,

    Suren

  • Hi Suren

    I disagree, this thread should not be closed. As far as I'm concerned this is the perfect forum to clarify and resolve this issue. After all this issue was caused by a kernel commit from TI's development team.

    Unfortunately there is no mechanism here to invite people to chime in. How am I able to invite Peter here on this thread?

    Best regards,

    Rolf