【i2s】Milk-V Duo添加speaker——i2s2

来源:转载自https://community.milkv.io/t/i2s-milk-v-duo-speaker-i2s2/666,原作者 LangZhao

前言

本章介绍duo的i2s2接口。
后续章节再介绍如何添加max98357a驱动,使用i2s驱动max98357a播放音频。

一、电路

1.1 duo音频接口




外接的i2s只有1和2,而2被eth复用了,需要移除eth然后再配置i2s2

1.2 I2S2连接

在这里插入图片描述

对应电路图中,从上到下
LRCK
BCK
DO
DI

duo可以利用i2s2外接codec+speaker进行播放音频。

二、I2S2介绍

查看cv1800的dtsi配置,可以看到其实是复用的cv1835的i2s驱动。
duo-buildroot-sdk-develop\build\boards\default\dts\cv180x\cv180x_base.dtsi

	i2s2: i2s@04120000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04120000 0x0 0x2000>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <2>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 6 1 1 /* read channel */
			&dmac 1 1 1>; /* write channel */
		dma-names = "rx", "tx";
		capability = "txrx";
		mclk_out = "false";
	};
&i2s2 {
	status = "okay";
	#sound-dai-cells = <0>;
}

我这里参考2个平台的dts
cv1835:i2s的驱动复用的该平台
cv182x:cv1800和该平台较为接近,而且adc定义有用该名称

2.1 参考cv182x的dts实现

duo-buildroot-sdk-develop\build\boards\default\dts\cv182x\cv182x_asic.dtsi

仅供参考

	i2s0: i2s@04100000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04100000 0x0 0x2000>;
		interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <0>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 0 1 1>; /* read channel */
		dma-names = "rx";
		capability = "rx"; /* I2S0 connect to internal ADC as RX */
		mclk_out = "false";
	};

	i2s1: i2s@04110000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04110000 0x0 0x2000>;
		interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <1>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 2 1 1 /* read channel */
			&dmac 3 1 1>; /* write channel */
		dma-names = "rx", "tx";
		capability = "txrx";
		mclk_out = "false";
	};

	i2s2: i2s@04120000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04120000 0x0 0x2000>;
		interrupts = <GIC_SPI 128 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <2>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 6 1 1 /* read channel */
			&dmac 1 1 1>; /* write channel */
		dma-names = "rx", "tx";
		capability = "txrx";
		mclk_out = "false";

	};

	i2s3: i2s@04130000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04130000 0x0 0x2000>;
		interrupts = <GIC_SPI 132 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <3>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 7 1 1>; /* write channel */
		dma-names = "tx";
		capability = "tx"; /* I2S3 connect to internal DAC as TX */
		mclk_out = "true";
	};

	adc: adc@0300A100 {
		compatible = "cvitek,cv182xadc";
		reg = <0x0 0x0300A100 0x0 0x100>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		clk_source = <0x04130000>; /* MCLK source is I2S3 */
	};

	dac: dac@0300A000 {
		compatible = "cvitek,cv182xdac";
		reg = <0x0 0x0300A000 0x0 0x100>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
	};

	pdm: pdm@0x041D0C00 {
		compatible = "cvitek,cv1835pdm";
		reg = <0x0 0x041D0C00 0x0 0x100>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
	};

	sound_adc {
		compatible = "cvitek,cv182x-adc";
		cvi,model = "CV182X";
		cvi,card_name = "cv182x_adc";
	};

	sound_dac {
		compatible = "cvitek,cv182x-dac";
		cvi,model = "CV182X";
		cvi,card_name = "cv182x_dac";
	};

	sound_PDM {
		compatible = "cvitek,cv182x-pdm";
		cvi,model = "CV182X";
		cvi,card_name = "cv182x_internal_PDM";
	};

	sound_ext1 {
		compatible = "cvitek,cv1835-adau1372";
		cvi,model = "CV1835";
		cvi,mode = "I2S";
		cvi,fmt = "IBNF";
		cvi,card_name = "cvi_sound_card_0";
		cvi,slot_no=<2>;

		dai@0 {
			cvi,dai_name = "cv1835-i2s-1";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4110000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4110000.i2s";
			cvi,codec_name = "adau1372.1-003c";
			cvi,role = "master";
		};
		dai@1 {
			cvi,dai_name = "cv1835-i2s-2";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4120000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4120000.i2s";
			cvi,codec_name = "adau1372.1-003c";
			cvi,role = "slave";
		};
	};

	/* sound_ext2 use external codec */
	sound_ext2 {
		compatible = "cvitek,cv1835-adau1372";
		cvi,model = "CV1835";
		cvi,mode = "I2S";
		cvi,fmt = "IBNF";
		cvi,card_name = "cv1835_external_card";
		cvi,slot_no=<2>;

		dai@0 {
			cvi,dai_name = "cv1835-i2s-2";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4120000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4120000.i2s";
			cvi,codec_name = "adau1372.0-003c";
			cvi,role = "master";
		};
		dai@1 {
			cvi,dai_name = "cv1835-i2s-3";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4130000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4130000.i2s";
			cvi,codec_name = "adau1372.0-003c";
			cvi,role = "slave";
		};
	};

2.2 参考cv1835_fpga

i2s的定义来看需要开cv1835相关的配置,可以参考这里定义的外接声卡。

可以参考duo-buildroot-sdk-develop\build\boards\cv183x\cv1835_fpga\linux\cv1835_fpga.dts的定义

# audio driver
#CONFIG_SOUND=y
#CONFIG_SND=y
#CONFIG_SND_SOC=y
#CONFIG_SND_CV1835_I2S=y
#CONFIG_CV1835_I2S_SUBSYS=y

# 待添加max的config

#CONFIG_SND_SOC_ADAU1372=y
#CONFIG_SND_SOC_ADAU_UTILS=y
#CONFIG_SND_SOC_ADAU1372_I2C=y
#CONFIG_SND_SOC_CV1835_ADAU1372=y
# CONFIG_SND_SOC_ADAU1372_SPI is not set
#CONFIG_SND_SOC_CV1835_CONCURRENT_I2S=y	#!!!!

#CONFIG_SND_SOC_CV1835_USE_AUDIO_PLL=y
#CONFIG_SND_SOC_CV1835_CV1835PDM=y
#CONFIG_SND_SOC_CV1835PDM=y
#CONFIG_SND_SOC_CV1835_CV1835ADC=y
#CONFIG_SND_SOC_CV1835ADC=y
#CONFIG_SND_SOC_CV1835_CV1835DAC=y
#CONFIG_SND_SOC_CV1835DAC=y
	i2s_mclk: i2s_mclk {
		clock-output-names = "i2s_mclk";
		clock-frequency = <24576000>; /* use internal audio PLL */
		#clock-cells = <0x0>;
		compatible = "fixed-clock";
	};

#ifdef CV1835_AUDIO_CODEC_EN
	adc: adc@0300A000 {
		compatible = "cvitek,cv1835adc";
		reg = <0x0 0x0300A000 0x0 0x100>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
	};

	dac: dac@0300A400 {
		compatible = "cvitek,cv1835dac";
		reg = <0x0 0x0300A400 0x0 0x100>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
	};

	sound_adc {
		compatible = "cvitek,cv1835-adc";
		cvi,model = "CV1835";
		cvi,card_name = "cvi_adc";
	};

	sound_dac {
		compatible = "cvitek,cv1835-dac";
		cvi,model = "CV1835";
		cvi,card_name = "cvi_dac";
	};
#endif


	i2s0: i2s@04100000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04100000 0x0 0x2000>;
		interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <0>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 0 1 1 /* read channel */
			&dmac 1 1 1>; /* write channel */
		dma-names = "rx", "tx";
		capability = "rx"; /* I2S0 connect to internal ADC as RX */
	};

	i2s1: i2s@04110000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04110000 0x0 0x2000>;
		interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <1>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 2 1 1 /* read channel */
			&dmac 3 1 1>; /* write channel */
		dma-names = "rx", "tx";
#ifndef CV1835_CONCURRENT_I2S /* refer to /include/dt-bindings/sound/cv1835-audio.h */
		capability = "txrx";
#else
		capability = "tx";
#endif
	};

#ifdef CV1835_EXT_CARD_1_EN
	/* sound_ext1 use external codec */
	sound_ext1 {
		compatible = "cvitek,cv1835-adau1372";
		cvi,model = "CV1835";
		cvi,mode = "I2S";
		cvi,fmt = "IBNF";
		cvi,card_name = "cvi_sound_card_0";
		cvi,slot_no=<2>;
		dai@0 {
			cvi,dai_name = "cv1835-i2s-1";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4110000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4110000.i2s";
			cvi,codec_name = "adau1372.0-003c";
			cvi,role = "master";
		};
#ifdef CV1835_CONCURRENT_I2S
		dai@1 {
			cvi,dai_name = "cv1835-i2s-0";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4100000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4100000.i2s";
			cvi,codec_name = "adau1372.0-003c";
			cvi,role = "slave";
		};
#endif
	};
#endif

	pdm: pdm@0x041D0C00 {
		compatible = "cvitek,cv1835pdm";
		reg = <0x0 0x041D0C00 0x0 0x100>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
	};

#ifdef CV1835_PDM_EN
	/* sound_PDM use PDM to transfer DMIC signal to I2S signal as audio input */
	sound_PDM {
		compatible = "cvitek,cv1835-pdm";
		cvi,model = "CV1835";
		cvi,card_name = "cv1835_internal_PDM";
	};
#endif

	i2s2: i2s@04120000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04120000 0x0 0x2000>;
		interrupts = <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <2>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 4 1 1 /* read channel */
			&dmac 5 1 1>; /* write channel */
		dma-names = "rx", "tx";
#ifndef CV1835_CONCURRENT_I2S
		capability = "txrx";
#else
		capability = "rx";
#endif
	};

	i2s3: i2s@04130000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04130000 0x0 0x2000>;
		interrupts = <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <3>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 6 1 1 /* read channel */
			&dmac 7 1 1>; /* write channel */
		dma-names = "rx", "tx";
		capability = "tx"; /* I2S3 connect to internal DAC as TX */
	};
#ifdef CV1835_EXT_CARD_2_EN
	/* sound_ext2 use external codec */
	sound_ext2 {
		compatible = "cvitek,cv1835-adau1372";
		cvi,model = "CV1835";
		cvi,mode = "I2S";
		cvi,fmt = "IBNF";
		cvi,card_name = "cv1835_external_card";
		cvi,slot_no=<2>;

		dai@0 {
			cvi,dai_name = "cv1835-i2s-2";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4120000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4120000.i2s";
			cvi,codec_name = "adau1372.0-003c";
			cvi,role = "master";
		};
#ifdef CV1835_CONCURRENT_I2S
		dai@1 {
			cvi,dai_name = "cv1835-i2s-3";
			cvi,stream_name = "adau1372-aif";
			cvi,cpu_dai_name = "4130000.i2s";
			cvi,codec_dai_name = "adau1372-aif";
			cvi,platform_name = "4130000.i2s";
			cvi,codec_name = "adau1372.0-003c";
			cvi,role = "slave";
		};
#endif
	};
#endif

2.3 cv180x

cv1800b_milkv_duo_sd.dts

  • cv180x_base_riscv.dtsi
    • cv180x_base.dtsi (定义i2s接口)
  • cv180x_asic_qfn.dtsi (移除sound接口)
  • cv180x_asic_sd.dtsi
  • cv180x_default_memmap.dtsi

平台定义的i2s接口,最终包该定义。
cv180x_base.dtsi
duo-buildroot-sdk-develop\build\boards\default\dts\cv180x

	i2s_mclk: i2s_mclk {
		clock-output-names = "i2s_mclk";
		clock-frequency = <24576000>;
		#clock-cells = <0x0>;
		compatible = "fixed-clock";
	};

	i2s_subsys {
		compatible = "cvitek,i2s_tdm_subsys";
		reg = <0x0 0x04108000 0x0 0x100>;
		clocks = <&i2s_mclk>, <&clk CV180X_CLK_A0PLL>,
			<&clk CV180X_CLK_SDMA_AUD0>, <&clk CV180X_CLK_SDMA_AUD1>,
			<&clk CV180X_CLK_SDMA_AUD2>, <&clk CV180X_CLK_SDMA_AUD3>;
		clock-names = "i2sclk", "clk_a0pll",
			"clk_sdma_aud0", "clk_sdma_aud1",
			"clk_sdma_aud2", "clk_sdma_aud3";
		master_base = <0x04110000>; /* I2S1 is master, only useful while using multi I2S IPs work on same IO */
	};

	i2s0: i2s@04100000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04100000 0x0 0x2000>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <0>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 0 1 1>; /* read channel */
		dma-names = "rx";
		capability = "rx"; /* I2S0 connect to internal ADC as RX */
		mclk_out = "false";
	};

	i2s1: i2s@04110000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04110000 0x0 0x2000>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <1>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 2 1 1 /* read channel */
			&dmac 3 1 1>; /* write channel */
		dma-names = "rx", "tx";
		capability = "txrx";
		mclk_out = "false";
	};

	i2s2: i2s@04120000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04120000 0x0 0x2000>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <2>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 6 1 1 /* read channel */
			&dmac 1 1 1>; /* write channel */
		dma-names = "rx", "tx";
		capability = "txrx";
		mclk_out = "false";

	};

	i2s3: i2s@04130000 {
		compatible = "cvitek,cv1835-i2s";
		reg = <0x0 0x04130000 0x0 0x2000>;
		clocks = <&i2s_mclk 0>;
		clock-names = "i2sclk";
		dev-id = <3>;
		#sound-dai-cells = <0>;
		dmas = <&dmac 7 1 1>; /* write channel */
		dma-names = "tx";
		capability = "tx"; /* I2S3 connect to internal DAC as TX */
		mclk_out = "true";
	};

cv180x_base_riscv.dtsi

	i2s0: i2s@04100000 {
		interrupts = <40 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-parent = <&plic0>;
	};

	i2s1: i2s@04110000 {
		interrupts = <41 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-parent = <&plic0>;
	};

	i2s2: i2s@04120000 {
		interrupts = <42 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-parent = <&plic0>;
	};

	i2s3: i2s@04130000 {
		interrupts = <43 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-parent = <&plic0>;
	};

cv180x_asic_qfn.dtsi

	/delete-node/ i2s@04110000;
	/delete-node/ i2s@04120000;
	/delete-node/ sound_ext1;
	/delete-node/ sound_ext2;
	/delete-node/ sound_PDM;

cv1800移除了i2s1&i2s2,我们需要启动该节点。

PS:可以通过dtsi最终生成的tmp文件查看节点状况
路径:duo_buildroot_sdk\duo-buildroot-sdk\u-boot-2021.10\build\cv1800b_milkv_duo_sd\arch\riscv\dts.cv1800b_milkv_duo_sd.dtb.dts.tmp

2.4 改动——保留i2s

cv180x_asic_qfn.dtsi

	/delete-node/ i2s@04110000;

	/delete-node/ sound_ext1;
	/delete-node/ sound_ext2;
	/delete-node/ sound_PDM;

删除i2s2的移除配置。

2.5 I2S小结

为了eth做准备,默认是删除i2s2。
我们需要保留i2s2的配置。

cv1800没有定义i2s的外接声卡,只有内部使用的pdm、adc、dac。
speaker需要自己添加声卡驱动以及声卡节点。

3 小结

本章只是介绍duo的i2s2,由于编辑器篇幅有限,max98357a的添加放在第二帖。

后一章节