RK3399 探索之旅 / Audio 驱动层速读
目的:
从驱动开发的角度大致了解一下 RK3399 Audio 功能。
环境:
NanoPC-T4 / Ubuntu-18.04 / Linux-4.4
目录:
1. 测试功能
2. 浏览硬件信息
3. 查看 driver 层
4. 应用层查看声卡信息
1. 测试功能
播放:
# 查看 playback 设备
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: realtekrt5651co [realtek,rt5651-codec], device 0: ff880000.i2s-rt5651-aif1 rt5651-aif1-0 []Subdevices: 1/1Subdevice #0: subdevice #0# 在 card 0, device 0 上播放 wav 文件
$ aplay -D hw:0,0 /root/Music/test.wav
录音:
# 查看 record 设备
$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: realtekrt5651co [realtek,rt5651-codec], device 0: ff880000.i2s-rt5651-aif1 rt5651-aif1-0 []Subdevices: 1/1Subdevice #0: subdevice #0# 从 card0, device 0 上录音
$ arecord -D hw:0,0 -f dat filename.wav
2. 浏览硬件信息
1) 查看原理图
关键点:
Audio Codec的型号:Realtek-ALC5651
控制:I2C1
数据:I2S0
耳机插拔检测:PHONE_DET -> HP_DET_H -> GPIO4_D3_d
录音:MIC_IN2P / MIC_IN2N
2) 查看 RK3399 的 datasheet
关键点:
3) 查看 RK3399 的 TRM
关键点:(Chapter 22 I2S/PCM Controller):
概述 / Overview
I2S/PCM controllers 的 features
框图 / Block Diagram
-
点击查看大图
功能描述 / Function description
master / slave
i2s 的3种 mode
pcm 的4种 mode
寄存器描述 / Register Description,最重点的章节,深度定制时需完整阅读。
引脚配置 / Interface description
应用说明 / Application Notes
-
点击查看大图
4) 查看 Audio Codec/Realtek-ALC5651 的 datasheet
关键点:
2.Features
4.Function Block and Mixer Path
-
点击查看大图
Audio Mixer Path
Digital Mixer Path
6.Pin Descriptions
Digital I/O Pins
Analog I/O Pins
7.Function Description,最重点的章节,深度定制时需完整阅读。
8.Registers List
3. 查看 driver 层
阅读下面的内容需要有 audio driver 相关的开发经验,不过我也会尽量给出必要的概念说明。
Soc Audio 简化模型:
DAI 是什么?
Digital Audio Interface.
Provides audio data to the codec. Formats are usually AC97, I2S, PCM
ASoc 是什么?
ALSA System on Chip.
A Linux kernel subsystem created to provide better ALSA support for system-on-chip and portable audio codecs. It allows to reuse codec drivers across multiple architectures and provides an API to integrate them with the SoC audio interface.
ASoc 包括什么?
Platform drivers,提供了配置/使能 SoC audio interface (或称 CPU DAI) 的能力;
Codec drivers,提供了配置/使能 Codec 的能力;
Machine drivers,描述了应该如何控制 CPU DAI 和 Codec,使他们互相配合在一起工作;
3.1 查看 Machine driver
DT bindings:
arch/arm64/boot/dts/rockchip/rk3399-nanopi4-common.dtsi:
rt5651_card: rt5651-sound {status = "okay";compatible = "simple-audio-card";pinctrl-names = "default";pinctrl-0 = <&hp_det>;simple-audio-card,name = "realtek,rt5651-codec";simple-audio-card,format = "i2s";simple-audio-card,mclk-fs = <256>;simple-audio-card,hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_HIGH>;simple-audio-card,widgets ="Microphone", "Mic Jack","Headphone", "Headphone Jack";simple-audio-card,routing ="Mic Jack", "MICBIAS1","IN1P", "Mic Jack","Headphone Jack", "HPOL","Headphone Jack", "HPOR";simple-audio-card,cpu {sound-dai = <&i2s0>;};simple-audio-card,codec {sound-dai = <&rt5651>;};
};
这里没有选择自己编写 Machine driver,而是采用了 simple-audio-card 这个通用的 Machine driver。
在 simple-audio-card 已经够用的情况下,建议优先使用 simple-audio-card 框架,代码会更加简洁一些。
相关文档和代码:
Documentation/devicetree/bindings/sound/simple-card.txt
Documentation/devicetree/bindings/sound/widgets.txt
Documentation/sound/alsa/soc/machine.txt
sound/soc/generic/simple-card.c
simple-card.c 做了什么?
虽然 simple-card.c 不是单板相关的东西,但还是有必要简单说明一下它的内容。
既然 simple-audio-card 是一个 Machine driver,Machine driver 最重要的事情是:构造并注册 struct snd_soc_card,可以认为一个 snd_soc_card 就代表着一个 soc 声卡:
static int asoc_simple_card_probe() {struct snd_soc_dai_link *dai_link;[...]/* Init snd_soc_card */priv->snd_card.owner = THIS_MODULE;priv->snd_card.dev = dev;dai_link = priv->dai_link;priv->snd_card.dai_link = dai_link;priv->snd_card.num_links = num_links;[...]/* 根据设备树的配置进一步初始化 snd_soc_card,* 包括 struct snd_soc_dai_link。*/asoc_simple_card_parse_of(np, priv);/* Register snd_soc_card */devm_snd_soc_register_card(&pdev->dev, &priv->snd_card);
}
snd_soc_card 里有一个比较重要的成员变量 struct snd_soc_dai_link,snd_soc_dai_link 建立了 CPU DAI 和 Codec DAI 的连接 (link)。simple-card.c 会根据设备树里的配置对 snd_soc_dai_link 进行初始化。
后面就不再展开继续分析了,将关注点放在单板相关的部分。
分析设备树
1) 指定 platform & codec
simple-audio-card,cpu {sound-dai = <&i2s0>;
};
simple-audio-card,codec {sound-dai = <&rt5651>;
};
指明了:
platform driver 是 i2s0;
codec driver 是 rt5651;
2) 定义单板相关的 Widget
simple-audio-card,widgets ="Microphone", "Mic Jack","Headphone", "Headphone Jack";
什么是 Widget?
在 Asoc 驱动中,用 Widget 来描述一个声卡的功能部件
参考文档:Documentation/sound/alsa/soc/dapm.txt
这里定义了 2 个 Widget:
Mic Jack,代表麦克风
Headphone Jack,代表 3.5 mm 耳机座
3) 设置单板相关的 Routing
simple-audio-card,routing ="Mic Jack", "MICBIAS1","IN1P", "Mic Jack","Headphone Jack", "HPOL","Headphone Jack", "HPOR";
将 CPU DAI 和 Codec DAI 连接起来后,还需要设置 Codec 的 input 和 output 路径,对应的术语就是 Routing。
simple-audio-card,routing 的作用:
A list of the connections between audio components.
Each entry is a pair of strings, the first being the connection's sink, the second being the connection's source.
不过我认为设备树里的这些 Widget 和 Routing 都是没必要的,在 Codec drvier/rt5651.c 已经定义了足够让声卡正常工作的 Widget 和 Routing,有待考证。
3.2 查看 Platform driver
DT bindings:
arch/arm64/boot/dts/rockchip/rk3399.dtsi
i2s0: i2s@ff880000 {compatible = "rockchip,rk3399-i2s", "rockchip,rk3066-i2s";reg = <0x0 0xff880000 0x0 0x1000>;rockchip,grf = <&grf>;interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH 0>;dmas = <&dmac_bus 0>, <&dmac_bus 1>;dma-names = "tx", "rx";clock-names = "i2s_clk", "i2s_hclk";clocks = <&cru SCLK_I2S0_8CH>, <&cru HCLK_I2S0_8CH>;resets = <&cru SRST_I2S0_8CH>, <&cru SRST_H_I2S0_8CH>;reset-names = "reset-m", "reset-h";pinctrl-names = "default";pinctrl-0 = <&i2s0_8ch_bus>;power-domains = <&power RK3399_PD_SDIOAUDIO>;status = "disabled";
};
相关文档和代码:
Documentation/devicetree/bindings/sound/rockchip-i2s.txt
sound/soc/rockchip/rockchip_i2s.c
rockchip_i2s.c 做了什么?
Asoc 里的 Platform driver 一般由 CPU 厂商负责编写,但是了解其内部实现有有利于我们宏观把握整个 ASoc 驱动框架。
rockchip_i2s.c 核心工作就是对外提供配置和使能 i2s 接口的能力,它最核心的工作如下。
1) 定义 1个 CPU DAI
static struct snd_soc_dai_driver rockchip_i2s_dai = {.probe = rockchip_i2s_dai_probe,.playback = {.stream_name = "Playback",.channels_min = 2,.channels_max = 8,.rates = SNDRV_PCM_RATE_8000_192000,...},.capture = {.stream_name = "Capture",.channels_min = 2,.channels_max = 2,.rates = SNDRV_PCM_RATE_8000_192000,...},.ops = &rockchip_i2s_dai_ops,
};
一个 snd_soc_dai_driver 就代表着一个 CPU DAI,该结构体提供了这个 CPU DAI的所有能力。
2) 定义CPU DAI 的操作集
static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = {.hw_params = rockchip_i2s_hw_params,.set_sysclk = rockchip_i2s_set_sysclk,.set_fmt = rockchip_i2s_set_fmt,.trigger = rockchip_i2s_trigger,
};
这部分基本就是 i2s 最底层的硬件配置接口了,基本就是围绕着 clocking / format / channel / master-slave 等需求来操作寄存器。这些接口会被 Machine driver 所使用,以和 Codec 端进行配合,一般我们最关心的就是 clock 是否匹配,简化模型如下:
3) 注册 CPU DAI
static int rockchip_i2s_probe(struct platform_device *pdev)
{memcpy(soc_dai, &rockchip_i2s_dai, sizeof(*soc_dai));ret = devm_snd_soc_register_component(&pdev->dev,&rockchip_i2s_component,soc_dai, 1);
}
3.3 查看 Codec driver
DT bindings:
&i2c1 {status = "okay";i2c-scl-rising-time-ns = <150>;i2c-scl-falling-time-ns = <30>;clock-frequency = <200000>;rt5651: rt5651@1a {#sound-dai-cells = <0>;compatible = "rockchip,rt5651";reg = <0x1a>;clocks = <&cru SCLK_I2S_8CH_OUT>;clock-names = "mclk";pinctrl-names = "default";pinctrl-0 = <&i2s_8ch_mclk>;status = "okay";};
};
相关代码和文档:
sound/soc/codecs/rt5651.c
Documentation/sound/alsa/soc/dapm.txt
rt5651.c 里比较关键的点
Audio Codec 的驱动代码都是由 Codec 厂商提供的,了解其内部实现有利于我们根据自己的需求进行定制。一般 Audio Codec里会有如下的关键信息用于表征整个的 Codec 的内部构造。
1) 定义一堆的 snd_kcontrol_new
/* Digital Mixer */
static const struct snd_kcontrol_new rt5651_snd_controls[] = {/* Headphone Output Volume */SOC_DOUBLE_TLV("HP Playback Volume", RT5651_HP_VOL,RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, 39, 1, out_vol_tlv),/* OUTPUT Control */SOC_DOUBLE_TLV("OUT Playback Volume", RT5651_LOUT_CTRL1,RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, 39, 1, out_vol_tlv),...
}static const struct snd_kcontrol_new rt5616_sto1_adc_l_mix[] = {SOC_DAPM_SINGLE("ADC1 Switch", RT5616_STO1_ADC_MIXER,RT5616_M_STO1_ADC_L1_SFT, 1, 1),
};
...
snd_kcontrol_new 是 构造 snd_kcontrol 的原材料。
snd_kcontrol(简称 kcontrol ) 是 Audio Codec 里的一个配置项,一般对应着寄存器里的某个字段。
2) 定义一堆的 Widget
static const struct snd_soc_dapm_widget rt5616_dapm_widgets[] = {SND_SOC_DAPM_SUPPLY("PLL1", RT5616_PWR_ANLG2,RT5616_PWR_PLL_BIT, 0, NULL, 0),...SND_SOC_DAPM_MIXER("Stereo1 ADC MIXL", SND_SOC_NOPM, 0, 0,rt5616_sto1_adc_l_mix,ARRAY_SIZE(rt5616_sto1_adc_l_mix)),...
Widget 是 Audio Codec 里的功能部件,看下面这个示意图会比较容易理解:
Widget 的类型包括:
o Mixer - Mixes several analog signals into a single analog signal.o Mux - An analog switch that outputs only one of many inputs.o PGA - A programmable gain amplifier or attenuation widget.o ADC - Analog to Digital Convertero DAC - Digital to Analog Convertero Switch - An analog switcho Input - A codec input pino Output - A codec output pino Headphone - Headphone (and optional Jack)o Mic - Mic (and optional Jack)o Line - Line Input/Output (and optional Jack)o Speaker - Speakero Supply - Power or clock supply widget used by other widgets.o Regulator - External regulator that supplies power to audio components.o Clock - External clock that supplies clock to audio components.o AIF IN - Audio Interface Input (with TDM slot mask).o AIF OUT - Audio Interface Output (with TDM slot mask).o Siggen - Signal Generator.o DAI IN - Digital Audio Interface Input.o DAI OUT - Digital Audio Interface Output.o DAI Link - DAI Link between two DAI structures */o Pre - Special PRE widget (exec before all others)o Post - Special POST widget (exec after all others)
Widget 可以和某个 kcontrol 绑定在一起,典型的就是 mixer/mux widget。
3) 定义一个描述 Audio Codec 内部 Routing 的结构体: snd_soc_dapm_route
static const struct snd_soc_dapm_route rt5616_dapm_routes[] = {{"IN1P", NULL, "LDO"},{"IN2P", NULL, "LDO"},...{"LOUT L Playback", "Switch", "LOUT MIX"},{"LOUT R Playback", "Switch", "LOUT MIX"},
这里的 Route 有点类似网络中的路由表,路由表中的每一项定义了一段路径。将多个路由器里的某个路径都连接在一起后,就形成一个完整的音频播放 / 录制路径。
第1个参数是目的地;
第2个参数是会用到的 kcontrol,可以为 NULL;
第3个成员是来源;
4) 用一个结构体来汇总上面的所有Codec 描述信息:snd_soc_codec_driver
static struct snd_soc_codec_driver soc_codec_dev_rt5651 = {.probe = rt5651_probe,.suspend = rt5651_suspend,.resume = rt5651_resume,.set_bias_level = rt5651_set_bias_level,.idle_bias_off = true,.controls = rt5651_snd_controls,.num_controls = ARRAY_SIZE(rt5651_snd_controls),.dapm_widgets = rt5651_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(rt5651_dapm_widgets),.dapm_routes = rt5651_dapm_routes,.num_dapm_routes = ARRAY_SIZE(rt5651_dapm_routes),
};
snd_soc_codec_driver 就代表了一个 Codec driver。
5) 注册 codec driver: snd_soc_register_codec()
static int rt5651_i2c_probe() {...ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5651,rt5651_dai, ARRAY_SIZE(rt5651_dai));
}
将 codec driver 注册进系统后,系统就有能力动态地判断是否应该使能 Audio Codec 内部的就某个 Path,只有当 Path 上的各个 Route 是连接的并且有应用程序在使用声卡,才需要真正地给 Audio Codec 上电。
rt5651_dai 是 Codec 端的 DAI,它向 Machine driver 提供配置 Codec 的能力:
static const struct snd_soc_dai_ops rt5651_aif_dai_ops = {.hw_params = rt5651_hw_params,.set_fmt = rt5651_set_dai_fmt,.set_sysclk = rt5651_set_dai_sysclk,.set_pll = rt5651_set_dai_pll,
};static struct snd_soc_dai_driver rt5651_dai[] = {{.name = "rt5651-aif1",.id = RT5651_AIF1,.playback = {.stream_name = "AIF1 Playback",...},.capture = {.stream_name = "AIF1 Capture",...},.ops = &rt5651_aif_dai_ops,},...
到此 Machine driver 就有了协调控制 Platform 端和 Codec 端的能力了。
4. 应用层查看声卡信息
查看所有的 DAI:
$ cat /sys/kernel/debug/asoc/dais
i2s-hifi
i2s-hifi
ff870000.spdif
ff8a0000.i2s
ff880000.i2s // cpu dai
dit-hifi
rt5651-aif2
rt5651-aif1 // codec dai
snd-soc-dummy-dai
查看 Audio Codec 的寄存器:
$ cat /sys/kernel/debug/regmap/1-001a/registers
000: 0000
002: 8888
003: c8c8
005: 0000
00d: 0200
...
查看 Widget 的状态:
$ cat /sys/devices/platform/rt5651-sound/ff880000.i2s-rt5651-aif1/dapm_widget
I2S1 ASRC: Off
I2S2 ASRC: Off
STO1 DAC ASRC: Off
STO2 DAC ASRC: Off
ADC ASRC: Off
...
查看和配置 Kcontrol:
$ tinymix --help
usage: tinymix [options] <command>
options:-h, --help : prints this help message and exits-v, --version : prints this version of tinymix and exits-D, --card NUMBER : specifies the card number of the mixer
commands:get NAME|ID : prints the values of a controlset NAME|ID VALUE : sets the value of a controlcontrols : lists controls of the mixercontents : lists controls of the mixer and their contents
5. 参考
韦东山视频教程/音频专题 https://www.100ask.net/index
Rockchip_RK3399TRM_V1.4_Part1-20170408.pdf
ALC5651 DataSheet_V0.92.pdf
https://wiki.st.com/stm32mpu/wiki/ALSA_overview
RK3399 探索之旅 / Audio 驱动层速读相关推荐
- RICO BOARD驱动探索之旅_环境搭建与点亮LED
今天开始更新我的博客,一个专题: RICO BOARD驱动探索之旅 机缘巧合,我申请到了RICO BOARD,一直想自己玩玩,现在开播!!! 这款板的渊源我就不赘述了,它是以TI Sitara AM4 ...
- 美团外卖商家端视频探索之旅
美团外卖商家端视频探索之旅 背景 美团外卖至今已迅猛发展了六年,随着外卖业务量级与日俱增,单一的文字和图片已无法满足商家的需求,商家迫切需要更丰富的商品描述手段吸引用户,增加流量,进而提高下单转化率和 ...
- Imagepy图像处理框架中neighbors函数的探索之旅
Imagepy图像处理框架中neighbors函数的探索之旅 https://github.com/Image-Py 一.探索函数 1.1 neighbors函数 1.2 结果早知 def neigh ...
- Linux 探索之旅 | 第一部分第四课:磁盘分区 + 完成 Ubuntu 安装
-- 作者 谢恩铭 转载请注明出处 内容简介 第一部分第四课:磁盘分区+完成Ubuntu安装 第一部分第五课预告:Unity桌面,人生若只如初见 磁盘分区+完成Ubuntu安装 上一课Linux探索之 ...
- 网络是怎样连接的 - 探索之旅路线图
虽然Web服务器在收到订单数据之后和销售系统一起对订单进行实际处理的操作很复杂,但其实浏览器和Web服务器之间的交互却很简单,概括如下. (1)浏览器向Web服务器发送请求. (2) Web服务器根据 ...
- BlueTooth: 嵌入式蓝牙网关中蓝牙驱动层研究与实现
0.引言 随着智能终端设备的不断增多,用来连接各种笔记本.PDA.台式电脑等设备的电缆布线会越来越复杂,它不 仅影响了美观性,还增加了安装.维护的费用和难度,蓝牙技术是解决上述问题的有效途径之一,家庭 ...
- 发射、加速与着陆:一群科学家的奥林帕斯探索之旅
有一个关于科学的基本分类,世界上有"已知的已知事物",即我们知道自己已经了解的事物,比如万有引力:有"未知的未知事物",也就是那些我们不知道自己是否了解的事物, ...
- “脑电波”辨别香水喜好,可定制唇色“口红打印机”,AI粉底适配器,新兴美妆元宇宙的探索之旅 | 美通社头条...
美通社消息:欧洲规模最大的科技行业展会之一--2022年Viva Technology峰会(Viva Tech)于6月15至18日在巴黎召开.作为美妆行业先行者,欧莱雅集团带领参观者踏上了一次Web3 ...
- 【Linux探索之旅】第二部分第五课:用户和权限,有权就任性
内容简单介绍 1.第二部分第五课:用户和权限,有权就任性 2.第二部分第六课预告:Nano,刚開始学习的人的文本编辑器 用户和权限.有权就任性 今天的标题也挺任性的啊,虽说小编是一个非常本分的人(真的 ...
最新文章
- 人人都会Vue,你的优势在哪里?
- 个人重构之一般用户实现
- lighttpd+PHP上传文件
- 关于java中多态的理解,涉及到内存空间
- ubuntu11.04解决root不能登录的问题
- php在页面循环输出标签,自定义页面循环
- sublime+virtualenv+pyspark执行的时候报Unable to load native-hadoop library for your platform
- IOS中的事件响应链,事件冒泡机制基本了解
- npm 与yarn CLI 命令比较
- ubutun下虚拟环境的配置
- c语言中关键字的分类,C语言关键字分类整理
- 电脑版微信公众号文章加载不出来,空白的可能解决办法
- 亚瑟王(arthur)
- java实现-现奔跑吧小恐龙-小游戏
- PE、PM、PD、PR分别是什么岗位?
- MS2109高清采集卡 HDMI转USB2.0免驱,支持MJPEG YUV数据格式 demo原理图介绍
- 计算机网络 网际控制报文协议 ICMP
- 【数据结构】堆和二叉堆
- c语言二目运算符. -
- Urban Airship Android Client - Helium Push
热门文章
- python html网页合并单元格,python之DataFrame实现excel合并单元格
- 高级Android面试题
- 涉密外业计算机管理和使用情况,吉林省自然资源厅
- 关于生日的那个经典逻辑题解释
- Spring Cloud + Nacos + Seata整合
- php实现下载url快捷方式
- SAP中采购审批过程PR/PO测试过程及结果记录
- 头歌平台(EduCoder)————软件测试(测试过程与策略)
- 错综复杂!“两桶油”与多个APP上演暧昧秀
- HTML5+CSS3学习笔记(三)文档流和盒子模型【附练习:图片列表、左侧导航栏】