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:这个选项用于启用动态调试功能,可以在运行时动态控制内核输出的调试信息;
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤