现在的位置: 首页 > 综合 > 正文

PCM data flow之二:ASoC data structure

2013年08月28日 ⁄ 综合 ⁄ 共 6634字 ⁄ 字号 评论关闭

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

·          独立的codec驱动,标准的ALSA驱动框架里面codec驱动往往与SoC CPU耦合过于紧密,不利于在多样化的平台/机器上移植复用。

·          方便codec与SoC通过音频总线PCM/I2S建立链接,这通常在machine驱动里面通过dai_link定义的codec_dai_name和cpu_dai_name来匹配绑定对应的codec_dai和cpu_dai。

·          动态音频电源管理DAPM,使得codec任何时候都工作在最低功耗状态,同时负责音频路由的创建。这部分在我的另一个系列文章dapm里有详细的分析,是ASoC的重点和难点。

·          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

包括Audio DMA和SoC DAI Drivers两部分。

·          Audio DMA实现音频DMA操作函数集,具体见snd_pcm_ops结构体定义。

·          SoC DAI Driver实现数字音频接口的描述和配置、系统时钟配置、休眠唤醒等。

ASoC Machine Driver

·          作为链结Platform和Codec的载体,它必须定义dai_link为音频物理链路选定Platform和Codec。

·          处理机器特有的音频控件和音频事件,例如回放时打开外放功放。

以下UML类图标示着ASoC中重要的数据结构以及它们之间的联系,帮助理解整个ASoC系统。通过各种颜色标示数据结构对应的模块,其中soc-corepcm_native是核心,platform_drvcodec_drvcpu_dai_drvmachine_drv是我们要实现的alsa-driver,主要是一些结构体的定义和回调函数集的实现。

注:对于Linux内核来说,整理清楚模块中重要的数据结构,包括主要成员的作用以及各个数据结构之间的联系,那么就等于把握了该模块的脉络,剩下的只是细节。因此在各模块的分析之前,作者会把模块重要的数据结构先列出来,逐一介绍,然后才是源码分析。



snd_soc_pcm_runtime

整个ASoC都以snd_soc_pcm_runtime为桥梁来操作,可以这么理解:每一个音频物理连接对应一个或多个dai_link,而每个dai_link都有着自身的设备私有数据,这个私有数据就保存在snd_soc_pcm_runtime中。

/* SoC machine DAI configuration, glues a codec and cpu DAI together */
struct snd_soc_pcm_runtime {
    struct device *dev;
    struct snd_soc_card *card;
    struct snd_soc_dai_link *dai_link;
    struct mutex pcm_mutex;
    enum snd_soc_pcm_subclass pcm_subclass;
    struct snd_pcm_ops ops;

    unsigned int complete:1;
    unsigned int dev_registered:1;

    long pmdown_time;

    /* runtime devices */
    struct snd_pcm *pcm;
    struct snd_soc_codec *codec;
    struct snd_soc_platform *platform;
    struct snd_soc_dai *codec_dai;
    struct snd_soc_dai *cpu_dai;

    struct delayed_work delayed_work;
};

·          card:SoC card,在Machine中会把它作为名为"soc-audio"的platform_device的私有数据,主要包含dai_link的定义、Machine的休眠唤醒回调、Machine特定的dapm微件和路由、Machine特定的控件等。

·          dai_link:该snd_soc_pcm_runtime对应的音频接口链路,声明该链路需要绑定的codec、cpu_dai、platform。

·          ops:pcm回调函数集,用于调用codec_dai、cpu_dai、platform的音频操作接口,后面在soc_new_pcm()详细分析。

·          pcm:pcm中间层核心结构,保存着多个snd_pcm_substream分别用于playback和capture,而每个snd_pcm_substream有着自身的pcm dma硬件操作接口和runtime信息。

·          codec:在codec_drv中通过snd_soc_register_codec()创建,可以认为它是对snd_soc_codec_driver的封装,通过它可以访问操作codec_drv。

·          platform:在platform_drv中通过snd_soc_register_platform()创建,可以认为它是对snd_soc_platform_driver的封装,通过它可以访问操作pcm dma。

·          codec_dai:通过snd_soc_register_dais()创建,通过它可以访问操作codec_dai。

·          cpu_dai:在cpu_dai_drv中通过snd_soc_register_dai()创建,通过它可以访问操作cpu_dai。

snd_pcm_substream

pcm中间层核心结构,如果说snd_soc_pcm_runtime是ASoC的桥梁的话,那么snd_pcm_substream就是pcm native的桥梁,事实上它包含了pcm数据传输所需要的一切元素,如pcm dma operations、dma buffer、runtime information等等。

struct snd_pcm_substream {
    struct snd_pcm *pcm;
    struct snd_pcm_str *pstr;
    void *private_data;     /* copied from pcm->private_data */
    int number;
    char name[32];          /* substream name */
    int stream;         /* stream (direction) */
    struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
    size_t buffer_bytes_max;    /* limit ring buffer size */
    struct snd_dma_buffer dma_buffer;
    unsigned int dma_buf_id;
    size_t dma_max;
    /* -- hardware operations -- */
    struct snd_pcm_ops *ops;
    /* -- runtime information -- */
    struct snd_pcm_runtime *runtime;
        /* -- timer section -- */
    struct snd_timer *timer;        /* timer */
    // ...省略...
};

·          private_data:私有数据,是pcm->private_data的拷贝,事实上在soc-pcm中,把snd_soc_pcm_runtime赋给了它。因此对于ASoC来说,拿到了snd_pcm_substream就可以取得snd_soc_pcm_runtime,这样就得到整个ASoC Driver(codec/dma/i2s)所有元素。

·          dma_buffer:用于保存Audio Platform驱动分配的dma buffer相关信息,包括dma buffer的物理地址、虚拟地址、大小以及设备类型。其中dma buffer物理地址用于设定dma传输的源地址(对于回放来说)或者目的地址(对于录制来说),虚拟地址用于与用户态的音频数据拷贝。

·          ops:指向pcm dma操作函数集,通过它,pcm中间层可以启动dma实现音频数据的搬运。

·          runtime:音频数据传输的运行期信息。


snd_pcm_runtime

该结构包含pcm数据传输的运行期信息,如dma buffer管理信息、硬件参数、软件参数、dma buffer相关信息等。由于该结构体比较大,就不全贴出来了,将分段介绍。

·          dma buffer management information:用于计算dma buffer的剩余空间、当前位置指针等。这些计算是pcm native的一个难点,后面详细分析。

    snd_pcm_uframes_t avail_max;
    snd_pcm_uframes_t hw_ptr_base;  /* Position at buffer restart */
    snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
    unsigned long hw_ptr_jiffies;   /* Time when hw_ptr is updated */
    unsigned long hw_ptr_buffer_jiffies; /* buffer time in jiffies */
    snd_pcm_sframes_t delay;    /* extra delay; typically FIFO size */

·          hardware paramters:包括pcm数据格式、采样率、声道数、周期大小、周期数、量化位数等参数

    /* -- HW params -- */
    snd_pcm_access_t access;    /* access mode */
    snd_pcm_format_t format;    /* SNDRV_PCM_FORMAT_* */
    snd_pcm_subformat_t subformat;  /* subformat */
    unsigned int rate;      /* rate in Hz */
    unsigned int channels;      /* channels */
    snd_pcm_uframes_t period_size;  /* period size */
    unsigned int periods;       /* periods */
    snd_pcm_uframes_t buffer_size;  /* buffer size */
    snd_pcm_uframes_t min_align;    /* Min alignment for the format */
    size_t byte_align;
    unsigned int frame_bits;
    unsigned int sample_bits;
    unsigned int info;
    unsigned int rate_num;
    unsigned int rate_den;
    unsigned int no_period_wakeup: 1;

·          sw parameters:主要留意start_threshold、stop_threshold和silence_threshold

    /* -- SW params -- */
    int tstamp_mode;        /* mmap timestamp is updated */
    unsigned int period_step;
    snd_pcm_uframes_t start_threshold;
    snd_pcm_uframes_t stop_threshold;
    snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
                        noise is nearest than this */
    snd_pcm_uframes_t silence_size; /* Silence filling size */
    snd_pcm_uframes_t boundary; /* pointers wrap point */

    snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
    snd_pcm_uframes_t silence_filled; /* size filled with silence */

·          dma buffer

    /* -- DMA -- */           
    unsigned char *dma_area;    /* DMA area */
    dma_addr_t dma_addr;        /* physical bus address (not accessible from main CPU) */
    size_t dma_bytes;       /* size of DMA area */

    struct snd_dma_buffer *dma_buffer_p;    /* allocated buffer */

其中dma_buffer_p和snd_pcm_substream的dma_buffer字段是一致的,保存着Audio Platform驱动中分配的dma buffer相关信息。至于dma_area、dma_addr与dma_buffer_p的关系,看如下的函数就一目了然了:

static inline void snd_pcm_set_runtime_buffer(struct snd_pcm_substream *substream,
                          struct snd_dma_buffer *bufp)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    if (bufp) {
        runtime->dma_buffer_p = bufp;
        runtime->dma_area = bufp->area;
        runtime->dma_addr = bufp->addr;
        runtime->dma_bytes = bufp->bytes;
    } else {
        runtime->dma_buffer_p = NULL;
        runtime->dma_area = NULL;
        runtime->dma_addr = 0;
        runtime->dma_bytes = 0;
    }
}

dma_area正是dma buffer的虚拟地址,dma_addr是dma buffer的物理地址。

以上都是alsa-core的数据结构,而对于asoc-driver(codec/dma/i2s)来说,我们可能关心:

Ÿ          snd_soc_codec_driver:codec_drv

Ÿ          snd_soc_dai_driver:dai_drv,codec_dai和cpu_dai配置信息

Ÿ          snd_soc_platform_driver:platform_drv,pcm dma配置信息

Ÿ          snd_soc_dai_link:machine_drv,音频链路配置信息

后面分析这些数据结构如何注册到soc-core中。

下面是goni_wm8994的UML类图,从这个类图中,我们可以看到goni_wm8994整个音频驱动的框架是怎样的。

抱歉!评论已关闭.