程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

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或其他类型;

  • -r:录音采样率,即Hz,可以根据实际情况选择;

  • -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 --> RECMIXL(这是一个Mixer,通过BST3 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) --> 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文件中的数据格式是每一个行代表一个时钟,以空格分隔的字段表示不同的信息,以下是常见的字段及其含义:

  • clk:时钟的名称;
  • enable count:时钟被启用的次数,每当该时钟被启用(enable)一次,该计数就会递增;每当时钟被禁用(disable)依次,该计数就会递减。这个计数可以反映出该时钟的使用频率或依赖关系;如果这个值大于0,表明时钟被启用,hardware enable则为Y;当这个值为0时,表示没有设备使用这个时钟,那么这个时钟将会被关闭;
  • prepare count:表示该时钟被准备好的次数。每当该时钟被准备(prepare)一次,该计数就会递增;每当该时钟被取消准备(unprepare)一次,该计数就会递减。时钟在被使用之前通常需要进行准备工作,例如打开时钟电源、初始化时钟等,而准备计数可以表示该时钟被准备的频率;
  • protect count:表示时钟的保护计数,保护计数是用于跟踪当前时钟对象被保护的次数。当时钟被保护时,其计数会递增;当不再需要保护时,计数会递减。通常,时钟对象在被使用或引用时会被保护,以确保其不会被意外或无效地关闭或修改;保护计数的作用是在多个模块中对时钟对象进行共享和保护。通过跟踪保护计数,可以确保在所有使用该时钟对象的地方都正确地保持对该时钟对象的保护,从而避免不必要的错误或不一致性。
  • rate:时钟的频率;
  • accuracy:时钟频率设置的精度;
  • phase:时钟的相位,在周期性信号中,相位表示信号波形相对于某个参考点的时间偏移量;
  • duty cycle:占空比;
  • hardware enable:硬件使能;

查看所有与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

5.3 dummy-codec声卡驱动

由于我们使用的开发板,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.文中文档, 我们知道了 RK3399 的电源域需要配置包含bt565,audio,sdmmc,gpio1830,以及 PMUGRF 下面的 pmu1830 这几个 supply,后面的 The supplyconnected to ***_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调试小节的方法来定位问题。

5.6 总结

结果我们不懈的努力,终于解决了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

[3] Linux 原生跟踪工具 Ftrace

[4] linux tracers使用介绍

[5] Linux音频调试示例

[6] RK3399使用ICS-43432音频录制Codec芯片

[7] Rockchip RK3399等全芯片GPIO常见问题

[8] Rockchip IO-Domain 开发指南

Playback
posted @ 2023-07-31 19:24  大奥特曼打小怪兽  阅读(1501)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步