Edit online

关键流程设计

2 Dec 2024
Read time: 4 minute(s)

操作函数集实现

在 S 的驱动设计中,snd_soc_dai_ops 是一个非常重要的结构体,它是 dai 的操作函数集,所有对 S 接口的设置都是通过此结构体完成。所以,I2S 驱动中一项非常重要的部分就是实现此结构体中的函数接口。snd_soc_dai_ops 函数集可以分为如下几个部分:

  1. cpu_dai 时钟配置函数,通常由 machine 驱动调用

    • set_sysclk:设置 dai 的主时钟 MCLK

    • set_clkdiv:设置分频系数,用于实现 BCLK 和 LRCK 的分频系数

    • set_bclk_ratio:设置 BCLK 和 LRCK 的比率

  2. cpu_dai 格式设置,通常由 machine 驱动调用

    • set_fmt:设置主从模式(LRCK 和 BCLK 时钟由 SOC 提供还是由 codec 提供),BCLK 和 LRCK 的极性,以及传输模式

    • set_tdm_slot:cpu_dai 支持时分复用时,用于设置时分复用的 slot

    • set_channel_map:声道时分复用时的映射关系设置

  3. ALSA PCM 音频操作,由 ALSA 的 soc-core 在执行音频操作时调用

    • hw_params:硬件参数设置,一般用于采样精度,通道位宽的设置

    • trigger:命令触发函数,用于执行音频数据传输的开始、结束、暂停、恢复等

在 S 的驱动中,需要实现的接口有:
static const struct snd_soc_dai_ops aic_i2s_dai_ops = {
        .set_sysclk = aic_i2s_set_sysclk,
        .set_bclk_ratio = aic_i2s_set_bclk_ratio,
        .set_fmt = aic_i2s_set_fmt,
        .set_tdm_slot = aic_i2s_set_tdm_slot,
        .hw_params = aic_i2s_hw_params,
        .trigger = aic_i2s_trigger,
};

在实现的几个接口函数中,除 params 和 trigger 外,其它函数是需要在 machine 驱动中根据 I2S 和 codec 双方所支持的格式、时钟等进行调用设置的,使 S 和 codec 两边的格式设置相同。

I2S 时钟设置

  1. MCLK 主时钟设置。

    MCLK 是 S 的主时钟,主要作用是向外部的 codec 芯片提供工作时钟,由 S 模块的工作时钟分频得到。在驱动中由 sysclk 设置 MCLK 的频率,MCLK 一般采用 128fs,256fs,512fs 的表示方式,具体的设置需要参考实际使用的 codec 芯片规格书。Fs 是采样频率,常见的采样频率有 44.1khz,48khz,32khz 等,可以据此算出 MCLK 的频率值。一般会在 machine 驱动中调用设置 MCLK 的函数。

  2. LRCK 和 BCLK 左右声道时钟设置。

    LRCK 是左右声道时钟。LRCK 的时钟频率等于 fs,在 D211 中,通过 PERIOD 位域设置 LRCK 的频率,LRCK_PERIOD 表示一个 LRCK 时钟周期内,有多少个 BCLK 周期。在 S 模式下,若为立体声(2 通道),32bit 采样深度,则 BCLK=64fs,则 PERIOD 应设置为(64/2-1)。若为 4 通道,24bit 采样深度,则 BCLK=96fs,则 PERIOD 应设置为(96/2-1)。由采样频率可以算出 BCLK 时钟的频率。并由 BCLK 的频率算出 LRCK,即采样率。

  3. period bytes 对齐
    在使用 DMA 传输音频数据时,DMA 要求每次传输的数据长度必须 128bytes/8bytes 对齐。在 ALSA 框架下,音频数据以 period 为周期调用 DMA 传输,每次传输的数据长度为 bytes。所以,必须满足 bytes 按照 128bytes/8bytes 对齐。ALSA 中提供了相应的 API 接口(snd_pcm_hw_constraint_step)来满足这一需求。
    static int aic_i2s_startup(struct snd_pcm_substream *substream,
                struct snd_soc_dai *dai)
    {
        int ret;
    
        /* Make sure that the period bytes are 8/128 bytes aligned according to
        * the DMA transfer requested.
        */
        if (of_device_is_compatible(dai->dev->of_node,
            "artinchip,aic-i2s-v1.0")) {
            ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
                        SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 8);
            if (ret < 0) {
                dev_err(dai->dev,
                    "Could not apply period step: %d\n", ret);
                return ret;
            }
    
            ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
                        SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 8);
            if (ret < 0) {
                dev_err(dai->dev,
                    "Could not apply buffer step: %d\n", ret);
                return ret;
            }
        } else {
            ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
                        SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
            if (ret < 0) {
                dev_err(dai->dev,
                    "Could not apply period step: %d\n", ret);
                return ret;
            }
    
            ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
                        SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
            if (ret < 0) {
                dev_err(dai->dev,
                    "Could not apply buffer step: %d\n", ret);
                return ret;
            }
        }
    
        return ret;
    }

Record 流程

  • init 流程
    1. 初始化 DMA 传输的起始地址、buf_len 以及 period_len

      I2S 模块使用 DMA 传输音频数据,DMA 采用环形链表形式,依次将音频数据传送到硬件。所以需要配置 DMA 传输时的起始地址(即 RX buffer 地址)以及 buf_lenperiod_len

    2. 注册 HAL 层的回调函数。

      在 Driver 层驱动,将 buf_len 配置为 period_len 的 2 倍,DMA 每传输 period_len 长度的数据,触发一次 DMA 中断,通知 CPU 向 pipe 设备写入数据。

  • start 流程

    按照 RT-Thread audio 的框架,在执行 rt_device_open 时,就会调用 start 流程,开始音频的录制,然后再通过 rt_device_control 设置音频的格式(采样率,通道数等)。按照这个流程,最开始可能会录制一段不符合设置的音频格式的数据,这显然是不合理的。所以,在 Driver 层的驱动实现中,start 流程并未做任何处理,而是在设置完音频格式后才开始音频的录制。

  • DMA 中断流程

    DMA 每传输完 period_len 长度的数据后,触发一次 DMA 中断,然后通过 DMA 回调函数的逐级调用,最终调用 rt_audio_rx_done,将 RX buffer 的数据写入到 pipe 设备,每次写入 period_len 长度的音频数据。