RK3399 Audio驱动讲解 |
您所在的位置:网站首页 › es8388驱动 › 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/1 Subdevice #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/1 Subdevice #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_IN2N2) 查看 RK3399 的 datasheet 关键点: 3) 查看 RK3399 的 TRM 关键点: (Chapter 22 I2S/PCM Controller): 概述 / Overview I2S/PCM controllers 的 features框图 / Block Diagram4) 查看 Audio Codec/Realtek-ALC5651 的 datasheet 关键点: 2.Features4.Function Block and Mixer Path阅读下面的内容需要有 audio driver 相关的开发经验,不过我也会尽量给出必要的概念说明。 Soc Audio 简化模型: DAI 是什么? Digital Audio Interface. Provides audio data to the codec. Formats are usually AC97, I2S, PCMASoc 是什么? 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 driverDT 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 = ; simple-audio-card,name = "realtek,rt5651-codec"; simple-audio-card,format = "i2s"; simple-audio-card,mclk-fs = ; simple-audio-card,hp-det-gpio = ; 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 = ; }; simple-audio-card,codec { sound-dai = ; }; };这里没有选择自己编写 Machine driver,而是采用了 simple-audio-card 这个通用的 Machine driver。 在 simple-audio-card 已经够用的情况下,建议优先使用 simple-audio-card 框架,代码会更加简洁一些。 相关文档和代码: Documentation/devicetree/bindings/sound/simple-card.txtDocumentation/devicetree/bindings/sound/widgets.txtDocumentation/sound/alsa/soc/machine.txtsound/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 = ; }; simple-audio-card,codec { sound-dai = ; };指明了: 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 driverDT bindings: arch/arm64/boot/dts/rockchip/rk3399.dtsi i2s0: i2s@ff880000 { compatible = "rockchip,rk3399-i2s", "rockchip,rk3066-i2s"; reg = ; rockchip,grf = ; interrupts = ; dmas = , ; dma-names = "tx", "rx"; clock-names = "i2s_clk", "i2s_hclk"; clocks = , ; resets = , ; reset-names = "reset-m", "reset-h"; pinctrl-names = "default"; pinctrl-0 = ; power-domains = ; status = "disabled"; };相关文档和代码: Documentation/devicetree/bindings/sound/rockchip-i2s.txtsound/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 driverDT bindings: &i2c1 { status = "okay"; i2c-scl-rising-time-ns = ; i2c-scl-falling-time-ns = ; clock-frequency = ; rt5651: rt5651@1a { #sound-dai-cells = ; compatible = "rockchip,rt5651"; reg = ; clocks = ; clock-names = "mclk"; pinctrl-names = "default"; pinctrl-0 = ; status = "okay"; }; };相关代码和文档: sound/soc/codecs/rt5651.cDocumentation/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 Converter o DAC - Digital to Analog Converter o Switch - An analog switch o Input - A codec input pin o Output - A codec output pin o Headphone - Headphone (and optional Jack) o Mic - Mic (and optional Jack) o Line - Line Input/Output (and optional Jack) o Speaker - Speaker o 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] 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 control set NAME|ID VALUE : sets the value of a control controls : lists controls of the mixer contents : lists controls of the mixer and their contents 5. 参考 韦东山视频教程/音频专题 https://www.100ask.net/indexRockchip_RK3399TRM_V1.4_Part1-20170408.pdfALC5651 DataSheet_V0.92.pdfhttps://wiki.st.com/stm32mpu/wiki/ALSA_overview 思考技术,也思考人生要学习技术,更要学习如何生活。 你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。 对 嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,关注公众号:嵌入式Hacker。 觉得文章对你有价值,不妨点个 在看和赞。 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |