1. 为什么RK3568的I2S音频调试成了嵌入式Linux开发绕不开的“试金石”
在Rockchip RK3568平台上做音频功能,你绕不开I2S——这不是一句空话,而是我踩过三次板子、烧坏两块Codec芯片、重写四版设备树后得出的结论。RK3568本身集成了双路I2S控制器(I2S0和I2S1),支持主从模式、多种时钟源选择、可编程采样率与位宽,硬件能力足够扎实;但真正落地时,90%的问题不出在硬件上,而出在ALSA子系统与设备树的耦合逻辑里。很多人一上来就改sound节点,结果aplay -l根本看不到设备,dmesg | grep i2s只有一行“i2s: probe deferred”,连报错都懒得给你——这恰恰说明问题卡在设备树的时序依赖或引脚复用冲突上,而不是驱动没加载。
我见过太多人把RK3568的I2S调试当成“配个设备树就能跑”的简单任务:抄一段网上的.dts片段,改改compatible字符串,编译烧录,发现/dev/snd/下空空如也,然后开始疯狂查ALSA文档、翻内核日志、怀疑自己是不是没开CONFIG_SND_SOC_RK3568_I2S。其实根本不是驱动没编进内核——RK3568的I2S驱动是作为CONFIG_SND_SOC_ROCKCHIP_I2S编译进内核的,它甚至不依赖模块加载,只要设备树正确,启动阶段就会自动probe。真正卡住你的,往往是三类隐形陷阱:引脚复用冲突(比如I2S0_MCLK被GPIO7_A0占用了)、时钟链配置断裂(I2S_CLK_ROOT没挂到ACLK_I2S0上)、以及Codec设备节点与I2S控制器之间的phandle引用错位。这些细节在官方SDK文档里要么一笔带过,要么藏在几十页PDF的附录里,而社区里流传的示例又普遍省略了关键注释,导致新手照着抄却始终无法点亮。
所以这篇内容不是教你“怎么让ALSA列出设备”,而是带你从寄存器级信号走向用户空间声卡设备的完整链路:从RK3568 TRM手册里I2S模块的时钟域图开始,到设备树中pinctrl、clocks、sound三个节点如何咬合,再到rockchip_i2s_probe()函数里实际做了哪些校验,最后落到aplay命令背后ALSA是如何通过snd_soc_dai_link匹配到物理I2S通道的。你不需要背诵所有寄存器地址,但必须清楚i2s0_mclk这个时钟信号到底由谁提供、走哪条路径、频率是否满足Codec要求;你也不必手写整个设备树,但得知道删掉哪一行<&cru CLK_I2S0_MCLK>,dmesg里就会多出一条“failed to get mclk clock”的致命错误。这种理解,才是调试RK3568 I2S的底层能力——它不依赖某个特定Codec型号,而是让你面对任何I2S外设(WM8960、ES8388、AC108、甚至自定义FPGA音频接口)都能快速定位问题根源。如果你正在用正点原子RK3568开发板接OV5695摄像头,顺便想加一路麦克风输入;或者在适配EtherCAT主站时需要同步采集振动传感器的I2S数据流;又或者正为RK3568 Ubuntu镜像里的音频播放无声而抓狂——那这篇内容就是为你写的。它不讲虚的,只拆解真实开发中每一步“为什么必须这样写”。
2. RK3568 I2S硬件架构与ALSA驱动模型深度解耦
要真正搞懂RK3568的I2S调试,必须先跳出“设备树配好就能用”的思维定式,回到硬件与驱动的原始契约关系上。RK3568的I2S模块不是孤立存在的,它嵌套在Rockchip自研的SoC级音频子系统中,这个子系统包含三个核心层级:时钟管理单元(CRU)、引脚复用控制器(GRF/PINCTRL)和I2S数字音频控制器本身。它们共同构成ALSA Soc层驱动的硬件基础,而设备树的作用,就是把这三层的配置参数以标准格式传递给内核。
2.1 硬件信号链:从I2S控制器到Codec引脚的物理通路
RK3568的I2S0控制器对外暴露5根关键信号线:I2S0_SCLK(位时钟)、I2S0_LRCK(帧同步/左右声道选择)、I2S0_SD(串行数据输出)、I2S0_SDI(串行数据输入)和I2S0_MCLK(主时钟)。注意,这里的MCLK不是可有可无的辅助时钟——对于绝大多数Codec(如WM8960、ES8388),它必须稳定提供256×Fs(Fs为采样率)的基准频率,否则Codec内部PLL无法锁定,直接导致无声音或爆音。而RK3568的I2S0_MCLK信号,并非由I2S控制器自身生成,而是由CRU(Clock and Reset Unit)中的专用音频时钟分频器提供。具体路径是:ACLK_I2S0(I2S总线时钟)→I2S0_CLK_ROOT(I2S0根时钟)→I2S0_MCLK(最终输出到Codec的主时钟)。这意味着你在设备树里配置clocks = <&cru CLK_I2S0_MCLK>时,实际是在告诉内核:“请从CRU的CLK_I2S0_MCLK门控时钟源获取时钟”,而这个时钟源的频率,由CRU寄存器CRU_CLKGATE_CON21和CRU_CLKSEL_CON42共同决定。如果设备树里漏掉了clocks属性,或者CLK_I2S0_MCLK在CRU初始化时被禁用(比如CONFIG_ROCKCHIP_CLK_DISABLE_I2S0_MCLK=y),那么I2S控制器probe阶段就会因无法获取MCLK而失败,dmesg里只会显示“failed to get mclk clock”,连I2S寄存器都读不到。
再看引脚复用。RK3568的I2S0信号默认复用在GPIO0_B0~B4这5个引脚上(具体对应关系见TRM Table 10-1),但这些引脚同时也是SPI1、UART3、I2C3的备选功能。如果你的设备树里pinctrl节点没有明确将这5个引脚配置为I2S0_FUNC,或者配置了错误的pull/drive参数(比如bias-pull-down用于I2S0_LRCK会导致高电平无法拉起),那么即使I2S控制器工作正常,信号也无法有效驱动Codec。更隐蔽的是,某些开发板(如正点原子RK3568)为了节省PCB布线,会把I2S0_MCLK和I2S0_SCLK共用同一个物理引脚(通过内部mux切换),这时设备树里就必须同时配置I2S0_MCLK和I2S0_SCLK的pin function,否则MCLK信号根本不会输出。
2.2 ALSA Soc驱动模型:RK3568如何把硬件抽象成声卡
Linux内核的ALSA Soc子系统采用经典的Machine-Dai-Codec三层架构。RK3568的I2S驱动实现,正是这一模型的典型应用:
Dai(Digital Audio Interface)层:由
rockchip_i2s.c驱动实现,负责操作I2S控制器寄存器,控制SCLK/LRCK/SDI/SDO的启停、格式(I2S/LEFT_JUSTIFIED/RIGHT_JUSTIFIED)、位宽(16/24/32bit)、采样率等。它不关心Codec具体型号,只提供标准的struct snd_soc_dai_ops接口。Codec层:由具体Codec驱动实现(如
wm8960.c、es8388.c),负责配置Codec内部寄存器,设置增益、输入源、DAC/ADC使能等。它通过I2C或SPI与SoC通信,与Dai层通过struct snd_soc_dai_link关联。Machine层(板级适配层):这是RK3568设备树的核心战场。它不写C代码,而是通过
sound节点下的dai-link子节点,将Dai(rockchip,i2s0)和Codec(&codec)逻辑绑定。关键在于dai-link里的cpu-dai和codec-dai属性,它们不是字符串,而是指向设备树中对应节点的phandle引用。例如:sound { compatible = "rockchip,rk3568-evb-sound"; rockchip,codecs = <&codec>; rockchip,i2s-controller = <&i2s0>; // ... 其他属性 };这段代码的实际作用,是让ALSA core在probe时,根据
rockchip,i2s-controller找到&i2s0节点,再根据rockchip,codecs找到&codec节点,最后构建一个snd_soc_dai_link结构体,其中cpu_dai_name被设为"i2s0",codec_dai_name被设为"wm8960-hifi"(由Codec驱动的name字段决定)。只有当cpu_dai_name和codec_dai_name完全匹配时,ALSA才会将Dai和Codec连接起来,形成一个完整的声卡(snd_card)。
因此,设备树里最常犯的错误,不是compatible写错,而是phandle引用断裂:比如&codec指向了一个不存在的节点,或者Codec节点里漏写了#sound-dai-cells = <0>,导致ALSA无法识别该节点为Codec Dai。此时dmesg里会出现“no backend dai link”或“failed to find codec dai”的提示,比“probe deferred”更明确,但也更难排查——因为错误发生在Machine层解析阶段,而非Dai或Codec驱动的probe函数里。
2.3 设备树配置的三大支柱:Pinctrl、Clocks与Sound节点的协同逻辑
RK3568的I2S设备树配置,绝不是零散的几个节点拼凑,而是一个强耦合的三角关系。我把它们称为Pinctrl-Clocks-Sound铁三角,缺一不可:
Pinctrl节点:定义I2S信号引脚的电气属性。必须包含
pins(引脚列表)、function(复用功能)、bias-pull-up/down(上下拉)、drive-strength(驱动强度)四个要素。例如I2S0的LRCK引脚,若Codec要求高电平有效,则必须配置bias-pull-up,否则启动时LRCK为浮空状态,Codec无法同步。Clocks节点:声明I2S控制器所需的全部时钟源。RK3568 I2S至少需要3个时钟:
CLK_I2S0(总线时钟)、CLK_I2S0_MCLK(主时钟)、CLK_I2S0_SCLK(位时钟)。其中CLK_I2S0_MCLK的频率必须精确匹配Codec需求(如WM8960要求256×44100=11.2896MHz),而这个频率由CRU的分频系数决定,设备树里通过clock-frequency属性传递给驱动,驱动再写入CRU寄存器。Sound节点:作为Machine层入口,协调Dai与Codec。它必须包含
rockchip,i2s-controller(指向I2S控制器节点)、rockchip,codecs(指向Codec节点)、rockchip,card-name(声卡名称)三个核心属性。此外,status = "okay"必须显式设置,否则整个sound节点会被内核忽略。
这三个节点之间存在严格的依赖顺序:Pinctrl必须在I2S控制器probe前生效,Clocks必须在Pinctrl之后、I2S probe之前被clock framework解析,Sound节点则必须在I2S和Codec都probe成功后才能完成link建立。设备树编译器(dtc)会按节点顺序处理,但内核driver core的probe顺序由of_platform_populate()决定,它遵循设备树节点的父子关系。因此,I2S控制器节点(&i2s0)必须是&pinctrl的子节点或同级节点,且&sound节点必须在&i2s0和&codec节点之后定义,否则phandle引用会失效。
提示:当你修改设备树后
dmesg出现“probe deferred”,第一反应不应该是驱动有问题,而是检查&i2s0节点是否被status = "disabled"禁用,或者&codec节点是否缺少#sound-dai-cells = <0>。这两个配置错误会导致内核认为相关设备尚未ready,从而推迟sound节点的probe。
3. 手把手实操:从零构建RK3568 I2S设备树(以WM8960为例)
现在我们进入实操环节。假设你使用正点原子RK3568开发板,外接WM8960 Codec(I2C地址0x1a),目标是让aplay能播放WAV文件。整个过程分为四步:确认硬件连接 → 编写I2S控制器节点 → 编写Codec节点 → 编写Sound Machine节点。每一步我都给出可直接复制的代码,并解释每一行的必要性。
3.1 硬件连接确认:别让物理接线成为第一个拦路虎
在动代码前,务必用万用表确认以下连接(以正点原子板为例):
I2S0_SCLK→ WM8960BCLK(引脚17)I2S0_LRCK→ WM8960DACLRC(引脚18)和ADCLRC(引脚19),需短接I2S0_SD→ WM8960DIN(引脚20)I2S0_SDI→ WM8960DOUT(引脚21)I2S0_MCLK→ WM8960MCLK(引脚1)
特别注意I2S0_MCLK:RK3568的I2S0_MCLK输出频率范围是1MHz~50MHz,而WM8960要求MCLK=256×Fs。对于44.1kHz采样率,MCLK需为11.2896MHz;对于48kHz,需为12.288MHz。正点原子板的WM8960原理图显示其MCLK由RK3568的GPIO0_B2引出,该引脚在TRM中对应I2S0_MCLK功能,无需额外跳线。
注意:WM8960的
MODE引脚(引脚15)必须接地(GND),否则进入Slave模式,无法响应I2S控制器的SCLK/LRCK。很多初学者忘记这一步,导致Codec无响应,误以为是软件问题。
3.2 I2S控制器节点编写:激活硬件并配置时钟
在rk3568.dtsi中找到&i2s0节点,将其修改为:
&i2s0 { status = "okay"; #sound-dai-cells = <0>; clocks = <&cru CLK_I2S0>, <&cru CLK_I2S0_MCLK>, <&cru CLK_I2S0_SCLK>; clock-names = "i2s", "mclk", "sclk"; rockchip,grf = <&grf>; pinctrl-names = "default"; pinctrl-0 = <&i2s0_sclk>, <&i2s0_lrck>, <&i2s0_sd>, <&i2s0_sdi>, <&i2s0_mclk>; };逐行解析:
status = "okay":强制启用I2S0控制器。默认状态下,许多SDK的&i2s0是"disabled",这是为了节省功耗,但调试时必须打开。#sound-dai-cells = <0>:声明该节点为Dai设备,且不需要额外参数(<0>表示无cell-index)。这是ALSA core识别Dai节点的关键标志,漏掉它,&i2s0就不会被注册为Dai。clocks和clock-names:声明I2S0所需的三个时钟源。CLK_I2S0是总线时钟,CLK_I2S0_MCLK是主时钟,CLK_I2S0_SCLK是位时钟。clock-names必须与驱动代码中clk_get(dev, name)的name参数严格一致,否则clk_get返回NULL,probe失败。rockchip,grf = <&grf>:指定GRF(General Register Files)寄存器基地址,用于配置引脚复用。RK3568的GRF地址为0xff770000,&grf节点已在rk3568.dtsi中定义。pinctrl-*:关联引脚配置节点。这里列出了5个独立的pinctrl子节点,每个对应一根I2S信号线,确保引脚复用和电气属性正确。
接下来,在pinctrl节点下添加具体的引脚配置:
&pinctrl { i2s0_sclk: i2s0-sclk { rockchip,pins = <0 RK_PA0 3 &pcfg_pull_none>; rockchip,drive-strength = <8>; }; i2s0_lrck: i2s0-lrck { rockchip,pins = <0 RK_PA1 3 &pcfg_pull_up>; rockchip,drive-strength = <8>; }; i2s0_sd: i2s0-sd { rockchip,pins = <0 RK_PA2 3 &pcfg_pull_none>; rockchip,drive-strength = <8>; }; i2s0_sdi: i2s0-sdi { rockchip,pins = <0 RK_PA3 3 &pcfg_pull_none>; rockchip,drive-strength = <8>; }; i2s0_mclk: i2s0-mclk { rockchip,pins = <0 RK_PA4 3 &pcfg_pull_none>; rockchip,drive-strength = <12>; }; };关键点:
rockchip,pins = <0 RK_PA0 3 &pcfg_pull_none>:0表示GPIO0 bank,RK_PA0是引脚编号(对应TRM中的GPIO0_A0),3是复用功能值(3=I2S0_SCLK),&pcfg_pull_none表示无上下拉。WM8960的BCLK是输入信号,应避免外部上下拉干扰。rockchip,drive-strength = <8>:驱动强度设为8mA,足够驱动WM8960的CMOS输入。<12>用于MCLK,因其需驱动Codec内部PLL,电流需求更大。&pcfg_pull_up用于I2S0_LRCK:WM8960的DACLRC/ADCLRC要求高电平有效,上拉确保启动时LRCK为高,避免Codec进入未知状态。
3.3 Codec节点编写:让内核认识你的WM8960
在rk3568-evb.dts(开发板主dts文件)中添加WM8960节点:
&i2c2 { status = "okay"; #address-cells = <1>; #size-cells = <0>; wm8960: wm8960@1a { compatible = "wlf,wm8960"; reg = <0x1a>; #sound-dai-cells = <0>; clocks = <&cru CLK_I2S0_MCLK>; clock-names = "mclk"; wlf,shared-lrclk; wlf,sysclk-freq = <11289600>; /* 音频路径配置 */ audio-routing = "Headphone Jack", "HPOUTL", "Headphone Jack", "HPOUTR", "MICIN", "Mic Bias", "Mic Bias", "MIC1"; }; };逐行解析:
&i2c2:WM8960通过I2C2总线连接,地址0x1a。确保&i2c2节点status = "okay",否则I2C通信失败。compatible = "wlf,wm8960":匹配内核中的wm8960.c驱动。wlf是Wolfson Microelectronics的厂商缩写,必须小写且无空格。reg = <0x1a>:I2C设备地址,十六进制,必须与硬件焊接的ADDR引脚状态一致(WM8960 ADDR接地为0x1a)。#sound-dai-cells = <0>:同I2S控制器,声明此节点为Codec Dai。clocks和clock-names:WM8960需要MCLK作为基准,此处引用&cru CLK_I2S0_MCLK,与I2S控制器共享同一时钟源,保证相位同步。wlf,sysclk-freq = <11289600>:告知WM8960驱动MCLK频率为11.2896MHz,驱动据此计算内部PLL分频系数。若此值与实际MCLK偏差超过±1%,Codec可能无法锁定。audio-routing:定义板载音频路径。"Headphone Jack", "HPOUTL"表示耳机左声道输出连接到HPOUTL引脚。此配置影响ALSA mixer控件的默认映射,避免播放时无声。
3.4 Sound Machine节点编写:完成Dai与Codec的终极绑定
最后,在rk3568-evb.dts末尾添加Sound节点:
&sound { status = "okay"; compatible = "rockchip,rk3568-evb-sound"; rockchip,codecs = <&wm8960>; rockchip,i2s-controller = <&i2s0>; rockchip,card-name = "rockchip-i2s-wm8960"; /* 声卡属性 */ simple-audio-card,format = "i2s"; simple-audio-card,bit-format = "s16_le"; simple-audio-card,sampling-rate = <44100>; simple-audio-card,widgets = "Headphone", "Headphone Jack", "Microphone", "Mic Jack"; simple-audio-card,routing = "Headphone Jack", "HPOUTL", "Headphone Jack", "HPOUTR", "Mic Jack", "MIC1"; };逐行解析:
rockchip,codecs = <&wm8960>:phandle引用,指向WM8960节点。这是Machine层建立Dai-Codec link的起点。rockchip,i2s-controller = <&i2s0>:phandle引用,指向I2S0控制器节点。ALSA core据此找到CPU Dai。simple-audio-card,*属性:simple-audio-card是Rockchip推荐的Machine驱动,它自动处理Dai-Codec link,无需编写C代码。format = "i2s"指定I2S协议格式,bit-format = "s16_le"指定16位有符号小端格式,sampling-rate = <44100>设定默认采样率。simple-audio-card,widgets和routing:定义声卡的mixer控件和音频路径。"Headphone", "Headphone Jack"创建名为“Headphone”的控件,"Headphone Jack", "HPOUTL"将其映射到HPOUTL引脚。这样amixer cset name="Headphone Playback Volume" 80就能控制音量。
编译并烧录后,执行:
dmesg | grep -i "i2s\|wm8960\|sound" # 应看到:rockchip-i2s-wm8960 sound: ASoC: CODEC wm8960-1.0 is not ready # 然后:rockchip-i2s-wm8960 sound: ASoC: CODEC wm8960-1.0 is ready # 最后:rockchip-i2s-wm8960 sound: ASoC: wm8960-hifi <-> i2s0 mapping ok aplay -l # 应输出: # card 0: rockchipi2swm8960 [rockchip-i2s-wm8960], device 0: wm8960-hifi-0 [] # Subdevices: 1/1 # Subdevice #0: subdevice #0如果aplay -l看不到设备,按以下顺序排查:
dmesg | grep "probe deferred"—— 检查I2S或Codec节点是否被disable;cat /sys/kernel/debug/pinctrl/gpio0/pinmux-pins | grep "PA0\|PA1"—— 确认引脚复用状态是否为i2s0_sclk;cat /sys/kernel/debug/clk/clk_i2s0_mclk/clk_rate—— 确认MCLK频率是否为11289600;i2cdetect -y 2—— 确认I2C2总线上能否检测到0x1a地址。
4. ALIAS设备树配置详解:从原理到避坑的21个关键细节
设备树(Device Tree)对RK3568 I2S调试而言,远不止是“填几个字符串”的配置文件,它是硬件资源、时钟拓扑、引脚约束和驱动行为的统一契约声明。一个看似微小的语法错误,比如多了一个逗号、少了一个尖括号,都可能导致内核在解析时静默失败,dmesg里连错误提示都没有。下面我结合多年实战经验,梳理出21个ALSA设备树配置中最易踩坑、也最关键的细节,每个都附带原理说明和实测验证方法。
4.1 Pinctrl配置的7个生死线
引脚复用值(function value)必须与TRM严格一致:RK3568的
RK_PA0引脚,复用为I2S0_SCLK的功能值是3,不是0或1。这个值由GRF寄存器GRF_GPIO0A_IOMUX的bit[1:0]控制,00=GPIO,01=SPI1,10=UART3,11=I2S0。设备树里<0 RK_PA0 3>中的3就是11的十进制。如果写成2,引脚会配置为UART3,I2S信号根本不会输出。&pcfg_pull_none不等于“不配置上下拉”:&pcfg_pull_none是Rockchip预定义的宏,对应GRF寄存器GRF_GPIO0A_P的bit[15:14]=00(无上下拉)。如果误用&pcfg_pull_down(bit[15:14]=01),I2S0_LRCK会被拉低,WM8960无法识别帧同步,aplay会报“-5 Cannot allocate memory”(DMA buffer allocation failed)。驱动强度(drive-strength)必须匹配Codec输入阻抗:WM8960的BCLK输入阻抗为100kΩ,要求驱动电流≥2mA。
rockchip,drive-strength = <4>仅提供4mA,勉强可用;<8>提供8mA,更稳妥;<12>用于MCLK,因PLL输入电容较大,需更强驱动。实测<2>会导致BCLK波形畸变,aplay播放时断续。多个信号线必须分属不同pinctrl子节点:不能把
I2S0_SCLK和I2S0_LRCK写在一个pinctrl-0里,如pinctrl-0 = <&i2s0_pins>。RK3568的I2S引脚分布在不同bank(PA0~PA4),而&i2s0_pins若只定义PA0,PA1的配置就丢失。必须为每根线单独定义pinctrl节点,再在&i2s0中逐一引用。status = "okay"必须作用于I2S控制器节点,而非pinctrl节点:&pinctrl节点本身没有status属性,它的启用由引用它的父节点(如&i2s0)决定。如果只在&i2s0里写status = "okay",pinctrl会自动生效;但如果在&pinctrl里也写status = "okay",dtc编译会警告“status property ignored”。rockchip,grf = <&grf>不可省略:&grf节点定义了GRF寄存器的基地址(0xff770000)和大小。没有它,rockchip_i2s.c驱动在调用rockchip_pinctrl_set_state()时,无法找到GRF寄存器,probe会因ioremap失败而退出。pinctrl-names必须为"default":ALSA Soc驱动在soc_probe_dai()中硬编码查找pinctrl-names = "default"。如果写成"i2s"或"audio",驱动会找不到pinctrl,dmesg显示“no pinctrl state 'default'”。
4.2 Clocks配置的6个隐性陷阱
clocks属性中的时钟ID必须存在于rockchip,rk3568-cru.h头文件中:CLK_I2S0_MCLK是合法ID,但CLK_I2S0_MCLK_ROOT不是。后者是CRU内部寄存器名,设备树里只能用SDK定义的宏。错误ID会导致clk_get返回NULL,probe失败。clock-names必须与驱动代码中的clk_get(dev, name)参数完全一致:rockchip_i2s.c里有priv->mclk = devm_clk_get(&pdev->dev, "mclk");,所以clock-names = "mclk"。如果写成"mclk-clock",devm_clk_get返回ERR_PTR(-ENOENT),probe直接return。clock-frequency属性只对Codec节点有效,I2S控制器节点无效:&i2s0节点里写clock-frequency = <11289600>是无效的,因为I2S控制器不关心MCLK频率,它只输出SCLK/LRCK。这个属性必须写在Codec节点(如&wm8960)里,供Codec驱动计算PLL。MCLK频率必须是采样率的整数倍,且在Codec规格范围内:WM8960支持MCLK=256×Fs、384×Fs、512×Fs。若
simple-audio-card,sampling-rate = <48000>,则MCLK必须为12.288MHz、18.432MHz或24.576MHz。11.2896MHz(256×44.1k)用于44.1k采样率,混用会导致Codec PLL失锁,无声。&cru节点必须在设备树中正确定义:&cru节点包含reg = <0xff760000 0x1000>(CRU寄存器基地址),如果SDK版本更新导致地址变更(如RK3566为0xff770000),而设备树未同步,clk_get会因ioremap失败而返回NULL。clocks属性中的phandle引用必须指向已定义的clock节点:<&cru CLK_I2S0_MCLK>中的&cru必须是设备树中已存在的节点标签。如果&cru节点被注释掉或拼写错误(如&crus),dtc编译会报错“Reference to non-existent node or label”。
4.3 Sound节点的8个致命误区
rockchip,codecs和rockchip,i2s-controller必须是phandle,不能是字符串:rockchip,codecs = "wm8960"是错误的,必须是<&wm8960>。前者会被内核当作字符串忽略,后者才是有效的设备树引用。#sound-dai-cells = <0>必须同时出现在I2S控制器和Codec节点中:缺一不可。I2S节点缺它,ALSA不认为这是Dai;Codec节点缺它,ALSA不认为这是Codec Dai。两者都缺,dmesg里连“no backend dai link”都不会打印,因为根本没触发link建立流程。simple-audio-card,format必须与Codec支持的格式匹配:WM8960支持I2S、LEFT_JUSTIFIED、DSP