Rockchip RK3399 - ALC5651音频调试
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux :6.3
----------------------------------------------------------------------------------------------------------------------------
一、内核配置
1.1 配置内核
修改sound/soc/codecs/Kconfig文件,搜索config SND_SOC_RT5651,将该项修
config SND_SOC_RT5651 tristate depends on I2C
改为,如果不修改的话,make menuconfig是看不到该配置项的;
config SND_SOC_RT5651 tristate "Realtek ALC5651 CODEC" depends on I2C
在linux内核根目录下执行make menuconfig配置以下选项:
Device Drivers ---> <*> Sound card support ---> <*> Advanced Linux Sound Architecture --->
<*> Sequencer support <*> ALSA for SoC audio support ---> <*> ASoC support for Rockchip {*} Rockchip I2S Device Driver CODEC drivers ---> <*> Realtek RT5651 CODEC <*> ASoC Simple sound card support
至于为啥配这些,可以看下面的介绍。
1.1.1 支持simple-audio-card驱动
要想将sound/soc/generic/simple-card.c文件对应的驱动编译到内核,我们需要配置CONFIG_SND_SIMPLE_CARD,我们定位到sound/soc/generic/Makefile文件;
# SPDX-License-Identifier: GPL-3.0 snd-soc-simple-card-utils-objs := simple-card-utils.o snd-soc-simple-card-objs := simple-card.o snd-soc-audio-graph-card-objs := audio-graph-card.o obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o obj-$(CONFIG_SND_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o
这里定义了snd-soc-simple-card.o的生成规则:
snd-soc-simple-card-objs := simple-card.o
它表示将当前目录下的 simple-card.o文件编译成一个snd-soc-simple-card.o。我们可以查看sound/soc/generic/.snd-soc-simple-card.o.cmd文件找到编译命令:
root@zhengyang:/work/sambashare/rk3399/linux-5.3.8# cat sound/soc/generic/.snd-soc-simple-card.o.cmd cmd_sound/soc/generic/snd-soc-simple-card.o := arm-linux-ld -EL -maarch64elf -r -o sound/soc/generic/snd-soc-simple-card.o sound/soc/generic/simple-card.o
1.1.2 支持I2S驱动
要想将sound/soc/rockchip/rockchip_i2s.c文件对应的驱动编译到内核,我们需要配置CONFIG_SND_SOC_ROCKCHIP_I2S,我们定位到sound/soc/rockchip/Makefile文件;
# SPDX-License-Identifier: GPL-3.0 # ROCKCHIP Platform Support snd-soc-rockchip-i2s-objs := rockchip_i2s.o snd-soc-rockchip-pcm-objs := rockchip_pcm.o snd-soc-rockchip-pdm-objs := rockchip_pdm.o snd-soc-rockchip-spdif-objs := rockchip_spdif.o obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S) += snd-soc-rockchip-i2s.o snd-soc-rockchip-pcm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_PDM) += snd-soc-rockchip-pdm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_SPDIF) += snd-soc-rockchip-spdif.o
这里定义了snd-soc-rockchip-i2s.o的生成规则:
snd-soc-rockchip-i2s-objs := rockchip_i2s.o
它表示将当前目录下的rockchip_i2s.o文件编译成一个snd-soc-rockchip-i2s.o。
1.1.3 支持rt5651驱动
要想将sound/soc/codecs/rt5651.c文件对应的驱动编译到内核,我们需要配置CONFIG_SND_SOC_RT5651,我们定位到sound/soc/codecs/文件;
snd-soc-rt5651-objs := rt5651.o
obj-$(CONFIG_SND_SOC_RT5651) += snd-soc-rt5651.o
这里定义了snd-soc-rt5651.o的生成规则:
snd-soc-rt5651-objs := rt5651.o
它表示将当前目录下的rt5651.o文件编译成一个snd-soc-rt5651.o。
1.1.4 支持seq
在 sound/core/Makefile文件中,如果配置CONFIG_SND_SEQUENCER,将seq编译到内核,这个文件作用后面有时间再研究;
obj-$(CONFIG_SND_SEQUENCER) += seq/
1.2 保存配置
配置完内核之后记得保存配置:
存档:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# mv rk3399_defconfig ./arch/arm64/configs/
重新配置内核:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# make rk3399_defconfig
1.3 编译内核
在linux内核根目录下执行如下命令进行编译内核:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# make -j8
u-boot-2023.04路径下的mkimage工具拷贝过来,然后在命令行使用mkimage工具编译即可:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp ../u-boot-2023.04/tools/mkimage ./ root@zhengyang:/work/sambashare/rk3399/linux-6.3# ./mkimage -f kernel.its kernel.itb
1.4 通过tftp烧录内核
给开发板上电,同时连接上网线,进入uboot命令行。我们将内核拷贝到tftp文件目录:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# cp kernel.itb /work/tftpboot/
接着给开发板上电。通过uboot命令行将kernel.itb下到内存地址0x10000000处:
=> tftp 0x10000000 kernel.itb
通过mmc write命令将内核镜像烧录到eMMC第0x8000个扇区处:
=> mmc erase 0x8000 0xA000 => mmc write 0x10000000 0x8000 0xA000
1.5 启动内核
我们重新启动开发板,如果声卡驱动正常加载,我们会看到类似下面的日志:
[ 4.150436] ff880000.i2s-rt5651-aif1 [ 4.150516] asoc-simple-card rt5651-sound: ASoC: binding ff880000.i2s-rt5651-aif1 [ 4.163124] asoc-simple-card rt5651-sound: ASoC: CPU DAI (null)registered [ 4.170806] asoc-simple-card rt5651-sound: ASoC: CODEC DAI rt5651-aif1 registered [ 4.179339] ---------------------asoc_simple_init_jack--------------,prefix:simple-audio-card, [ 4.179416] -----------------no error--------------- [ 4.189134] ---asoc_simple_init_jack----Headphone detection [ 4.194961] ---------------------asoc_simple_init_jack--------------,prefix:simple-audio-card, [ 4.201281] -----------------no error--------------- [ 4.218558] enable clock mclk i2s_runtime_resume -------- [ 4.227516] -------dai_link params null,dapm_connect_dai_pair [ 4.240244] asoc-simple-card rt5651-sound: connected DAI link ff880000.i2s:Playback -> rt5651.1-001a:AIF1 Playback [ 4.251913] dev ff880000.i2s [ 4.251923] asoc-simple-card rt5651-sound: connected DAI link rt5651.1-001a:AIF1 Capture -> ff880000.i2s:Capture [ 4.266750] dev 1-001a [ 4.270674] input: realtek,rt5651-codec Headphones as /devices/platform/rt5651-sound/sound/card0/input10 [ 4.287877] ALSA device list: [ 4.291280] #0: realtek,rt5651-codec
二、查看声卡设备
下载alsa工具:
root@rk3399:/# apt-get install alsa-base alsa-utils alsa-oss alsa-tools
2.1 查看音频设备节点
查看音频设备节点:
root@rk3399:/# ll /dev/snd total 0 drwxr-xr-x 2 root root 60 Jul 26 23:31 by-path/ crw-rw---- 1 root audio 116, 4 Jul 26 23:31 controlC0 crw-rw---- 1 root audio 116, 3 Jul 26 23:31 pcmC0D0c crw-rw---- 1 root audio 116, 2 Jul 26 23:31 pcmC0D0p crw-rw---- 1 root audio 116, 1 Jul 26 23:31 seq crw-rw---- 1 root audio 116, 33 Jul 26 23:31 timer
其中:
- controlC0:用于声卡的控制,例如通道选择,混音,麦克控制,音量加减,开关等;
- pcmC0D0c:用于录音的pcm设备;
- pcmC0D0p:用于播放的pcm设备;
- seq:音序器;
- timer:定时器;
C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。
从上面的列表可以看出,声卡0下下挂了3个设备,根据声卡的实际能力,驱动实际上可以挂载更多种类的设备,我们通常更关心的是pcm和control这两种设备,默认一个声卡对应一个Control设备。
2.2 查看所有声卡
查看所有声卡:
root@rk3399:/# cat /proc/asound/cards 0 [realtekrt5651co]: simple-card - realtek,rt5651-codec realtek,rt5651-codec
0为声卡编号,realtekrt5651co为ALSA声卡的唯一标识,因为struct snd_card id字段其长度为16,所以存放的就是realtek,rt5651-codec去除特殊字符之后的的前15个字符。
声卡分两种通道,一种是Capture、一种是Playback。Capture是输入通道,Playback是输出通道;我们以声卡0为例;
root@rk3399:/# ll /proc/asound/card0 -r--r--r-- 1 root root 0 Jul 23 16:02 id dr-xr-xr-x 4 root root 0 Jul 23 16:02 pcm0c/ dr-xr-xr-x 4 root root 0 Jul 23 16:02 pcm0p/ root@rk3399:/# cat /proc/asound/card0/id realtekrt5651co
pcm0p属于声卡0输出通道,pcm0c属于声卡0输入通道。
2.2.1 查看播放设备
root@rk3399:/# aplay -l **** List of PLAYBACK Hardware Devices **** card 0: realtekrt5651co [realtek,rt5651-codec], device 0: ff880000.i2s-rt5651-aif1 rt5651-aif1-0 [ff880000.i2s-rt5651-aif1 rt5651-aif1-0] Subdevices: 1/1 Subdevice #0: subdevice #0
这里看到的ff880000.i2s-rt5651-aif1 rt5651-aif1-0为音频数据链路的名称。
2.2.2 查看录音设备
root@rk3399:/# arecord -l **** List of CAPTURE Hardware Devices **** card 0: realtekrt5651co [realtek,rt5651-codec], device 0: ff880000.i2s-rt5651-aif1 rt5651-aif1-0 [ff880000.i2s-rt5651-aif1 rt5651-aif1-0] Subdevices: 1/1 Subdevice #0: subdevice #0
2.2.3 播放音频测试
找到一个wav音频文件,播放音频:
root@rk3399:/# cd / root@rk3399:/# aplay AbuduOffice.wav Playing WAVE 'AbuduOffice.mp3' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
插入耳机,如果我们发现耳机并没有声音,说明音频播放失败,我们后面会介绍排查方法。
2.2.4 录音测试
接下来使用arecord来录音,它有许多参数可供选择,常用的参数如下:
-
-d:录音的持续时间,以秒为单位;
-
-f:录音的格式,可以根据实际情况选择;
-
-c;声道数,可以选择单声道或立体声;
-
-t:标记类型,可以选择wav、ogg或其他类型;
-
-
-v:音量调整,可以根据实际情况选择。
例如:
arecord -d 10 -f cd -c 2 -t wav -r 44100 -v record_test.wav
以上命令表示将录制10秒,以CD格式,立体声,44100采样,输出文件为record_test.wav,按回车开始录音。录音完成后,使用Ctrl + C退出。
2.3 查看pcm设备列表
root@rk3399:/# cat /proc/asound/pcm 00-00: ff880000.i2s-rt5651-aif1 rt5651-aif1-0 : ff880000.i2s-rt5651-aif1 rt5651-aif1-0 : playback 1 : capture 1
2.4 查看asoc调试信息
查看/sys/kernel/debug/asoc文件夹:
root@rk3399:/# ll /sys/kernel/debug/asoc -r--r--r-- 1 root root 0 Jan 1 1970 components -r--r--r-- 1 root root 0 Jan 1 1970 dais drwxr-xr-x 6 root root 0 Jan 1 1970 realtek,rt5651-codec/
可以看到该目录下包含了component信息,dai信息,以及音频数据链路信息。
2.4.1 查看component
ASoC使用同一的数据结构来描述codec设备和platform设备,该数据结构就是snd_soc_component,此处输出的就是所有注册的component的名称;
root@rk3399:/# cat /sys/kernel/debug/asoc/components ff880000.i2s // 描述platform设备的component,这里为啥出现两次很奇怪 ff880000.i2s rt5651.1-001a // 描述codec设备的component snd-soc-dummy // 描述虚拟声卡设备的component 这里为啥出现两次很奇怪 snd-soc-dummy // 描述虚拟声卡设备的component
我们之前分析了codec驱动、以及platform驱动注册流程,其各注册了一个component,然而这里多出了snd-soc-dummy,这个实际上是在sound/soc/soc-core.c文件snd_soc_init函数注册的虚拟声卡设备。
2.4.2 查看dai
在注册component的同时会为每个snd_soc_dai_driver 分配一个snd_soc_dai,在codec驱动中会创建两个snd_soc_dai,dai名字分别为rt5651-aif1、rt5651-aif2;在platform驱动中会创建1个snd_soc_dai,dai名字为ff880000.i2s;
root@rk3399:/# cat /sys/kernel/debug/asoc/dais ff880000.i2s rt5651-aif1 rt5651-aif2 snd-soc-dummy-dai // 虚拟的dai
2.4.3 查看音频数据链路
在machine驱动中,注册了一个ASoC声卡,声卡名称为realtek,rt5651-codec;
root@rk3399:/# ll /sys/kernel/debug/asoc/realtek,rt5651-codec/ drwxr-xr-x 2 root root 0 Jan 1 1970 dapm/ -rw-r--r-- 1 root root 0 Jan 1 1970 dapm_pop_time drwxr-xr-x 3 root root 0 Jan 1 1970 dma:ff880000.i2s/ drwxr-xr-x 3 root root 0 Jan 1 1970 ff880000.i2s/ drwxr-xr-x 3 root root 0 Jan 1 1970 rt5651.1-001a/
查看platform驱动中定义的dapm widget:
root@rk3399:/# ll /sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/ drwxr-xr-x 2 root root 0 Jan 1 1970 dapm/ root@rk3399:/# ll /sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/dapm/ -r--r--r-- 1 root root 0 Jan 1 1970 Capture // 为cpu dai创建的capture widget -r--r--r-- 1 root root 0 Jan 1 1970 Playback // 为cpu dai创建的playback widget -r--r--r-- 1 root root 0 Jan 1 1970 bias_level
查看codec驱动中定义的dapm widget:
root@rk3399:/# ll /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/ drwxr-xr-x 2 root root 0 Jan 1 1970 dapm/ root@rk3399:/# ls /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/ 'ADC ASRC' 'DAC R1' 'I2S1 ASRC' INR2 'STO2 DAC ASRC' 'ADC L' 'DAC R1 Power' I2S2 'INR2 VOL' 'Stereo DAC MIXL' 'ADC L Power' 'DAC R2 Mux' 'I2S2 ASRC' LDO 'Stereo DAC MIXR' 'ADC R' 'DAC R2 Volume' 'IF1 ADC1' 'LOUT L Playback' 'Stereo1 ADC L1 Mux' 'ADC R Power' 'DD MIXL' 'IF1 ADC2' 'LOUT L Playback Switch Autodisable' 'Stereo1 ADC L2 Mux' 'AIF1 Capture' 'DD MIXR' 'IF1 DAC' 'LOUT MIX' 'Stereo1 ADC MIXL' 'AIF1 Playback' 'DMIC CLK' 'IF1 DAC1 L' 'LOUT R Playback' 'Stereo1 ADC MIXR' AIF1RX 'DMIC L1' 'IF1 DAC1 R' 'LOUT R Playback Switch Autodisable' 'Stereo1 ADC R1 Mux' AIF1TX 'DMIC R1' 'IF1 DAC2 L' LOUTL 'Stereo1 ADC R2 Mux' 'AIF2 Capture' 'HP Amp' 'IF1 DAC2 R' LOUTR 'Stereo1 Filter' 'AIF2 Playback' 'HP L Amp' 'IF2 ADC' MIC1 'Stereo2 ADC L1 Mux' AIF2RX 'HP Post' 'IF2 DAC' MIC2 'Stereo2 ADC L2 Mux' AIF2TX 'HP R Amp' 'IF2 DAC L' MIC3 'Stereo2 ADC MIXL' 'Amp Power' 'HPO L Playback' 'IF2 DAC R' 'OUT MIXL' 'Stereo2 ADC MIXR' 'Audio DSP' 'HPO L Playback Switch Autodisable' IN1P 'OUT MIXR' 'Stereo2 ADC R1 Mux' BST1 'HPO R Playback' IN2N 'OUTVOL L' 'Stereo2 ADC R2 Mux' BST2 'HPO R Playback Switch Autodisable' IN2P 'OUTVOL R' 'Stereo2 Filter' BST3 HPOL IN3P 'PDM L Mux' 'Stero1 DAC Power' 'DAC L1' 'HPOL MIX' INL1 'PDM R Mux' 'Stero2 DAC Power' 'DAC L1 Power' HPOR 'INL1 VOL' PDML bias_level 'DAC L2 Mux' 'HPOR MIX' INL2 PDMR micbias1 'DAC L2 Volume' 'HPOVOL L' 'INL2 VOL' RECMIXL 'DAC MIXL' 'HPOVOL R' INR1 RECMIXR 'DAC MIXR' I2S1 'INR1 VOL' 'STO1 DAC ASRC'
查看machine驱动中定义的dapm widget:
root@rk3399:/# ll /sys/kernel/debug/asoc/realtek,rt5651-codec/dapm total 0 drwxr-xr-x 2 root root 0 Jan 1 1970 ./ drwxr-xr-x 6 root root 0 Jan 1 1970 ../ -r--r--r-- 1 root root 0 Jan 1 1970 Headphones -r--r--r-- 1 root root 0 Jan 1 1970 'Mic Jack' -r--r--r-- 1 root root 0 Jan 1 1970 bias_level
(1)我们可以查看dapm widget的路由以及电源状态:比如查看为cpu dai创建的Playback widget;
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/dapm/Playback Playback: Off in 0 out 2 // 下电状态 stream Playback inactive // 非激活状态 out "static" "AIF1 Playback"
如果在音频播放的时候查看:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/dapm/Playback Playback: On in 1 out 10 // 上电状态 stream Playback active // 在播放的时候dai widget会被设置为激活状态,可以参考snd_soc_dapm_dai_stream_event方法 out "static" "AIF1 Playback"
(2)查看为codec dai创建的Playback widget;
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/"AIF1 Playback" AIF1 Playback: Off in 0 out 10 // 下电状态 stream AIF1 Playback inactive // 非激活状态 out "static" "AIF1RX" in "static" "Playback"
如果在音频播放的时候查看:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/"AIF1 Playback" AIF1 Playback: On in 1 out 10 // 上电状态 stream AIF1 Playback active // 在播放的时候dai widget会被设置为激活状态,可以参考snd_soc_dapm_dai_stream_event方法 out "static" "AIF1RX" in "static" "Playback"
(3)查看codec中注册的名字为"DAC MIXL"的widget:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/'DAC MIXL' DAC MIXL: Off in 0 out 1 // 下电状态 out "DD MIX" "PDM L Mux" out "DAC L1 Switch" "Stereo DAC MIXR" out "static" "Audio DSP" in "INF1 Switch" "IF1 DAC1 L"
如果在音频播放的时候查看:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/'DAC MIXL' DAC MIXL: On in 1 out 5 // 上电状态 out "DD MIX" "PDM L Mux" out "static" "Audio DSP" in "INF1 Switch" "IF1 DAC1 L"
(4)查看为cpu dai创建的Capture widget;
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/dapm/Capture Capture: Off in 8 out 0 // 下电状态 stream Capture inactive // 非激活状态 in "static" "AIF1 Capture"
如果在音频捕获的时候查看:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/ff880000.i2s/dapm/Capture Capture: On in 8 out 1 // 上电状态 stream Capture active // 在录音的时候dai widget会被设置为激活状态,可以参考snd_soc_dapm_dai_stream_event方法 in "static" "AIF1 Capture"
(5)查看为codec dai创建的Capture widget;
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/"AIF1 Capture" AIF1 Capture: Off in 8 out 0 // 下电状态 stream AIF1 Capture inactive // 非激活状态 out "static" "Capture" in "static" "AIF1TX"
如果在音频捕获的时候查看:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/"AIF1 Capture" AIF1 Capture: On in 8 out 1 // 上电状态 stream AIF1 Capture active // 在录音的时候dai widget会被设置为激活状态,可以参考snd_soc_dapm_dai_stream_event方法 out "static" "Capture" in "static" "AIF1TX"
(6)查看codec中注册的名字为"AIF1TX "的widget:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/AIF1TX AIF1TX: Off in 8 out 0 // 下电状态 stream AIF1 Capture inactive // 非激活状态 out "static" "AIF1 Capture" in "static" "IF1 ADC2" in "static" "IF1 ADC1"
如果在音频捕获的时候查看:
root@rk3399:/# cat /sys/kernel/debug/asoc/realtek,rt5651-codec/rt5651.1-001a/dapm/AIF1TX AIF1TX: On in 8 out 1 // 上电状态 stream AIF1 Capture inactive // 非激活状态 除了dai widget外的widget都是非激活状态 out "static" "AIF1 Capture" in "static" "IF1 ADC2" in "static" "IF1 ADC1"
(7)此外也可以通过以下命令查看每一个dapm widiget的状态:
root@rk3399:/# cat /sys/devices/platform/rt5651-sound/ff880000.i2s-rt5651-aif1/dapm_widget I2S1 ASRC: On I2S2 ASRC: Off STO1 DAC ASRC: On STO2 DAC ASRC: On ADC ASRC: Off LDO: Off micbias1: Off DMIC CLK: Off BST1: Off BST2: Off BST3: Off .....
三、查看寄存器
以下测试内容都是在音频可以正常播放/录音下查看到的寄存器的值,音频未播放,或者播放异常、或者音量、通路设置不同均会导致寄存器有差异,因此本节内容仅可以作为参考。
3.1 音频播放
3.1.1 查看ALC5651寄存器值
在音频播放时运行如下命令查看ALC5651寄存器值,1-001a中的1为I2C适配器的编号,即I2C1,001a为I2C1总线上的从设备地址;
root@rk3399:/# cat /sys/kernel/debug/regmap/1-001a/registers 000: 0000 002: 0808 003: c8c8 005: 0000 00d: 0200 00e: 0000 00f: 0808 010: 0808 019: 9a9a 01a: afaf 01b: 0c00 01c: 2f2f 01d: 2f2f 01e: 0000 027: 3820 ......
播放音频时查看ALC5651电源控制寄存器1的值:
root@rk3399:/# cat /sys/kernel/debug/regmap/1-001a/registers | grep 061 061: 9800 #1001 1000 0000 0000
每一位表示的意义如下,可以看到数字音频接口I2S1、模拟DACL1、,模拟DACR1均被上电;
播放音频时查看ALC5651 DACL1/R1 Digital Volume寄存器的值:
root@rk3399:/# cat /sys/kernel/debug/regmap/1-001a/registers | grep 019 019: afaf #0x1010 1111 1010 1111
每一位表示的意义如下,可以看到数字音频接口I2S1、模拟DACL1、,模拟DACR1均被上电;
3.1.2 查看RK3399 I2S0控制寄存器
在音频播放时运行如下命令查看I2S0控制器寄存器值:
root@rk3399:/# cat /sys/kernel/debug/regmap/ff880000.i2s/registers 00: 0000000f 04: 0000000f 08: 00033f3f 0c: 00000011 10: 000f0110 # 第8位写入0x01,Transmit DMA enabled 14: 01f00000 18: 00000000 1c: 00000003 # 第0位写入0x01,开始TX传输 20: 00000000 24: 00000000
名称 | 偏移 | 大小 | 复位值 | 描述 |
---|---|---|---|---|
I2S_TXCR | 0x0000 | W | 0x0000000f | 传输操作控制器寄存器 |
I2S_RXCR | 0x0004 | W | 0x0000000f | 接收操作控制器寄存器 |
I2S_CKR | 0x0008 | W | 0x00071f1f | 时钟发生寄存器 |
I2S_TXFIFOLR | 0x000C | W | 0x00000000 | TX FIFO level寄存器 |
I2S_DMACR | 0x0010 | W | 0x001f0000 | DMA控制寄存器 |
I2S_INTCR | 0x0014 | W | 0x00000000 | 中断控制寄存器 |
I2S_INTSR | 0x0018 | W | 0x00000000 | 中断状态寄存器 |
I2S_XFER | 0x001C | W | 0x00000000 | I2S数据传输开始寄存器 |
I2S_CLR | 0x0020 | W | 0x00000000 | SCLK域逻辑清除寄存器 |
I2S_TXDR | 0x0024 | W | 0x00000000 | 传输FIFO数据寄存器 |
I2S_RXDR | 0x0028 | W | 0x00000000 | 接收FIFO数据寄存器 |
I2S_RXFIFOLR | 0x002C | W | 0x00000000 | RX FIFO level寄存器 |
3.2 音频捕获
3.2.1 查看ALC5651寄存器值
在只有麦克风2进行录音时运行如下命令查看ALC5651寄存器值,1-001a中的1为I2C适配器的编号,即I2C1,001a为I2C1总线上的从设备地址;
root@rk3399:/# cat /sys/kernel/debug/regmap/1-001a/registers 000: 0000 002: 8989 003: c8c8 005: 0000 00d: 0000 00e: 0000 00f: 0808 010: 0808 019: a4a4 01a: adad 01b: 0400 01c: 2f2f 01d: 2f2f 01e: f000 027: 3820 028: 7070 029: 8080 02a: 1212 02b: 5454 02f: 0000 030: 5000 03b: 0000 03c: 006b 03d: 0000 03e: 006b 045: 4000 04d: 0000 04e: 0000 04f: 0278 050: 0000 051: 0000 052: 0278 053: f000 061: 8006 062: 8000 063: e81e 064: 4810 065: 0c00 066: 0000 06a: 00b4 06c: 0800 06d: 0000 070: 8000 071: 8000 073: 0104 074: 0c00 075: 1400 077: 0c00 078: 4000 079: 0123 080: 0000 081: 0000 082: 0000 083: 0800 084: 8800 085: 0008 086: 0000 087: 0000 089: 0000 08a: 0000 08e: 0005 08f: 1140 090: 0737 091: 0e00 ......
在只有麦克风3进行录音时运行如下命令查看ALC5651寄存器值,1-001a中的1为I2C适配器的编号,即I2C1,001a为I2C1总线上的从设备地址;
root@rk3399:/# cat /sys/kernel/debug/regmap/1-001a/registers 000: 0000 002: 8989 003: c8c8 005: 0000 00d: 0000 00e: 0000 00f: 0808 010: 0808 019: a4a4 01a: adad 01b: 0400 01c: 2f2f 01d: 2f2f 01e: f000 027: 3820 028: 7070 029: 8080 02a: 1212 02b: 5454 02f: 0000 030: 5000 03b: 0000 03c: 0067 03d: 0000 03e: 0067 045: 4000 04d: 0000 04e: 0000 04f: 0278 050: 0000 051: 0000 052: 0278 053: f000 061: 8006 062: 8000 063: e81e 064: 2808 065: 0c00 066: 0000 06a: 0024 06c: 0420 06d: 0000 070: 8000 071: 8000 073: 0104 074: 0c00 075: 1400 077: 0c00 078: 4000 079: 0123 080: 0000 081: 0000 082: 0000 083: 0800 084: 8800 085: 0008 086: 0000 087: 0000 089: 0000 08a: 0000 08e: 0005 08f: 1140 090: 0737 091: 0e00 ......
3.2.2 查看RK3399 I2S0控制寄存器
在录音时运行如下命令查看I2S0控制器寄存器值:
root@rk3399:/# cat /sys/kernel/debug/regmap/ff880000.i2s/registers 00: 0000000f 04: 0000000f 08: 00033f3f 0c: 00000000 10: 010f0010 14: 01f00000 18: 00000000 1c: 00000003 20: 00000000 24: 00000000
四、音频调试
4.1 amixer controls
通过amixer controls显示控制接口(这个对应的应该就是我们在声卡驱动中注册的kcontrol):
root@rk3399:/# amixer controls numid=1,iface=CARD,name='Headphones Jack' numid=13,iface=MIXER,name='Mono ADC Capture Volume' numid=6,iface=MIXER,name='Mono DAC Playback Volume' numid=14,iface=MIXER,name='ADC Boost Gain' numid=18,iface=MIXER,name='ADC IF2 Data Switch' numid=11,iface=MIXER,name='ADC Capture Switch' numid=12,iface=MIXER,name='ADC Capture Volume' numid=19,iface=MIXER,name='DAC IF2 Data Switch' numid=51,iface=MIXER,name='DAC L2 Mux' numid=48,iface=MIXER,name='DAC MIXL INF1 Switch' numid=47,iface=MIXER,name='DAC MIXL Stereo ADC Switch' numid=50,iface=MIXER,name='DAC MIXR INF1 Switch' numid=49,iface=MIXER,name='DAC MIXR Stereo ADC Switch' ....
比如我们以DAC MIXR INF1 Switch为例:
numid=50,iface=MIXER,name='DAC MIXR INF1 Switch'
其中INF1 Switch为kcontrol的名称,DAC MIXR为kcontrol所属widget的名称,iface=MIXER表示kcontrol的类型为Mixer、numid为kcontrol的编码;
// kcontrol SOC_DAPM_SINGLE("INF1 Switch", RT5651_AD_DA_MIXER, // 寄存器配置为RT5651_AD_DA_MIXER=0x29,偏移位配置为14 RT5651_M_IF1_DAC_L_SFT, 1, 1) // 输出端widget SND_SOC_DAPM_MIXER("DAC MIXL", SND_SOC_NOPM, 0, 0, // 寄存器设置为SND_SOC_NOPM,表示没有寄存器可以控制该widget的上下电 rt5651_dac_l_mix, ARRAY_SIZE(rt5651_dac_l_mix)); // Left DAC Mixer包含2个kcontrol,每个kcontrol控制着Mixer的一个输入端的开启和关闭 // route {"DAC MIXL", "INF1 Switch", "IF1 DAC1 L"}
4.2 音频播放路径设置
在Rockchip RK3399 - Codec驱动( Realtek ALC5651)我们分析了音频播放路径:
通过amixer contents查看所有的配置参数:
root@rk3399:/# amixer contents numid=1,iface=CARD,name='Headphones Jack' ; type=BOOLEAN,access=r-------,values=1 : values=on numid=13,iface=MIXER,name='Mono ADC Capture Volume' ; type=INTEGER,access=rw---R--,values=2,min=0,max=127,step=0 : values=47,47 | dBminmax-min=-17.62dB,max=30.00dB numid=6,iface=MIXER,name='Mono DAC Playback Volume' ; type=INTEGER,access=rw---R--,values=2,min=0,max=175,step=0 : values=175,175 | dBminmax-min=-65.62dB,max=0.00dB numid=14,iface=MIXER,name='ADC Boost Gain' ; type=INTEGER,access=rw---R--,values=2,min=0,max=3,step=0 : values=3,3 | dBscale-min=0.00dB,step=12.00dB,mute=0 numid=18,iface=MIXER,name='ADC IF2 Data Switch' ; type=ENUMERATED,access=rw------,values=1,items=4 ; Item #0 'Normal' ; Item #1 'Swap' ; Item #2 'left copy to right' ; Item #3 'right copy to left' : values=0 numid=11,iface=MIXER,name='ADC Capture Switch' ; type=BOOLEAN,access=rw------,values=2 : values=on,on numid=12,iface=MIXER,name='ADC Capture Volume' ; type=INTEGER,access=rw---R--,values=2,min=0,max=127,step=0 ......
以左声道为例,完整音频路径如下:
- Playback (位于platform驱动中,snd_soc_dapm_dai_in类型)--> AIF1 Playback(位于codec驱动中,snd_soc_dapm_dai_in类型的playback dai widget);
- AIF1 Playback(位于codec驱动中,snd_soc_dapm_dai_in类型的playback dai widget) --> AIF1RX :AIF表示音频数字接口;
- AIF1RX --> IF1 DAC;
- IF1 DAC --> IF1 DAC1 L;
- IF1 DAC1 L --> DAC MIXL:通过rt5651_dac_l_mix(名称为INF1 Switch)控制通断,由MX29寄存器位14来实现静音控制(0非静音,1静音);
- DAC MIXL --> Audio DSP;
- Audio DSP --> Stereo DAC MIXL:通过rt5651_sto_dac_l_mix(名称DAC L1 Switch)控制通断,由MX2A寄存器位14来实现静音控制(0非静音,1静音);
- Stereo DAC MIXL --> DAC L1;
- DAC L1 --> OUT MIXL :通过rt5651_out_l_mix(名称为DAC L1 Switch)控制通断,由MX4F寄存器位0来实现静音控制(0非静音,1静音);
- OUT MIXl --> HPOVOL l:通过hpovol_l_control(名称为Switch)控制通断,由MX02寄存器位14来实现静音控制(0非静音,1静音);
- HPOVOL L --> HPOL MIX:通过rt5651_hpo_mix(名称为HPO MIX HPVOL Switch)控制通断,由MX45寄存器位13来实现静音控制(0非静音,1静音);
- HPOL MIX --> HP Amp;
- HP Amp -> HPO L Playback:通过hpo_l_mute_control(名称为Switch)控制通断,由MX02寄存器位15来实现静音控制(0非静音,1静音);
- HPO L Playback --> HPOL ;
- HPOL --> Headphones(最后一个path定义在Machine驱动中,snd_soc_dapm_hp类型的widget);
我们需要设置该条路径上的kcontrol设置为连接状态;
4.2.1 DAC MIXL INF1 Switch
打开DAC MIXL INF1 Switch、DAC MIXR INF1 Switch:
root@rk3399:/# amixer cset numid=48 1 numid=48,iface=MIXER,name='DAC MIXL INF1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on root@rk3399:/# amixer cset numid=50 1 numid=50,iface=MIXER,name='DAC MIXR INF1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on
我们可以查看MX29寄存器位14,看看是不是已经设置为0(非静音);
root@rk3399:/# cat /sys/kernel/debug/regmap/1-001a/registers | grep 029 029: 8080 #1000 0000 1000 0000
如果有兴趣的话,我们可以通过关闭DAC MIXL INF1 Switch,查看MX29寄存器位14是不是已经设置为1(静音);
root@rk3399:/# amixer cset numid=48 0 numid=48,iface=MIXER,name='DAC MIXL INF1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# cat /sys/kernel/debug/regmap/1-001a/registers | grep 029 029: c080 root@rk3399:/# amixer cset numid=48 1 numid=48,iface=MIXER,name='DAC MIXL INF1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on
4.2.2 Stereo DAC MIXL DAC L1 Switc
打开Stereo DAC MIXL DAC L1 Switch、Stereo DAC MIXR DAC R1 Switch:
root@rk3399:/# amixer cset numid=53 1 numid=53,iface=MIXER,name='Stereo DAC MIXL DAC L1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on root@rk3399:/# amixer cset numid=56 1 numid=56,iface=MIXER,name='Stereo DAC MIXR DAC R1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on
4.2.3 OUT MIXL DAC L1 Switch
打开OUT MIXL DAC L1 Switch、OUT MIXR DAC R1 Switch:
root@rk3399:/# amixer cset numid=74 1 numid=74,iface=MIXER,name='OUT MIXR DAC R1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on root@rk3399:/# amixer cset numid=69 1 numid=69,iface=MIXER,name='OUT MIXL DAC L1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on
4.2.4 HPOVOL L Switch
打开HPOVOL L Switch、HPOVOL R Switch:
root@rk3399:/# amixer cset numid=77 1 numid=77,iface=MIXER,name='HPOVOL L Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on root@rk3399:/# amixer cset numid=78 1 numid=78,iface=MIXER,name='HPOVOL R Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on
4.2.5HPO MIX HPVOL Switch
打开HPO MIX HPVOL Switch:
root@rk3399:/# amixer cset numid=80 1 numid=80,iface=MIXER,name='HPO MIX HPVOL Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on
4.2.6 HPO L Playback Switch
打开HPO L Playback Switch、HPO R Playback Switch:
root@rk3399:/# amixer cset numid=85 1 numid=85,iface=MIXER,name='HPO L Playback Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on root@rk3399:/# amixer cset numid=86 1 numid=86,iface=MIXER,name='HPO R Playback Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on
4.2.7 总结
合并需要修改的配置项:
amixer cset numid=48 1 amixer cset numid=50 1 amixer cset numid=53 1 amixer cset numid=56 1 amixer cset numid=74 1 amixer cset numid=69 1 amixer cset numid=77 1 amixer cset numid=78 1 amixer cset numid=80 1 amixer cset numid=85 1 amixer cset numid=86 1 alsactl store
需要注意的是:如果声卡编号不是0,可以通过-c num指定声卡编号。
同时屏蔽以下可能已经开启的无效的通路,比如:
root@rk3399:/# amixer cget numid=58 numid=58,iface=MIXER,name='Stereo DAC MIXR DAC L1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on root@rk3399:/# amixer cset numid=58 0 numid=58,iface=MIXER,name='Stereo DAC MIXR DAC L1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# alsactl store
配置好之后执行: alsactl store 保存配置,配置会保存在/var/lib/alsa/asound.state。
4.3 音频捕获路径设置
4.3.1 麦克风2
像分析音频播放路径一样,我们可以得到完整麦克风2/线路输入音频捕获路径:
- Mic Jack(snd_soc_dapm_mic类型的widget) --> IN2P --> BST2/INR1 VOL --> RECMIXL(这是一个Mixer,通过BST2 Switch/INL1 Switch控制通断)--> ADC L --> Stereo1 ADC L1 Mux (通过ADC控制通断)--> Stereo1 ADC MIXL(通过ADC1 Switch控制通断)-->IF1 ADC1 --> AIF1TX --> AIF1 Capture(位于codec驱动中,snd_soc_dapm_dai_out类型的capture dai widget)--> Capture(位于platform驱动中,snd_soc_dapm_dai_out类型的capture dai widget) ;
- Mic Jack(snd_soc_dapm_mic类型的widget) -->IN2N --> BST2/INR1 VOL --> RECMIXR(这是一个Mixer,通过BST2 Switch/INR1 Switch控制通断)-->ADC R --> Stereo1 ADC R1 Mux(通过ADC控制通断)--> Stereo1 ADC MIXR(通过ADC1 Switch控制通断)-->IF1 ADC1 --> AIF1TX --> AIF1 Capture(位于codec驱动中,snd_soc_dapm_dai_out类型的capture dai widget) --> Capture(位于platform驱动中,snd_soc_dapm_dai_out类型的capture dai widget) ;
需要修改的配置项:
amixer cset numid=22 1 amixer cset numid=26 1 amixer cset numid=30 1 amixer cset numid=31 1 amixer cset numid=36 1 amixer cset numid=38 1 alsactl store
需要注意的是此时需要确保BST3->RECMIXL、BST3->RECMIXR、BST1->RECMIXL、BST1->RECMIXR、INL1 VOL->RECMIXL、INR1 VOL->RECMIXR是断开的,不要把几个麦克风的录音混在一起,不然会有很多噪音:
具体配置如下:
root@rk3399:/# amixer cset numid=21 0 numid=21,iface=MIXER,name='RECMIXL BST3 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=on root@rk3399:/# amixer cset numid=23 0 numid=23,iface=MIXER,name='RECMIXL BST1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=25 0 numid=25,iface=MIXER,name='RECMIXR BST3 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=27 0 numid=27,iface=MIXER,name='RECMIXR BST1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=20 0 numid=20,iface=MIXER,name='RECMIXL INL1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=24 0 numid=24,iface=MIXER,name='RECMIXR INR1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# alsactl store
简短配置:
amixer cset numid=21 0 amixer cset numid=23 0 amixer cset numid=25 0 amixer cset numid=27 0 amixer cset numid=20 0 amixer cset numid=24 0 alsactl store
4.3.2 麦克风3
像分析音频播放路径一样,我们可以得到完整麦克风3音频捕获路径:
-
-
Mic Jack(snd_soc_dapm_mic类型的widget) --> IN3P --> BST3 --> RECMIXR(这是一个Mixer,通过BST3 Switch控制通断)-->ADC R --> Stereo1 ADC R1 Mux(通过ADC控制通断)--> Stereo1 ADC MIXR(通过ADC1 Switch控制通断)-->IF1 ADC1 --> AIF1TX --> AIF1 Capture(位于codec驱动中,snd_soc_dapm_dai_out类型的capture dai widget)--> Capture(位于platform驱动中,snd_soc_dapm_dai_out类型的capture dai widget);
需要修改的配置项:
amixer cset numid=21 1 amixer cset numid=25 1 amixer cset numid=30 1 amixer cset numid=31 1 amixer cset numid=36 1 amixer cset numid=38 1 alsactl store
需要注意的是此时需要确保BST2->RECMIXL、BST2->RECMIXR、BST1->RECMIXL、BST1->RECMIXR、INL1 VOL->RECMIXL、INR1 VOL->RECMIXR是断开的,不要把几个麦克风的录音混在一起,不然会有很多噪音:
root@rk3399:/# amixer cset numid=22 0 numid=22,iface=MIXER,name='RECMIXL BST2 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=23 0 numid=23,iface=MIXER,name='RECMIXL BST1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=26 0 numid=26,iface=MIXER,name='RECMIXR BST2 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=27 0 numid=27,iface=MIXER,name='RECMIXR BST1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=20 0 numid=20,iface=MIXER,name='RECMIXL INL1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# amixer cset numid=24 0 numid=24,iface=MIXER,name='RECMIXR INR1 Switch' ; type=BOOLEAN,access=rw------,values=1 : values=off root@rk3399:/# alsactl store
五、耳机没有声音处理
找到一个wav音频文件,播放音频:
root@rk3399:/# cd / root@rk3399:/# aplay AbuduOffice.wav Playing WAVE 'AbuduOffice.mp3' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
插入耳机,如果我们发现耳机并没有声音,说明音频播放失败,下面我们介绍几种问题排查方法。
5.1 amixer工具排查
可以进入alsamixer,逐一审查各个配置是否设置正确,‘MM’表示静音(mute),按M键切换为非静音即可。
root@rk3399:/# alsamixer
进入可视化界面:
在播放音频的同时,调整耳机音量项:Item: HP [dB gain: -7.50, -7.50],看看会不会有声音输出,我这边调整音量的时候,会有噪声输出;
实际上通过可视化界面进行配置和使用amixer cset命令效果是一样的。
5.2 检查时钟
如果我们有示波器或者逻辑分析仪的话,可以检查RK3399 I2S0接口的MCLK信号线,看看有没有提供MCLK主时钟给音频芯片。
我后来尝试向开发板下载了rk3399-sd-friendlycore-bionic-4.4-arm64-20220819.img镜像文件,然后在播放音频文件的时候,使用逻辑分析仪测量了一下I2S_CLK(GPIO4_PA0/I2S0_MCLK)引脚的信息,如下图所示:
这里一个时钟周期,姑且认为100ns,换算成频率就是10MHz,实际上时钟频率为11.2896MHz。
这里我们并没有把I2S通信的所有时钟信号都测量出来,比如I2S0_SCLK、I2S0_LRCK_RX、I2S0_LRCK_TX,究其原因还是因为我们使用的NanoPC-T4开发板并没有将这些引脚单独引出来,因此想测量这些引脚是的信号是非常困难的。对于NanoPC-T4开发板而言,开发板中的40 Pin GPIO引脚中包含了I2S_CLK,如下图所示:
后来我向开发板下载了我们自己移植的内核版本linux 6.3版本,发现I2S_CLK引脚并没有信号输出,因此播放音频文件的时候不会有声音输出来。
5.2.1 使能mclk时钟
我们在Rockchip RK3399 - Codec驱动( Realtek ALC5651)介绍codec驱动的时候,虽然在设备树中配置了mclk时钟节点,但是驱动中并没有使用到;
clocks = <&cru SCLK_I2S_8CH_OUT>; clock-names = "mclk";
因此就需要在源码里加入以下代码,获得设备mclk时钟,设置时钟频率并使能时钟。
(1) 修改sound/soc/codecs/rt5651.h文件,在rt5651_priv数据结构的定义中添加;
struct clk *mclk;
(2) 修改sound/soc/codecs/rt5651.c文件,rt5651_probe函数修改为:
static int rt5651_probe(struct snd_soc_component *component) { int ret = 0; struct rt5651_priv *rt5651 = snd_soc_component_get_drvdata(component); /* Check if MCLK provided */ rt5651->mclk = devm_clk_get(component->dev, "mclk"); if (IS_ERR(rt5651->mclk)){ dev_err(component->dev,"Can't retieve i2s master clock\n"); return PTR_ERR(rt5651->mclk); } ret = clk_set_rate(rt5651->mclk,11289600); if(ret) dev_err(component->dev,"Fail to set mclk %d\n",ret); ret = clk_prepare_enable(rt5651->mclk); if (ret) return ret; rt5651->component = component; snd_soc_component_update_bits(component, RT5651_PWR_ANLG1, RT5651_PWR_LDO_DVO_MASK, RT5651_PWR_LDO_DVO_1_2V); snd_soc_component_force_bias_level(component, SND_SOC_BIAS_OFF); rt5651_apply_properties(component); return 0; }
并引入头文件:
#include <linux/clk.h>>
我们重新编译内核,并烧录到开发板,重启系统;播放音频文件后,查看clk_i2sout时钟频率;
root@rk3399:/home/zhengyang# cat /sys/kernel/debug/clk/clk_i2sout/clk_rate 11289600
/sys/kernel/debug/clk/clk_summary文件中的数据格式是每一个行代表一个时钟,以空格分隔的字段表示不同的信息,以下是常见的字段及其含义:
- enable count:时钟被启用的次数,每当该时钟被启用(enable)一次,该计数就会递增;每当时钟被禁用(disable)依次,该计数就会递减。这个计数可以反映出该时钟的使用频率或依赖关系;如果这个值大于0,表明时钟被启用,hardware enable则为Y;当这个值为0时,表示没有设备使用这个时钟,那么这个时钟将会被关闭;
- prepare count:表示该时钟被准备好的次数。每当该时钟被准备(prepare)一次,该计数就会递增;每当该时钟被取消准备(unprepare)一次,该计数就会递减。时钟在被使用之前通常需要进行准备工作,例如打开时钟电源、初始化时钟等,而准备计数可以表示该时钟被准备的频率;
- protect count:表示时钟的保护计数,保护计数是用于跟踪当前时钟对象被保护的次数。当时钟被保护时,其计数会递增;当不再需要保护时,计数会递减。通常,时钟对象在被使用或引用时会被保护,以确保其不会被意外或无效地关闭或修改;保护计数的作用是在多个模块中对时钟对象进行共享和保护。通过跟踪保护计数,可以确保在所有使用该时钟对象的地方都正确地保持对该时钟对象的保护,从而避免不必要的错误或不一致性。
- rate:时钟的频率;
- accuracy:时钟频率设置的精度;
- phase:时钟的相位,在周期性信号中,相位表示信号波形相对于某个参考点的时间偏移量;
- duty cycle:占空比;
查看所有与i2s0相关的时钟,如下图所示:
root@rk3399:/sys/kernel/debug/clk# cat clk_summary | grep i2s hclk_i2s2 0 0 0 100000000 0 0 50000 N hclk_i2s1 0 0 0 100000000 0 0 50000 N hclk_i2s0 1 2 0 100000000 0 0 50000 Y clk_i2s2_div 0 0 0 800000000 0 0 50000 N clk_i2s2_frac 0 0 0 40000000 0 0 50000 N clk_i2s1_div 0 0 0 800000000 0 0 50000 N clk_i2s1_frac 0 0 0 40000000 0 0 50000 N clk_i2s0_div 1 1 0 800000000 0 0 50000 Y clk_i2s0_frac 1 1 0 11289600 0 0 50000 Y clk_i2s0_mux 1 1 0 11289600 0 0 50000 Y clk_i2s0 1 2 0 11289600 0 0 50000 Y clk_i2sout_src 1 1 0 11289600 0 0 50000 Y clk_i2sout 1 1 0 11289600 0 0 50000 Y clk_i2s2_mux 0 0 0 0 0 0 50000 Y clk_i2s2 0 0 0 0 0 0 50000 N clk_i2s1_mux 0 0 0 0 0 0 50000 Y clk_i2s1 0 0 0 0 0 0 50000 N
虽然从上面输出的信息看到clk_i2sout(下图中的mclk)已经使能了,并且输出的时钟频率为11289600,但是实际上测量I2S_CLK引脚并没有波形输出。
之所以使能clk_i2sout没有效果的原因在于:在platform驱动中我们已经初始化了时钟clk_i2s0,而clk_i2s0到clk_i2sout之间这段链路的的寄存器的默认值实际上就是红色这条通路;因此对于clk_i2sout时钟的使能就有点多此一举。
到了这里,其实我们可以确定的是时钟配置(时钟频率、时钟使能)没有什么问题的,那么既然时钟配置没问题,那么I2S_CLK引脚为啥没有波形输出,这次我猜测可能是I2S引脚功能复用的问题。
5.2.2 I2S0引脚功能复用
为了验证我的猜测,我修改了sound/soc/rockchip/rockchip_i2s.c文件中的i2s_pinctrl_select_bclk_on函数,打印了日志:
static int i2s_pinctrl_select_bclk_on(struct rk_i2s_dev *i2s) { int ret = 0; if (!IS_ERR(i2s->pinctrl) && !IS_ERR_OR_NULL(i2s->bclk_on)) ret = pinctrl_select_state(i2s->pinctrl, i2s->bclk_on); printk("enable i2s pin ---------- %s",__func__); if (ret) dev_err(i2s->dev, "bclk enable failed %d\n", ret); return ret; }
调试发现,当存在完整的音频播放路径时,会执行rockchip_i2s_trigger函数,函数内部会调用i2s_pinctrl_select_bclk_on;也就是说会配置I2S相关引脚功能复用为I2S功能。
以下是命令dmeg看到的信息:
...... [ 14.755182] genirq: Failed to request resources for bt_default_wake_host_irq (irq 67) on irqchip rockchip_gpio_irq [ 16.333450] rk_gmac-dwmac fe300000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx [ 38.102801] rfkill: input handler disabled [ 169.810604] enable clock mclk i2s_runtime_resume -------- [ 169.959907] enable i2s pin ---------- i2s_pinctrl_select_bclk_on
因此从从输出日志信息来看,I2S引脚功能复用配置应该也是没有什么问题的。
我们通过内核命令查看一下IO复用情况:
root@rk3399:/# cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep i2s pin 120 (gpio3-24): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 121 (gpio3-25): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 122 (gpio3-26): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 123 (gpio3-27): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 124 (gpio3-28): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 125 (gpio3-29): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 126 (gpio3-30): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 127 (gpio3-31): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus pin 128 (gpio4-0): ff880000.i2s (GPIO UNCLAIMED) function i2s0 group i2s0-8ch-bus
由于我们使用的开发板,I2S0相关的引脚只是引出了I2S_CLK,这里我想测量一下I2S0_SCLK、I2S0_LRCK_RX、I2S0_LRCK_TX以及I2S0_SDO0、I2S0_SDI0等引脚的信号都没法实现。
因此这里我想了一个办法,我们去编写一个dummy-codec驱动,然后使用I2S1(开发板将相关引脚引出来了,可以直接测量),这样在播放音频的时候,我们就可以测量I2S1相关的引脚信号了。
5.3.1 介绍
dummy-code表示虚拟声卡,就是为了在SoC外部没有连接codec芯片的情况下,为了匹配声卡驱动框架,虚拟的一个设备,类似于占位符之类的东西的作用。
使用dummy-codec也支持多声道,例如I2S0支持8个输出声道,2个输入声道,则可以在i2s0_sdo0~3接上4个回放型codec,在i2s0_sdi0接上1个录制型codec。linux系统使用aplay播放就可以选择8个通道,使用arecord录制可以选择2个通道。
5.3.2 设备树配置
(1) 我们在arch/arm64/boot/dts/rockchip/rk3399-evb.dts文件添加 dummy_codec设备节点:
dummy_codec: dummy-codec { #sound-dai-cells = <0>; compatible = "dummy-codec"; pinctrl-names = "default"; pinctrl-0 = <&i2s_8ch_mclk>; status = "okay"; };
这里没有去配置clocks时钟信号,之前已经说了配置这个实际上是多余的。如果需要配置的话,需要修改sound/soc/soc-utils.c加入mclk时钟使能的代码;
(2) 同时在pinctrl设备节点下添加引脚配置节点i2s_8ch_mclk:
i2s_8ch_mclk: i2s0-8ch-mclk { rockchip,pins = <4 RK_PA0 1 &pcfg_pull_none>; };
(3) 同时添加dummy-sound设备节点:
dummy-sound { status = "okay"; compatible = "simple-audio-card"; simple-audio-card,name = "rockchip,dummy-codec"; simple-audio-card,format = "i2s"; simple-audio-card,mclk-fs = <256>; simple-audio-card,cpu { sound-dai = <&i2s1>; }; simple-audio-card,codec { sound-dai = <&dummy_codec>; }; };
(4) 同时我们需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts文件添加如下属性,启用I2S1控制器:
&i2s1 { rockchip,playback-channels = <2>; rockchip,capture-channels = <2>; status = "okay"; };
而设备节点i2s1已经定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi文件:
i2s1: i2s@ff890000 { compatible = "rockchip,rk3399-i2s", "rockchip,rk3066-i2s"; reg = <0x0 0xff890000 0x0 0x1000>; interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH 0>; dmas = <&dmac_bus 2>, <&dmac_bus 3>; dma-names = "tx", "rx"; clock-names = "i2s_clk", "i2s_hclk"; clocks = <&cru SCLK_I2S1_8CH>, <&cru HCLK_I2S1_8CH>; pinctrl-names = "default"; pinctrl-0 = <&i2s1_2ch_bus>; power-domains = <&power RK3399_PD_SDIOAUDIO>; #sound-dai-cells = <0>; status = "disabled"; };
这里设置了default状态对应的引脚配置为i2s1_2ch_bus,这里主要配置I2S1相关引脚复用为I2S功能;
i2s1_2ch_bus: i2s1-2ch-bus { rockchip,pins = <4 RK_PA3 1 &pcfg_pull_none>, <4 RK_PA4 1 &pcfg_pull_none>, <4 RK_PA5 1 &pcfg_pull_none>, <4 RK_PA6 1 &pcfg_pull_none>, <4 RK_PA7 1 &pcfg_pull_none>; };
关于各个设备节点的属性我就不重复介绍了,因为前面的文章我们已经说得很详细了。
(5) 由于I2S0、I2S1共用了I2S_CLK引脚,因此我最好将rt5651_card、i2s0、rt5651设备节点禁用:
rt5651_card: rt5651-sound { status = "disabled"; ... } .....
(6) 修改sound/soc/soc-utils.c文件:
屏蔽snd_soc_util_init函数中注册平台设备相关的代码,因为我们需要采用设备树的方式注册平台设备,也就是上面的dummy_codec设备节点;
//soc_dummy_dev = // platform_device_register_simple("snd-soc-dummy", -1, NULL, 0);
屏蔽snd_soc_util_exit函数中卸载平台设备相关的代码:
//platform_device_unregister(soc_dummy_dev);
修改soc_dummy_driver添加设备树匹配:
static const struct of_device_id simple_of_match[] = { // 用于匹配设备树 { .compatible = "dummy-codec", }, {}, }; static struct platform_driver soc_dummy_driver = { .driver = { .name = "snd-soc-dummy", .of_match_table = simple_of_match, }, .probe = snd_soc_dummy_probe, };
5.3.3 配置内核
运行make menuconfig,开启内核调试接口;
Kernel hacking ---> Generic Kernel Debugging Instruments ---> [*] Debug Filesystem printk and dmesg options ---> [*] Enable dynamic printk() support -*- Enable core function of dynamic debug support CLOCK_ALLOW_WRITE_DEBUGFS
即配置:
-
CONFIG_DEBUG_FS:这是一个内核配置选项,用于启用调试文件系统(DebugFS)。该调试文件系统包含了内核调试接口的文件;
-
CONFIG_DYNAMIC_DEBUG:这个选项用于启用动态调试功能,可以在运行时动态控制内核输出的调试信息;
修改drivers/clk/clk.c,添加如下代码:
#define CLOCK_ALLOW_WRITE_DEBUGFS
同时注释掉代码里面的:
#undef CLOCK_ALLOW_WRITE_DEBUGFS
CLOCK_ALLOW_WRITE_DEBUGFS:允许对调试文件系统进行写入操作,如果不配置在修改/sys/kernel/debug/clk下的文件时出现如下错误:
root@rk3399:/# echo 0 > /sys/kernel/debug/clk/clk_i2sout/clk_prepare_enable bash: echo: write error: Permission denied
5.3.4 测试
按照上面介绍的编译流程,编译内核并烧录到开发板。我们在源码中加入一些日志输出,比如这里启动内核过程会输出一些dummy-code相关信息:
[ 4.235116] ff890000.i2s-snd-soc-dummy-dai [ 4.235188] asoc-simple-card dummy-sound: ASoC: binding ff890000.i2s-snd-soc-dummy-dai [ 4.248888] asoc-simple-card dummy-sound: ASoC: CPU DAI (null)registered [ 4.256461] asoc-simple-card dummy-sound: ASoC: CODEC DAI snd-soc-dummy-dai registered [ 4.265487] ---------------------asoc_simple_init_jack--------------,prefix:simple-audio-card, [ 4.265526] -----------------no error--------------- [ 4.275245] ---------------------asoc_simple_init_jack--------------,prefix:simple-audio-card, [ 4.280865] -----------------no error--------------- [ 4.290672] enable clock mclk i2s_runtime_resume -------- [ 4.298381] -------dai_link params null,dapm_connect_dai_pair [ 4.315235] ALSA device list: [ 4.318647] #0: rockchip,dummy-codec
我们查看一下声卡设备:
root@rk3399:/# cat /proc/asound/cards 0 [rockchipdummyco]: simple-card - rockchip,dummy-codec rockchip,dummy-codec
可以看到我们注册虚拟声卡驱动。我们查看一下时钟信号:
root@rk3399:/# cat /sys/kernel/debug/clk/clk_summary | grep i2s hclk_i2s2 0 0 0 100000000 0 0 50000 N hclk_i2s1 1 2 0 100000000 0 0 50000 Y hclk_i2s0 1 2 0 100000000 0 0 50000 Y clk_i2s2_div 0 0 0 800000000 0 0 50000 N clk_i2s2_frac 0 0 0 40000000 0 0 50000 N clk_i2s1_div 0 0 0 800000000 0 0 50000 N clk_i2s1_frac 0 0 0 40000000 0 0 50000 N clk_i2s0_div 0 0 0 800000000 0 0 50000 N clk_i2s0_frac 0 0 0 40000000 0 0 50000 N clk_i2s2_mux 0 0 0 0 0 0 50000 Y clk_i2s2 0 0 0 0 0 0 50000 N clk_i2s1_mux 0 1 0 0 0 0 50000 Y clk_i2s1 0 1 0 0 0 0 50000 N clk_i2s0_mux 0 1 0 0 0 0 50000 Y clk_i2s0 0 1 0 0 0 0 50000 N clk_i2sout_src 0 0 0 0 0 0 50000 Y clk_i2sout 0 0 0 0 0 0 50000 N
从上图可以看到clk_i2sout_src的父时钟为clk_i2s0,我们将时钟clk_i2sout_src的父时钟更改为clk_i2s1:
root@rk3399:/# cat /sys/kernel/debug/clk/clk_i2sout_src/clk_parent clk_i2s0 root@rk3399:/# echo 1 > /sys/kernel/debug/clk/clk_i2sout_src/clk_parent root@rk3399:/# cat /sys/kernel/debug/clk/clk_i2sout_src/clk_parent clk_i2s1
此外我们还可以执行如下命令关闭/开启特定时钟,比如这里我关闭时钟hclk_i2s0、开启时钟clk_i2sout:
root@rk3399:/# echo 0 > /sys/kernel/debug/clk/hclk_i2s0/clk_prepare_enable root@rk3399:/# echo 1 > /sys/kernel/debug/clk/clk_i2sout/clk_prepare_enable root@rk3399:/# cat /sys/kernel/debug/clk/clk_summary | grep i2s hclk_i2s2 0 0 0 100000000 0 0 50000 N hclk_i2s1 1 2 0 100000000 0 0 50000 Y hclk_i2s0 0 1 0 100000000 0 0 50000 N clk_i2s2_div 0 0 0 800000000 0 0 50000 N clk_i2s2_frac 0 0 0 40000000 0 0 50000 N clk_i2s1_div 0 0 0 800000000 0 0 50000 N clk_i2s1_frac 0 0 0 40000000 0 0 50000 N clk_i2s0_div 0 0 0 800000000 0 0 50000 N clk_i2s0_frac 0 0 0 40000000 0 0 50000 N clk_i2s2_mux 0 0 0 0 0 0 50000 Y clk_i2s2 0 0 0 0 0 0 50000 N clk_i2s1_mux 0 1 0 0 0 0 50000 Y clk_i2s1 0 1 0 0 0 0 50000 N clk_i2sout_src 0 0 0 0 0 0 50000 Y clk_i2sout 1 1 0 0 0 0 50000 Y clk_i2s0_mux 0 1 0 0 0 0 50000 Y clk_i2s0 0 1 0 0 0 0 50000 N
我们查看一下I2S1相关寄存器的信息:
root@rk3399:/# cat /sys/kernel/debug/regmap/ff890000.i2s/registers 00: 0000000f 04: 0000000f 08: 00071f1f 0c: XXXXXXXX 10: 001f0000 14: 01f00000 18: XXXXXXXX 1c: 00000000 20: XXXXXXXX 24: XXXXXXXX root@rk3399:/# cat /sys/kernel/debug/asoc/components ff890000.i2s ff890000.i2s dummy-codec dummy-codec root@rk3399:/# cat /sys/kernel/debug/asoc/dais i2s-hifi ff890000.i2s snd-soc-dummy-dai
接着我们打开一个新的终端播放音频:
root@rk3399:/# aplay AbuduOffice.wav MobaXterm X11 proxy: Unsupported authorisation protocol xcb_connection_has_error() returned true XDG_RUNTIME_DIR (/run/user/1000) is not owned by us (uid 0), but by uid 1000! (This could e.g. happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.) Playing WAVE 'AbuduOffice.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
然后播放的同时查看I2S1相关寄存器以及时钟的信息:
root@rk3399:/# cat /sys/kernel/debug/regmap/ff890000.i2s/registers 00: 0000000f 04: 0000000f 08: 00033f3f 0c: 00000017 10: 000f0110 14: 01f00000 18: 00000000 1c: 00000003 20: 00000000 24: 00000000 root@rk3399:/# cat /sys/kernel/debug/clk/clk_summary | grep i2s hclk_i2s2 0 0 0 100000000 0 0 50000 N hclk_i2s1 2 2 0 100000000 0 0 50000 Y hclk_i2s0 0 1 0 100000000 0 0 50000 N clk_i2s2_div 0 0 0 800000000 0 0 50000 N clk_i2s2_frac 0 0 0 40000000 0 0 50000 N clk_i2s1_div 1 1 0 800000000 0 0 50000 Y clk_i2s1_frac 1 1 0 11289600 0 0 50000 Y clk_i2s1_mux 1 1 0 11289600 0 0 50000 Y clk_i2s1 2 2 0 11289600 0 0 50000 Y clk_i2sout_src 1 1 0 11289600 0 0 50000 Y clk_i2sout 1 1 0 11289600 0 0 50000 Y clk_i2s0_div 0 0 0 800000000 0 0 50000 N clk_i2s0_frac 0 0 0 40000000 0 0 50000 N clk_i2s2_mux 0 0 0 0 0 0 50000 Y clk_i2s2 0 0 0 0 0 0 50000 N clk_i2s0_mux 0 1 0 0 0 0 50000 Y clk_i2s0 0 1 0 0 0 0 50000 N
同时我们使用逻辑分析仪测量I2S1引脚的信号,结果发现还是没有输出,经过再三测试,引脚就是没有输出,无力愤怒啊..........。
最终经过我不懈的努力,终于定位到了这个问题,奶奶的熊的,还要配置audio io domoin,具体参考下面电源配置章节;audio io domoin配置完成后;逻辑分析仪的接线如下:
- 通道0:I2S_CLK;时钟频率为11.2896MHz;
- 通道1:I2S1_SCLK;时钟频率为2.8224MHz;
- 通道2:I2S1_LRCK_TX;时钟频率为44.1KHz;
- 通道3:I2S1_SDO0:每半个LRCK时钟周期内,包含32个SCLK时钟周期;其中16个SCLK时钟周期内传输有效数据,剩下16个时钟周期并没有传输数据;
测量波形如下:
5.4 查看ALC5651供电
5.4.1 查看ALC5651供电
VCCA3V0_CODEC:rk808电源管理芯片14号输出引脚VLDO5,电压输出范围1.8~3.4V;
VCCA1V8_CODEC:rk808电源管理芯片5号输出引脚VLDO7,电压输出范围0.8~2.5V;
因此需要检查是否配置如下设备节点:
rk808: pmic@1b { compatible = "rockchip,rk808"; ...... regulators { vcca3v0_codec: LDO_REG5 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <3000000>; regulator-max-microvolt = <3000000>; regulator-name = "vcca3v0_codec"; regulator-state-mem { regulator-off-in-suspend; }; }; vcca1v8_codec: LDO_REG7 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-name = "vcca1v8_codec"; regulator-state-mem { regulator-off-in-suspend; }; }; ..... }; };
内核启动可以看到电压调整相关信息:
[ 2.807204] rk808 0-001b: chip id: 0x0 [ 2.816135] rk808-regulator rk808-regulator.1.auto: there is no dvs0 gpio [ 2.823797] rk808-regulator rk808-regulator.1.auto: there is no dvs1 gpio [ 2.835414] vcc3v0_touch: Bringing 1800000uV into 3000000-3000000uV [ 2.846064] vcca3v0_codec: Bringing 1800000uV into 3000000-3000000uV [ 2.855601] vcca1v8_codec: Bringing 800000uV into 1800000-1800000uV
5.4.2 配置io domain
一般 IO 电源的电压有 1.8v,3.3v,2.5v,5.0v 等,有些 IO 同时支持多种电压,io-domain 就是配置IO电源域的寄存器,依据真实的硬件电压范围来配置对应的电压寄存器,否则无法正常工作。
在RK3399的datasheet中我们搜索GRF_IO_VSEL寄存器,位于GRF偏移地址 offset (0x0e640):
-
寄存器配置成 1,一般对应的电压范围是 1.62v ~ 1.98v,typical 电压 1.8v;
-
寄存器配置成 0,一般对应的电压范围是 3.00v ~ 3.60v,typical 电压 3.3v。
在 Documentation/devicetree/bindings/power/rockchip-io-domain.yaml文中有关于RK3399 IO 电源域的配置描述描述:
rk3399:
if:
properties:
compatible:
contains:
const: rockchip,rk3399-io-voltage-domain
then:
properties:
audio-supply:
description: The supply connected to APIO5_VDD.
bt656-supply:
description: The supply connected to APIO2_VDD.
gpio1830-supply:
description: The supply connected to APIO4_VDD.
sdmmc-supply:
description: The supply connected to SDMMC0_VDD.
rk3399-pmu:
if:
properties:
compatible:
contains:
const: rockchip,rk3399-pmu-io-voltage-domain
then:
properties:
pmu1830-supply:
description: The supply connected to PMUIO2_VDD.
我们在 rockchip-io-domain.yml中找到了audio对应的硬件原理图上表示为 APIO5_VDD。所以通过搜索 APIO5_VDD得到NanoPC-T4开发板 硬件原理图上的 APIO5_VDD电源是由rk808下的 VCCA1V8_CODEC供给,也就是我们上面说的rk808电源管理芯片5号输出引脚VLDO7;
得到了配置的名称和供电源头,在 设备树里面找到对应的regulator: vcca1v8_codec,就可以在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中追加如下配置;
&io_domains { status = "okay"; audio-supply = <&vcca1v8_codec>; };
5.5 Frace调试
如果插入耳机仍然没有声音,可以参考Frace调试小节的方法来定位问题。
结果我们不懈的努力,终于解决了I2S引脚没有信号输出的问题,到了这里,我们通过修改arch/arm64/boot/dts/rockchip/rk3399-evb.dts文件将我们之前配置的虚拟声卡驱动相关的设备节点屏蔽掉(或者删除),同时使能rt5651_card、i2s0、rt5651设备节点,重新编译并烧录内核。
找到一个wav音频文件,播放音频:
root@rk3399:/# cd / root@rk3399:/# aplay AbuduOffice.wav Playing WAVE 'AbuduOffice.mp3' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
插入耳机,这个时候就能听到声音了。
六、Ftrace调试
Ftrace 是一个内核中的追踪器,用于帮助系统开发者或设计者查看内核运行情况,它可以被用来调试或者分析延迟/性能问题。最早ftrace是一个function tracer,仅能够记录内核的函数调用流程。如今ftrace已经成为一个framework,采用plugin的方式支持开发人员添加更多种类的trace功能。
6.1 配置内核
运行make menuconfig,配置以下选项:
Kernel hacking ---> [*] Tracers ---> [*] Kernel Function Tracer [*] Kernel Function Graph Tracer (NEW) -*- enable/disable function tracing dynamically Device Drivers ---> <*> Sound card support ---> <*> Advanced Linux Sound Architecture ---> [*] Debug [*] More verbose debug [*] Enable PCM ring buffer overrun/underrun debugging [*] Validate input data to control API [*] Enable debugging feature for control API [*] Sound jack injection interface via debugfs
编译内核,并烧录到开发板。
6.2 Ftrace使用
Ftrace通过debugfs向用户态提供访问接口。配置内核时激活debugfs后会创建目录/sys/kernel/debug ,debugfs文件系统就是挂载到该目录。
6.2.1 运行时挂载
官方挂载方法 :
root@rk3399:/# mount -t debugfs nodev /sys/kernel/debug mount: /sys/kernel/debug: nodev already mounted or mount point busy. root@rk3399:/# cd /sys/kernel/debug/tracing
debugfs挂载路径下的tracing是ftrace的根路径。
6.2.2 系统启动自动挂载
要在系统启动自动挂载debugfs,需要将如下内容添加到/etc/fstab 文件:
debugfs /sys/kernel/debug debugfs defaults 0 0
6.2.3 选择一种 tracer
查看当前追踪器:
root@rk3399:/sys/kernel/debug/tracing# cat current_tracer nop
查看当前内核中可用跟踪器:
root@rk3399:/sys/kernel/debug/tracing# cat available_tracers function_graph function nop
我们选用 function_graph 追踪器:
root@rk3399:/sys/kernel/debug/tracing# echo function_graph > current_tracer
6.2.4 打开/关闭跟踪
在老一点版本的内核上tracing目录下有tracing_enabled,需要给tracing_enabled和tracing_on同时赋值1才能打开追踪,而在比较新的内核上已经去掉tracing_enabled ,我们只需要控制tracing_on即可打开关闭追踪。
打开跟踪:
root@rk3399:/sys/kernel/debug/tracing# echo 1 > /sys/kernel/debug/tracing/tracing_on
关闭跟踪:
root@rk3399:/sys/kernel/debug/tracing# echo 0 > /sys/kernel/debug/tracing/tracing_on
追踪指定的进程:
root@rk3399:/sys/kernel/debug/tracing# echo pid > /sys/kernel/debug/tracing/set_ftrace_pid
我们写程序时可以使用getpid获取进程PID,然后使用write将pid 写入 /sys/kernel/debug/tracing/set_ftrace_pid ,并使用write写1到tracing_on打开追踪(因为在用户空间使用不了tracing_on函数),此时即可追踪当前这个进程。
6.2.5 跟踪事件
首先查看事件文件夹下面有哪些选项:
root@rk3399:/sys/kernel/debug/tracing# ls /sys/kernel/debug/tracing/events/ 9p cros_ec filelock interconnect mdio nfs rcu smbus timer alarmtimer dev filemap io_uring migrate nfs4 regmap sock tlb asoc devfreq ftrace iomap mmap oom regulator spi udp block devlink gadget iommu mmap_lock optee rpcgss spmi ufs bpf_test_run dma_fence gpio ipi mmc page_isolation rpm sunrpc vmalloc bpf_trace dpaa2_eth gpu_scheduler irq module page_pool rpmh swiotlb vmscan bridge dpaa_eth header_event jbd2 mtu3 pagemap rseq task watchdog cgroup drm header_page kmem musb percpu rtc tcp workqueue chipidea dwc3 huge_memory kvm napi power sched tegra_apb_dma writeback clk enable hwmon kyber neigh printk scmi thermal xdp cma error_report i2c libata net qdisc scsi thermal_power_allocator xhci-hcd compaction ext4 i2c_slave lock netfs ras signal thermal_pressure cpuhp fib initcall maple_tree netlink raw_syscalls skb thp root@rk3399:/sys/kernel/debug/tracing# ls /sys/kernel/debug/tracing/events/asoc enable snd_soc_bias_level_start snd_soc_dapm_path snd_soc_dapm_widget_event_done snd_soc_jack_irq filter snd_soc_dapm_connected snd_soc_dapm_start snd_soc_dapm_widget_event_start snd_soc_jack_notify snd_soc_bias_level_done snd_soc_dapm_done snd_soc_dapm_walk_done snd_soc_dapm_widget_power snd_soc_jack_report
关闭所有事件跟踪:
root@rk3399:/sys/kernel/debug/tracing# echo 0 > /sys/kernel/debug/tracing/events/enable
追踪若干事件:
root@rk3399:/sys/kernel/debug/tracing# echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_path/enable root@rk3399:/sys/kernel/debug/tracing# echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_start/enable root@rk3399:/sys/kernel/debug/tracing# echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_walk_done/enable root@rk3399:/sys/kernel/debug/tracing# echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_widget_power/enable
追踪某一类事件:
root@rk3399:/sys/kernel/debug/tracing# echo 1 > /sys/kernel/debug/tracing/events/asoc/enable
6.2.6 查看跟踪结果
ftrace 的输出信息主要保存在3个文件中;
- trace:该文件保存ftrace的输出信息,其内容可以直接阅读;
- trace_pipe:是一个管道文件,主要为了方便应用程序读取trace内容,算是扩展接口吧;
所以可以直接查看trace追踪文件,也可以在追踪之前使用trace_pipe将追踪结果直接导向其他的文件。
比如:cat trace_pipe > /tmp/log & 使用trace_pipe将跟踪结果导入/tmp/log 里,我们可以直接cat /tmp/log” 查看跟踪信息。
当然也可以直接查看trace文件cat trace或者使用cat trace > /tmp/log将跟踪信息导入 /tmp/log。
6.3 跟踪音频
6.3.1 打开trace开关
echo 0 > /sys/kernel/debug/tracing/events/enable echo 1 > /sys/kernel/debug/tracing/tracing_on echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_path/enable echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_start/enable echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_walk_done/enable echo 1 > /sys/kernel/debug/tracing/events/asoc/snd_soc_dapm_widget_power/enable
这里我们解释一下snd_soc_dapm_start、snd_soc_dapm_path、snd_soc_dapm_walk_done、snd_soc_dapm_widget_power(如果不想查看widget电源信息,可以关闭掉这个跟踪),其对应linux中的trace_xxxxx函数;这几个函数都被dapm_power_widgets调用,调用顺序如下:
trace_snd_soc_dapm_start(card) ...... list_for_each_entry(w, &card->dapm_dirty, dirty) // 遍历dapm_dirty链表 dapm_power_one_widgett(w, &up_list, &down_list)
// 调用自身的power_check来获取widget期望的电源状态 判断一个widget是否上电或下电基于这个widget是否在完整音频路径上 dapm_widget_power_check(w)
// 从当前wiget向前遍历,获取连接至SND_SOC_DAPM_EP_SOURCE类型端点路径数量 is_connected_input_ep(w, NULL, NULL) is_connected_ep(w, NULL, SND_SOC_DAPM_DIR_IN,is_connected_input_ep, NULL) snd_soc_dapm_widget_for_each_path(widget, SND_SOC_DAPM_DIR_OUT, path) // 遍历以widget作为输出端的path widget <- path <- widget1 trace_snd_soc_dapm_path(w, SND_SOC_DAPM_DIR_IN, path) // 从当前widget向后遍历,获取连接至SND_SOC_DAPM_EP_SINK类型端点的路径数量 is_connected_output_ep(w, NULL, NULL) is_connected_ep(w, NULL, SND_SOC_DAPM_DIR_OUT,is_connected_output_ep, NULL) snd_soc_dapm_widget_for_each_path(widget, SND_SOC_DAPM_DIR_IN, path) // 遍历以widget作为输入端的path widget -> path -> widget1 trace_snd_soc_dapm_path(w, SND_SOC_DAPM_DIR_OUT, path) trace_snd_soc_dapm_widget_power(w, power) ...... trace_snd_soc_dapm_walk_done(card) ...... trace_snd_soc_dapm_done(card)
(1) 以trace_snd_soc_dapm_start为例,该函数定义在include/trace/events/asoc.h:
DEFINE_EVENT(snd_soc_dapm_basic, snd_soc_dapm_start, TP_PROTO(struct snd_soc_card *card), TP_ARGS(card) );
我们定位到snd_soc_dapm_basic,内容如下,其中TP_prink为输出内容,实际上输出的就是card的name;
DECLARE_EVENT_CLASS(snd_soc_dapm_basic, TP_PROTO(struct snd_soc_card *card), TP_ARGS(card), TP_STRUCT__entry( __string( name, card->name ) ), TP_fast_assign( __assign_str(name, card->name); ), TP_printk("card=%s", __get_str(name)) );
(1) 以trace_snd_soc_dapm_path为例,该函数定义在include/trace/events/asoc.h:可以该函数会输出输出widget和path、以及path->node[dir]的流向关系;
TRACE_EVENT(snd_soc_dapm_path, TP_PROTO(struct snd_soc_dapm_widget *widget, // path其中一端连接的widget enum snd_soc_dapm_direction dir, // 另一个widget位于path的哪一端 0:输入端 1:输出端 struct snd_soc_dapm_path *path), // path TP_ARGS(widget, dir, path), TP_STRUCT__entry( __string( wname, widget->name ) __string( pname, path->name ? path->name : DAPM_DIRECT) __string( pnname, path->node[dir]->name ) // dir=0,source widget;dir=1,sink widget __field( int, path_node ) __field( int, path_connect ) __field( int, path_dir ) ), TP_fast_assign( __assign_str(wname, widget->name); __assign_str(pname, path->name ? path->name : DAPM_DIRECT); __assign_str(pnname, path->node[dir]->name); __entry->path_connect = path->connect; __entry->path_node = (long)path->node[dir]; __entry->path_dir = dir; ), TP_printk("%c%s %s %s %s %s", (int) __entry->path_node && (int) __entry->path_connect ? '*' : ' ', // 如果path为连接状态,返回* __get_str(wname), DAPM_ARROW(__entry->path_dir), // widget name , <- 或者 -> __get_str(pname), DAPM_ARROW(__entry->path_dir), // path name(对于中间有kcontrol的两个widget,就是kcontrol名称,否则就是direct), <- 或者 -> __get_str(pnname)) // path的另一端连接的widget );
6.3.2 激活音频播放流
找到一个音频文件,播放音频:
root@rk3399:/# cd / root@rk3399:/# aplay AbuduOffice.wav Playing WAVE 'AbuduOffice.mp3' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
查看/sys/kernel/debug/tracing/trace:
root@rk3399:/# cat /sys/kernel/debug/tracing/trace # tracer: nop # # entries-in-buffer/entries-written: 52/52 #P:6 # # _-----=> irqs-off/BH-disabled # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / _-=> migrate-disable # |||| / delay # TASK-PID CPU# ||||| TIMESTAMP FUNCTION # | | | ||||| | | aplay-5071 [004] ..... 249.340109: snd_soc_dapm_start: card=realtek,rt5651-codec aplay-5071 [004] ..... 249.340138: snd_soc_dapm_path: *AIF1RX <- (direct) <- AIF1 Playback aplay-5071 [004] ..... 249.340140: snd_soc_dapm_path: *IF1 DAC <- (direct) <- AIF1RX aplay-5071 [004] ..... 249.340145: snd_soc_dapm_path: *IF1 DAC2 R <- (direct) <- IF1 DAC aplay-5071 [004] ..... 249.340146: snd_soc_dapm_path: *IF1 DAC2 L <- (direct) <- IF1 DAC aplay-5071 [004] ..... 249.340148: snd_soc_dapm_path: *IF1 DAC1 R <- (direct) <- IF1 DAC aplay-5071 [004] ..... 249.340150: snd_soc_dapm_path: *IF1 DAC1 L <- (direct) <- IF1 DAC aplay-5071 [004] ..... 249.340153: snd_soc_dapm_path: *DAC MIXR <- INF1 Switch <- IF1 DAC1 R aplay-5071 [004] ..... 249.340155: snd_soc_dapm_path: DAC MIXR <- Stereo ADC Switch <- Stereo1 ADC MIXR aplay-5071 [004] ..... 249.340158: snd_soc_dapm_path: *DAC MIXL <- INF1 Switch <- IF1 DAC1 L aplay-5071 [004] ..... 249.340159: snd_soc_dapm_path: DAC MIXL <- Stereo ADC Switch <- Stereo1 ADC MIXL aplay-5071 [004] ..... 249.340162: snd_soc_dapm_path: *PDM R Mux <- DD MIX <- DAC MIXR aplay-5071 [004] ..... 249.340163: snd_soc_dapm_path: PDM R Mux <- Stereo DAC MIX <- Stereo DAC MIXR aplay-5071 [004] ..... 249.340165: snd_soc_dapm_path: *Audio DSP <- (direct) <- DAC MIXR aplay-5071 [004] ..... 249.340167: snd_soc_dapm_path: *Audio DSP <- (direct) <- DAC MIXL aplay-5071 [004] ..... 249.340169: snd_soc_dapm_path: *PDM L Mux <- DD MIX <- DAC MIXL aplay-5071 [004] ..... 249.340170: snd_soc_dapm_path: PDM L Mux <- Stereo DAC MIX <- Stereo DAC MIXL aplay-5071 [004] ..... 249.340173: snd_soc_dapm_path: *PDMR <- (direct) <- PDM R Mux aplay-5071 [004] ..... 249.340175: snd_soc_dapm_path: Stereo DAC MIXR <- DAC L1 Switch <- DAC MIXL aplay-5071 [004] ..... 249.340176: snd_soc_dapm_path: Stereo DAC MIXR <- DAC R2 Switch <- DAC R2 Volume aplay-5071 [004] ..... 249.340178: snd_soc_dapm_path: *Stereo DAC MIXR <- DAC R1 Switch <- Audio DSP aplay-5071 [004] ..... 249.340181: snd_soc_dapm_path: Stereo DAC MIXL <- DAC R1 Switch <- DAC MIXR aplay-5071 [004] ..... 249.340182: snd_soc_dapm_path: Stereo DAC MIXL <- DAC L2 Switch <- DAC L2 Volume aplay-5071 [004] ..... 249.340183: snd_soc_dapm_path: *Stereo DAC MIXL <- DAC L1 Switch <- Audio DSP aplay-5071 [004] ..... 249.340186: snd_soc_dapm_path: *PDML <- (direct) <- PDM L Mux aplay-5071 [004] ..... 249.340191: snd_soc_dapm_path: *DAC R1 <- (direct) <- Stereo DAC MIXR aplay-5071 [004] ..... 249.340194: snd_soc_dapm_path: *DAC L1 <- (direct) <- Stereo DAC MIXL aplay-5071 [004] ..... 249.340197: snd_soc_dapm_path: *OUT MIXR <- DAC R1 Switch <- DAC R1 aplay-5071 [004] ..... 249.340199: snd_soc_dapm_path: OUT MIXR <- REC MIXR Switch <- RECMIXR aplay-5071 [004] ..... 249.340200: snd_soc_dapm_path: OUT MIXR <- INR1 Switch <- INR1 VOL aplay-5071 [004] ..... 249.340202: snd_soc_dapm_path: OUT MIXR <- BST1 Switch <- BST1 aplay-5071 [004] ..... 249.340203: snd_soc_dapm_path: OUT MIXR <- BST2 Switch <- BST2 aplay-5071 [004] ..... 249.340206: snd_soc_dapm_path: *OUT MIXL <- DAC L1 Switch <- DAC L1 aplay-5071 [004] ..... 249.340208: snd_soc_dapm_path: OUT MIXL <- REC MIXL Switch <- RECMIXL aplay-5071 [004] ..... 249.340209: snd_soc_dapm_path: OUT MIXL <- INL1 Switch <- INL1 VOL aplay-5071 [004] ..... 249.340211: snd_soc_dapm_path: OUT MIXL <- BST2 Switch <- BST2 aplay-5071 [004] ..... 249.340212: snd_soc_dapm_path: OUT MIXL <- BST1 Switch <- BST1 aplay-5071 [004] ..... 249.340214: snd_soc_dapm_path: *HPOVOL R <- Switch <- OUT MIXR aplay-5071 [004] ..... 249.340217: snd_soc_dapm_path: *HPOVOL L <- Switch <- OUT MIXL aplay-5071 [004] ..... 249.340220: snd_soc_dapm_path: *HPOR MIX <- HPO MIX HPVOL Switch <- HPOVOL R aplay-5071 [004] ..... 249.340221: snd_soc_dapm_path: HPOR MIX <- HPO MIX DAC1 Switch <- DAC R1 aplay-5071 [004] ..... 249.340224: snd_soc_dapm_path: *HPOL MIX <- HPO MIX HPVOL Switch <- HPOVOL L aplay-5071 [004] ..... 249.340226: snd_soc_dapm_path: HPOL MIX <- HPO MIX DAC1 Switch <- DAC L1 aplay-5071 [004] ..... 249.340230: snd_soc_dapm_path: *HP Amp <- (direct) <- HPOR MIX aplay-5071 [004] ..... 249.340232: snd_soc_dapm_path: *HP Amp <- (direct) <- HPOL MIX aplay-5071 [004] ..... 249.340239: snd_soc_dapm_path: *HPO R Playback <- Switch <- HP Amp aplay-5071 [004] ..... 249.340242: snd_soc_dapm_path: *HPO L Playback <- Switch <- HP Amp aplay-5071 [004] ..... 249.340244: snd_soc_dapm_path: *HPOR <- (direct) <- HPO R Playback aplay-5071 [004] ..... 249.340247: snd_soc_dapm_path: *HPOL <- (direct) <- HPO L Playback aplay-5071 [004] ..... 249.340249: snd_soc_dapm_path: *Headphones <- (direct) <- HPOR aplay-5071 [004] ..... 249.340251: snd_soc_dapm_path: *Headphones <- (direct) <- HPOL aplay-5071 [004] ..... 249.340271: snd_soc_dapm_walk_done: realtek,rt5651-codec: checks 48 power, 31 path, 79 neighbour
alsa在录音/播放前会先检查snd_soc_dapm_path(带"*")能否连成一路完整的routing,如果失败,录音/播放的进程会自动停止。
如果录音/播放的trace不完整,检查codec驱动中的snd_soc_dapm_route,从右到左,能否连接成一路complete path。
6.3.3 激活音频捕获流
我们使用麦克风2进行录音,即rt5651-sound设备节点只配置"IN2P", "Mic Jack", "IN2N", "Mic Jack":
root@rk3399:/# arecord -d 10 -f cd -c 2 -t wav -r 44100 -v record_test.wav Recording WAVE 'record_test.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo Plug PCM: Hardware PCM card 0 'realtek,rt5651-codec' device 0 subdevice 0 Its setup is: stream : CAPTURE access : RW_INTERLEAVED format : S16_LE subformat : STD channels : 2 rate : 44100 exact rate : 44100 (44100/1) msbits : 16 buffer_size : 22052 period_size : 5513 period_time : 125011 tstamp_mode : NONE tstamp_type : MONOTONIC period_step : 1 avail_min : 5513 period_event : 0 start_threshold : 1 stop_threshold : 22052 silence_threshold: 0 silence_size : 0 boundary : 6207086186423386112 appl_ptr : 0 hw_ptr : 0
查看/sys/kernel/debug/tracing/trace:
root@rk3399:/# cat /sys/kernel/debug/tracing/trace # tracer: nop # # entries-in-buffer/entries-written: 39/39 #P:6 # # _-----=> irqs-off/BH-disabled # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / _-=> migrate-disable # |||| / delay # TASK-PID CPU# ||||| TIMESTAMP FUNCTION # | | | ||||| | | arecord-5111 [004] ..... 270.995015: snd_soc_dapm_start: card=realtek,rt5651-codec arecord-5111 [004] ..... 270.995048: snd_soc_dapm_path: *AIF1TX -> (direct) -> AIF1 Capture arecord-5111 [004] ..... 270.995051: snd_soc_dapm_path: *IF1 ADC2 -> (direct) -> AIF1TX arecord-5111 [004] ..... 270.995053: snd_soc_dapm_path: IF1 ADC2 -> IF1 ADC2 -> IF2 ADC arecord-5111 [004] ..... 270.995055: snd_soc_dapm_path: *IF1 ADC1 -> (direct) -> AIF1TX arecord-5111 [004] ..... 270.995056: snd_soc_dapm_path: *IF1 ADC1 -> IF1 ADC1 -> IF2 ADC arecord-5111 [004] ..... 270.995062: snd_soc_dapm_path: Stereo1 ADC MIXR -> Stereo ADC Switch -> DAC MIXR arecord-5111 [004] ..... 270.995063: snd_soc_dapm_path: *Stereo1 ADC MIXR -> (direct) -> IF1 ADC1 arecord-5111 [004] ..... 270.995066: snd_soc_dapm_path: Stereo1 ADC MIXL -> Stereo ADC Switch -> DAC MIXL arecord-5111 [004] ..... 270.995068: snd_soc_dapm_path: *Stereo1 ADC MIXL -> (direct) -> IF1 ADC1 arecord-5111 [004] ..... 270.995072: snd_soc_dapm_path: *Stereo1 ADC R1 Mux -> ADC1 Switch -> Stereo1 ADC MIXR arecord-5111 [004] ..... 270.995074: snd_soc_dapm_path: *Stereo1 ADC L1 Mux -> ADC1 Switch -> Stereo1 ADC MIXL arecord-5111 [004] ..... 270.995077: snd_soc_dapm_path: *Stereo2 ADC MIXR -> (direct) -> IF1 ADC2 arecord-5111 [004] ..... 270.995079: snd_soc_dapm_path: *Stereo2 ADC MIXL -> (direct) -> IF1 ADC2 arecord-5111 [004] ..... 270.995081: snd_soc_dapm_path: ADC R -> ADCR -> Stereo2 ADC R1 Mux arecord-5111 [004] ..... 270.995083: snd_soc_dapm_path: *ADC R -> ADC -> Stereo1 ADC R1 Mux arecord-5111 [004] ..... 270.995085: snd_soc_dapm_path: *ADC L -> ADCL -> Stereo2 ADC L1 Mux arecord-5111 [004] ..... 270.995086: snd_soc_dapm_path: *ADC L -> ADC -> Stereo1 ADC L1 Mux arecord-5111 [004] ..... 270.995090: snd_soc_dapm_path: RECMIXR -> REC MIXR Switch -> OUT MIXR arecord-5111 [004] ..... 270.995091: snd_soc_dapm_path: *RECMIXR -> (direct) -> ADC R arecord-5111 [004] ..... 270.995095: snd_soc_dapm_path: RECMIXL -> REC MIXL Switch -> OUT MIXL arecord-5111 [004] ..... 270.995097: snd_soc_dapm_path: *RECMIXL -> (direct) -> ADC L arecord-5111 [004] ..... 270.995099: snd_soc_dapm_path: BST2 -> BST2 Switch -> OUT MIXR arecord-5111 [004] ..... 270.995101: snd_soc_dapm_path: BST2 -> BST2 Switch -> OUT MIXL arecord-5111 [004] ..... 270.995102: snd_soc_dapm_path: *BST2 -> BST2 Switch -> RECMIXR arecord-5111 [004] ..... 270.995104: snd_soc_dapm_path: *BST2 -> BST2 Switch -> RECMIXL arecord-5111 [004] ..... 270.995107: snd_soc_dapm_path: INR1 VOL -> INR1 Switch -> OUT MIXR arecord-5111 [004] ..... 270.995108: snd_soc_dapm_path: INR1 VOL -> INR1 Switch -> RECMIXR arecord-5111 [004] ..... 270.995111: snd_soc_dapm_path: INL1 VOL -> INL1 Switch -> OUT MIXL arecord-5111 [004] ..... 270.995112: snd_soc_dapm_path: INL1 VOL -> INL1 Switch -> RECMIXL arecord-5111 [004] ..... 270.995114: snd_soc_dapm_path: *IN2N -> (direct) -> INR1 VOL arecord-5111 [004] ..... 270.995116: snd_soc_dapm_path: *IN2N -> (direct) -> BST2 arecord-5111 [004] ..... 270.995118: snd_soc_dapm_path: *IN2P -> (direct) -> INL1 VOL arecord-5111 [004] ..... 270.995119: snd_soc_dapm_path: *IN2P -> (direct) -> BST2 arecord-5111 [004] ..... 270.995122: snd_soc_dapm_path: *Mic Jack -> (direct) -> IN2N arecord-5111 [004] ..... 270.995123: snd_soc_dapm_path: *Mic Jack -> (direct) -> IN2P arecord-5111 [004] ..... 270.995126: snd_soc_dapm_path: *MIC2 -> (direct) -> IN2N arecord-5111 [004] ..... 270.995127: snd_soc_dapm_path: *MIC2 -> (direct) -> IN2P arecord-5111 [004] ..... 270.995151: snd_soc_dapm_walk_done: realtek,rt5651-codec: checks 35 power, 22 path, 50 neighbour
我们使用麦克风3进行录音,即rt5651-sound设备节点只配置"IN3P", "Mic Jack",查看/sys/kernel/debug/tracing/trace:
root@rk3399:/# cat /sys/kernel/debug/tracing/trace # tracer: nop # # entries-in-buffer/entries-written: 28/28 #P:6 # # _-----=> irqs-off/BH-disabled # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / _-=> migrate-disable # |||| / delay # TASK-PID CPU# ||||| TIMESTAMP FUNCTION # | | | ||||| | | arecord-5105 [004] ..... 373.793024: snd_soc_dapm_start: card=realtek,rt5651-codec arecord-5105 [004] ..... 373.793055: snd_soc_dapm_path: *AIF1TX -> (direct) -> AIF1 Capture arecord-5105 [004] ..... 373.793058: snd_soc_dapm_path: *IF1 ADC2 -> (direct) -> AIF1TX arecord-5105 [004] ..... 373.793060: snd_soc_dapm_path: IF1 ADC2 -> IF1 ADC2 -> IF2 ADC arecord-5105 [004] ..... 373.793061: snd_soc_dapm_path: *IF1 ADC1 -> (direct) -> AIF1TX arecord-5105 [004] ..... 373.793063: snd_soc_dapm_path: *IF1 ADC1 -> IF1 ADC1 -> IF2 ADC arecord-5105 [004] ..... 373.793069: snd_soc_dapm_path: Stereo1 ADC MIXR -> Stereo ADC Switch -> DAC MIXR arecord-5105 [004] ..... 373.793070: snd_soc_dapm_path: *Stereo1 ADC MIXR -> (direct) -> IF1 ADC1 arecord-5105 [004] ..... 373.793073: snd_soc_dapm_path: Stereo1 ADC MIXL -> Stereo ADC Switch -> DAC MIXL arecord-5105 [004] ..... 373.793075: snd_soc_dapm_path: *Stereo1 ADC MIXL -> (direct) -> IF1 ADC1 arecord-5105 [004] ..... 373.793079: snd_soc_dapm_path: *Stereo1 ADC R1 Mux -> ADC1 Switch -> Stereo1 ADC MIXR arecord-5105 [004] ..... 373.793082: snd_soc_dapm_path: *Stereo1 ADC L1 Mux -> ADC1 Switch -> Stereo1 ADC MIXL arecord-5105 [004] ..... 373.793085: snd_soc_dapm_path: *Stereo2 ADC MIXR -> (direct) -> IF1 ADC2 arecord-5105 [004] ..... 373.793087: snd_soc_dapm_path: *Stereo2 ADC MIXL -> (direct) -> IF1 ADC2 arecord-5105 [004] ..... 373.793089: snd_soc_dapm_path: ADC R -> ADCR -> Stereo2 ADC R1 Mux arecord-5105 [004] ..... 373.793091: snd_soc_dapm_path: *ADC R -> ADC -> Stereo1 ADC R1 Mux arecord-5105 [004] ..... 373.793094: snd_soc_dapm_path: *ADC L -> ADCL -> Stereo2 ADC L1 Mux arecord-5105 [004] ..... 373.793095: snd_soc_dapm_path: *ADC L -> ADC -> Stereo1 ADC L1 Mux arecord-5105 [004] ..... 373.793099: snd_soc_dapm_path: RECMIXR -> REC MIXR Switch -> OUT MIXR arecord-5105 [004] ..... 373.793101: snd_soc_dapm_path: *RECMIXR -> (direct) -> ADC R arecord-5105 [004] ..... 373.793104: snd_soc_dapm_path: RECMIXL -> REC MIXL Switch -> OUT MIXL arecord-5105 [004] ..... 373.793105: snd_soc_dapm_path: *RECMIXL -> (direct) -> ADC L arecord-5105 [004] ..... 373.793108: snd_soc_dapm_path: *BST3 -> BST3 Switch -> RECMIXR arecord-5105 [004] ..... 373.793110: snd_soc_dapm_path: *BST3 -> BST3 Switch -> RECMIXL arecord-5105 [004] ..... 373.793112: snd_soc_dapm_path: *IN3P -> (direct) -> BST3 arecord-5105 [004] ..... 373.793115: snd_soc_dapm_path: *Mic Jack -> (direct) -> IN3P arecord-5105 [004] ..... 373.793117: snd_soc_dapm_path: *MIC3 -> (direct) -> IN3P arecord-5105 [004] ..... 373.793141: snd_soc_dapm_walk_done: realtek,rt5651-codec: checks 31 power, 19 path, 38 neighbour
参考文章:
[1] 如何查看声卡、pcm设备以及tinyplay、tinymix、tinycap的使用
[2] linux音频 DAPM之二:audio paths与dapm kcontrol
[5] Linux音频调试示例
[6]
[7]
Playback