继续上面的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 进行录制音频