PCM data flow - part 4: ASoC platform driver

http://blog.csdn.net/azloong/article/details/17317829


概述中提到音频Platform驱动主要作用是音频数据的传输,这里又细分为两步:

·          把音频数据从dma buffer通过dma操作搬运到cpu_dai FIFO,这部分驱动用snd_soc_platform_driver描述,后面分析用pcm_dma指代它。

·          把音频数据从cpu_dai FIFO通过数字音频接口(I2S/PCM/AC97)传送到codec_dai,这部分驱动用snd_soc_dai_driver描述,后面分析用cpu_dai指代它。

那么dma buffer中的音频数据从哪而来?保留这个问题,在后面章节pcm native分析。


我们浏览下platform_drv中的几个重要的结构体,其中浅蓝色部分是cpu_dai相关的,浅绿色部分是pcm_dma相关的。而snd_soc_dai是cpu_dai注册时所创建的dai实例,snd_soc_platform是pcm_dma注册时所创建的platform实例,这些实例方便asoc-core管理。从snd_soc_dai中,我们看到一个dai必须绑定它链结的codec和platform。



cpu dai


从上图我们可知,实现一个cpu_dai驱动主要工作有:

·          实现dai操作函数,见snd_soc_dai_ops字段定义,用于配置控制dai,如系统时钟设置set_sysclk()、格式设置set_fmt()、硬件参数设置hw_params()、触发dai启动或停止传输trigger()等。

·          实现dai_drv的probe函数(dai初始化工作)、remove函数(dai卸载工作)、suspend/resume函数(dai电源管理配置)

·          初始化dai_drv实例snd_soc_dai_driver,包括playback和capture的能力描述信息、dai操作函数集指针、probe/remove回调、电源管理相关的suspend/resume回调。

·          通过snd_soc_register_dai()把初始化完成的snd_soc_dai_driver注册到soc-core:首先创建并初始化一个snd_soc_dai实例,然后把该snd_soc_dai实例插入到dai_list链表,Machine驱动初始化时会遍历该链表,以找到dai_link声明的cpu_dai并绑定。

  1. /** 
  2.  * snd_soc_register_dai - Register a DAI with the ASoC core 
  3.  * 
  4.  * @dai: DAI to register 
  5.  */  
  6. int snd_soc_register_dai(struct device *dev,  
  7.         struct snd_soc_dai_driver *dai_drv)  
  8. {  
  9.     struct snd_soc_dai *dai;  
  10.   
  11.     dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);  
  12.     if (dai == NULL)  
  13.         return -ENOMEM;  
  14.   
  15.     /* create DAI component name */  
  16.     dai->name = fmt_single_name(dev, &dai->id);  
  17.     if (dai->name == NULL) {  
  18.         kfree(dai);  
  19.         return -ENOMEM;  
  20.     }  
  21.   
  22.     dai->dev = dev;  
  23.     dai->driver = dai_drv;  
  24.     if (!dai->driver->ops)  
  25.         dai->driver->ops = &null_dai_ops;  
  26.   
  27.     mutex_lock(&client_mutex);  
  28.     list_add(&dai->list, &dai_list);  
  29.     mutex_unlock(&client_mutex);  
  30.   
  31.     return 0;  
  32. }  

注意dai操作函数的实现是cpu_dai驱动的主体,主要工作是配置好相关寄存器让硬件正常运转,因此这依赖于具体平台的cpu dai specification。snd_soc_dai_ops字段的详细说明见Codec audio operations章节。

cpu_dai驱动应该算是这个系列中最简单的一环,因此不多花费笔墨在这里了。倒是某些平台上,dma设备信息(设备的总线地址、通道号、传输单元大小)是在这里初始化的,这点要留意,这些dma设备信息在pcm_dma驱动中用到。

以Exynos平台为例,代码位置sound/soc/samsung/i2s.c。

Samsung Exynos平台的音频dma设备信息用s3c_dma_params结构体描述:

  1. struct s3c_dma_params {  
  2.     struct s3c2410_dma_client *client;  /* stream identifier */  
  3.     int channel;                /* Channel ID */  
  4.     dma_addr_t dma_addr;  
  5.     int dma_size;           /* Size of the DMA transfer */  
  6.     unsigned ch;  
  7.     struct samsung_dma_ops *ops;  
  8. };  

·          client:流标识符

·          channel:通道号

·          dma_addr:设备的总线地址,这里通常指向I2S tx FIFO或I2S rx FIFO地址

·          dma_size:dma传输单元大小

·          ops:平台dma操作函数

sound/soc/samsung/i2s.c中设置dma设备信息的相关代码片段:

  1. struct i2s_dai {  
  2.     // ...  
  3.     /* Driver for this DAI */  
  4.     struct snd_soc_dai_driver i2s_dai_drv;  
  5.     /* DMA parameters */  
  6.     struct s3c_dma_params dma_playback; // playback dma描述信息  
  7.     struct s3c_dma_params dma_capture;  // capture dma描述信息  
  8.     struct s3c_dma_params idma_playback;// playback idma描述信息,idma仅用于回放,用于三星平台的LPA(低功耗音频)模式  
  9.     // ...  
  10. };  
  11.   
  12. static __devinit int samsung_i2s_probe(struct platform_device *pdev)  
  13. {  
  14.     // ...  
  15.     // 从platform_device中取得resource,得到playback dma通道号  
  16.     res = platform_get_resource(pdev, IORESOURCE_DMA, 0);  
  17.     if (!res) {  
  18.         dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");  
  19.         return -ENXIO;  
  20.     }  
  21.     dma_pl_chan = res->start; // dma_pl_chan中的pl是playback简写  
  22.   
  23.     // 从platform_device中取得resource,得到capture dma通道号  
  24.     res = platform_get_resource(pdev, IORESOURCE_DMA, 1);  
  25.     if (!res) {  
  26.         dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");  
  27.         return -ENXIO;  
  28.     }  
  29.     dma_cp_chan = res->start; // dma_cp_chan中的cp是capture的简写  
  30.   
  31.     // 从platform_device中取得resource,得到playback idma通道号  
  32.     res = platform_get_resource(pdev, IORESOURCE_DMA, 2);  
  33.     if (res)  
  34.         dma_pl_sec_chan = res->start;  
  35.     else  
  36.         dma_pl_sec_chan = 0;  
  37.   
  38.     // 从platform_device中取得resource,得到I2S的基地址  
  39.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  40.     if (!res) {  
  41.         dev_err(&pdev->dev, "Unable to get I2S SFR address\n");  
  42.         return -ENXIO;  
  43.     }  
  44.   
  45.     if (!request_mem_region(res->start, resource_size(res),  
  46.                             "samsung-i2s")) {  
  47.         dev_err(&pdev->dev, "Unable to request SFR region\n");  
  48.         return -EBUSY;  
  49.     }  
  50.     regs_base = res->start;  
  51.       
  52.     // ...  
  53.     pri_dai->dma_playback.dma_addr = regs_base + I2STXD; // 设置playback dma设备地址为I2S tx FIFO地址  
  54.     pri_dai->dma_capture.dma_addr = regs_base + I2SRXD; // 设置capture dma设备地址为I2S rx FIFO地址  
  55.     pri_dai->dma_playback.client =  
  56.         (struct s3c2410_dma_client *)&pri_dai->dma_playback;  
  57.     pri_dai->dma_capture.client =  
  58.         (struct s3c2410_dma_client *)&pri_dai->dma_capture;  
  59.     pri_dai->dma_playback.channel = dma_pl_chan; // 设置playback dma通道号  
  60.     pri_dai->dma_capture.channel = dma_cp_chan; // 设置capture dma通道号  
  61.     pri_dai->src_clk = i2s_cfg->src_clk;  
  62.     pri_dai->dma_playback.dma_size = 4; // 设置playback dma传输单元大小为4个字节  
  63.     pri_dai->dma_capture.dma_size = 4; // 设置capture dma传输单元大小为4个字节  
我们再看看Board初始化时,如何设定platform_device的resource,文件arch/arm/mach-exynos/dev-audio.c:
  1. static struct resource exynos4_i2s0_resource[] = {  
  2.     [0] = {  
  3.         .start  = EXYNOS4_PA_I2S0, // start字段保存的是I2S基地址  
  4.         .end    = EXYNOS4_PA_I2S0 + 0x100 - 1,  
  5.         .flags  = IORESOURCE_MEM,  // 置该resource标识为MEM资源  
  6.     },  
  7.     [1] = {  
  8.         .start  = DMACH_I2S0_TX,   // start字段保存的是用于回放的dma通道号  
  9.         .end    = DMACH_I2S0_TX,  
  10.         .flags  = IORESOURCE_DMA,  // 置该resource标识为DMA资源  
  11.     },  
  12.     [2] = {  
  13.         .start  = DMACH_I2S0_RX,   // start字段保存的是用于录制的dma通道号  
  14.         .end    = DMACH_I2S0_RX,  
  15.         .flags  = IORESOURCE_DMA,  // 置该resource标识为DMA资源  
  16.     },  
  17.     [3] = {  
  18.         .start  = DMACH_I2S0S_TX,  // start字段保存的是用于回放的idma通道号  
  19.         .end    = DMACH_I2S0S_TX,  
  20.         .flags  = IORESOURCE_DMA,  // 置该resource标识为DMA资源  
  21.     },  
  22. };  
  23.   
  24. struct platform_device exynos4_device_i2s0 = {  
  25.     .name = "samsung-i2s"// platform_device名称标识为"samsung-i2s",将与i2s.c中的samsung_i2s_driver匹配  
  26.     .id = 0,  
  27.     .num_resources = ARRAY_SIZE(exynos4_i2s0_resource),  
  28.     .resource = exynos4_i2s0_resource,  
  29.     .dev = {  
  30.         .platform_data = &i2sv5_pdata,  
  31.     },  
  32. };  

当samsung_i2s_driver初始化时,通过platform_get_resource()函数来获取platform_device声明的resource。


struct resource结构中我们通常关心start、end和flags这3个字段,分别标明资源的开始值、结束值和类型。flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。start、end的含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,譬如说某设备占据了2个内存区域,则可以定义2个IORESOURCE_MEM资源。
以上内容摘自:http://21cnbao.blog.51cto.com/109393/337609


pcm dma


pcm数据管理可以说是ALSA系统中最核心的部分,这部分的工作有两个:

·          copy_from_user把用户态的音频数据拷贝到dma buffer;

·          启动dma把音频数据从dma buffer传送到I2S tx FIFO,这是回放的情形。

当数据送到I2S tx FIFO后,剩下的是启动I2S把数据传送到codec,然后DAC把数字PCM信号转换成模拟信号,送给SPK/HS输出,关于I2S(cpu_dai)和codec,在以上两章已经描述清楚了。

为什么要使用dma传输?两个原因:首先在数据传输过程中,不需要cpu的参与,节省了cpu的开销;其次传输速度快,提高硬件设备的吞吐量。对于Arm,它不能直接把数据从A地址搬运到B地址,只能把数据从A地址搬运到一个寄存器,然后再从这个寄存器搬运到B地址;而dma有突发(Burst)传输能力,这种模式下一次能传输几个甚至十几个字节的数据,尤其适合大数据的高速传输。一个dma传输块里面,可以划分若干个帧,每传输完一帧都可以产生一个中断。

写这个文档的初衷是为了描述清楚pcm数据流向,这里先剖析pcm dma驱动,以便后面pcm native的分析。

以Exynos平台为例,代码位置sound/soc/samsung/dma.c。


·          浅绿色:pcm_dma驱动共有的实现

·          浅灰色:samsung exynos平台的pcm_dma驱动特有的实现

·          浅紫色:pcm native关键结构

·          浅橙色:snd_soc_platform是pcm_dma注册时所创建的platform实例


我们先看看dma相关的结构,对于回放来说,dma把内存缓冲区的音频数据传送到I2S tx FIFO;对于录制来说,dma把I2S rx FIFO的音频数据传送到内存缓存区。因此在dma传输之前,必须确定data buffer和I2S FIFO信息。

snd_dma_buffer:数据缓存区,用于保存从用户态拷贝过来的音频数据,包括dma buffer的物理首地址,虚拟首地址、大小等信息;其中物理地址用于设定dma传输的源地址(回放情形)或目的地址(录制情形),虚拟地址用于与用户态的音频数据拷贝。

s3c_dma_params:dma设备描述,包括设备总线地址(回放的情形下为I2S tx FIFO首地址,设置为dma传输的目的地址)、dma通道号、dma传输数据单元大小,这些信息在i2s.c中获取。

runtime_data:dma运行期信息

·          state:记录dma状态,启动或停止;

·          dma_loaded:dma装载计数,每当启动一次dma传输,该计数加一;每当完成一次dma传输,该计数减一;

·          dma_period:dma周期数据大小;

·          dma_start:指向dma buffer的物理首地址;

·          dma_pos:记录dma buffer当前指针位置,当dma每传输一次数据,都会更新该指针;

·          dma_end:dma buffer结束位置;

·          params:dma设备描述信息,包括设备总线地址、dma通道号、传输单元大小。


pcm operations


pcm_dma操作函数的实现是本模块实现的主体,它用snd_pcm_ops结构体描述:

  1. struct snd_pcm_ops {  
  2.     int (*open)(struct snd_pcm_substream *substream);  
  3.     int (*close)(struct snd_pcm_substream *substream);  
  4.     int (*ioctl)(struct snd_pcm_substream * substream,  
  5.              unsigned int cmd, void *arg);  
  6.     int (*hw_params)(struct snd_pcm_substream *substream,  
  7.              struct snd_pcm_hw_params *params);  
  8.     int (*hw_free)(struct snd_pcm_substream *substream);  
  9.     int (*prepare)(struct snd_pcm_substream *substream);  
  10.     int (*trigger)(struct snd_pcm_substream *substream, int cmd);  
  11.     snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);  
  12.     int (*copy)(struct snd_pcm_substream *substream, int channel,  
  13.             snd_pcm_uframes_t pos,  
  14.             void __user *buf, snd_pcm_uframes_t count);  
  15.     int (*silence)(struct snd_pcm_substream *substream, int channel,   
  16.                snd_pcm_uframes_t pos, snd_pcm_uframes_t count);  
  17.     struct page *(*page)(struct snd_pcm_substream *substream,  
  18.                  unsigned long offset);  
  19.     int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);  
  20.     int (*ack)(struct snd_pcm_substream *substream);  
  21. };  

下面介绍几个重要的回调函数:

·          open:打开pcm逻辑设备时,会回调该函数,用于为runtime设定硬件约束,为runtime的private_data申请一个私有结构,保存dma资源如通道号、传输单元、缓冲区信息、IO设备信息等。代码如下:

  1. static const struct snd_pcm_hardware dma_hardware = {  
  2.     .info           = SNDRV_PCM_INFO_INTERLEAVED |  
  3.                     SNDRV_PCM_INFO_BLOCK_TRANSFER |  
  4.                     SNDRV_PCM_INFO_MMAP |  
  5.                     SNDRV_PCM_INFO_MMAP_VALID |  
  6.                     SNDRV_PCM_INFO_PAUSE |  
  7.                     SNDRV_PCM_INFO_RESUME,  
  8.     .formats        = SNDRV_PCM_FMTBIT_S16_LE |  
  9.                     SNDRV_PCM_FMTBIT_U16_LE |  
  10.                     SNDRV_PCM_FMTBIT_U8 |  
  11.                     SNDRV_PCM_FMTBIT_S8,  
  12.     .channels_min       = 2,  
  13.     .channels_max       = 2,  
  14.     .buffer_bytes_max   = 128*1024,  
  15.     .period_bytes_min   = PAGE_SIZE,  
  16.     .period_bytes_max   = PAGE_SIZE*2,  
  17.     .periods_min        = 2,  
  18.     .periods_max        = 128,  
  19.     .fifo_size      = 32,  
  20. };  
  21.   
  22. static int dma_open(struct snd_pcm_substream *substream)  
  23. {  
  24.     struct snd_pcm_runtime *runtime = substream->runtime;  
  25.     struct runtime_data *prtd;  
  26.   
  27.     pr_debug("Entered %s\n", __func__);  
  28.   
  29.     // 设置substream运行期信息中的hw字段,可以把dma_hardware看成该platform的硬件约束  
  30.     snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);  
  31.     snd_soc_set_runtime_hwparams(substream, &dma_hardware);  
  32.   
  33.     // 为runtime_data分配内存,用于保存dma资源,包括缓冲区信息、IO设备信息、通道号、传输单元大小   
  34.     prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);  
  35.     if (prtd == NULL)  
  36.         return -ENOMEM;  
  37.   
  38.     spin_lock_init(&prtd->lock);  
  39.   
  40.     // runtime中的private_data字段指向runtime_data   
  41.     runtime->private_data = prtd;  
  42.     return 0;  
  43. }  

·          hw_params:设置pcm硬件参数时(cmd:SNDRV_PCM_IOCTL_HW_PARAMS),会回调该函数,一般用于初始化dma资源,包括通道号、传输单元、缓冲区信息、IO设备信息等。代码如下:

  1. static int dma_hw_params(struct snd_pcm_substream *substream,  
  2.     struct snd_pcm_hw_params *params)  
  3. {  
  4.     struct snd_pcm_runtime *runtime = substream->runtime;  
  5.     struct runtime_data *prtd = runtime->private_data;  
  6.     struct snd_soc_pcm_runtime *rtd = substream->private_data;  
  7.     unsigned long totbytes = params_buffer_bytes(params);   
  8.     struct s3c_dma_params *dma =  
  9.         snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); // 从cpu dai驱动i2s.c取得dma设备资源  
  10.     struct samsung_dma_info dma_info;  
  11.   
  12.     /* return if this is a bufferless transfer e.g. 
  13.      * codec <--> BT codec or GSM modem -- lg FIXME */  
  14.     if (!dma)  
  15.         return 0;  
  16.   
  17.     /* this may get called several times by oss emulation 
  18.      * with different params -HW */  
  19.     if (prtd->params == NULL) {  
  20.         /* prepare DMA */  
  21.         prtd->params = dma; // 该字段保存的是dma设备资源,如I2S tx FIFO地址、dma通道号、dma传输单元等  
  22.   
  23.         prtd->params->ops = samsung_dma_get_ops(); // dma操作函数集,这些操作函数实现见:arch/arm/plat-samsung/dma-ops.c  
  24.   
  25.         //...  
  26.         prtd->params->ch = prtd->params->ops->request(  
  27.                 prtd->params->channel, &dma_info);  
  28.     }  
  29.   
  30.     snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 这里把dma buffer相关信息赋给substream runtime,dma_buffer在创建pcm逻辑设备时分配  
  31.   
  32.     runtime->dma_bytes = totbytes;  
  33.   
  34.     spin_lock_irq(&prtd->lock);  
  35.     prtd->dma_loaded = 0;  
  36.     prtd->dma_period = params_period_bytes(params);  
  37.     prtd->dma_start = runtime->dma_addr; // dma buffer的物理首地址  
  38.     prtd->dma_pos = prtd->dma_start;  
  39.     prtd->dma_end = prtd->dma_start + totbytes;  
  40.     spin_unlock_irq(&prtd->lock);  
  41.   
  42.     return 0;  
  43. }  

·          prepare:当数据已准备好时(cmd:SNDRV_PCM_IOCTL_PREPARE),会回调该函数告知dma数据已就绪。代码如下:

  1. static int dma_prepare(struct snd_pcm_substream *substream)  
  2. {  
  3.     struct runtime_data *prtd = substream->runtime->private_data;  
  4.     int ret = 0;  
  5.   
  6.     pr_debug("Entered %s\n", __func__);  
  7.   
  8.     /* return if this is a bufferless transfer e.g. 
  9.      * codec <--> BT codec or GSM modem -- lg FIXME */  
  10.     if (!prtd->params)  
  11.         return 0;  
  12.   
  13.     /* flush the DMA channel */  
  14.     prtd->params->ops->flush(prtd->params->ch);  
  15.   
  16.     prtd->dma_loaded = 0; // 初始化dma装载计数  
  17.     prtd->dma_pos = prtd->dma_start; // 设置dma buffer当前指针为dma buffer首地址  
  18.   
  19.     /* enqueue dma buffers */  
  20.     dma_enqueue(substream); // 插入到dma传输队列中  
  21.   
  22.     return ret;  
  23. }  

dma_enqueue()函数,把当前dma buffer插入到dma传输队列中,当触发trigger()启动dma传输后,dma系统将会把dma buffer数据传送到FIFO(回放的情形)。

注意:每次dma传输完成后,都要调用snd_pcm_period_elapsed()告知pcm native一个周期的数据已经传送到FIFO上了,然后再次调用dma_enqueue(),dma传输...如此循环,直到触发trigger()停止dma传输。

·          trigger:pcm数据传送开始、停止、暂停、恢复时,会回调该函数启动或停止dma传输(补充:当上层第一次调用pcm_write()时,触发trigger启动dma传输;当上层调用pcm_stop()或pcm_drop()时,触发trigger停止dma传输)。trigger函数里面的操作必须是原子的,不能有可能引起睡眠的操作,并且应尽量简单。代码如下:

  1. static int dma_trigger(struct snd_pcm_substream *substream, int cmd)  
  2. {  
  3.     struct runtime_data *prtd = substream->runtime->private_data;  
  4.     int ret = 0;  
  5.   
  6.     pr_debug("Entered %s\n", __func__);  
  7.   
  8.     spin_lock(&prtd->lock);  
  9.   
  10.     switch (cmd) {  
  11.     case SNDRV_PCM_TRIGGER_START:  
  12.     case SNDRV_PCM_TRIGGER_RESUME:  
  13.     case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:  
  14.         prtd->state |= ST_RUNNING;  
  15.         prtd->params->ops->trigger(prtd->params->ch); // 启动dma传输  
  16.         break;  
  17.   
  18.     case SNDRV_PCM_TRIGGER_STOP:  
  19.     case SNDRV_PCM_TRIGGER_SUSPEND:  
  20.     case SNDRV_PCM_TRIGGER_PAUSE_PUSH:  
  21.         prtd->state &= ~ST_RUNNING;  
  22.         prtd->params->ops->stop(prtd->params->ch); // 停止dma传输  
  23.         break;  
  24.   
  25.     default:  
  26.         ret = -EINVAL;  
  27.         break;  
  28.     }  
  29.   
  30.     spin_unlock(&prtd->lock);  
  31.   
  32.     return ret;  
  33. }  

·          pointer:该回调函数返回传输数据的当前位置。当dma每完成一次传输后,都会调用该函数获得传输数据的当前位置,这样pcm native可根据它来计算dma buffer指针位置及可用空间。该函数也是原子的。代码如下:

  1. static snd_pcm_uframes_t  
  2. dma_pointer(struct snd_pcm_substream *substream)  
  3. {  
  4.     struct snd_pcm_runtime *runtime = substream->runtime;  
  5.     struct runtime_data *prtd = runtime->private_data;  
  6.     unsigned long res;  
  7.   
  8.     pr_debug("Entered %s\n", __func__);  
  9.   
  10.     res = prtd->dma_pos - prtd->dma_start; // 当前位置减去首地址,其实就是已传输数据的size  
  11.   
  12.     pr_debug("Pointer offset: %lu\n", res);  
  13.   
  14.     /* we seem to be getting the odd error from the pcm library due 
  15.      * to out-of-bounds pointers. this is maybe due to the dma engine 
  16.      * not having loaded the new values for the channel before being 
  17.      * called... (todo - fix ) 
  18.      */  
  19.   
  20.     if (res >= snd_pcm_lib_buffer_bytes(substream)) {  
  21.         if (res == snd_pcm_lib_buffer_bytes(substream))  
  22.             res = 0;  
  23.     }  
  24.   
  25.     return bytes_to_frames(substream->runtime, res); // 单位转化为frames  
  26. }  


dma buffer allocation


pcm operations小节,数次提及dma buffer,即dma数据缓冲区,用于保存上层拷贝过来的音频数据。dma buffer的分配,一般发生在pcm_dma驱动初始化阶段(probe)或pcm逻辑设备创建阶段(pcm_new)。代码如下:

  1. static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)  
  2. {  
  3.     struct snd_pcm_substream *substream = pcm->streams[stream].substream;  
  4.     struct snd_dma_buffer *buf = &substream->dma_buffer;  
  5.     size_t size = dma_hardware.buffer_bytes_max; // dma buffer大小不得超过hardware的buffer_bytes_max  
  6.   
  7.     buf->dev.type = SNDRV_DMA_TYPE_DEV;  
  8.     buf->dev.dev = pcm->card->dev;  
  9.     buf->private_data = NULL;  
  10.     buf->area = dma_alloc_writecombine(pcm->card->dev, size,  
  11.                        &buf->addr, GFP_KERNEL); // area字段是dma buffer的虚拟首地址,addr字段是dma buffer的物理首地址  
  12.     if (!buf->area)  
  13.         return -ENOMEM;  
  14.     buf->bytes = size;  
  15.     return 0;  
  16. }  
  17.   
  18. static int dma_new(struct snd_soc_pcm_runtime *rtd)  
  19. {  
  20.     struct snd_card *card = rtd->card->snd_card;  
  21.     struct snd_pcm *pcm = rtd->pcm;  
  22.     int ret = 0;  
  23.   
  24.     if (!card->dev->dma_mask)  
  25.         card->dev->dma_mask = &dma_mask;  
  26.     if (!card->dev->coherent_dma_mask)  
  27.         card->dev->coherent_dma_mask = DMA_BIT_MASK(32);  
  28.   
  29.     if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {  
  30.         ret = preallocate_dma_buffer(pcm, // 为playback stream分配的dma buffer  
  31.             SNDRV_PCM_STREAM_PLAYBACK);  
  32.         if (ret)  
  33.             goto out;  
  34.     }  
  35.   
  36.     if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {  
  37.         ret = preallocate_dma_buffer(pcm, // 为capture stream分配的dma buffer  
  38.             SNDRV_PCM_STREAM_CAPTURE);  
  39.         if (ret)  
  40.             goto out;  
  41.     }  
  42. out:  
  43.     return ret;  
  44. }  
  45.   
  46. static struct snd_soc_platform_driver samsung_asoc_platform = {  
  47.     .ops        = &dma_ops,  
  48.     .pcm_new    = dma_new,  
  49.     .pcm_free   = dma_free_dma_buffers,  
  50. };  

当soc-core调用soc_new_pcm()创建pcm逻辑设备时,会回调pcm_new()完成dma buffer的分配,注意回放子流和录制子流都有着各自的dma buffer。


pcm dma register


上两个小节,我们介绍了pcm_dma操作函数的作用及实现和dma buffer的分配,本小节分析pcm_dma注册过程。

当platform_driver:

  1. static struct platform_driver asoc_dma_driver = {  
  2.     .driver = {  
  3.         .name = "samsung-audio",  
  4.         .owner = THIS_MODULE,  
  5.     },  
  6.     .probe = samsung_asoc_platform_probe,  
  7.     .remove = __devexit_p(samsung_asoc_platform_remove),  
  8. };  

name = "samsung-audio"的platform_device(该platform_device在arch/arm/plat-samsung/devs.c中创建)匹配后,立即回调samsung_asoc_platform_probe()注册platform:

  1. static struct snd_soc_platform_driver samsung_asoc_platform = {  
  2.     .ops        = &dma_ops,  
  3.     .pcm_new    = dma_new,  
  4.     .pcm_free   = dma_free_dma_buffers,  
  5. };  
  6.   
  7. static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)  
  8. {  
  9.     return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);  
  10. }  

snd_soc_register_platform:将platform_drv注册到asoc-core。

·          创建一个snd_soc_platform实例,包含platform_drv(snd_soc_platform_driver)的相关信息,封装给asoc-core使用。

·          把以上创建并初始化好的platform插入到platform_list链表上,Machine驱动初始化时会遍历该链表,以找到dai_link声明的platform并绑定。

代码实现:

  1. /** 
  2.  * snd_soc_register_platform - Register a platform with the ASoC core 
  3.  * 
  4.  * @platform: platform to register 
  5.  */  
  6. int snd_soc_register_platform(struct device *dev,  
  7.         struct snd_soc_platform_driver *platform_drv)  
  8. {  
  9.     struct snd_soc_platform *platform;  
  10.   
  11.     platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);  
  12.     if (platform == NULL)  
  13.         return -ENOMEM;  
  14.   
  15.     /* create platform component name */  
  16.     platform->name = fmt_single_name(dev, &platform->id);  
  17.     if (platform->name == NULL) {  
  18.         kfree(platform);  
  19.         return -ENOMEM;  
  20.     }  
  21.   
  22.     platform->dev = dev;  
  23.     platform->driver = platform_drv;  
  24.     platform->dapm.dev = dev;  
  25.     platform->dapm.platform = platform;  
  26.     platform->dapm.stream_event = platform_drv->stream_event;  
  27.     mutex_init(&platform->mutex);  
  28.   
  29.     mutex_lock(&client_mutex);  
  30.     list_add(&platform->list, &platform_list);  
  31.     mutex_unlock(&client_mutex);  
  32.   
  33.     return 0;  
  34. }  


至此,完成了platform驱动的实现,回放的情形下,pcm_dma负责把dma buffer中的数据搬运到I2S tx FIFO,I2S负责把FIFO中的数据传送到codec。

至于alsa如何把音频数据从userspace拷贝到dma buffer,如何管理dma buffer,如何启动I2S和DMA传输,见后续pcm native分析。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值