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,再次启动