Linux ALSA声卡驱动之八:ASoC架构中的Platform
1. Platform驱动在ASoC中的作用
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
2. snd_soc_platform_driver的注册
- 定义一个snd_soc_platform_driver结构的实例;
- 在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;
- 实现snd_soc_platform_driver中的各个回调函数;
以kernel3.3中的/sound/soc/samsung/dma.c为例:
- static struct snd_soc_platform_driver samsung_asoc_platform = {
- .ops = &dma_ops,
- .pcm_new = dma_new,
- .pcm_free = dma_free_dma_buffers,
- };
- static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
- {
- return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
- }
- static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)
- {
- snd_soc_unregister_platform(&pdev->dev);
- return 0;
- }
- static struct platform_driver asoc_dma_driver = {
- .driver = {
- .name = "samsung-audio",
- .owner = THIS_MODULE,
- },
- .probe = samsung_asoc_platform_probe,
- .remove = __devexit_p(samsung_asoc_platform_remove),
- };
- module_platform_driver(asoc_dma_driver);
snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。它的代码已经清晰地表达了它的实现过程:
- 为snd_soc_platform实例申请内存;
- 从platform_device中获得它的名字,用于Machine驱动的匹配工作;
- 初始化snd_soc_platform的字段;
- 把snd_soc_platform实例连接到全局链表platform_list中;
- 调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;
3. cpu的snd_soc_dai driver驱动的注册
- 定义一个snd_soc_dai_driver结构的实例;
- 在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;
- 实现snd_soc_dai_driver结构中的probe、suspend等回调;
- 实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;
snd_soc_dai 该结构在snd_soc_register_dai函数中通过动态内存申请获得, 简要介绍一下几个重要字段:
- driver 指向关联的snd_soc_dai_driver结构,由注册时通过参数传入;
- playback_dma_data 用于保存该dai播放stream的dma信息,例如dma的目标地址,dma传送单元大小和通道号等;
- capture_dma_data 同上,用于录音stream;
- platform 指向关联的snd_soc_platform结构;
snd_soc_dai_driver 该结构需要自己根据不同的soc芯片进行定义,关键字段介绍如下:
- probe、remove 回调函数,分别在声卡加载和卸载时被调用;
- suspend、resume 电源管理回调函数;
- ops 指向snd_soc_dai_ops结构,用于配置和控制该dai;
- playback snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;
- capture snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;
4. snd_soc_dai_driver中的ops字段
ops字段指向一个snd_soc_dai_ops结构,该结构实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:
工作时钟配置函数 通常由machine驱动调用:
- set_sysclk 设置dai的主时钟;
- set_pll 设置PLL参数;
- set_clkdiv 设置分频系数;
- dai的格式配置函数 通常由machine驱动调用:
- set_fmt 设置dai的格式;
- set_tdm_slot 如果dai支持时分复用,用于设置时分复用的slot;
- set_channel_map 声道的时分复用映射设置;
- set_tristate 设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;
标准的snd_soc_ops回调 通常由soc-core在进行PCM操作时调用:
- startup
- shutdown
- hw_params
- hw_free
- prepare
- trigger
抗pop,pop声 由soc-core调用:
- digital_mute
以下这些api通常被machine驱动使用,machine驱动在他的snd_pcm_ops字段中的hw_params回调中使用这些api:
- snd_soc_dai_set_fmt() 实际上会调用snd_soc_dai_ops或者codec driver中的set_fmt回调;
- snd_soc_dai_set_pll() 实际上会调用snd_soc_dai_ops或者codec driver中的set_pll回调;
- snd_soc_dai_set_sysclk() 实际上会调用snd_soc_dai_ops或者codec driver中的set_sysclk回调;
- snd_soc_dai_set_clkdiv() 实际上会调用snd_soc_dai_ops或者codec driver中的set_clkdiv回调;
snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)的第二个参数fmt在这里特别说一下,ASoC目前只是用了它的低16位,并且为它专门定义了一些宏来方便我们使用:
bit 0-3 用于设置接口的格式:
- #define SND_SOC_DAIFMT_I2S 1 /* I2S mode */
- #define SND_SOC_DAIFMT_RIGHT_J 2 /* Right Justified mode */
- #define SND_SOC_DAIFMT_LEFT_J 3 /* Left Justified mode */
- #define SND_SOC_DAIFMT_DSP_A 4 /* L data MSB after FRM LRC */
- #define SND_SOC_DAIFMT_DSP_B 5 /* L data MSB during FRM LRC */
- #define SND_SOC_DAIFMT_AC97 6 /* AC97 */
- #define SND_SOC_DAIFMT_PDM 7 /* Pulse density modulation */
bit 4-7 用于设置接口时钟的开关特性:
- #define SND_SOC_DAIFMT_CONT (1 << 4) /* continuous clock */
- #define SND_SOC_DAIFMT_GATED (2 << 4) /* clock is gated */
bit 8-11 用于设置接口时钟的相位:
- #define SND_SOC_DAIFMT_NB_NF (1 << 8) /* normal bit clock + frame */
- #define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */
- #define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */
- #define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM */
bit 12-15 用于设置接口主从格式:
- #define SND_SOC_DAIFMT_CBM_CFM (1 << 12) /* codec clk & FRM master */
- #define SND_SOC_DAIFMT_CBS_CFM (2 << 12) /* codec clk slave & FRM master */
- #define SND_SOC_DAIFMT_CBM_CFS (3 << 12) /* codec clk master & frame slave */
- #define SND_SOC_DAIFMT_CBS_CFS (4 << 12) /* codec clk & FRM slave */
5. snd_soc_platform_driver中的ops字段
该ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:
ops.open
当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。
ops.hw_params
驱动的hw_params阶段,该函数会被调用。通常,该函数会通过snd_soc_dai_get_dma_data函数获得对应的dai的dma参数,获得的参数一般都会保存在snd_pcm_runtime结构的private_data字段。然后通过snd_pcm_set_runtime_buffer函数设置snd_pcm_runtime结构中的dma buffer的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。
ops.prepare
正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。
ops.trigger
数据传送的开始,暂停,恢复和停止时,该函数会被调用。
ops.pointer
该函数返回传送数据的当前位置。
6. 音频数据的dma操作
soc-platform驱动的最主要功能就是要完成音频数据的传送,大多数情况下,音频数据都是通过dma来完成的。
6.1. 申请dma buffer
因为dma的特殊性,dma buffer是一块特殊的内存,比如有的平台规定只有某段地址范围的内存才可以进行dma操作,而多数嵌入式平台还要求dma内存的物理地址是连续的,以方便dma控制器对内存的访问。在ASoC架构中,dma buffer的信息保存在snd_pcm_substream结构的snd_dma_buffer *buf字段中,它的定义如下
- 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 */
- };
那么,在哪里完成了snd_dam_buffer结构的初始化赋值操作呢?答案就在snd_soc_platform_driver的pcm_new回调函数中,还是以/sound/soc/samsung/dma.c为例:
- static struct snd_soc_platform_driver samsung_asoc_platform = {
- .ops = &dma_ops,
- .pcm_new = dma_new,
- .pcm_free = dma_free_dma_buffers,
- };
- static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
- {
- return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
- }
pcm_new字段指向了dma_new函数,dma_new函数进一步为playback和capture分别调用preallocate_dma_buffer函数,我们看看preallocate_dma_buffer函数的实现:
- static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
- {
- struct snd_pcm_substream *substream = pcm->streams[stream].substream;
- struct snd_dma_buffer *buf = &substream->dma_buffer;
- size_t size = dma_hardware.buffer_bytes_max;
- pr_debug("Entered %s\n", __func__);
- buf->dev.type = SNDRV_DMA_TYPE_DEV;
- buf->dev.dev = pcm->card->dev;
- buf->private_data = NULL;
- buf->area = dma_alloc_writecombine(pcm->card->dev, size,
- &buf->addr, GFP_KERNEL);
- if (!buf->area)
- return -ENOMEM;
- buf->bytes = size;
- return 0;
- }
该函数先是获得事先定义好的buffer大小,然后通过dma_alloc_weitecombine函数分配dma内存,然后完成substream->dma_buffer的初始化赋值工作。上述的pcm_new回调会在声卡的建立阶段被调用,调用的详细的过程请参考Linux ALSAs声卡驱动之六:ASoC架构中的Machine中的图3.1。
在声卡的hw_params阶段,snd_soc_platform_driver结构的ops->hw_params会被调用,在该回调用,通常会使用api:snd_pcm_set_runtime_buffer()把substream->dma_buffer的数值拷贝到substream->runtime的相关字段中(.dma_area, .dma_addr, .dma_bytes),这样以后就可以通过substream->runtime获得这些地址和大小信息了。
dma buffer获得后,即是获得了dma操作的源地址,那么目的地址在哪里?其实目的地址当然是在dai中,也就是前面介绍的snd_soc_dai结构的playback_dma_data和capture_dma_data字段中,而这两个字段的值也是在hw_params阶段,由snd_soc_dai_driver结构的ops->hw_params回调,利用api:snd_soc_dai_set_dma_data进行设置的。紧随其后,snd_soc_platform_driver结构的ops->hw_params回调利用api:snd_soc_dai_get_dma_data获得这些dai的dma信息,其中就包括了dma的目的地址信息。这些dma信息通常还会被保存在substream->runtime->private_data中,以便在substream的整个生命周期中可以随时获得这些信息,从而完成对dma的配置和操作。
6.2 dma buffer管理
- snd_pcm_runtime.hw_ptr_base 环形缓冲区每一圈的基地址,当读写指针越过一圈后,它按buffer size进行移动;
- snd_pcm_runtime.status->hw_ptr 硬件逻辑位置,播放时相当于读指针,录音时相当于写指针;
- snd_pcm_runtime.control->appl_ptr 应用逻辑位置,播放时相当于写指针,录音时相当于读指针;
- snd_pcm_runtime.boundary 扩展后的逻辑缓冲区大小,通常是(2^n)*size;
通过这几个字段,我们可以很容易地获得缓冲区的有效数据,剩余空间等信息,也可以很容易地把当前逻辑位置映射回真实的dma buffer中。例如,获得播放缓冲区的空闲空间:
- static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)
- {
- snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;
- if (avail < 0)
- avail += runtime->boundary;
- else if ((snd_pcm_uframes_t) avail >= runtime->boundary)
- avail -= runtime->boundary;
- return avail;
- }
要想映射到真正的缓冲区位置,只要减去runtime->hw_ptr_base即可。下面的api用于更新这几个指针的当前位置:
- int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
以播放(playback)为例,我现在知道至少有3个途径可以完成对dma buffer的写入:
- 应用程序调用alsa-lib的snd_pcm_writei、snd_pcm_writen函数;
- 应用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
- 应用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;
以上几种方式最终把数据写入dma buffer中,然后修改runtime->control->appl_ptr的值。
- 更新dma的硬件的当前位置,该数值通常保存在runtime->private_data中;
- 调用snd_pcm_period_elapsed函数,该函数会进一步调用snd_pcm_update_hw_ptr0函数更新上述所说的4个缓冲区管理字段,然后唤醒相应的等待进程;
- <span style="font-family:Arial, Verdana, sans-serif;"><span style="white-space: normal;"></span></span><pre name="code" class="cpp">void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
- {
- struct snd_pcm_runtime *runtime;
- unsigned long flags;
- if (PCM_RUNTIME_CHECK(substream))
- return;
- runtime = substream->runtime;
- if (runtime->transfer_ack_begin)
- runtime->transfer_ack_begin(substream);
- snd_pcm_stream_lock_irqsave(substream, flags);
- if (!snd_pcm_running(substream) ||
- snd_pcm_update_hw_ptr0(substream, 1) < 0)
- goto _end;
- if (substream->timer_running)
- snd_timer_interrupt(substream->timer, 1);
- _end:
- snd_pcm_stream_unlock_irqrestore(substream, flags);
- if (runtime->transfer_ack_end)
- runtime->transfer_ack_end(substream);
- kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
- }
- </pre>如果设置了transfer_ack_begin和transfer_ack_end回调,snd_pcm_period_elapsed还会调用这两个回调函数。<br>
- <br>
- <pre></pre>
- <pre></pre>
- <pre></pre>
7. 图说代码
最后,反正图也画了,好与不好都传上来供参考一下,以下这张图表达了 ASoC中Platform驱动的几个重要数据结构之间的关系:
图7.1 ASoC Platform驱动
一堆的private_data,很重要但也很容易搞混,下面的图不知对大家有没有帮助:
图7.2 private_data
Linux ALSA声卡驱动之八:ASoC架构中的Platform的更多相关文章
- Linux ALSA声卡驱动之七:ASoC架构中的Codec
1. Codec简介(ad/da) 在移动设备中,Codec的作用可以归结为4种,分别是: 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号 对Mic.Linein或者其他输入源的模拟信 ...
- Linux ALSA声卡驱动之六:ASoC架构中的Machine
前面一节的内容我们提到,ASoC被分为Machine.Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上 ...
- Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)
转自http://blog.csdn.net/droidphone/article/details/7165482 1. ASoC的由来 ASoC--ALSA System on Chip ,是建立 ...
- Linux ALSA声卡驱动之二:声卡的创建
1. struct snd_card 1.1. snd_card是什么 snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都 ...
- Linux ALSA声卡驱动之一:ALSA架构简介
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! 一. 概述 ALSA是Advanced Linux Sound Architecture ...
- Linux ALSA声卡驱动之一:ALSA架构简介【转】
本文转载自:http://blog.csdn.net/droidphone/article/details/6271122 声明:本博内容均由http://blog.csdn.net/droidpho ...
- Linux ALSA声卡驱动之三:PCM设备的创建
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! 1. PCM是什么 模数转换 模拟信号经过pcm(脉冲编码调制)后为pcm数据: PCM是 ...
- Linux ALSA声卡驱动之四:Control设备的创建
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! Control接口 Control接口主要让用户空间的应用程序(alsa-lib)可以访问 ...
- 36、ALSA声卡驱动和应用
(注意:内核上电的时候会把一些没运行的控制器模块的时钟都关掉,所有在写驱动的时候需要在使用的使用使用clk_get和clk_enable使能时钟) (说明:与ALSA声卡对应的是OSS架构,第二期视频 ...
随机推荐
- C++ 程序的编译
一.编译器都具备集成开发环境(Integrated Developed Environment,IDE) 二.程序源文件命名约定: C++ 的后缀一般是 .cpp .cc .C .cpp .cxx 三 ...
- netstat查看服务器连接数端口并发数
简介 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Member ...
- jekyll本地环境搭建(Windows)
序:最近一直在搞Github建站,所以一直没机会写文章,那边的环境虽然搞好了,但是网站的界面却是个问题,不想用别人的,总想自己设计个,却感觉没经验吧,就一直耽搁了.所以也就没心情在那边写文章,很久没写 ...
- SwiftyUserDefaults对NSUserDefaults的封装扩展
SwiftyUserDefaults 是对NSUserDefaults的一些封装和扩展,这个库这个只有一个类,操作起来十分简单方便: 这里只有两个步骤来使用SwiftyUserDefaults: st ...
- C++ string使用
在c语言里,我们使用一个字符串时,是通过字符数组或者字符指针的方式来进行使用,在C++里,标准模板库已经给我们提供了string类型(string是以类的方式提供给我们使用). 定义和初始化strin ...
- anaconda镜像
下载一个包老是下载不下来,于是放弃了官方版改为国内镜像. 清华镜像 (打开Anaconda Prompt 或者 打开cmd,运行下面的命令) conda config --add channels ...
- <MyBatis>入门二 全局配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC ...
- JDK的下载---官方
1.去到官方网站 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 根据箭头选择, ...
- IDLE in Python (Ubuntu)
To lauch IDLE in the Current Woking Directory >>> usr/bin/idle3 Alt + n # next command Alt ...
- 马拉车,O(n)求回文串
马拉车,O(n)求回文串 对整个马拉车算法步骤做个总结: 第一步:将每个原字母用两个特殊字符包围如: aaa --> #a#a#a# abab -->#a#b#a#b 同时可以由这个翻倍的 ...