1. 程式人生 > >Linux ALSA 音訊系統:物理鏈路篇

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

其中有著各種功能部件,包括但不限於 :

Widget Description
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                                  |            +-----------+