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_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

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现在Unity2D中点击某个物体后,物体改变速度,并在一段时间后恢复原始速度,可以按照以下步骤进行: 1. 创建物体:首先创建一个物体,可以使用Unity的模型编辑器或导入自定义模型。 2. 添加组件:给物体添加刚体组件和碰撞器组件,以便进行物理模拟。 3. 编写脚本:创建一个脚本来控制物体的速度变化和复原。在脚本中,可以使用协程来延迟一段时间后恢复原始速度。 ```csharp using UnityEngine; public class ObjectController : MonoBehaviour { public float originalSpeed = 5f; // 物体的原始速度 public float changedSpeed = 10f; // 物体改变后的速度 public float restoreDelay = 2f; // 物体恢复原始速度的延迟时间 private Rigidbody2D objectRigidbody; private float currentSpeed; void Start() { objectRigidbody = GetComponent<Rigidbody2D>(); currentSpeed = originalSpeed; } void Update() { // 点击鼠标左键时改变物体速度 if (Input.GetMouseButtonDown(0)) { Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); Collider2D collider = Physics2D.OverlapPoint(mousePosition); if (collider != null && collider.gameObject == gameObject) { ChangeSpeed(changedSpeed); StartCoroutine(RestoreSpeed(restoreDelay)); } } // 应用物体速度 Vector2 velocity = transform.up * currentSpeed; objectRigidbody.velocity = velocity; } void ChangeSpeed(float newSpeed) { currentSpeed = newSpeed; } System.Collections.IEnumerator RestoreSpeed(float delay) { yield return new WaitForSeconds(delay); currentSpeed = originalSpeed; } } ``` 4. 在场景中放置物体实例:在场景中放置一个物体的实例,并将物体控制脚本(ObjectController)添加到物体的GameObject上。 通过以上步骤,当点击该物体时,物体的速度将改变为指定的速度,并在一段时间后恢复原始速度。你可以根据需要调整原始速度、改变后的速度和恢复延迟时间。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值