1、引言

(1)应用程序使用声卡的时候,数据流程是:应用程序把数据发送给驱动,驱动把数据发送给硬件声卡,声卡把数据转换成声音数据播放出去。

(2)可以使用两种方式发送数据

第一种:app发数据,等驱动处理完后再发下一段(处理完再发下一段就会导致声音会断断续续 )

第二种:应用程序不断地发数据,驱动程序不断地取数据,不断地发给硬件。解决了声音断续的问题,但是要创建一个非常大的缓冲区(在驱动程序里面申请的 ,称其为buffer)

一个采样点的数据包括左声道数据和右声道数据

这里hw_ptr是指针( 更新是指指针向后移)

2、怎么写驱动(s3c2440_dma.c(platform))

(1)负责数据传输的是平台部分里面的DMA文件,修改s3c2440_dma.c

3、分配/释放buffer

创建声卡时,s3c2440_dma_new函数被调用,函数里分配dma buffer.销毁声卡时,释放dma buffer

(1)分配DMA BUFFER

static int s3c2440_dma_new(struct snd_soc_pcm_runtime *rtd)

{

struct snd_card *card = rtd->card->snd_card;

struct snd_pcm *pcm = rtd->pcm;

struct snd_pcm_substream *substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;

struct snd_dma_buffer *buf = &substream->dma_buffer;





int ret = 0;





    /* 1. 分配DMA BUFFER */

if (!card->dev->dma_mask)

card->dev->dma_mask = &dma_mask;

if (!card->dev->coherent_dma_mask)

card->dev->coherent_dma_mask = DMA_BIT_MASK(32);





if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {//播放功能

   playback_dma_info.virt_addr = (unsigned int)dma_alloc_writecombine(pcm->card-   >dev, s3c2440_dma_hardware.buffer_bytes_max,

  &playback_dma_info.phy_addr, GFP_KERNEL);//分配DMA BUFFER   //virt_addr
是buffer的虚拟地址,buffer_bytes_max是分配缓冲区的大小,phy_addr是所分配缓冲区的物理地址

        if (!playback_dma_info.virt_addr)//如果虚拟地址为空,返回错误

        {

            return -ENOMEM; //返回没有内存的错误标志

        }

        playback_dma_info.buf_max_size = s3c2440_dma_hardware.buffer_bytes_max;//分配成功,记录分配buffer的大小,尽管分配128K,但用到多少是app决定的 



    //把buffer信息告诉alsa的核心层

    buf->dev.type = SNDRV_DMA_TYPE_DEV;

    buf->dev.dev = pcm->card->dev;

    buf->private_data = NULL;

        buf->area = playback_dma_info.virt_addr;//播放相关的dma  buffer  的信息的结构体

        buf->bytes = playback_dma_info.buf_max_size;//buf的大小

}//





return ret;

}

(2)释放DMA BUFFER

static void s3c2440_dma_free(struct snd_pcm *pcm)

{

dma_free_writecombine(pcm->card->dev, playback_dma_info.buf_max_size,

     (void *)playback_dma_info.virt_addr, playback_dma_info.phy_addr);//释放的是playback(播放)部分的dma buffer(buffer大小,buffer的虚拟地址,buffer的物理地址)

}

4、request_irq

(1)open函数

static int s3c2440_dma_open(struct snd_pcm_substream *substream)

{

struct snd_pcm_runtime *runtime = substream->runtime;

    int ret;

    /* 注册中断 */

    ret = request_irq(IRQ_DMA2,s3c2440_dma2_irq, IRQF_DISABLED, "myalsa for playback",substream);

    if (ret)   //IRQ_DMA2是中断号,s3c2440_dma2_irq是中断处理函数, IRQF_DISABLED是标志(当发生中断时,在中断处理过程中,中断是保持屏蔽的), "myalsa
for playback"是中断名字,substream是device ID 

    {

        printk("request_irq error!\n");

        return -EIO;

    }





return 0;

}

(2)中断请求函数(数据传输成功后,更新hw_ptr等信息)

static irqreturn_t s3c2440_dma2_irq(int irq, void *devid) //左边参数是中断号,右边是设备ID

{

    struct snd_pcm_substream *substream = devid;//在open函数调用的请求中断函数 request_irq中的给中断处理函数s3c2440_dma2_irq的参数substream

        

    /* 更新状态信息 */  当传输完一个DMA后,发生中断

    playback_dma_info.dma_ofs += playback_dma_info.period_size;//偏移地址向后移一个period

    if (playback_dma_info.dma_ofs >= playback_dma_info.buffer_size)//如果偏移地址超过buffer 的范围

        playback_dma_info.dma_ofs = 0;//就指向buffer的开头

    

    /* 更新hw_ptr等信息,(驱动部分)

     * 并且判断:如果buffer里没有数据了,则调用trigger来停止DMA 

     */

传完一个DMA,

    snd_pcm_period_elapsed(substream);  





    if (playback_dma_info.be_running)

    {

        /* 如果还有数据,be_running是1表明在运行

         * 1. 加载下一个period 

         * 2. 再次启动DMA传输

         */

        load_dma_period();

        s3c2440_dma_start();

    }





    return IRQ_HANDLED;

}

5、 s3c2440_dma_hw_params(准备DMA传输的参设设置)

static int s3c2440_dma_hw_params(struct snd_pcm_substream *substream,

struct snd_pcm_hw_params *params)

{

struct snd_pcm_runtime *runtime = substream->runtime;

unsigned long totbytes = params_buffer_bytes(params);//所使用的buffer大小由应用程序传进来的参数params决定的

    

    /* 根据params设置DMA */

snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);





    /* s3c2440_dma_new(驱动程序里面)分配了很大的DMA BUFFER,应用程序会用多大是由应用程序决定的,传进来的参数params

     * params决定使用多大

     */

runtime->dma_bytes            = totbytes;//记录所使用的大小,以后会用到,应用程序写到尾部时会返回来,尾部在哪里由尾部决定的。

    playback_dma_info.buffer_size = totbytes;//buffer_size

    playback_dma_info.period_size = params_period_bytes(params);//period_size,buffer里面,一次DMA传输是以period传输的





    return 0;

}

6、prepare函数(准备DMA传输的数据)

static int s3c2440_dma_prepare(struct snd_pcm_substream *substream)

{

    /* 准备DMA传输 */



    /* 复位各种状态信息(清零) */

    playback_dma_info.dma_ofs = 0;//偏移值设置为0,以后DMA传输从开始处开始

    playback_dma_info.be_running = 0;//表示DMA尚未启动,还没运行

    

    /* 加载第1个period */

    load_dma_period();





return 0;

}

return ret;

}

7、加载需要传输的数据

/* 数据传输: 源,目的,长度 */

static void load_dma_period(void)

{       

/* 把源,目的,长度告诉DMA */

dma_regs->disrc      = playback_dma_info.phy_addr + playback_dma_info.dma_ofs;       /* 源的物理地址 */物理地址+偏移地址

dma_regs->disrcc     = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */

dma_regs->didst      = 0x55000010;        /* 目的的物理地址 */IIC控制器

dma_regs->didstc     = (0<<2) | (1<<1) | (1<<0); /* 目的位于APB总线, 目的地址不变 */





    /* bit22: 1-noreload *表示传完一段数据后会重新加载某些值,自己启动DMA,在我们的中断程序是自己启动DMA的,所以不让它自动下载,因而把它设置为1,自动加载的话在中断函数还没有处理前就自动传输数据/解决播放存在杂音的问题

dma_regs->dcon       = (1<<31)|(0<<30)|(1<<29)|(0<<28)|(0<<27)|(0<<24)|(1<<23)|(1<<22)|(1<<20)|(playback_dma_info.period_size/2);  /* 使能中断,单个传输,硬件触发
*/period_size长度,每次传输不是传输整个buffer,是传输里面的某个period,period_size大小。buffer_size是应用程序所使用buffer的大小,buf_max_size是驱动程序分配的buffer大小,应用程序只可能使用里面的一部分。除以2是因为(1<<20)也就是2个字节,因而以字节为单位所以要除以2。也就是传输多少次,每次传输2个字节。

}

8、dma_trigger函数(触发DMA传输,传输完成后,产生中断,进入中断处理函数s3c2440_dma2_irq)

static int s3c2440_dma_trigger(struct snd_pcm_substream *substream, int cmd)

{

int ret = 0;





    /* 根据cmd启动或停止DMA传输 */

switch (cmd) {

case SNDRV_PCM_TRIGGER_START:

case SNDRV_PCM_TRIGGER_RESUME:

case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:

        /* 启动DMA传输 */

        playback_dma_info.be_running = 1;//还有数据的状态信息

        s3c2440_dma_start();

break;





case SNDRV_PCM_TRIGGER_STOP:

case SNDRV_PCM_TRIGGER_SUSPEND:

case SNDRV_PCM_TRIGGER_PAUSE_PUSH:

        /* 停止DMA传输 */

        playback_dma_info.be_running = 0;//传完数据的状态信息

        s3c2440_dma_stop();

break;





default:

ret = -EINVAL;

break;

}

9、dma_pointer函数(表示下一次DMA传输的位置(每一次DMA传输一个period),hw_ptr指针是根据DMA位置来确定的)

/* 返回结果是frame(单位是frame) */

static snd_pcm_uframes_t s3c2440_dma_pointer(struct snd_pcm_substream *substream)

{

return bytes_to_frames(substream->runtime, playback_dma_info.dma_ofs);//playback_dma_info.dma_ofs是DMA传输的偏移地址,如果偏移地址超出buffer的范围,就应该回到开始的位置,假如一个period里面有3个frame,如果位置指向period开始处,返回值是3,指向右边是6.也就是

}



10、总结

(1)创建声卡时导致 s3c2440_dma_new函数被调用

(2)s3c2440_dma_new函数里面分配DMA  BUFFER(分配最大的,128K,但并不表示一定要用完(根据应用程序参数决定))

(3)应用程序打开设备,播放声音的时候,在open函数(s3c2440_dma_open),设置属性,注册中断;在close函数中,释放中断

(4)设置参数(在s3c2440_dma_hw_params函数中),驱动程序分配了很大一块空间,确定buffer_size(应用程序传进来的params参数决定的),period_size(buffer里是逐个peroid传输的)

一次DMA传输是以period传输的

(5)准备DMA传输(s3c2440_dma_prepare),加载第一个period(load_dma_period)

(6)启动DMA 传输(s3c2440_dma_trigger),一个period传输完后会产生中断)调用中断处理函数(s3c2440_dma2_irp更新偏移地址。更新alsa驱动的其他状态信息。如果还有数据,再次加载period,再次启动

转自:http://blog.csdn.net/qingkongyeyue/article/details/54633478

ALSA声卡10_从零编写之数据传输_学习笔记的更多相关文章

  1. ALSA声卡08_从零编写之框架_学习笔记

    1.整体框架 (1)图示((DAI(全称Digital Audio Interface)接口)) 在嵌入式系统里面,声卡驱动是ASOC,是在ALSA驱动上封装的一层,包括以下三大块 (2)程序框架 m ...

  2. ALSA声卡11_从零编写之调试——学习笔记

    1.调试 (1)把程序拷贝到服务器上进行编译 (2)把程序放到内核上面去 重新配置内核,吧原来的声卡驱动程序去掉 a. 修改语法错误 11th_myalsa b. 配置内核去掉原来的声卡驱动 -> ...

  3. ALSA声卡09_从零编写之参数设置_学习笔记

    1.参数设置分析 (1)open: soc_pcm_open 依次调用cpu_dai, dma, codec_dai, machine的open或startup函数 只在dma的open函数里添加参数 ...

  4. ALSA声卡12_从零编写之添加音量控制_学习笔记

    1.设置音量时应用程序的调用过程 (1)strace分析: amixer cset numid=1 30 (设置音量) /dev/snd/controlC0 open SNDRV_CTL_IOCTL_ ...

  5. ALSA声卡16_编写ALSA声卡应用程序_学习笔记

    1.体验 (1)ALSA声卡使用体验:使用arecord录音,使用aplay播放,在Alsa-utils里面) 准备: cd linux-3.4.2 patch -p1 < ../linux-3 ...

  6. ALSA声卡07_分析调用过程_学习笔记

    1.编译新的strace工具分析aplay和amixer应用程序对声卡的调用过程 (1)因为旧的strace工具不能识别不能识别alsa声卡驱动程序里面的ioctrl. (2)编译过程参考http:/ ...

  7. 从零开始构建并编写神经网络---Keras【学习笔记】[1/2]

    Keras简介:   Keras是由纯python编写的基于theano/tensorflow的深度学习框架.   Keras是一个高层神经网络API,支持快速实验,能够把你的idea迅速转换为结果, ...

  8. 60分钟内从零起步驾驭Hive实战学习笔记

    本博文的主要内容是: 1. Hive本质解析 2. Hive安装实战 3. 使用Hive操作搜索引擎数据实战 SparkSQL前身是Shark,Shark强烈依赖于Hive.Spark原来没有做SQL ...

  9. 60分钟内从零起步驾驭Hive实战学习笔记(Ubuntu里安装mysql)

    本博文的主要内容是: 1. Hive本质解析 2. Hive安装实战 3. 使用Hive操作搜索引擎数据实战 SparkSQL前身是Shark,Shark强烈依赖于Hive.Spark原来没有做SQL ...

随机推荐

  1. PostgreSQL 系统参数调整及并行设置(转)

    转自:https://yq.aliyun.com/teams/5 OS 准备 # yum -y install coreutils glib2 lrzsz sysstat e4fsprogs xfsp ...

  2. .NET Web开发技术简单整理 转

    .NET Web开发技术简单整理 原文:http://www.cnblogs.com/SanMaoSpace/p/3157293.html 在最初学习一些编程语言.一些编程技术的时候,做的更多的是如何 ...

  3. Ubuntu16.04 --> 14.04

    从16到14 自认为14是比较稳定的.从安装依赖上说. 14安装应用 更多参见[请直接拉到"华丽丽的分割线"下面] Java9 注意,添加源的时候先把lantern打开!!! 添加 ...

  4. [sklearn]性能度量之AUC值(from sklearn.metrics import roc_auc_curve)

    原创博文,转载请注明出处! 1.AUC AUC(Area Under ROC Curve),即ROC曲线下面积. 2.AUC意义 若学习器A的ROC曲线被学习器B的ROC曲线包围,则学习器B的性能优于 ...

  5. 全面进军javascript!

    前两天经过新华书店,进去转了转,又买了两本书.这次买的是<javascript学习指南>和<HTML5经典实例>(都是图灵动物系列,我已经有三本了*^_^*),其实我是想去买& ...

  6. linux自学(六)之开始centos学习,更换yum源

    上一篇:linux自学(五)之开始centos学习,Xshell远程连接 1. 备份原来的yum源 cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repo ...

  7. LOJ2323. 「清华集训 2017」小 Y 和地铁 【搜索】【思维】【好】

    LINK 思路 首先如果直接算每一个段有三个决策 左/右 上/下 跨不跨过端点 这样的复杂度是\((2^3)^{22}\),显然是无法接受的 然后考虑怎么优化这个东西 首先左右这个决策是没有意义的 因 ...

  8. BZOJ4540 Hnoi2016 序列 【莫队+RMQ+单调栈预处理】*

    BZOJ4540 Hnoi2016 序列 Description 给定长度为n的序列:a1,a2,-,an,记为a[1:n].类似地,a[l:r](1≤l≤r≤N)是指序列:al,al+1,-,ar- ...

  9. Apache报错You don't have permission to access on this server

    解决方法: 打开httpd.conf文件 <Directory /> AllowOverride none Require all denied </Directory> 修改 ...

  10. 几种分布式调用技术的比较 -- RPC VS REST

    我之前在传统IT公司干活,后来来了互联网,感受到了很多不同,其中有一点就是两者使用到的技术有一些差别.比如说分布式调用技术. 我在的这家公司内部的服务架构是基于Thrift的,服务基于Thrift进行 ...