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(¶ms->formats, str);
ret = of_property_read_u32(subnp, "srate", ¶ms->rate_min);
params->rate_max = params->rate_min;
ret = of_property_read_u32(subnp, "num-channel",
¶ms->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;
}
设备树中重要属性
- bitclock-slave 和 frame-slave
这些属性定义了设备在 I2S 总线上的主/从模式。具体来说:
bitclock-slave:
定义设备在 I2S 总线上的 Bit Clock (BCLK) 工作在从模式。在这种模式下,设备不生成 BCLK,而是接收 BCLK 信号。
frame-slave:
定义设备在 I2S 总线上的帧同步信号 (Frame Sync 或 LRCK) 工作在从模式。在这种模式下,设备不生成帧同步信号,而是接收帧同步信号。 - 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区别
-
set_fmt
set_fmt 回调函数用于配置数据传输格式。这包括 I2S 总线的时序、数据格式、主/从模式等。此回调在音频流开始之前调用,用于设置基本的传输参数。
主要配置内容:
主/从模式(Master/Slave)
数据格式(如 I2S、左对齐、右对齐等)
时钟极性
位时钟和帧时钟的相位 -
hw_params
hw_params 回调函数用于配置硬件参数。这些参数更具体,包括采样率、位宽、通道数等。此回调在音频流的硬件参数设置阶段调用,通常在打开音频流之后,但在实际数据传输之前。
主要配置内容:
采样率(Sample Rate)
位宽(Bit Depth)
通道数(Channels)
缓冲区大小