3. 设备树配置:I2S控制器节点、DMA通道绑定、时钟与GPIO配置、codec节点实例
设备树,说白了就是Linux内核用来描述硬件拓扑的“说明书”。你想想看,内核要驱动一个I2S音频接口,它得知道:I2S控制器挂在哪条总线上?用哪个DMA通道?主时钟从哪来?GPIO怎么配?Codec的I2C地址是多少?这些信息,全写在设备树里。
我个人习惯,拿到一块新板子,第一件事就是读它的设备树。不是看原理图,就是看dts。因为设备树里写的,就是硬件工程师和驱动开发者之间的“契约”。
3.1 I2S控制器节点
先看一个典型的I2S控制器节点。以三星Exynos系列为例,它的I2S控制器在设备树里长这样:
i2s0: i2s@3830000 {
compatible = "samsung,s5pv210-i2s";
reg = <0x03830000 0x100>;
interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
dmas = <pdma0 9>, <pdma0 8>;
dma-names = "tx", "rx";
clocks = <&clock CLK_I2S0>, <&clock CLK_SCLK_I2S0>;
clock-names = "i2s", "i2s_opclk0";
pinctrl-names = "default";
pinctrl-0 = <&i2s0_bus>;
#sound-dai-cells = <0>;
status = "okay";
};
这里每个字段都有讲究:
| 属性 | 含义 | 我在项目中踩过的坑 |
|---|---|---|
compatible |
匹配驱动的字符串 | 拼写错一个字母,驱动就加载不上 |
reg |
寄存器基地址和范围 | 地址写错,内核直接panic |
interrupts |
中断号、类型 | 中断号对不上,音频卡顿无声音 |
dmas / dma-names |
DMA通道绑定 | 顺序反了,录音变成播放 |
clocks / clock-names |
时钟源配置 | 时钟频率不对,采样率全乱套 |
pinctrl |
引脚复用配置 | GPIO没配成I2S功能,波形出不来 |
#sound-dai-cells |
DAI链接标识 | 漏了这个,ASoC无法绑定 |
reg属性里的地址范围写小了(只写了0x80,实际寄存器需要0x100),结果访问到后面的寄存器时,内核报了一堆“external abort”。查了两天才发现是设备树的问题。所以,地址范围一定要和芯片手册对清楚。
3.2 DMA通道绑定
I2S传输数据,靠的是DMA。CPU如果一条一条地去搬音频数据,那啥也别干了。所以,设备树里必须把DMA通道绑对。
看上面例子里的这两行:
dmas = <pdma0 9>, <pdma0 8>;
dma-names = "tx", "rx";
这里的意思是:
- 第一个DMA通道(pdma0的9号请求线)用于发送(tx)
- 第二个DMA通道(pdma0的8号请求线)用于接收(rx)
你想想看,如果我把dma-names写成"rx", "tx",那播放音乐时,DMA会从录音通道拿数据,出来的声音就是一片噪音。嗯,我确实见过有人这么干过。
3.3 时钟与GPIO配置
时钟是I2S的心脏。没有正确的时钟,采样率就是空中楼阁。
设备树里时钟配置的核心是clocks和clock-names:
clocks = <&clock CLK_I2S0>, <&clock CLK_SCLK_I2S0>;
clock-names = "i2s", "i2s_opclk0";
这里有两个时钟:
- "i2s":I2S控制器的总线时钟(APB时钟),用于寄存器访问
- "i2s_opclk0":I2S的操作时钟(位时钟的根时钟),用于生成BCLK和LRCLK
我曾经遇到一个案例:板子播放16kHz采样率的音频没问题,但一换到48kHz就变调。查了半天,发现是i2s_opclk0的父时钟被设成了固定的24.576MHz,而这个频率无法通过分频精确得到48kHz的BCLK。后来改了时钟树,问题解决。
GPIO配置则通过pinctrl子系统完成:
pinctrl-names = "default";
pinctrl-0 = <&i2s0_bus>;
对应的引脚节点通常在另一个dtsi文件里:
i2s0_bus: i2s0-bus {
samsung,pins = "gpi1-0", "gpi1-1", "gpi1-2", "gpi1-3";
samsung,pin-function = <3>; // I2S功能
samsung,pin-pud = <0>; // 无上下拉
samsung,pin-drv = <0>; // 驱动强度
};
3.4 Codec节点实例
Codec节点通常挂在I2C总线下,因为大多数Codec都是通过I2C来配置寄存器的。来看一个WM8960的实例:
&i2c1 {
wm8960: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clk IMX8MM_CLK_SAI3_ROOT>;
clock-names = "mclk";
#sound-dai-cells = <0>;
wlf,shared-lrclk;
};
};
这里要注意几个点:
reg = <0x1a>:这是Codec的I2C地址。WM8960的7位地址是0x1a(如果ADDR引脚接地)。我见过有人写成0x34(那是8位地址),结果I2C探测不到设备。clocks:Codec的主时钟(MCLK),通常由SoC的某个时钟输出提供。频率一般是256×采样率或512×采样率。wlf,shared-lrclk:这是WM8960特有的属性,表示左右声道共用LRCLK。不是所有Codec都需要,具体看数据手册。
完整的音频设备树,还需要一个sound节点来把I2S控制器和Codec绑定在一起:
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "wm8960-audio";
simple-audio-card,format = "i2s";
simple-audio-card,bitclock-master = <&sound_master>;
simple-audio-card,frame-master = <&sound_master>;
simple-audio-card,widgets =
"Speaker", "Speaker",
"Microphone", "Mic Jack";
simple-audio-card,routing =
"Speaker", "OUT3",
"Mic Jack", "MIC1";
sound_master: simple-audio-card,cpu {
sound-dai = <&i2s0>;
};
simple-audio-card,codec {
sound-dai = <&wm8960>;
};
};
这个sound节点,说白了就是告诉内核:I2S控制器和Codec是一对,它们之间用I2S格式通信,主时钟由I2S控制器提供。
3.5 知识结构总览
下面这张图,把整个设备树配置的脉络梳理清楚了:
从这张图可以看得很清楚:I2S控制器、DMA通道、Codec节点,三者通过sound节点汇聚到一起。任何一个环节配置错误,音频流都跑不通。
i2s、dma、wm8960(或你的Codec名字)来确认节点是否被正确解析。如果看到OF: fdt: not found之类的错误,那就是设备树没写对。