我是靠谱客的博主 动听世界,最近开发中收集的这篇文章主要介绍RK3399 探索之旅 / Audio 驱动层速读1. 测试功能2. 浏览硬件信息3. 查看 driver 层4. 应用层查看声卡信息5. 参考思考技术,也思考人生,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

哈喽,老吴又来分享学习心得啦!另外,欢迎大家加入嵌入式Hacker微信群~先加我,我再拉你。

目的:

  • 从驱动开发的角度大致了解一下 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_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 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] <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 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/index

  • Rockchip_RK3399TRM_V1.4_Part1-20170408.pdf

  • ALC5651 DataSheet_V0.92.pdf

  • https://wiki.st.com/stm32mpu/wiki/ALSA_overview


思考技术,也思考人生

要学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,关注公众号:嵌入式Hacker

觉得文章对你有价值,不妨点个 在看和赞

最后

以上就是动听世界为你收集整理的RK3399 探索之旅 / Audio 驱动层速读1. 测试功能2. 浏览硬件信息3. 查看 driver 层4. 应用层查看声卡信息5. 参考思考技术,也思考人生的全部内容,希望文章能够帮你解决RK3399 探索之旅 / Audio 驱动层速读1. 测试功能2. 浏览硬件信息3. 查看 driver 层4. 应用层查看声卡信息5. 参考思考技术,也思考人生所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部