linux内核配置打开声卡,浅谈linux内核alsa音频架构之Platform(三)

继续上面的Mainchine之后的 Platform简述

作为音频中的三大部件之一Platform作用是:通过DMA管理音频数据,借助dai(数字音频接口)把音频数据送往codec处理,最后由codec输出到外放驱动设备(人耳可辨别的声音)。

Platform可分为两大部分:音频相关的DMA管理操作(snd_soc_platform_driver)+soc数字音频(snd_soc_dai_driver)(DAI)的配置跟控制。其中在alsa标准中Platform要独立抽象出来,保证与target board 无关,以保证更好的兼容性及可移植性。

1.音频的DMA驱动怎么实现的呢?

移动平台(Asoc) 一般通过platform_driver将snd_soc_platform_driver注册到系统里

1. 声明一个snd_soc_platform_driver实例

2. 通过系统module_platform_driver回调probe 注册该实例

3. 填充实例的各种回调函数

以linux-3.12.26/sound/soc/idma.c 为例

static struct snd_soc_platform_driver asoc_idma_platform = {

.ops = &idma_ops,

.pcm_new = idma_new,

.pcm_free = idma_free,

};

static int asoc_idma_platform_probe(struct platform_device *pdev)

{

...........

return snd_soc_register_platform(&pdev->dev,&asoc_idma_platform);

}

static struct platform_driver asoc_idma_driver = {

.driver = {

.name = "samsung-idma",

.owner = THIS_MODULE,

},

.probe = asoc_idma_platform_probe,

.remove = asoc_idma_platform_remove,

};

module_platform_driver(asoc_idma_driver);

在snd_soc_register_platform里主要实现:

a. 为实例分配内存

b. 初始化实例字段(即snd_soc_platform_driver各成员)

c. 将实例添加到系统里(list_add(&platform->list, &platform_list);)

到了这里Machine驱动可以通过链表使用该实例(snd_soc_platform_driver)。

对外来说,他们关心的是获得该实例或者说驱动提供对外的api,即snd_soc_platform_driver里面的ops( snd_pcm_ops *)字段

1. ops.open

应用程序打开一个pcm设备节点之后,一般地会设置snd_pcm_runtime里面的hw相关参数,然后申请一个runtime_data内存以用来保存snd_pcm_runtime私有数据

2. ops.hw_params

主要完成dma的相关配置参数初始化,如width , fifo , channel 等等,在实际的应用中 该函数会被频繁的调用,对应的参数 设置 需要考虑到音频数据流rate_den、channel mask、rate_num等

3. ops.prepare

一般地 重新刷新dma 私有数据中的 channel ,然后往dma系统里加入新的buffer数据

4. ops.trigger

用于实现 dma数据开始传输 停止 恢复/暂停

5. ops.ioctl

一般地用于重置 音频流 、控制音频channel 、音频fifo size等

6. ops.mmap

dma 的mmap , 便于应用空间直接访问

2. soc数字音频DAI snd_soc_dai_driver实现

dai这部分主要的通过系统总线像i2c/i2s/pci等方式连接cpu与codec,来实现音频数据的采集传输 . 跟snd_soc_platform_driver实现方式一样, 以参考s3c24xx-i2s.c 为例 dai驱动实现框架如下:

1. 声明一个 snd_soc_dai_driver实例

2. 通过module_platform_driver的probe回调中snd_soc_register_dai 或snd_soc_register_dais注册 该实例

3. 实现snd_soc_dai_driver的各种回调函数

4. 实现snd_soc_dai_ops的各种回调函数

snd_soc_dai_driver 结构中的字段

1. probe \ suspend\resume\remove 实现 dai驱动的加载、卸载以及电源待机、唤醒等回调

2. playback 用于设置播放音频流的 channel rates formates 等

3. capture 用于录制音频流的 channel rates formates等参数 以及用于录制的ops (snd_soc_dai_ops)

snd_soc_dai_ops 字段有

/音频设备的 硬件参数配置 一般在machine里调用/

ops.set_sysclk

ops.set_fmt

…….

/* soc-core audio PCM operations 音频核心的PCM回调* /

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 *);

int (*bespoke_trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *);

上面详细可参考 include/sound/soc_dai.h

3. 音频数据的DMA管理操作

数据的传输三大要素: 源 大小 目的地址 。 开篇已经说了,soc中platform主要作用是实现音频的数据传输,通常情况下 是由dma完成了音频数据传输 。

linux系统中 不同平台 对于dma的传输以及访问 是有对应内存地址要求。在soc系统中 dma 数据由snd_pcm_substream 中的snd_dma_buffer 保存

/* * info for buffer allocation */

struct snd_dma_buffer {

struct snd_dma_device dev; /* device type */

unsigned char *area; /* virtual pointer */

dma_addr_t addr; /* physical address */

size_t bytes; /* buffer size in bytes */

void *private_data; /* private for allocator; don't touch */

};

源地址:

同样以 sound/soc/samsumg/idam.c为例

static struct snd_soc_platform_driver asoc_idma_platform = {

.ops = &idma_ops,

.pcm_new = idma_new, //pcm creation

.pcm_free = idma_free,

};

static int idma_new(struct snd_soc_pcm_runtime *rtd)

{

if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)

preallocate_idma_buffer(pcm,SNDRV_PCM_STREAM_PLAYBACK);

在Machine创建pcm_new的时候会回调这里设置 dam 原始大小

目的地址

由上面中的snd_soc_dai 中所实现,通常下面的dma data在snd_soc_dai_driver 里面probe回调hw.params api snd_soc_dai_set_dma_data设置的

/* DAI DMA data */

void *playback_dma_data;

void *capture_dma_data;

在上面的 snd_soc_platform_driver里面 回调ops.open会保存上面的dma信息 以保证在substream周期生命里的 可以通过api获取到

大小

播放音频时,应用不断的将音频数据 写入dma buffer , platform通过dai将之送往 codec中, 录音与之相反。

关于dma buffer的大小 操作比较复杂

主要也涉及snd_pcm_runtime 结构

struct snd_pcm_runtime {

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 */

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 */

struct snd_pcm_mmap_status *status;

struct snd_pcm_mmap_control *control;

.......

snd_pcm_runtime.hw_ptr_base: /* Position at buffer restart * /

snd_pcm_runtime.control->appl_ptr: app指针 播放=读指针 录音=写指针

snd_pcm_runtime.boundary

snd_pcm_runtime.status->hw_ptr: hw指针 播放=读指针 录音=写指针

关于的pcm的dma buffer管理 主要集中在 sound/core/pcm_lib.c

snd_pcm_update_state

snd_pcm_update_hw_ptr

应用通过alsa-utils 或者alsa-lib封装好的alsa api(open read wirte ioctl等等) 访问/控制 声卡设备 将音频数据 写入dmabuffer 然后调用 platform codec 进行播放 或者 调用 从dma 读取数据 调用 platform codec 进行录制音频

你可能感兴趣的:(linux内核配置打开声卡)