jetson nano音频驱动代码分析

jetson nano音频驱动代码分析

英伟达audio框架(abub)

在这里插入图片描述

Platform和Codec驱动程序的功能是
tegra210-admaif:代表音频 DMA (ADMA) 和音频集线器 (AHUB) 之间接口的内核驱动程序
tegra210-xxxx:代表AHUB中各种硬件加速器的内核驱动
tegra210-ahub:一个内核驱动程序,有助于配置各种硬件加速器之间的音频路由

音频处理引擎( APE) 是一个独立的硬件模块,可在 CPU 的最少监督下满足 Jetson 处理器的所有音频需求。其音频集线器 (AHUB) 包含许多硬件加速器和 DMA 引擎。

音频集线器(AHUB)硬件架构
在这里插入图片描述
音频集线器支持这些设备的不同接口和信号质量要求。ADMAIF生成/dev/snd/pcmC1Dp节点,XBAR上有很多RX/TX,主要用于Codec小组件,比如进行AMX,ADX,SFC,Codec输入均来自XBAR,Codec输出都给XBAR,I2S除外,I2S作为Codec存在,输出端为物理Codec。XBAR上的整个链路的输入输出都通过amixer配置。

1.每个 AHUB 模块至少具有 1 个 RX 端口或 1 个 TX 端口或两者。

2.RX端口从XBAR接收数据,TX端口向XBAR发送数据。因此,XBAR 是一个开关,根据使用情况,音频输入可以馈送到多个输出。

3.每个 ADMAIF 都有支持同时播放和捕获的 TX 和 RX FIFO。对于所有音频路由场景,ADMA 将数据传输到 ADMAIF FIFO。
ASoC驱动软件架构
Jetson 的 ASoC 驱动程序的软件架构利用了硬件支持的功能,并符合 ALSA 框架。

如前所述,ASoC 驱动程序由Platform、Codec和Machine驱动程序组成。下面简要描述了这些驱动程序的角色,并在后续部分中更详细地描述了这些驱动程序的角色。

ASoC 驱动程序为Platform和Codec驱动程序提供 NVIDIA 音频中心 (AHUB) 硬件加速。AHUB 直接内存访问接口 (ADMAIF) 被实现为具有用于播放和捕获的 PCM 接口的Platform驱动程序。其余的 AHUB 模块,例如交叉开关 (XBAR)、复用器 (AMX)、解复用器 (ADX) 和 IC 间声音 (I2S),均作为Codec驱动程序实现。每个驱动程序都通过Machine驱动程序内部的数字音频接口(DAI) 连接到 XBAR,形成音频集线器(AHUB)。

Machine驱动程序探针实例化声卡设备并注册 ADMAIF 公开的所有 PCM 接口。启动后,但在使用这些接口进行音频播放或捕获之前,您必须在 XBAR 内设置音频路径。默认情况下,XBAR 在启动时没有路由连接,也没有完整的 DAPM 路径来启动相应的小部件。XBAR 驱动程序为所有音频组件引入了 MUX 小部件,并使用 ALSAamixer实用程序从用户空间通过 kcontrol 实现自定义路由。如果音频路径不完整,则DAPM路径未关闭,硬件设置未应用,并且无法听到音频输出。
Platform驱动程序

ADMAIF
ADMAIF 是 Jetson ASoC 设计中的平台驱动程序。它实现了通过该结构公开的所需 PCM 接口snd_soc_component_driver。这些接口通过与 SoC DMA 引擎的上游 API 交互来帮助执行 DMA 操作。ADMAIF 平台驱动程序定义 DAI 并将其注册到 ASoC 内核。

ADMAIF 通道映射到:

/dev/snd/pcmC1Dp用于播放
/dev/snd/pcmC1Dc用于捕获
Codec驱动程序
在 ASoC 驱动程序实现中,除 ADMAIF 之外的其余 AHUB 模块均作为Codec驱动程序实现。
XBAR
XBAR Codec驱动程序为所有接口模块定义了 RX、TX 和 MUX 小部件:ADMAIF、AMX、ADX、I2S、DMIC、Mixer、SFC 和 MVC。MUX 小部件永久路由到结构内相应的 TX 小部件snd_soc_dapm_route。

XBAR 互连是通过使用 ALSA amixer 实用程序根据需要将任何 RX 小部件块连接到任何 MUX 小部件块来实现的。实现这些小部件的获取和放置处理程序,以便通过在硬件 MUX 寄存器中设置适当的位来存储音频连接。

Mixer Controls
如果开机后声卡可用,则表明Machine驱动程序已成功绑定所有Codec驱动程序和Platform驱动程序。在物理Codec上获取音频输出之前的剩余步骤涉及使用 MUX 小部件来建立 DAPM 路径,以便将数据从特定输入模块路由到特定输出模块。输入和输出模块取决于适用的用例。这为复杂的用例提供了灵活性。

该命令实现内部AHUB路径“ADMAIF1 RX to XBAR to I2S1 TX”:

$ amixer –c APE cset name=‘I2S1 Mux’ ‘ADMAIF1’

AMX
音频多路复用器 (AMX) 模块可以将多达 16 个通道(每通道最多 32 位)的四个流复用为最多 16 个通道(每通道最多 32 位)的时分复用 (TDM) 流。AMX 有 4 个 RX 端口用于从 XBAR 接收数据,1 个 TX 端口用于将多路复用输出传输到 XBAR。每个端口都作为 DAI 公开,如下图实线所示。路线是使用 DAPM 小部件建立的,如虚线所示。

AMX 代码驱动程序支持以下功能:

可以多路复用最多 4 个输入流,每个输入流最多 16 个通道,并生成一个最多 16 个通道的输出流,大概就是同时支持4个流的播放。
在这里插入图片描述

Mixer Controls
混音器控件由各自的Codec驱动程序为 AMX 的每个实例注册,用于配置音频数据的路径、特性和处理方法。
在这里插入图片描述
此示例演示如何使用 AMX 模块复用两个立体声流:DMIC2(连接到 RxCIF0)和 DMIC3(连接到 RxCIF1):

$ amixer -c APE cset name="AMX2 RX1 Mux" "DMIC2"				//解读:AMX2的RX1来自DMIC2
$ amixer -c APE cset name="AMX2 RX2 Mux" "DMIC3"				//解读:AMX2的RX2来自DMIC3
$ amixer -c APE cset name="AMX2 Output Audio Channels" 4		//解读:AMX2的TX为4个通道(每个Mic都是双通道 ?)
$ amixer -c APE cset name="ADMAIF<i> Mux" AMX2					//解读:ADMAIF<i>的RX为AMX2
$ amixer -c APE cset name="ADMAIF<i> Playback Audio Channels" 4
$ amixer -c APE cset name="ADMAIF<i> Capture Audio Channels" 4
$ amixer -c APE cset name="ADMAIF<i> Playback Client Channels" 4
$ amixer -c APE cset name="ADMAIF<i> Capture Client Channels" 4
$ amixer -c APE cset name="AMX2 Byte Map 0" 0
$ amixer -c APE cset name="AMX2 Byte Map 1" 1
$ amixer -c APE cset name="AMX2 Byte Map 2" 2
$ amixer -c APE cset name="AMX2 Byte Map 3" 3
$ amixer -c APE cset name="AMX2 Byte Map 4" 4
$ amixer -c APE cset name="AMX2 Byte Map 5" 5
$ amixer -c APE cset name="AMX2 Byte Map 6" 6
$ amixer -c APE cset name="AMX2 Byte Map 7" 7
$ amixer -c APE cset name="AMX2 Byte Map 8" 64
$ amixer -c APE cset name="AMX2 Byte Map 9" 65
$ amixer -c APE cset name="AMX2 Byte Map 10" 66
$ amixer -c APE cset name="AMX2 Byte Map 11" 67
$ amixer -c APE cset name="AMX2 Byte Map 12" 68
$ amixer -c APE cset name="AMX2 Byte Map 13" 69
$ amixer -c APE cset name="AMX2 Byte Map 14" 70
$ amixer -c APE cset name="AMX2 Byte Map 15" 71
$ arecord -D hw:APE,<i-1> -r 48000 -c 4 -f S16_LE <out_wav>

ADX
音频解复用器 (ADX) 模块可将最多 16 个通道、每通道最多 32 位的单个 TDM 流解复用为最多 16 个通道、每通道 32 位的四个流。ADX 的 RX 端口接收来自 XBAR 的输入数据,四个 TX 端口将解复用的输出传输到 XBAR。每个端口都作为 DAI 公开,由实线表示,并使用 DAPM 小部件建立路由,如下图中的虚线所示。
在这里插入图片描述
ADX 有一个输入 RxCIF,用于提供输入流。核心逻辑根据字节映射从该输入流中选择字节,并形成输出流,这些输出流被定向到 TxCIF FIFO 以传输到 AHUB 中的下游模块。

ADX 解复用器支持以下功能:

将一个最多 16 个通道的输入流解复用为四个最多 16 个通道的输出流
I2S
I2S xxxx驱动程序支持双向数据流,因此定义了 CIF 和 DAP RX/TX DAPM 小部件,其中 I2S 的 CIF 侧与 XBAR 接口,DAP 侧与 Jetson 设备上的物理编解码器接口。
设备树
此 I2S 节点启用给定芯片上的给定 I2S 实例:

aconnect@2a41000 {
	compatible = "nvidia,tegra210-aconnect";
 status = "okay";
	...
	tegra_axbar: ahub {
    	compatible = "nvidia,tegra186-ahub";
    	status = "okay";
    	...
    	tegra_i2s1: i2s@2901000 {
        	compatible = "nvidia,tegra210-i2s";
        	reg = <0x0 0x2901000 0x0 0x100>;
        	clocks = <&bpmp_clks TEGRA194_CLK_I2S1>,
            <&bpmp_clks TEGRA194_CLK_PLLA_OUT0>,
            <&bpmp_clks TEGRA194_CLK_I2S1_SYNC_INPUT>,
            <&bpmp_clks TEGRA194_CLK_SYNC_I2S1>,
            <&bpmp_clks TEGRA194_CLK_I2S1_SYNC_INPUT>;
        	clock-names = "i2s",pll_a_out0, "ext_audio_sync","audio_sync", "clk_sync_input";
        	assigned-clocks = <&bpmp_clks TEGRA194_CLK_I2S1>;
        	assigned-clock-parents =
            <&bpmp_clks TEGRA194_CLK_PLLA_OUT0>;
        	assigned-clock-rates = <1536000>;
        	fsync-width = <31>;
        	#sound-dai-cells = <1>;
        	sound-name-prefix = "I2S1";
        	status = "okay";
    	};
    	...
	};
};

更详细看原文链接

https://blog.csdn.net/daixiang789/article/details/135239138

https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/Communications/AudioSetupAndDevelopment.html#

machine端代码分析

设备树

tegra_sound: sound {
		status = "okay";
		compatible = "nvidia,tegra-audio-t210ref-mobile-rt565x";
		nvidia,model = "tegra-snd-t210ref-mobile-rt565x";

		clocks = <&tegra_car TEGRA210_CLK_PLL_A>,
			 <&tegra_car TEGRA210_CLK_PLL_A_OUT0>,
			 <&tegra_car TEGRA210_CLK_EXTERN1>;
		clock-names = "pll_a", "pll_a_out0", "extern1";
		assigned-clocks = <&tegra_car TEGRA210_CLK_EXTERN1>,
				  <&tegra_car TEGRA210_CLK_PLL_A_OUT0>,
				  <&tegra_car TEGRA210_CLK_PLL_A>;
		assigned-clock-parents = <&tegra_car TEGRA210_CLK_PLL_A_OUT0>;
		assigned-clock-rates = <12288000>, <49152000>, <368640000>;

		nvidia,num-codec-link = <4>;

		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";

		nvidia,xbar = <&tegra_axbar>;
		mclk-fs = <256>;

		hdr40_snd_link_i2s: i2s_dai_link1: nvidia,dai-link-1 {
			link-name = "spdif-dit-0";
			cpu-dai = <&tegra_i2s4>;
			codec-dai = <&spdif_dit0>;
			cpu-dai-name = "I2S4";
			codec-dai-name = "dit-hifi";
			format = "i2s";
			bitclock-slave;
			frame-slave;
			bitclock-noninversion;
			frame-noninversion;
			bit-format = "s16_le";
			srate = <48000>;
			num-channel = <2>;
			ignore_suspend;
			name-prefix = "x";
			status = "okay";
		};
};

代码位置:

sound/soc/tegra-alt/machine_drivers/tegra_machine_driver_mobile.c

tegra_machine_driver_probe:

static int tegra_machine_driver_probe(struct platform_device *pdev)
{

	/* parse card name first to log errors with proper device name */
	// 根据设备树节点中的nvidia,model设置声卡名字
	ret = snd_soc_of_parse_card_name(card, "nvidia,model");


	match = of_match_device(tegra_machine_of_match, &pdev->dev);

	machine = devm_kzalloc(&pdev->dev, sizeof(*machine), GFP_KERNEL);

	machine->soc_data = (struct tegra_machine_soc_data *)match->data;

	platform_set_drvdata(pdev, card);
	snd_soc_card_set_drvdata(card, machine);

	// 解析audio routing,分配多个snd_soc_dapm_route,把"x Headphone"作为sink,"x OUT"作为source,保存在card的of_dapm_routes中
	ret = snd_soc_of_parse_audio_routing(card,
				"nvidia,audio-routing");

	tegra_machine_dma_set_mask(pdev);
	// 重要函数,解析dai link节点,设置dai_link
	ret = add_dai_links(pdev);
	// 做一些时钟的初始化
	ret = tegra_alt_asoc_utils_init(&machine->audio_clock,
					&pdev->dev,
					card);
	// 注册声卡
	ret = snd_soc_register_card(card);
	// 添加几个i2s相关的ctrl
	tegra_machine_add_i2s_codec_controls(card,
					machine->soc_data->num_ahub_links +
					machine->num_codec_links);
	return 0;
}

add_dai_links:

static int add_dai_links(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);
	struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
	int ret;
	// 分配一个asoc结构体,这个结构体记录了所有dai_link有关的信息
	/* struct tegra_asoc {
		struct snd_soc_codec_conf *codec_confs;
		struct snd_soc_dai_link *dai_links;
		unsigned int num_links;
		unsigned int num_confs;
		unsigned int *tx_slot;
		unsigned int *rx_slot;
	};*/
	machine->asoc = devm_kzalloc(&pdev->dev, sizeof(*machine->asoc),
				     GFP_KERNEL);
	// 解析设备树节点
	ret = tegra_asoc_populate_dai_links(pdev);
	if (ret < 0)
		return ret;

	ret = tegra_asoc_populate_codec_confs(pdev);
	if (ret < 0)
		return ret;
	// 设置所有dai_Link的Init函数,作用是根据不同的dai_link,找到他连接的codec_dai_driver,调用它的set_sysclock初始化时钟
	ret = codec_init(machine);
	// 设置所有的dai_link的Ops为tegra_machine_pcm_ops
	set_dai_ops(machine);
	return 0;
}

tegra_asoc_populate_dai_links:

int tegra_asoc_populate_dai_links(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node, *subnp = NULL;
	struct snd_soc_card *card = platform_get_drvdata(pdev);
	struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
	struct snd_soc_dai_link *dai_links, *ahub_links;
	struct snd_soc_pcm_stream *params;
	char dai_link_name[MAX_STR_SIZE], *str;
	unsigned int num_codec_links, num_ahub_links, num_links,
		link_count = 0, i, j, *rx_slot, *tx_slot;
	int ret;
	// 这个声卡的dai link由两部分组成,一个是ahub相关的dai_link, admaif为cpu_dai, axbar为codec_dai,另一部分来源于设备树,设备树中一个dai_link节点会创建从两条dai_link,以i2s为例,一条用于axbar和i2s之间连接,axbar为cpu_dai, i2s为codec_dai,另一条用于i2s和真实的物理codec连接,i2s为cpu_dai, 物理codec为codec_dai
	num_ahub_links = machine->soc_data->num_ahub_links;
	//ahub_links为admaif和axbar之间的dai_link,对应结构体tegra210_xbar_dai_links来源于soc_data
	ahub_links = machine->soc_data->ahub_links;

	if (!np || !num_ahub_links || !ahub_links)
		return -EINVAL;

	/* read number of codec links exposed via DT */
	// 获取设备树中的dai_link数量
	ret = of_property_read_u32(np, "nvidia,num-codec-link",
				   &machine->num_codec_links);
	num_codec_links = machine->num_codec_links;

	/*
	 * each codec link specified in device tree will result into one DAP
	 * and one CIF link. For example, i2s dai link will look like below.
	 * ahub <----CIF----> i2s <----DAP----> codec
	 */
	 // 如上所说num_codec_links << 1对设备树的dai_link数目做了乘二
	num_links = num_ahub_links + (num_codec_links << 1);

	machine->asoc->num_links = num_links;
	// 申请这个数量的dai_links结构体
	machine->asoc->dai_links = devm_kzalloc(&pdev->dev,
						sizeof(*dai_links) * num_links,
						GFP_KERNEL);
	dai_links = machine->asoc->dai_links;


	machine->asoc->rx_slot = devm_kzalloc(&pdev->dev,
					      sizeof(*rx_slot) * num_links,
					      GFP_KERNEL);
	rx_slot = machine->asoc->rx_slot;

	machine->asoc->tx_slot = devm_kzalloc(&pdev->dev,
					      sizeof(*tx_slot) * num_links,
					      GFP_KERNEL);
	tx_slot = machine->asoc->tx_slot;
	

	/* populate now ahub links */
	// 拷贝admaif和axbar的已经初始化好的dai_links
	memcpy(dai_links, ahub_links, num_ahub_links * sizeof(*dai_links));

	/* populate now CIF and DAP links from device tree */
	// 初始化设备树中的一个节点对应两条dai_link的剩下的dai_link,i为i2s和codec之间的,j为i2s和axbar之间的
	for (i = num_ahub_links, j = num_ahub_links + num_codec_links;
	     i < num_ahub_links + num_codec_links; i++, j++) {
		memset((void *)dai_link_name, '\0', MAX_STR_SIZE);
		sprintf(dai_link_name, "nvidia,dai-link-%d", ++link_count);
		subnp = of_get_child_by_name(np, dai_link_name);
	

		/* DAP DAI link configuration */
		dai_links[i].stream_name = "Playback";
		// 根据codec_of_node 进行匹配,dai_link匹配cpu_dai和codec_dai不只是只能根据cpu_dai_name和codec_dai_name进行匹配,也可以比较dai_driver所属的设备节点和这个of_node是否相等来匹配,同时一个dai_driver既可以做cpu_dai也可以做codec_dai,无论使用什么函数注册的
		// 设置codec_of_node 为codec-dai所指的Phandle设备节点
		dai_links[i].codec_of_node = of_parse_phandle(subnp,
							      "codec-dai", 0);

		// 同理设置cpu的
		dai_links[i].cpu_of_node = of_parse_phandle(subnp, "cpu-dai",
							    0);
		ret = of_property_read_string(subnp, "link-name", &dai_links[i].name);

		/*
		 * special case for DSPK
		 * Two mono codecs can be connected to the controller
		 * DAP2 is required for DAPM path completion
		 * TODO revisit this when ASoC has multi-codec support
		 */
		if (!strcmp(dai_links[i].name, "dspk-playback-r"))
			dai_links[i].cpu_dai_name = "DAP2";
		else
			dai_links[i].cpu_dai_name = "DAP";
		// 解析各种属性
		dai_links[i].dai_fmt = snd_soc_of_parse_daifmt(subnp, NULL,
							       NULL, NULL);
		// 这个param就是dai_link中的param,记录了这个dai_link的bit-format, srate(采样率),channel_num通道数等,在其他platform,cpu_dai,codec_dai通过dai_link进行匹配时,这几个格式要与dai_link的param兼容
		params = devm_kzalloc(&pdev->dev, sizeof(*params), GFP_KERNEL);
		if (!params) {
			ret = -ENOMEM;
			break;
		}

		ret = of_property_read_string(subnp, "bit-format",
					      (const char **)&str);

		ret = tegra_machine_get_format(&params->formats, str);

		ret = of_property_read_u32(subnp, "srate", &params->rate_min);

		params->rate_max = params->rate_min;

		ret = of_property_read_u32(subnp, "num-channel",
					   &params->channels_min);
		params->channels_max = params->channels_min;
		// 设置这个dai_links支持的参数,采样率,format,通道数等
		dai_links[i].params = params;
		// 设置codec_dai_name
		ret = of_property_read_string(subnp, "codec-dai-name",
					      &dai_links[i].codec_dai_name);

		of_property_read_u32(subnp, "rx-mask", (u32 *)&rx_slot[i]);
		of_property_read_u32(subnp, "tx-mask", (u32 *)&tx_slot[i]);

		/*
		 * CIF DAI link configuration
		 * CIF link is towards XBAR, hence xbar node is cpu_of_node
		 * and codec_of_node is same as DAP's cpu_of_node.
		 */
		 // 设置i2s和xbar之间的dai_link的codec_of_node为i2s,cpu的为"nvidia,xbar"节点对应的设备节点,也就是tegra_axbar节点所注册的dai_driver
		dai_links[j].codec_of_node = of_parse_phandle(subnp, "cpu-dai",
							      0);
		dai_links[j].cpu_of_node = of_parse_phandle(np, "nvidia,xbar",
							    0);

		/*
		 * special case for DSPK
		 * Two mono codecs can be connected to the controller
		 * CIF2 is required for DAPM path completion
		 * TODO revist this when ASoC has multi-codec support
		 */
		if (!strcmp(dai_links[i].name, "dspk-playback-r"))
			dai_links[j].codec_dai_name = "CIF2";
		else
			dai_links[j].codec_dai_name = "CIF";

		ret = of_property_read_string(subnp, "cpu-dai-name",
					      &dai_links[j].cpu_dai_name);

		str = devm_kzalloc(&pdev->dev,
			sizeof(dai_links[j].cpu_dai_name) +
			1 + sizeof(dai_links[j].codec_dai_name),
			GFP_KERNEL);
		str = strcat(str, dai_links[j].cpu_dai_name);
		str = strcat(str, " ");
		str = strcat(str, dai_links[j].codec_dai_name);
		// 设置和上一条dai一样的参数
		dai_links[j].name = dai_links[j].stream_name = str;
		dai_links[j].params = dai_links[i].params;

		of_node_put(subnp);
	}
	// 将初始化好的dai_links赋值给dai_link
	card->num_links = num_links;
	card->dai_link = dai_links;
	// 由此可见i2s节点需要注册两条dai_link,一条名字为DAP,用于和物理codec之间的连接,一条名字为CIF,用于和xbar之间的连接
	return 0;
}

platform端adamif驱动分析

前面说了,admaif的dai都为cpu_dai,其他的ahub模块如xbar为codec_dai,admaif同时也是platform驱动
代码位置:

sound/soc/tegra-alt/tegra210_admaif_alt.c

static int tegra_admaif_probe(struct platform_device *pdev)
{
	int ret, i;
	struct tegra_admaif *admaif;
	void __iomem *regs;
	struct resource *res;
	const struct of_device_id *match;
	unsigned int buffer_size;

	match = of_match_device(tegra_admaif_of_match, &pdev->dev);
	if (!match) {
		dev_err(&pdev->dev, "Error: No device match found\n");
		return -ENODEV;
	}

	admaif = devm_kzalloc(&pdev->dev, sizeof(*admaif), GFP_KERNEL);
	if (!admaif)
		return -ENOMEM;

	admaif->refcnt = 0;
	admaif->dev = &pdev->dev;
	admaif->soc_data = (struct tegra_admaif_soc_data *)match->data;
	dev_set_drvdata(&pdev->dev, admaif);
	// 根据soc_data中的num_ch通道数分配记录dma信息结构体的内存,这里num_ch为10
	admaif->capture_dma_data = devm_kzalloc(&pdev->dev,
			sizeof(struct tegra_alt_pcm_dma_params) *
				admaif->soc_data->num_ch,
			GFP_KERNEL);


	admaif->playback_dma_data = devm_kzalloc(&pdev->dev,
			sizeof(struct tegra_alt_pcm_dma_params) *
				admaif->soc_data->num_ch,
			GFP_KERNEL);


	admaif->override_channels = devm_kzalloc(&pdev->dev,
			sizeof(int) * admaif->soc_data->num_ch,
			GFP_KERNEL);

	admaif->tx_mono_to_stereo = devm_kzalloc(&pdev->dev,
			sizeof(int) * admaif->soc_data->num_ch,
			GFP_KERNEL);

	admaif->rx_stereo_to_mono = devm_kzalloc(&pdev->dev,
			sizeof(int) * admaif->soc_data->num_ch,
			GFP_KERNEL);
	if (!admaif->rx_stereo_to_mono)
		return -ENOMEM;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	regs = devm_ioremap_resource(&pdev->dev, res);
	// 记录映射之后的地址
	admaif->base_addr = regs;
	// 对寄存器的操作统统用regmap
	admaif->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
					       admaif->soc_data->regmap_conf);

	regcache_cache_only(admaif->regmap, true);

	if (admaif->soc_data->is_isomgr_client)
		tegra_isomgr_adma_register();
	// 遍历每一个通道
	for (i = 0; i < admaif->soc_data->num_ch; i++) {
		// 设置每一个通道对应Playback和Capture的dma fifo的首地址
		admaif->playback_dma_data[i].addr = res->start +
				admaif->soc_data->tx_base +
				TEGRA_ADMAIF_XBAR_TX_FIFO_WRITE +
				(i * TEGRA_ADMAIF_CHANNEL_REG_STRIDE);

		admaif->capture_dma_data[i].addr = res->start +
				TEGRA_ADMAIF_XBAR_RX_FIFO_READ +
				(i * TEGRA_ADMAIF_CHANNEL_REG_STRIDE);
		// 记录传输一次的数据长度和通道号,和名字
		admaif->playback_dma_data[i].width = 32;
		admaif->playback_dma_data[i].req_sel = i + 1;
		ret = of_property_read_string_index(pdev->dev.of_node,
				"dma-names",
				(i * 2) + 1,
				&admaif->playback_dma_data[i].chan_name);

		buffer_size = 0;
		if (of_property_read_u32_index(pdev->dev.of_node,
				"dma-buffer-size",
				(i * 2) + 1,
				&buffer_size) < 0) {
			dev_dbg(&pdev->dev,
				"Missing property nvidia,dma-buffer-size\n");
		}
		admaif->playback_dma_data[i].buffer_size = buffer_size;

		admaif->capture_dma_data[i].width = 32;
		admaif->capture_dma_data[i].req_sel = i + 1;
		ret = of_property_read_string_index(pdev->dev.of_node,
				"dma-names",
				(i * 2),
				&admaif->capture_dma_data[i].chan_name);
		if (ret < 0) {
			dev_err(&pdev->dev,
				"Missing property nvidia,dma-names\n");
			return ret;
		}
		buffer_size = 0;
		if (of_property_read_u32_index(pdev->dev.of_node,
				"dma-buffer-size",
				(i * 2),
				&buffer_size) < 0) {
			dev_dbg(&pdev->dev,
				"Missing property nvidia,dma-buffer-size\n");
		}
		admaif->capture_dma_data[i].buffer_size = buffer_size;
	}
	// 使能admaif
	regmap_update_bits(admaif->regmap, admaif->soc_data->global_base +
			   TEGRA_ADMAIF_GLOBAL_ENABLE, 1, 1);
	// 注册admaif的Cpu_dai_driver
	// 类似与ADMAIF1-ADMAIF20,每一条通路对应一个设备的传输,对应的codec_dai_driver在xbar驱动中
	/*
	{							\
		.name = "ADMAIF" #id,				\
		.probe = tegra_admaif_dai_probe,		\
		.playback = {					\
			.stream_name = "Playback " #id,		\
			.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,	\
		},						\
		.capture = {					\
			.stream_name = "Capture " #id,		\
			.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,		\
		},						\
		.ops = &tegra_admaif_dai_ops,			\
	}*/
	// xbar中的一个dai_link为
	/*.name = "ADMAIF1 CIF",
		.stream_name = "ADMAIF1 CIF",
		.cpu_dai_name = "ADMAIF1",
		.codec_dai_name = "ADMAIF1",
		.cpu_name = "tegra210-admaif",
		.codec_name = "tegra210-axbar",
		.platform_name = "tegra210-admaif",
		.ignore_pmdown_time = 1,*/
	// cpu_dai_name = "ADMAIF1",就对应上面的那个"ADMAIF" #id,codec_dai_name = "ADMAIF1"为xbar注册的dai_driver
	ret = devm_snd_soc_register_component(&pdev->dev,
					      &tegra_admaif_dai_driver,
					      tegra_admaif_dais,
					      admaif->soc_data->num_ch);
	// 猜测是和回环相关的codec_driver
	ret = snd_soc_register_codec(&pdev->dev, admaif->soc_data->admaif_codec,
				     admaif->soc_data->codec_dais,
				     admaif->soc_data->num_ch * 2);
	// 注册dma数据传输功能的audio platform驱动,这个platform驱动在dai probe时会为cpu_dai绑定dma通道
	ret = tegra_alt_pcm_platform_register(&pdev->dev);


	return 0;

}

xbar驱动分析

xbar驱动注册了和admaif向连接的codec_dai,注意,

static int tegra210_xbar_registration(struct platform_device *pdev)
{
	int ret;
	int num_dapm_widgets, num_dapm_routes;

	num_dapm_widgets = (TEGRA210_NUM_MUX_WIDGETS * 3) +
		(TEGRA210_NUM_DAIS - TEGRA210_NUM_MUX_WIDGETS) * 2;
	num_dapm_routes =
		(TEGRA210_NUM_DAIS - TEGRA210_NUM_MUX_WIDGETS) * 2 +
		(TEGRA210_NUM_MUX_WIDGETS * TEGRA210_NUM_MUX_INPUT);

	tegra210_xbar_codec.component_driver.num_dapm_widgets =
			num_dapm_widgets;
	tegra210_xbar_codec.component_driver.num_dapm_routes =
			num_dapm_routes;

	ret = snd_soc_register_codec(&pdev->dev, &tegra210_xbar_codec,
				tegra210_xbar_dais, TEGRA210_NUM_DAIS);

	if (ret != 0) {
		dev_err(&pdev->dev, "Could not register CODEC: %d\n", ret);
		return -EBUSY;
	}

	of_platform_populate(pdev->dev.of_node, NULL, tegra210_xbar_auxdata,
			     &pdev->dev);

	return 0;
}

设备树中重要属性

  1. bitclock-slave 和 frame-slave
    这些属性定义了设备在 I2S 总线上的主/从模式。具体来说:
    bitclock-slave:
    定义设备在 I2S 总线上的 Bit Clock (BCLK) 工作在从模式。在这种模式下,设备不生成 BCLK,而是接收 BCLK 信号。
    frame-slave:
    定义设备在 I2S 总线上的帧同步信号 (Frame Sync 或 LRCK) 工作在从模式。在这种模式下,设备不生成帧同步信号,而是接收帧同步信号。
  2. bitclock-noninversion 和 frame-noninversion
    这些属性定义了 I2S 总线的时钟和帧同步信号的极性。具体来说:
    bitclock-noninversion:
    定义设备接收或生成的 BCLK 信号是非反相的,意味着时钟信号的极性保持不变。这通常是默认配置,即在时钟上升沿或下降沿采样数据。
    frame-noninversion:
    定义设备接收或生成的帧同步信号是非反相的,意味着帧同步信号的极性保持不变。帧同步信号的高电平或低电平指示数据帧的开始,这也通常是默认配置。

这些配置通过在Probe函数中对设备树进行解析转换为相应的宏赋值给对应dai_link的dai_fmt字段,相关的宏:

// 位时钟和帧同步时钟是否需要反转
#define SND_SOC_DAIFMT_NB_NF		(0 << 8) /* normal bit clock + frame */
#define SND_SOC_DAIFMT_NB_IF		(2 << 8) /* normal BCLK + inv FRM */
#define SND_SOC_DAIFMT_IB_NF		(3 << 8) /* invert BCLK + nor FRM */
#define SND_SOC_DAIFMT_IB_IF		(4 << 8) /* invert BCLK + FRM */

// 位时钟和帧同步时钟的主从关系
#define SND_SOC_DAIFMT_CBP_CFP		(1 << 12) /* codec clk provider & frame provider */
#define SND_SOC_DAIFMT_CBC_CFP		(2 << 12) /* codec clk consumer & frame provider */
#define SND_SOC_DAIFMT_CBP_CFC		(3 << 12) /* codec clk provider & frame consumer */
#define SND_SOC_DAIFMT_CBC_CFC		(4 << 12) /* codec clk consumer & frame consumer */

set_fmt和hw_params区别

  1. set_fmt
    set_fmt 回调函数用于配置数据传输格式。这包括 I2S 总线的时序、数据格式、主/从模式等。此回调在音频流开始之前调用,用于设置基本的传输参数。
    主要配置内容:
    主/从模式(Master/Slave)
    数据格式(如 I2S、左对齐、右对齐等)
    时钟极性
    位时钟和帧时钟的相位

  2. hw_params
    hw_params 回调函数用于配置硬件参数。这些参数更具体,包括采样率、位宽、通道数等。此回调在音频流的硬件参数设置阶段调用,通常在打开音频流之后,但在实际数据传输之前。
    主要配置内容:
    采样率(Sample Rate)
    位宽(Bit Depth)
    通道数(Channels)
    缓冲区大小

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/716056.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎么缩小pdf文件大小

在数字化时代&#xff0c;pdf文件已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着pdf文件内容的增多&#xff0c;其大小也会相应增加&#xff0c;这给文件的传输、存储和共享带来了诸多不便。因此&#xff0c;如何有效地压缩pdf文件大小&#xff0c;成为了…

21组Midjourney绘画关键词,专为游戏设计的奇异生物的盛宴

21组AI绘画关键词&#xff0c;无一不在专为游戏设计的领域中发挥着巨大的作用。从2D游戏艺术到Xbox 360图形&#xff0c;从科幻怪物到异世界写实&#xff0c;这些关键词为游戏开发者提供了丰富多彩的创意资源。通过AI绘画工具&#xff0c;游戏画面呈现更加逼真、独特的设计风格…

【Linux应用】Linux系统的设备管理——Udev

1.udev概述 udev是 Linux2.6内核里的一个功能&#xff0c;它替代了原来的 devfs&#xff0c;成为当前 Linux 默认的设备管理工具&#xff0c;能够根据系统中的硬件设备的状态动态更新设备文件&#xff0c;包括设备文件的创建&#xff0c;删除等。 udev以守护进程的形式运行&am…

开源、无广告的小巧的动态壁纸软件

一、简介 1、开源、无广告的小巧的动态壁纸软件。它是绿色软件&#xff0c;软件压缩包大小仅有 0.5MB&#xff0c;目前仅支持 Windows 平台。它简单好用&#xff0c;支持将各种尺寸、分辨率的 MP4、MOV 等主流格式的视频文件设置为桌面动态壁纸&#xff0c;性能表现以及稳定性都…

【NOI】C++程序结构入门之循环结构四——带余除法

文章目录 前言一、带余除法1.1 概念1.2 编程中的使用1.2.1 模运算1.2.2 判断奇偶性1.2.3 判断倍数关系1.2.4 循环和迭代控制1.2.5 密码学与安全1.2.6 算法设计1.2.7 数据验证与错误处理 二、例题讲解问题&#xff1a;1389 - 数据分析问题&#xff1a;1750 - 有0的数问题&#x…

【Unity实战篇】| 快速制作一个简易时钟,包括2D和3D时钟

前言 【Unity实战篇】| 快速制作一个时钟&#xff0c;包括2D和3D时钟一、2D时钟制作1.1 钟表盘制作1.2 指针制作1.3 钟表搭建1.4 设置时钟的中心点1.5 时钟旋转逻辑 二、3D时钟制作2.1 搭建表盘和指针2.2 调整指针的位置和节点2.3 时钟旋转逻辑 总结 前言 时钟 这个东西想必不…

【C++】数据类型、函数、头文件、断点调试

四、基本概念 这部分和C语言重复的部分就简写速过&#xff0c;因为我之前写过一个C语言的系列&#xff0c;非常详细。C和C这些都是一样的&#xff0c;所以这里不再一遍遍重复码字了。感兴趣的同学可以翻看我之前的C语言系列文章。 1、数据类型 编程的本质就是操作数据。 操…

PyCharm配置教程,手把手教你如何配置

文章目录 引言1. 安装 PyCharm1.1 下载和安装1.2 初次启动 2. 基本配置2.1 设置界面2.2 常用配置项 3. 项目配置3.1 创建新项目3.2 配置解释器 4. 虚拟环境配置4.1 创建虚拟环境4.2 使用已有虚拟环境4.3 管理依赖 5. 插件和扩展5.1 安装插件5.2 推荐插件 6. 调试配置6.1 配置调…

React+TS前台项目实战(九)-- 全局常用组件弹窗Dialog封装

文章目录 前言Dialog公共弹窗组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局公共弹窗Dialog组件封装&#xff0c;将用到上篇封装的模态框Modal组件。有时在前台项目中&#xff0c;偶尔要用到一两个常用的组件&#xff0c;如 弹窗&#x…

Linux常⽤服务器构建-samba

目录 1. 介绍 2. 安装 3. 配置 3.1 创建存放共享⽂件的路径 3.2 创建samba账户 4 重启samba 5. 访问共享⽂件 5.1 mac下访问⽅式 5.2 windows下访问⽅式 1. 介绍 Samba 是在 Linux 和 UNIX 系统上实现 SMB 协议的⼀个免费软件&#xff0c;能够完成在 windows 、 mac 操作系统…

6.12ctf练习

[西湖论剑 2022]Node Magical Login 源码在这里&#xff1a;GitHub - CTF-Archives/2022-xhlj-web-node_magical_login: A web challenge in 2022 西湖论剑大赛打开 打开环境是个登录框&#xff0c;先进行了扫描和抓包都没有看见什么有价值的东西&#xff0c;看源码 大致连接…

LeetCode136只出现一次的数字

题目描述 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 解析 需要想到异或运算&#…

【BES2500x系列 -- RTX5操作系统】系列文章索引

&#x1f48c; 所属专栏&#xff1a;【BES2500x系列】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f49…

如何将asf转换成mp4?教你3种轻松转换方法

如何将asf转换成mp4&#xff1f;ASF是一种视频格式&#xff0c;但在现代的视频编辑和播放中&#xff0c;MP4格式因其广泛的兼容性和高质量的压缩技术而备受欢迎。因此&#xff0c;将ASF转换为MP4格式的需求时常出现。MP4还采用了高效的压缩技术&#xff0c;能在保证视频质量的同…

前端JS必用工具【js-tool-big-box】学习,打开全屏和关闭全屏

这一小节&#xff0c;我们说一下 js-tool-big-box 工具库中&#xff0c;打开全屏和关闭全屏的方法学习。 我们知道&#xff0c;浏览器想打开全屏&#xff0c;按一下 F11 键就可以了&#xff0c;但一来这个功能不一定所有使用的用户都知道&#xff0c;二来在一些例如大屏的需求…

芯片封测从入门到精通

文章目录 &#x1f4d1;前言一、作者简介二、书籍亮点三、内容简介四、适读人群 &#x1f4d1;前言 在科技日新月异的今天&#xff0c;芯片作为现代电子设备的核心部件&#xff0c;其性能与可靠性直接决定了整个产品的竞争力。而芯片封测&#xff0c;作为确保芯片性能与可靠性…

《Windows API每日一练》4.3 点和线的绘制

理论上&#xff0c;所有的图形设备驱动程序所需要的就是SetPixel函数和GetPixel函数。其余的一切都可以使用在GDI模块中实现的更高层的例程来处理。例如&#xff0c;画一条线&#xff0c;GDI可以不停地调整x和y坐标&#xff0c;然后连续调用多次SetPixel函数来实现。 事实上&a…

了解压电传感器:压电效应

压电加速度计的个关键方面是压电效应。一般来说&#xff0c;压电材料在受到机械应力时可以产生电力。 相反&#xff0c;对压电材料施加电场可以使其变形并产生小的机械力。尽管大多数电子工程师都熟悉压电效应&#xff0c;但有时并没有完全理解这种有趣现象的细节。 更深入地…

电脑剪贴板历史记录查看,让你的信息管理更加有序!

剪贴板是电脑中一个非常实用的功能&#xff0c;允许用户在不同的应用程序之间复制和粘贴文本、图像、文件等内容。然而&#xff0c;默认情况下&#xff0c;剪贴板只能存储最近一次复制的内容&#xff0c;这可能会限制我们的工作效率。幸运的是&#xff0c;电脑剪贴板历史记录查…

水滴式粉碎机:玉米饲料加工的新篇章

在饲料加工业中&#xff0c;玉米作为一种重要的原料&#xff0c;其加工方式直接影响到饲料的品质以及动物对饲料的消化吸收率。近年来&#xff0c;随着科技的进步&#xff0c;越多的环保的饲料加工设备被引入到饲料生产中&#xff0c;其中&#xff0c;水滴式粉碎机以其独特的优…