TLV320AIC3254 recording of I2S bclk settings

Hi Sir/Madam,
The TLV320AIC3254 codec 2-channel recording is normal, but the 1-channel recording is not normal, it is twice as fast as the 2-channel.
Is the bclk configuration of i2s incorrect? 44.1KHz 16 bits, 2 channels bclk=1.4112Mhz,
44.1Khz 16-bit, 1 channel Is bclk set to bclk=705.6Khz?

How to change this setting in the system?

L4T: R28.2.1

help me, thanks

Sorry,
it should be like this. I measured the wclk and bclk signal frequency with an oscilloscope. When recording, 2 channels 16Khz, 16 bits, BCLK=512Khz, WCLK=16KHZ,

in 1 channel recording, BCLK is still 512Khz but WCLK=8KHz ,

Is this correct? Isn’t it in the case of 1-channel recording, WCLK=16Khz, bclk=256KHz?

In file tegra20_i2s.c or file tegra30_i2s.c, the function tegra20_i2s_hw_params is described below.

static int tegra20_i2s_hw_params(struct snd_pcm_substream *substream,
				 struct snd_pcm_hw_params *params,
				 struct snd_soc_dai *dai)
{
	struct device *dev = dai->dev;
	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai);
	unsigned int mask, val;
	int ret, sample_size, srate, i2sclock, bitcnt;

	mask = TEGRA20_I2S_CTRL_BIT_SIZE_MASK;
	switch (params_format(params)) {
	case SNDRV_PCM_FORMAT_S16_LE:
		val = TEGRA20_I2S_CTRL_BIT_SIZE_16;
		sample_size = 16;
		break;
	case SNDRV_PCM_FORMAT_S24_LE:
		val = TEGRA20_I2S_CTRL_BIT_SIZE_24;
		sample_size = 24;
		break;
	case SNDRV_PCM_FORMAT_S32_LE:
		val = TEGRA20_I2S_CTRL_BIT_SIZE_32;
		sample_size = 32;
		break;
	default:
		return -EINVAL;
	}

	mask |= TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK;
	val |= TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED;

	regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val);

	srate = params_rate(params);

	/* Final "* 2" required by Tegra hardware */
	i2sclock = srate * params_channels(params) * sample_size * 2;

	ret = clk_set_rate(i2s->clk_i2s, i2sclock);
	if (ret) {
		dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
		return ret;
	}

	bitcnt = (i2sclock / (2 * srate)) - 1;
	if (bitcnt < 0 || bitcnt > TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
		return -EINVAL;
	val = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;

	if (i2sclock % (2 * srate))
		val |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE;

	regmap_write(i2s->regmap, TEGRA20_I2S_TIMING, val);

	regmap_write(i2s->regmap, TEGRA20_I2S_FIFO_SCR,
		     TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS |
		     TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS);

	return 0;
}

/* Final “* 2” required by Tegra hardware */
i2sclock = srate * params_channels(params) * sample_size * 2;

Why must it be multiplied by 2?

The description of struct tegra20_i2s_dai_template is as follows

static const struct snd_soc_dai_driver tegra20_i2s_dai_template = {
	.probe = tegra20_i2s_probe,
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_96000,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
	},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_96000,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
	},
	.ops = &tegra20_i2s_dai_ops,
	.symmetric_rates = 1,
};

is it not supported for single channel recording?

I don’t have the information and documentation, I don’t know how to modify the I2S configuration about the clock.

Hello Superlee!

If the codec is configured for normal I2S mode, then there is always 2 channels as defined by the I2S standard. You can check the current I2S format/mode for the I2S interface by …

$ cat /proc/device-tree/sound/nvidia,dai-link-1/format

Note that the following will tell you which I2S interface ‘nvidia,dai-link-1’ corresponds to …

cat /proc/device-tree/sound/nvidia,dai-link-1/cpu-dai-name

Typically dai-link-1 is for I2S1 and so if this is the interface you are using then you can check the above. Otherwise, you may need to check the configuration of a different dai-link.

For TX2 you need to refer to the kernel source file sound/soc/tegra-alt/tegra210_i2s_alt.c and here you will see …

static struct snd_soc_dai_driver tegra210_i2s_dais[] = {
        {
                .name = "CIF",
                .playback = {
                        .stream_name = "CIF Receive",
                        .channels_min = 1, 
                        .channels_max = 16,
                        .rates = SNDRV_PCM_RATE_8000_192000,
                        .formats = SNDRV_PCM_FMTBIT_S8 |
                                SNDRV_PCM_FMTBIT_S16_LE |
                                SNDRV_PCM_FMTBIT_S24_LE |
                                SNDRV_PCM_FMTBIT_S32_LE,
                },

So yes mono is supported and to use mono, you can do one of two things …

  1. Keep the I2S interface configured for I2S mode but then within the device we can convert from 2 channels to 1 channel. I will point out that there is a known bug [0] in rel28 that has been fixed in rel32 (just released) and recommend that you upgrade to rel32 otherwise you would need to apply this fix for rel28. Using rel32 or with the fix in place for rel28 you would then …

Force the I2S interface to receive 2 channels (because the codec will always output 2 channels in I2S mode):

amixer -c tegrasndt186ref cset name='I2S1 Channels' 2

Convert the stereo to mono within Tegra:

rel28:

amixer -c tegrasndt186ref cset name='I2S1 TX stereo to mono conv' CH0

rel32:

amixer -c tegrasndt186ref cset name='I2S1 Capture stereo to mono conv' CH0

Capture mono audio

arecord -D hw:tegrasndt186,0 -c 1 -f S16_LE -r 16000 cap.wav

The above assume you are using I2S1.

  1. Configure the I2S interface to use dsp-a/b mode instead of I2S mode which supports a single channel. It appears that the codec should aupport this. With rel28 the I2S format needs to be configured by editing the device-tree source and recompiled. This is done by editing the source file t18x-common-platforms/tegra186-quill-common.dtsi and changing the format for the appropriate dai-link. For example for I2S1 …
rt565x_dai_link: nvidia,dai-link-1 {
                        link-name = "rt565x-playback";
                        cpu-dai = <&tegra_i2s1>;
                        codec-dai = <&spdif_dit0>;
                        cpu-dai-name = "I2S1";
                        codec-dai-name = "dit-hifi";
                        format = "i2s";
                        bitclock-slave;
                        frame-slave;
                        bitclock-noninversion;
                        frame-noninversion;
                        bit-format = "s16_le";
                        bclk_ratio = <0>; 
                        srate = <48000>;
                        num-channel = <2>; 
                        ignore_suspend;
                        name-prefix = "x"; 
                        status = "okay";
                };

However, with rel32 you can now change the I2S mode directly from userspace using the following command …

amixer -c tegrasndt186ref cset name="I2S1 codec frame mode" dsp-a

And then record a single channel …

arecord -D hw:tegrasndt186,0 -c 1 -f S16_LE -r 16000 cap.wav

Hope this helps.

Regards,
Jon

[0] https://nv-tegra.nvidia.com/gitweb/?p=linux-nvidia.git;a=commit;h=33ae2f293b12697f9d5ca19ced1eb65e3d44d159

Hi jonathanh,
Thank you for your reply!
This information is very useful to me and solves my problem.

I want to ask another question.

Q: I copied the Image to /boot and restarted the device. The kernel is still the original version

My operation:
for x86, i copy the Image to /boot, and then update initramfs(Execute the update-initramfs script), reboot

for TX2:
I use config #3(ODMDATA=0x6090000) instead of config #2 to configure the system, I use the command:

sudo ./flash.sh -r -d kernel/dtb/tegra186-quill-p3310-1000-c03-00-base.dtb -o 0x6090000 jetson-tx2 mmcblk0p1

Why use the above command?
Because I found that change the configuration to config #3, only update the ODMDATA in the p2771-0000.conf.commony file, and then flash that does not seem to take effect, you must add the -o parameter, and the -k parameter to updates the kernel.

Copy the Image to /boot and restart it before using the above command. This operation is effective.
After using the above command, this operation will not take effect.

In addition, I found that after updating the kernel with the above command, the console does not have the output of the following information(No information about u-boot)

U-Boot 2016.07-g0ce7ca2 (Jul 28 2017 - 09:50:20 -0700)

TEGRA186
Model: NVIDIA P2771-0000-500
DRAM:  7.8 GiB
MC:   Tegra SD/MMC: 0, Tegra SD/MMC: 1
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   eth0: ethernet@2490000
Hit any key to stop autoboot:  0 
MMC: no card present
switch to partitions #0, OK
mmc0(part 0) is current device
Scanning mmc 0:1...
Found /boot/extlinux/extlinux.conf
Retrieving file: /boot/extlinux/extlinux.conf
213 bytes read in 70 ms (2.9 KiB/s)
p2771-0000 eMMC boot options
1:      primary kernel
Enter choice: 1:        primary kernel
Retrieving file: /boot/Image

It’s so painful now, it takes about 20 minutes to update a kernel.

copy the Image to the …/Linux_for_Tegra/kernel/ and flash by below command
sudo ./flash.sh -r -k kernel jetson-tx2 mmcblk0p1

Hi,
I’m very very Sorry,
the command submitted on the 5th floor is wrong. I am using the following command

sudo ./flash.sh -r -d kernel/dtb/tegra186-quill-p3310-1000-c03-00-base.dtb -o 0x6090000 -k kernel/Image jetson-tx2 mmcblk0p1

not

sudo ./flash.sh -r -d kernel/dtb/tegra186-quill-p3310-1000-c03-00-base.dtb -o 0x6090000 jetson-tx2 mmcblk0p1

In addition, I only want to copy the Image to /boot,then it will take effect after the restart, what should I do?

  1. Please replace the dtb at …/kernel/dtb and flash dtb by
    sudo ./flash.sh -r -k kernel-dtb jetson-tx2 mmcblk0p1

  2. Replace the Image at …/kernel/ and flash the kernel by
    sudo ./flash.sh -r -k kernel jetson-tx2 mmcblk0p1