Linux ALSA 音频系统:物理链路篇

Posted zyuanyun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux ALSA 音频系统:物理链路篇相关的知识,希望对你有一定的参考价值。

1. Overview

硬件平台及软件版本:

  • Kernel - 3.4.5
  • SoC - Samsung exynos
  • CODEC - WM8994
  • Machine - goni_wm8994
  • Userspace - tinyalsa

Linux ALSA 音频系统架构大致如下:

              +--------+  +--------+  +--------+
              |tinyplay|  |tinycap |  |tinymix |
              +--------+  +--------+  +--------+
                   |           ^           ^ 
                   V           |           V
              +--------------------------------+
              |        ALSA Library API        |
              |      (tinyalsa, alsa-lib)      |
              +--------------------------------+
  user space                   ^
-------------------------------|---------------------
  kernel space                 V
              +--------------------------------+
              |           ALSA CORE            |
              | +-------+ +-------+ +------+   |
              | |  PCM  | |CONTROL| | MIDI |...|
              | +-------+ +-------+ +------+   |
              +--------------------------------+
                               |
              +--------------------------------+
              |           ASoC CORE            |
              +--------------------------------+
                               |
              +--------------------------------+
              |        hardware driver         |
              |  +-------+ +--------+ +-----+  |
              |  |Machine| |Platform| |Codec|  |
              |  +-------+ +--------+ +-----+  |
              +--------------------------------+
  • Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制
  • ALSA Library API:alsa 用户库接口,常见有 tinyalsa、alsa-lib
  • ALSA CORE:alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC)
  • ASoC CORE:asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系
  • Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec

本主题不遵循自顶而下的原则,而先从硬件设备驱动说起,毕竟这些是看得见摸得着听得到的东西,容易对其有着直观的理解。

//
// 声明:本文由 http://blog.csdn.net/zyuanyun 原创,转载请注明出处,谢谢!
//

ALSA/ASoC 中硬件设备关系:

+------------------------------------------+
|                 Machine                  |
|  +--------------+      +--------------+  |
|  |   Platform   |      |     Codec    |  |
|  |              | I2S  |              |  |
|  |       cpu_dai|<---->|codec_dai     |  |
|  |              |      |              |  |
|  +--------------+      +--------------+  |
+------------------------------------------+
  • Platform:指某款 SoC 平台的音频模块,如 exynos、omap、qcom 等等。Platform 又可细分两部分:

    • cpu dai:在嵌入式系统里面通常指 SoC 的 I2S、PCM 总线控制器,负责把音频数据从 I2S tx FIFO 搬运到 CODEC(这是回放的情形,录制则方向相反)。cpu_dai 通过 snd_soc_register_dai() 来注册。注:DAI 是 Digital Audio Interface 的简称,分为 cpu_dai 和 codec_dai,这两者通过 I2S/PCM 总线连接;AIF 是 Audio Interface 的简称,嵌入式系统中一般是 I2S 和 PCM 接口。
    • pcm dma:负责把 dma buffer 中的音频数据搬运到 I2S tx FIFO。值得留意的是:某些情形下是不需要 dma 操作的,比如 Modem 和 CODEC 直连,因为 Modem 本身已经把数据送到 FIFO 了,这时只需启动 codec_dai 接收数据即可;该情形下,Machine 驱动 dai_link 中需要设定 .platform_name = "snd-soc-dummy", 这是虚拟 dma 驱动,实现见 sound/soc/soc-utils.c。音频 dma 驱动通过 snd_soc_register_platform() 来注册,故也常用 platform 来指代音频 dma 驱动(这里的 platform 需要与 SoC Platform 区分开)。
  • Codec:对于回放来说,userspace 送过来的音频数据是经过采样量化的数字信号,在 codec 经过 DAC 转换成模拟信号然后输出到外放或耳机,这样我们就可以听到声音了。Codec 字面意思是编解码器,但芯片里面的功能部件很多,常见的有 AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的 codec 芯片还有 EQ、DSP、SRC、DRC、AGC、Echo-Canceller、Noise-Suppression 等部件。

  • Machine:指某款机器,通过配置 dai_link 把 cpu_dai、codec_dai、modem_dai 各个音频接口给链结成一条条音频链路,然后注册 snd_soc_card。和上面两个不一样,Platform 和 CODEC 驱动一般是可以重用的,而 Machine 有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:SoC Platform 与 Codec 的差异;DAIs 之间的链结方式;通过某个 GPIO 打开 Amplifier;通过某个 GPIO 检测耳机插拔;使用某个时钟如 MCLK/External-OSC 作为 I2S、CODEC 的时钟源等等。

从上面的描述来看,对于回放的情形,PCM 数据流向大致是:

        copy_from_user           DMA                 I2S           DAC
              ^                   ^                   ^             ^
+---------+   |    +----------+   |   +-----------+   |   +-----+   |   +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+        +----------+       +-----------+       +-----+       +------+

几个音频物理链路的概念:

dai_link:machine 驱动中定义的音频数据链路,它指定链路用到的 codec、codec_dai、cpu_dai、platform。比如对于 goni_wm8994 平台的 media 链路:codec="wm8994-codec"、codec_dai="wm8994-aif1"、cpu_dai="samsung-i2s"、platform="samsung-audio",这四者就构成了一条音频数据链路用于多媒体声音的回放和录制。一个系统可能有多个音频数据链路,比如 media 和 voice,因此可以定义多个 dai_link 。如 WM8994 的典型设计,有三个 dai_link,分别是 AP<>AIF1 的 “HIFI”(多媒体声音链路),BP<>AIF2 的 “Voice”(通话语音链路),以及 BT<>AIF3(蓝牙 SCO 语音链路)。

WM8994 Typical AIF Connections

代码如下:

static struct snd_soc_dai_link goni_dai[] = {
{
    .name = "WM8994",
    .stream_name = "WM8994 HiFi",
    .cpu_dai_name = "samsung-i2s.0",
    .codec_dai_name = "wm8994-aif1",
    .platform_name = "samsung-audio",
    .codec_name = "wm8994-codec.0-001a",
    .init = goni_wm8994_init,
    .ops = &goni_hifi_ops,
}, {
    .name = "WM8994 Voice",
    .stream_name = "Voice",
    .cpu_dai_name = "goni-voice-dai",
    .codec_dai_name = "wm8994-aif2",
    .codec_name = "wm8994-codec.0-001a",
    .ops = &goni_voice_ops,
},
};

hw constraints:指平台本身的硬件限制,如所能支持的通道数/采样率/数据格式、DMA 支持的数据周期大小(period size)、周期次数(period count)等,通过 snd_pcm_hardware 结构体描述:

static const struct snd_pcm_hardware dma_hardware = {
    .info           = SNDRV_PCM_INFO_INTERLEAVED |
                    SNDRV_PCM_INFO_BLOCK_TRANSFER |
                    SNDRV_PCM_INFO_MMAP |
                    SNDRV_PCM_INFO_MMAP_VALID |
                    SNDRV_PCM_INFO_PAUSE |
                    SNDRV_PCM_INFO_RESUME,
    .formats        = SNDRV_PCM_FMTBIT_S16_LE |
                    SNDRV_PCM_FMTBIT_U16_LE |
                    SNDRV_PCM_FMTBIT_U8 |
                    SNDRV_PCM_FMTBIT_S8,
    .channels_min       = 2,
    .channels_max       = 2,
    .buffer_bytes_max   = 128*1024,
    .period_bytes_min   = PAGE_SIZE,
    .period_bytes_max   = PAGE_SIZE*2,
    .periods_min        = 2,
    .periods_max        = 128,
    .fifo_size      = 32,
};

hw params:用户层设置的硬件参数,如 channels、sample rate、pcm format、period size、period count;这些参数受 hw constraints 约束。

sw params:用户层设置的软件参数,如 start threshold、stop threshold、silence threshold。

2. ASoC

ASoC:ALSA System on Chip,是建立在标准 ALSA 驱动之上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系,它依赖于标准 ALSA 驱动框架。内核文档 Documentation/alsa/soc/overview.txt 中详细介绍了 ASoC 的设计初衷,这里不一一引用,简单陈述如下:

  • 独立的 codec 驱动,标准的 ALSA 驱动框架里面 codec 驱动往往与 SoC/CPU 耦合过于紧密,不利于在多样化的平台/机器上移植复用
  • 方便 codec 与 SoC 通过 PCM/I2S 总线建立链接
  • 动态音频电源管理 DAPM,使得 codec 任何时候都工作在最低功耗状态,同时负责音频路由的创建
  • POPs 和 click 音抑制弱化处理,在 ASoC 中通过正确的音频部件上下电次序来实现
  • Machine 驱动的特定控制,比如耳机、麦克风的插拔检测,外放功放的开关

在概述中已经介绍了 ASoC 硬件设备驱动的三大构成:Codec、Platform 和 Machine,下面列举各驱动的功能构成:

ASoC Codec Driver

  • Codec DAI 和 PCM 的配置信息
  • Codec 的控制接口,如 I2C/SPI
  • Mixer 和其他音频控件
  • Codec 的音频接口函数,见 snd_soc_dai_ops 结构体定义
  • DAPM 描述信息
  • DAPM 事件处理句柄
  • DAC 数字静音控制

ASoC Platform Driver: 包括 dma 和 cpu_dai 两部分:

  • dma 驱动实现音频 dma 操作,具体见 snd_pcm_ops 结构体定义
  • cpu_dai 驱动实现音频数字接口控制器的描述和配置

ASoC Machine Driver

  • 作为链结 Platform 和 Codec 的载体,它必须配置 dai_link 为音频数据链路指定 Platform 和 Codec
  • 处理机器特有的音频控件和音频事件,例如回放时打开外放功放

硬件设备驱动相关结构体:

  • snd_soc_codec_driver:音频编解码芯片描述及操作函数,如控件/微件/音频路由的描述信息、时钟配置、IO 控制等
  • snd_soc_dai_driver:音频数据接口描述及操作函数,根据 codec 端和 soc 端,分为 codec_dai 和 cpu_dai
  • snd_soc_platform_driver:音频 dma 设备描述及操作函数
  • snd_soc_dai_link:音频链路描述及板级操作函数

下面是 goni_wm8994 类图,从这个类图中,我们可以大致了解 goni_wm8994 整个音频驱动组成:

ASoC_goni_wm8994

3. Codec

上一章提到 codec_drv 的几个组成部分,下面逐一介绍,基本是以内核文档 Documentation/sound/alsa/soc/codec.txt 中的内容为脉络来分析的。Codec 的作用,之前已有描述,本章主要罗列下 Codec driver 中重要的数据结构及注册流程。

我们先看看 Codec 的硬件框图,以 WM8994 为例:

WM8994 Block Diagram

其中有着各种功能部件,包括但不限于 :

WidgetDescription
ADC把麦克风拾取的模拟信号转换成数字信号
DAC把音频接口过来的数字信号转换成模拟信号
AIF音频数字接口,用于 Codec 与其他器件(如AP、BB等)之间的数据传输
MIXER混音器,把多路输入信号混合成单路输出
DRC动态范围调节
LHPF高低通滤波

3.1. Codec DAI and PCM configuration

codec_dai 和 pcm 配置信息通过结构体 snd_soc_dai_driver 描述,包括 dai 的能力描述和操作接口,snd_soc_dai_driver 最终会被注册到 soc-core 中。

/*
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver {
    /* DAI description */
    const char *name;
    unsigned int id;
    int ac97_control;

    /* DAI driver callbacks */
    int (*probe)(struct snd_soc_dai *dai);
    int (*remove)(struct snd_soc_dai *dai);
    int (*suspend)(struct snd_soc_dai *dai);
    int (*resume)(struct snd_soc_dai *dai);

    /* ops */
    const struct snd_soc_dai_ops *ops;

    /* DAI capabilities */
    struct snd_soc_pcm_stream capture;
    struct snd_soc_pcm_stream playback;
    unsigned int symmetric_rates:1;

    /* probe ordering - for components with runtime dependencies */
    int probe_order;
    int remove_order;
};
  • name:codec_dai 的名称标识,dai_link 通过配置 codec_dai_name 来找到对应的 codec_dai;
  • probe:codec_dai 的初始化函数,注册声卡时回调;
  • playback:回放能力描述,如回放设备所支持的声道数、采样率、音频格式;
  • capture:录制能力描述,如录制设备所支持声道数、采样率、音频格式;
  • ops:codec_dai 的操作函数集,这些函数集非常重要,用于 dai 的时钟配置、格式配置、硬件参数配置。

例子,wm8994 有三个 dai,这里只列其一:

static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
    .set_sysclk = wm8994_set_dai_sysclk,
    .set_fmt    = wm8994_set_dai_fmt,
    .hw_params  = wm8994_hw_params,
    .shutdown   = wm8994_aif_shutdown,
    .digital_mute   = wm8994_aif_mute,
    .set_pll    = wm8994_set_fll,
    .set_tristate   = wm8994_set_tristate,
};

static struct snd_soc_dai_driver wm8994_dai[] = {
    {
        .name = "wm8994-aif1",
        .id = 1,
        .playback = {
            .stream_name = "AIF1 Playback",
            .channels_min = 1,
            .channels_max = 2,
            .rates = WM8994_RATES,
            .formats = WM8994_FORMATS,
            .sig_bits = 24,
        },
        .capture = {
            .stream_name = "AIF1 Capture",
            .channels_min = 1,
            .channels_max = 2,
            .rates = WM8994_RATES,
            .formats = WM8994_FORMATS,
            .sig_bits = 24,
         },
        .ops = &wm8994_aif1_dai_ops,
    },
    // ......

3.2. Codec control IO

移动设备的音频 Codec,其控制接口一般是 I2C 或 SPI,控制接口用于读写 codec 的寄存器。在 snd_soc_codec_driver 结构体中,有如下字段描述 Codec 的控制接口:

    /* codec IO */
    unsigned int (*read)(struct snd_soc_codec *, unsigned int);
    int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
    int (*display_register)(struct snd_soc_codec *, char *,
                size_t, unsigned int);
    int (*volatile_register)(struct snd_soc_codec *, unsigned int);
    int (*readable_register)(struct snd_soc_codec *, unsigned int);
    int (*writable_register)(struct snd_soc_codec *, unsigned int);
    unsigned int reg_cache_size;
    short reg_cache_step;
    short reg_word_size;
    const void *reg_cache_default;
    short reg_access_size;
    const struct snd_soc_reg_access *reg_access_default;
    enum snd_soc_compress_type compress_type;
  • read:读寄存器;
  • write:写寄存器;
  • volatile_register:判断指定的寄存器是否是 volatile 属性;假如是,则读取寄存器时不是读 cache,而直接访问硬件;
  • readable_register:判断指定的寄存器是否可读;
  • reg_cache_default:寄存器的缺省值;
  • reg_cache_size:缺省的寄存器值数组大小;
  • reg_word_size:寄存器宽度。

在 Linux-3.4.5 中,很多 codec 的控制接口都改用 regmap 了。soc-core 中判断是否用的是 regmap,如果是,则调用 regmap 接口,见如下函数:

int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
                unsigned int mask, unsigned int value)
{
    bool change;
    unsigned int old, new;
    int ret;

    if (codec->using_regmap) {
        // 当前使用 regmap,调用 regmap 接口,其中 codec->control_data 是 regmap 私有数据
        ret = regmap_update_bits_check(codec->control_data, reg,
                           mask, value, &change);
    } else {
        // 非 regmap,调用 snd_soc_codec_driver 实现的 read/write 回调
        ret = snd_soc_read(codec, reg);
        if (ret < 0)
            return ret;

        old = ret;
        new = (old & ~mask) | (value & mask);
        change = old != new;
        if (change)
            ret = snd_soc_write(codec, reg, new);
    }

    if (ret < 0)
        return ret;

    return change;
}

使用 regmap,使得控制接口抽象化,codec_drv 不用关心当前控制方式是什么;regmap 在线调试目录是 /sys/kernel/debug/regmap。关于 wm8994 的 regmap 描述,请自行查阅 driver/mfd/wm8994-regmap.c

3.3. Mixers and audio controls

音频控件多用于部件开关和音量的设定,音频控件可通过 soc.h 中的宏来定义,例如单一型控件:

#define SOC_SINGLE(xname, reg, shift, max, invert) \\
{   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \\
    .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\\
    .put = snd_soc_put_volsw, \\
    .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }

这种控件只有一个设置量,一般用于部件开关。宏定义的参数说明:

  • xname:控件的名称标识;
  • reg:控件对应的寄存器地址;
  • shift:控件控制位在寄存器中的偏移;
  • max:控件设置值范围;
  • invert:设定值是否取反。

其他类型控件类似,不一一介绍了。

上述只是宏定义,音频控件真正的结构是 snd_kcontrol_new

struct snd_kcontrol_new {
    snd_ctl_elem_iface_t iface; /* interface identifier */
    unsigned int device;        /* device/client number */
    unsigned int subdevice;     /* subdevice (substream) number */
    const unsigned char *name;  /* ASCII name of item */
    unsigned int index;     /* index of item */
    unsigned int access;        /* access rights */
    unsigned int count;     /* count of same elements */
    snd_kcontrol_info_t *info;
    snd_kcontrol_get_t *get;
    snd_kcontrol_put_t *put;
    union {
        snd_kcontrol_tlv_rw_t *c;
        const unsigned int *p;
    } tlv;
    unsigned long private_value;
};

Codec 初始化时,通过 snd_soc_add_codec_controls() 把所有定义好的音频控件注册到 alsa-core ,上层可以通过 tinymix、alsa_amixer 等工具查看修改这些控件的设定。

3.4. Codec audio operations

Codec 音频操作接口通过结构体 snd_soc_dai_ops 描述:

struct snd_soc_dai_ops {
    /*
     * DAI clocking configuration, all optional.
     * Called by soc_card drivers, normally in their hw_params.
     */
    int (*set_sysclk)(struct snd_soc_dai *dai,
        int clk_id, unsigned int freq, int dir);
    int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
        unsigned int freq_in, unsigned int freq_out);
    int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);

    /*
     * DAI format configuration
     * Called by soc_card drivers, normally in their hw_params.
     */
    int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
    int (*set_tdm_slot)(struct snd_soc_dai *dai,
        unsigned int tx_mask, unsigned int rx_mask,
        int slots, int slot_width);
    int (*set_channel_map)(struct snd_soc_dai *dai,
        unsigned int tx_num, unsigned int *tx_slot,
        unsigned int rx_num, unsigned int *rx_slot);
    int (*set_tristate)(struct snd_soc_dai *dai, int tristate);

    /*
     * DAI digital mute - optional.
     * Called by soc-core to minimise any pops.
     */
    int (*digital_mute)(struct snd_soc_dai *dai, int mute);

    /*
     * ALSA PCM audio operations - all optional.
     * Called by soc-core during audio PCM operations.
     */
    int (*startup)(struct snd_pcm_substream *,
        struct snd_soc_dai *);
    void (*shutdown)(struct snd_pcm_substream *,
        struct snd_soc_dai *);
    int (*hw_params)(struct snd_pcm_substream *,
        struct snd_pcm_hw_params *, struct snd_soc_dai *);
    int (*hw_free)(struct snd_pcm_substream *,
        struct snd_soc_dai *);
    int (*prepare)(struct snd_pcm_substream *,
        struct snd_soc_dai *);
    int (*trigger)(struct snd_pcm_substream *, int,
        struct snd_soc_dai *);
    /*
     * For hardware based FIFO caused delay reporting.
     * Optional.
     */
    snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
        struct snd_soc_dai *);
};

注释比较详细的了,Codec 音频操作接口分为 5 大部分:时钟配置、格式配置、数字静音、PCM 音频接口、FIFO 延迟。着重说下时钟配置及格式配置接口:

  • set_sysclk:codec_dai 系统时钟设置,当上层打开 pcm 设备时,需要回调该接口设置 Codec 的系统时钟,Codec 才能正常工作;
  • set_pll:Codec FLL 设置,Codec 一般接了一个 MCLK 输入时钟,回调该接口基于 MCLK 来产生 Codec FLL 时钟,接着 codec_dai 的 sysclk、bclk、lrclk 均可从 FLL 分频出来(假设 Codec 作为 master);
  • set_fmt:codec_dai 格式设置,具体见 soc-dai.h
    • SND_SOC_DAIFMT_I2S:音频数据是 I2S 格式,常用于多媒体音频;
    • SND_SOC_DAIFMT_DSP_A:音频数据是 PCM 格式,常用于通话语音;
    • SND_SOC_DAIFMT_CBM_CFM:Codec 作为 master,BCLK 和 LRCLK 由 Codec 提供;
    • SND_SOC_DAIFMT_CBS_CFS:Codec 作为 slave,BCLK 和 LRCLK 由 SoC/CPU 提供;
  • hw_params:codec_dai 硬件参数设置,根据上层设定的声道数、采样率、数据格式,来配置 codec_dai 相关寄存器。

以上接口一般在 Machine 驱动中回调,我们看看 Machine 驱动 goni_wm8994.cgoni_hifi_hw_params() 函数:

static int goni_hifi_hw_params(struct snd_pcm_substream *substream,
        struct snd_pcm_hw_params *params)
{
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    struct snd_soc_dai *codec_dai = rtd->codec_dai;
    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
    unsigned int pll_out = 24000000; // 这是 MCLK 的时钟频率,Codec 的源时钟
    int ret = 0;

    /* set the cpu DAI configuration */
    ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
            SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
    if (ret < 0)
        return ret;

    /* set codec DAI configuration */
    ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
            SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
    if (ret < 0)
        return ret;

    /* set the codec FLL */
    ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out,
            params_rate(params) * 256);
    if (ret < 0)
        return ret;

    /* set the codec system clock */
    ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
            params_rate(params) * 256, SND_SOC_CLOCK_IN);
    if (ret < 0)
        return ret;

    return 0;
}

其中 snd_soc_dai_set_fmt() 实际上会调用 cpu_dai 或 codec_dai 的 set_fmt() 回调, snd_soc_dai_set_pll()snd_soc_dai_set_sysclk() 也类似。

  • MCLK 作为 Codec 的源时钟,频率为 24Mhz;
  • 设置 cpu_dai 和 codec_dai 格式:数据格式是 I2S;Codec 作为 master,BCLK 和 LRCLK 由 Codec 提供;
  • 设置 codec_dai 的 FLL1:时钟源是 MCLK,时钟源频率是 24Mhz,目的时钟频率是 256fs(fs 是采样频率);
  • 设置 codec_dai 的系统时钟:时钟源是 FLL1,系统时钟频率是 256fs。

对于 dai(codec_dai 和 cpu_dai),都要非常留意时钟设置,它很关键又复杂,设置错误将会导致很多问题,典型如下:

  • 系统无声:检查 Codec 系统时钟、codec_dai 位时钟和帧时钟是否使能;
  • 声音失真:检查音频数据的采样率是否和 codec_dai 帧时钟一致;
  • 断续破音:检查 Codec 系统时钟和位时钟、帧时钟是否同步,出现这种情况,可能是因为 sysclk 和 BCLK/LRCLK 不是由同一个时钟源分频出来的。

如下是一个典型的音频系统时钟设置(Codec works as master mode):

+---------------------------------------------------------------------- -+
|                                 CODEC                                  |            +-----------+
|           +---------+                                                  |            |           |
| SLIMCLK+-->         |                                                  |            |           |
|           |         |                                                  |            |           |
| AIFnBCLK+->         |                                    +---------+   |            |           |
|           |         |   +-----+                          |         |   |            |           |
| AIFnLRCLK+> FLL_SRC +---> FLL +---+                      |         +---->AIFnBCLK+-->           |
|           |         |   +-----+   |                      |         |   |            |           |
|  MCLK1+--->         |             |       +---------+    |         +---->AIFnLRCLK+->           |
|   ^       |         |             +------->         |    |         |   |            | Processor |
|   | MCLK2+>         |                     |         |    |   AIFn  |   |            |           |
|   |  ^    +---------+           SLIMCLK+-->         |    |         |   |            |           |
|   |  |                                    |         +---->         <----+AIFnRX<----+           |
|   |  |                          AIFnBCLK+-> SYSCLK  |    |         |   |            |           |
|   |  |                                    |         |    |         +----+AIFnTX+---->           |
|   |  |                          MCLK1+---->         |    |         |   |            |           |
|   |  |                                    |         |    +---------+   |            |           |
|   |  |                          MCLK2+---->         |                  |            |           |
|   |  |                                    +---------+                  |            |           |
+------+-----------------------------------------------------------------+            +-----------+
    |
  +-+----------+
  | Oscillator |
  +------------+
                      AIF Master Mode, Using MCLK and FLL as Reference