×
嵌入式 > 嵌入式开发 > 详情

s3c2410上iis接口的uda341驱动的学习

发布时间:2020-08-11 发布时间:
|
首先看uda1341的datasheet,将其中的关键点记录下来:

接口: i2s, 还有一个L3接口,应该是控制其中的dsp(可以在playback模式提供soft mute等功能)
格式: MSB-justified and LSB-justified format compatible,Three combinational data formats with MSB data output and LSB 16, 18 or 20 bits data input. (从时序图上看, MSB和LSB表示大小端, justified可能是指ws信号变化后的第一个位时钟的上升沿采集第一位,原始的i2s格式是第2个位时钟上升沿开始采样的.
速率: 1fs input and output format data rate
引脚: 除去电压,余下的引脚主要就是i2s接口和L3接口的引脚了
uda1341还有个L3接口,由3根线构成:时钟,数据,模式.soc可以通过它来控制uda1341的音频处理功能,还可以获取一些状态信息. 从协议上看很简单,首先mode引脚拉低,送一个8bit的地址,地址的最后两位是一个选择分量,然后拉高mode,发送8bit数据,根据之前的选择分量又表示3种寄存器.这8位数据的高位本身又是个选择分量,datasheet里面定义了各种选择分量时对应值的表格,到时候根据它写个状态机就可以了.

uda1341需要了解的东西就这么多了,接下来就可以看实际的驱动了.网上找了个lfc修改过的uda1341驱动,不过仿佛是基于OSS的,先对它进行分析,了解下驱动本身的东西,后文贴的源码我以注释的形式增加了自己的一些理解.

模块初始化:

static int __init s3c2410_uda1341_init(void) {//初始化输入和输出的缓冲区(xxx_stream),这两个struct封装了dma会用到的一些信息.//这个结构后文用到的时候再分析memzero(&input_stream, sizeof(audio_stream_t));             memzero(&output_stream, sizeof(audio_stream_t));//内核提供的驱动注册函数return driver_register(&s3c2410iis_driver);}static struct device_driver s3c2410iis_driver = {.name = "s3c2410-iis",.bus = &platform_bus_type,.probe = s3c2410iis_probe,.remove = s3c2410iis_remove,};

driver_register是sysfs提供的注册驱动的函数,这里表示该驱动是为platform总线上的设备服务的.当总线上有新设备的时候就会调用s3c2410iis_probe来判断该设备是否存在且能使用.

static int s3c2410iis_probe(struct device *dev) {//转换为platform_device,因为该驱动是在platform总线上的,所以device结构肯定是嵌入在platform_device内struct platform_device *pdev = to_platform_device(dev);struct resource *res;unsigned long flags;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (res == NULL) {printk(KERN_INFO PFX "failed to get memory region resouce\n");return -ENOENT;}//2410iis的虚拟地址,不知道什么时候ioremap过来的iis_base = (void *)S3C24XX_VA_IIS ;if (iis_base == 0) {printk(KERN_INFO PFX "failed to ioremap() region\n");return -EINVAL;}//clk相关的操作,见后文的分析iis_clock = clk_get(dev, "iis");if (iis_clock == NULL) {printk(KERN_INFO PFX "failed to find clock source\n");return -ENOENT;}/**************************modify by lfc*****************************///2.6.11内核有此函数,意思是增加一个引用计数,最新的内核已经没有了clk_use(iis_clock);//使能iis的时钟,因为2410启动时disable了该时钟的//对此处修改有疑问,因为后面的init_s3c2410_iis_bus中又disable了iis的时钟clk_enable(iis_clock);/*****************************end add********************************/local_irq_save(flags);//配置L3接口,i2s接口占用的GPIO引脚/* GPB 4: L3CLOCK, OUTPUT */s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);s3c2410_gpio_pullup(S3C2410_GPB4,1);... ...local_irq_restore(flags);//初始化2410的iis控制器init_s3c2410_iis_bus();//初始化uda1341init_uda1341();//初始化缓冲用的stream//输出用dma的channel2output_stream.dma_ch = DMA_CH2;if (audio_init_dma(&output_stream, "UDA1341 out")) {audio_clear_dma(&output_stream,&s3c2410iis_dma_out);printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channels\n" );return -EBUSY;}//输入用dma的channel1input_stream.dma_ch = DMA_CH1;if (audio_init_dma(&input_stream, "UDA1341 in")) {audio_clear_dma(&input_stream,&s3c2410iis_dma_in);printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channels\n" );return -EBUSY;}//audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);printk(AUDIO_NAME_VERBOSE " initialized\n");return 0;}//初始化2410的iis控制器static void init_s3c2410_iis_bus(void){writel(0, iis_base + S3C2410_IISPSR);writel(0, iis_base + S3C2410_IISCON);writel(0, iis_base + S3C2410_IISMOD);writel(0, iis_base + S3C2410_IISFCON);clk_disable(iis_clock);}

clk_get(dev, "iis")定义在plat-s3c24xx的clock.c里面,这个文件提供了所有s3c系列的cpu的时钟方面的管理接口.

struct clk *clk_get(struct device *dev, const char *id){struct clk *p;struct clk *clk = ERR_PTR(-ENOENT);int idno;if (dev == NULL || dev->bus != &platform_bus_type)idno = -1;elseidno = to_platform_device(dev)->id;mutex_lock(&clocks_mutex);//从维护的一个struct clk链表中找到名字等于输入参数的结点,这个链表中的元素是具体的cpu的初始时注册进去的,见后文描述list_for_each_entry(p, &clocks, list) {if (p->id == idno &&strcmp(id, p->name) == 0 &&try_module_get(p->owner)) {clk = p;break;}}... ...mutex_unlock(&clocks_mutex);return clk;}

在match-s3c2410/clock.c里面,定义了启动时要使能或不使能的时钟源

static struct clk init_clocks_disable[] = {{.name      = "nand",.id      = -1,.parent      = &clk_h,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_NAND,}, {.name      = "iis",.id      = -1,.parent      = &clk_p,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_IIS,},... ...};static struct clk init_clocks[] = {{.name      = "lcd",.id      = -1,.parent      = &clk_h,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_LCDC,}, {.name      = "gpio",.id      = -1,.parent      = &clk_p,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_GPIO,}, ... ...};

系统初始化的时候会调用该文件的s3c2410_baseclk_add里面,调用了plat-s3c24xx提供的接口注册这些时钟源:

int __init s3c2410_baseclk_add(void){... ...clkp = init_clocks;for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {/* ensure that we note the clock state */clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0;ret = s3c24xx_register_clock(clkp);if (ret < 0) {printk(KERN_ERR "Failed to register clock %s (%d)\n",clkp->name, ret);}}clkp = init_clocks_disable;for (ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) {ret = s3c24xx_register_clock(clkp);if (ret < 0) {printk(KERN_ERR "Failed to register clock %s (%d)\n",clkp->name, ret);}s3c2410_clkcon_enable(clkp, 0);}... ...}

uda1341通过L3接口初始化:

static void init_uda1341(void){/* GPB 4: L3CLOCK *//* GPB 3: L3DATA *//* GPB 2: L3MODE */unsigned long flags;uda1341_volume = 62 - ((DEF_VOLUME * 61) / 100);uda1341_boost = 0;//        uda_sampling = DATA2_DEEMP_NONE;//        uda_sampling &= ~(DATA2_MUTE);local_irq_save(flags);s3c2410_gpio_setpin(S3C2410_GPB2,1);//L3MODE=1s3c2410_gpio_setpin(S3C2410_GPB4,1);//L3CLOCK=1local_irq_restore(flags);uda1341_l3_address(UDA1341_REG_STATUS);uda1341_l3_data(0x40 | STAT0_SC_384FS | STAT0_IF_MSB|STAT0_DC_FILTER); // reset uda1341uda1341_l3_data(STAT1 | STAT1_ADC_ON | STAT1_DAC_ON);uda1341_l3_address(UDA1341_REG_DATA0);uda1341_l3_data(DATA0 |DATA0_VOLUME(0x0)); // maximum volumeuda1341_l3_data(DATA1 |DATA1_BASS(uda1341_boost)| DATA1_TREBLE(0));uda1341_l3_data((DATA2 |DATA2_DEEMP_NONE) &~(DATA2_MUTE));uda1341_l3_data(EXTADDR(EXT2));uda1341_l3_data(EXTDATA(EXT2_MIC_GAIN(0x6)) | EXT2_MIXMODE_CH1);//input channel 1 select(input channel 2 off)}

其中的uda1341_l3_address(),uda1341_l3_data()都是操作的data,clk,mode gpio口的电平,配合udelay延时,来模拟的L3接口协议.传输时调用local_irq_save关了中断的.

probe函数的最后部分是对输入和输出流的初始化,主要就是初始化对应的dma通道.从2410的datasheet中dma部分可知,通道1支持 iis的sdi,通道2支持iis的sdi,sdo.所以这里将通道1用于输入流,通道2用于了输出流.下面是对dma部分的初始化:

static int __init audio_init_dma(audio_stream_t * s, char *desc){int ret ;//2.6.10内核定义的enum,就两个值,表示dma的源是hardware还是memorys3c2410_dmasrc_t source;int hwcfg;unsigned long devaddr;dmach_t channel;int dcon;unsigned int flags = 0;if(s->dma_ch == DMA_CH2){channel = 2;//因为是输出,所以源是内存source = S3C2410_DMASRC_MEM;hwcfg = 3;//2410的iis接口中fifo数据寄存器的物理地址,16bit宽devaddr = 0x55000010;//DCON寄存器的初始值,这里的取值表示://handshake mode,传输完成产生中断,DREQ and DACK are synchronized to PCLK (APB clock)//读写各一字节后释放总线,single service mode,auto reload,dma的请求源是iis sdodcon = 0xa0800000;//?flags = S3C2410_DMAF_AUTOSTART;//配置dma传输中,iis控制器一端的寄存器值//hwcfg=3,表示设备是在APB总线上,且传输过程中地址不递增//看2410datasheet的框图,可以看到iis控制器是在APB总线上//读iis数据fifo的时候是一直读同一个地址s3c2410_dma_devconfig(channel, source, hwcfg, devaddr);//源地址位宽为2个字节,硬件源触发,完成一次传输要产生中断s3c2410_dma_config(channel, 2, dcon);//安装一个回调函数s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback);//保存一个标志s3c2410_dma_setflags(channel, flags);//请求对应的dma通道,详细内容见后文ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_out, NULL);s->dma_ok = 1;return ret;}else if(s->dma_ch == DMA_CH1){channel =1;source =S3C2410_DMASRC_HW;hwcfg =3;devaddr = 0x55000010;//表示dma的请求源是i2s sdi,其他类似dcon = 0xa2900000;flags = S3C2410_DMAF_AUTOSTART;s3c2410_dma_devconfig(channel, source, hwcfg, devaddr);s3c2410_dma_config(channel, 2, dcon);s3c2410_dma_set_buffdone_fn(channel, audio_dmain_done_callback);s3c2410_dma_setflags(channel, flags);ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_in, NULL);s->dma_ok =1;return ret ;}elsereturn 1;}

int s3c2410_dma_devconfig(int channel,s3c2410_dmasrc_t source,int hwcfg,unsigned long devaddr){//系统定义了4个s3c2410_dma_chan_t结构,记录了这4个dma通道的所有信息//此处根据输入参数选择将要配置的通道s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];check_channel(channel);pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx\n",__FUNCTION__, (int)source, hwcfg, devaddr);chan->source = source;//保存源的目的地址,比如0x55000010,表示源就是2410的iis接口的fifo数据寄存器chan->dev_addr = devaddr;switch (source) {case S3C2410_DMASRC_HW:/* source is hardware */pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d\n",__FUNCTION__, devaddr, hwcfg);//源是iis控制器,所以地址固定,在APB总线上dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);dma_wrreg(chan, S3C2410_DMA_DISRC,  devaddr);//目的地址在AHB总线上,地址递增,因为是内存缓冲区dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));//add_reg指向dma的目的地址寄存器DIDSTchan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);return 0;case S3C2410_DMASRC_MEM:/* source is memory */pr_debug( "%s: mem source, devaddr=%08lx, hwcfg=%d\n",__FUNCTION__, devaddr, hwcfg);dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0));dma_wrreg(chan, S3C2410_DMA_DIDST,  devaddr);dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3);chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);return 0;}printk(KERN_ERR "dma%d: invalid source type (%d)\n", channel, source);return -EINVAL;}

int s3c2410_dma_config(dmach_t channel,int xferunit,int dcon){s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];check_channel(channel);switch (xferunit) {case 1://配置源地址的位宽dcon |= S3C2410_DCON_BYTE;break;case 2:dcon |= S3C2410_DCON_HALFWORD;break;case 4:dcon |= S3C2410_DCON_WORD;break;default:pr_debug("%s: bad transfer size %d\n", __FUNCTION__, xferunit);return -EINVAL;}//设置dma请求源是硬件,如iis sdi等dcon |= S3C2410_DCON_HWTRIG;//设置一次dma传输结束后触发中断dcon |= S3C2410_DCON_INTREQ;chan->dcon = dcon;chan->xfer_unit = xferunit;return 0;}

int s3c2410_dma_request(unsigned int channel, s3c2410_dma_client_t *client,void *dev){s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];unsigned long flags;int err;check_channel(channel);local_irq_save(flags);dbg_showchan(chan);//是否已经有人申请过该通道if (chan->in_use) {if (client != chan->client) {printk(KERN_ERR "dma%d: already in use\n", channel);local_irq_restore(flags);return -EBUSY;} else {printk(KERN_ERR "dma%d: client already has channel\n", channel);}}chan->client = client;chan->in_use = 1;if (!chan->irq_claimed) {//安装对应dma通道的中断,可以看出每个dma通道共用了同一个中断服务程序s3c2410_dma_irqerr = request_irq(chan->irq, s3c2410_dma_irq, SA_INTERRUPT,client->name, (void *)chan);if (err) {chan->in_use = 0;local_irq_restore(flags);printk(KERN_ERR "%s: cannot get IRQ %d for DMA %d\n",client->name, chan->irq, chan->number);return err;}chan->irq_claimed = 1;chan->irq_enabled = 1;}local_irq_restore(flags);return 0;}

至此,已经完成了dma等相关部分的初始化,最后调用oss驱动提供的接口注册音频相关的回调函数:

static struct file_operations smdk2410_audio_fops = {llseek: smdk2410_audio_llseek,write: smdk2410_audio_write,read: smdk2410_audio_read,poll: smdk2410_audio_poll,ioctl: smdk2410_audio_ioctl,open: smdk2410_audio_open,release: smdk2410_audio_release};static struct file_operations smdk2410_mixer_fops = {ioctl: smdk2410_mixer_ioctl,open: smdk2410_mixer_open,release: smdk2410_mixer_release};
接下来分析其中的open函数:

static int smdk2410_audio_open(struct inode *inode, struct file *file){//检查读写计数int cold = !audio_active;DPRINTK("audio_open\n");if ((file->f_flags & O_ACCMODE) == O_RDONLY) {if (audio_rd_refcount || audio_wr_refcount)return -EBUSY;audio_rd_refcount++;} else if ((file->f_flags & O_ACCMODE) == O_WRONLY) {if (audio_wr_refcount)return -EBUSY;audio_wr_refcount++;} else if ((file->f_flags & O_ACCMODE) == O_RDWR) {if (audio_rd_refcount || audio_wr_refcount)return -EBUSY;audio_rd_refcount++;audio_wr_refcount++;} elsereturn -EINVAL;if (cold) {//第一次open,赋初值//44100audio_rate = AUDIO_RATE_DEFAULT;//2audio_channels = AUDIO_CHANNELS_DEFAULT;//8192,8kaudio_fragsize = AUDIO_FRAGSIZE_DEFAULT;//8audio_nbfrags = AUDIO_NBFRAGS_DEFAULT;if ((file->f_mode & FMODE_WRITE)){//初始化iis接口的几个寄存器,为发送做准备init_s3c2410_iis_bus_tx();//清除输出流,后文见实现细节audio_clear_buf(&output_stream);}if ((file->f_mode & FMODE_READ)){init_s3c2410_iis_bus_rx();audio_clear_buf(&input_stream);}}return 0;}

clear stream的实现:

static void audio_clear_buf(audio_stream_t * s){DPRINTK("audio_clear_buf\n");//flush dma 通道if(s->dma_ok) s3c2410_dma_ctrl(s->dma_ch, S3C2410_DMAOP_FLUSH);if (s->buffers) {//如果已经分配了缓冲区,即不是第一次使用了,执行以下操作int frag;//对链上的每个非空的dma缓冲区进行释放,这些值的具体含义要到后文分配dma缓冲区的时候分析//看了audio_setup_buf后再看这段代码就明白了for (frag = 0; frag < s->nbfrags; frag++) {if (!s->buffers[frag].master)continue;dma_free_coherent(NULL,s->buffers[frag].master,s->buffers[frag].start,s->buffers[frag].dma_addr);}kfree(s->buffers);s->buffers = NULL;}s->buf_idx = 0;s->buf = NULL;}

接下来分析write函数,这里面会涉及到dma缓冲区申请等操作:

static ssize_t smdk2410_audio_write(struct file *file, const char *buffer,size_t count, loff_t * ppos){const char *buffer0 = buffer;audio_stream_t *s = &output_stream;int chunksize, ret = 0;switch (file->f_flags & O_ACCMODE) {case O_WRONLY:case O_RDWR:break;default:return -EPERM;}//如果还没有分配缓冲区,则调用audio_setup_buf,后文分析if (!s->buffers && audio_setup_buf(s))return -ENOMEM;//FIXME:将count的低2位置0,不知道目的是什么?count &= ~0x03;while (count > 0) {//b指向当前的fragmentaudio_buf_t *b = s->buf;//获取信号量,有阻塞和非阻塞两种方式if (file->f_flags & O_NONBLOCK) {ret = -EAGAIN;if (down_trylock(&b->sem))break;} else {ret = -ERESTARTSYS;if (down_interruptible(&b->sem))break;}if (audio_channels == 2) {//chunksize等于一个fragment的size(8k)减去这个fragment已经有了的数据的size(b->size),也就是还能容纳的字节数chunksize = s->fragsize - b->size;if (chunksize > count)chunksize = count;//从用户空间的buffer拷贝chunksize个字节到当前fragment的空闲位置if (copy_from_user(b->start + b->size, buffer, chunksize)) {up(&b->sem);return -EFAULT;}//已经有了的数据size加上本次复制的数据b->size += chunksize;} else {//单声道只写一半的数据,但是另一半的空间的还是要空出来chunksize = (s->fragsize - b->size) >> 1;if (chunksize > count)chunksize = count;DPRINTK("write %d to %d\n", chunksize*2, s->buf_idx);//单声道数据的拷贝办法,没看懂,和oss应用传递的数据格式有关,以后再看if (copy_from_user_mono_stereo(b->start + b->size,buffer, chunksize)) {up(&b->sem);return -EFAULT;}b->size += chunksize*2;}buffer += chunksize;count -= chunksize;if (b->size < s->fragsize) {//这次写的数据还没有写满这个fragment,退出,因为要写满才开始dma传输up(&b->sem);break;}//将本次的数据加入dma模块的待传输队列中,详见后文分析if((ret = s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size))) {printk(PFX"dma enqueue failed.\n");return ret;}b->size = 0;//FIXME:感觉这里的代码有问题,next_buf会指向一个fragment,但是没有考虑到setup buffer的时候没有分配到8个fragment的情况.//这个时候下一个fragment的数据是无效的NEXT_BUF(s, buf);}if ((buffer - buffer0))ret = buffer - buffer0;DPRINTK("audio_write : end count=%d\n\n", ret);return ret;}

建立流的实现:

static int audio_setup_buf(audio_stream_t * s){int frag;int dmasize = 0;char *dmabuf = 0;dma_addr_t dmaphys = 0;//缓冲区一共由8个fragment构成,每个fragment的size是8kif (s->buffers)return -EBUSY;s->nbfrags = audio_nbfrags;s->fragsize = audio_fragsize;//用kmalloc分配描述8个fragment的数据结构的空间,下面是这个数据结构的定义,它将会保存dma缓冲区的地址,大小等//typedef struct {//int size; /* buffer size *///char *start; /* point to actual buffer *///dma_addr_t dma_addr; /* physical buffer address *///struct semaphore sem; /* down before touching the buffer *///int master; /* owner for buffer allocation, contain size when true *///} audio_buf_t;s->buffers = (audio_buf_t *)kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL);if (!s->buffers)goto err;memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags);for (frag = 0; frag < s->nbfrags; frag++) {//循环8次,初始化这8个fragmentaudio_buf_t *b = &s->buffers[frag];if (!dmasize) {//第一个fragment首先尝试分配8*8k大小的dma缓冲区dmasize = (s->nbfrags - frag) * s->fragsize;do {//FIXME:采用的一致性dma映射方式,而不是流式,此处值得考究//LDD3中建议采用的流式映射,而且最后的GFP_DMA参数按理说是老式设备(不能寻址32位地址)才需要用到的//第一个struct device* 参数为null,不知道有没有影响dmabuf = dma_alloc_coherent(NULL, dmasize, &dmaphys, GFP_KERNEL|GFP_DMA);if (!dmabuf)//如果分配失败,就减小8k,继续尝试分配,直到分配成功或连最小的8k都无法分配idmasize -= s->fragsize;} while (!dmabuf && dmasize);//这个fragment没有分配到dma缓冲区,则报错if (!dmabuf)goto err;//分配成功后,fragment里面的master字段保存这个dma缓冲区的大小b->master = dmasize;}//start字段保存dma缓冲区的起始内核虚拟地址b->start = dmabuf;//dma_addr字段保存dma缓冲区的总线地址b->dma_addr = dmaphys;sema_init(&b->sem, 1);DPRINTK("buf %d: start %p dma %d\n", frag, b->start, b->dma_addr);//修改几个变量,将会用于下一个fragment的赋值dmabuf += s->fragsize;dmaphys += s->fragsize;dmasize -= s->fragsize;}//更新当前buffer的index和指针s->buf_idx = 0;s->buf = &s->buffers[0];return 0;err:printk(AUDIO_NAME ": unable to allocate audio memory\n ");audio_clear_buf(s);return -ENOMEM;}

从audio_setup_buf的实现可以看出,虽然指定了8个fragment,但是只有第一次进入循环的时候才调用 dma_alloc_coherent进行了dma缓冲区的申请操作,如果不能一次申请到8个fragment size的空间,就减1,尝试7个,依次类推.比如最终只申请到2*8k的dma缓冲区,那么fragment0就保存它的起始虚拟地址和起始总线地址, 并且设置master字段为整个dma缓冲区的size,这也标志了fragment0是保存的整个dma缓冲区的起始地址.fragment1的起始虚拟地址就是整个dma缓冲区的起始虚拟地址加8k(fragment size),fragment2即以后的fragment就是无效的.

2.6.10内核中s3c2410_dma_enqueue的分析,最新的内核修改了这个部分.

int s3c2410_dma_enqueue(unsigned int channel, void *id,dma_addr_t data, int size){s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];s3c2410_dma_buf_t *buf;unsigned long flags;check_channel(channel);//分配描述本次dma传输的数据块信息的数据结构,每次调用都会分配一个这个结构,并且加到它们构成的链表的末尾//因为调用这个函数的时候之前的传输可能还没有完成buf = (s3c2410_dma_buf_t *)kmalloc(sizeof(*buf), GFP_ATOMIC);if (buf == NULL) {pr_debug("%s: out of memory (%d alloc)\n",__FUNCTION__, sizeof(*buf));return -ENOMEM;}pr_debug("%s: new buffer %p\n", __FUNCTION__, buf);//dbg_showchan(chan);buf->next  = NULL;buf->data  = buf->ptr = data;buf->size  = size;buf->id    = id;buf->magic = BUF_MAGIC;local_irq_save(flags);if (chan->curr == NULL) {//这个dma channel上的链表还是空的chan->curr = buf;chan->end  = buf;chan->next = NULL;} else {//这个dma channel已经有数据在传输了,加到链表末尾chan->end->next = buf;chan->end = buf;}//下一个要load的bufif (chan->next == NULL)chan->next = buf;/* check to see if we can load a buffer */if (chan->state == S3C2410_DMA_RUNNING) {if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {printk(KERN_ERR "dma%d: loadbuffer:""timeout loading buffer\n",chan->number);dbg_showchan(chan);local_irq_restore(flags);return -EINVAL;}}while (s3c2410_dma_canload(chan) && chan->next != NULL) {s3c2410_dma_loadbuffer(chan, chan->next);}} else if (chan->state == S3C2410_DMA_IDLE) {if (chan->flags & S3C2410_DMAF_AUTOSTART) {//如果允许自动开始,那么加入队列后就启动dma传输s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START);}}local_irq_restore(flags);return 0;}

dma完成一次传输后的回调函数:

static void audio_dmaout_done_callback(s3c2410_dma_chan_t *ch, void *buf, int size,s3c2410_dma_buffresult_t result){audio_buf_t *b = (audio_buf_t *) buf;//释放信号量,可以在写函数里面处理下一个fragment了up(&b->sem);wake_up(&b->sem.wait);}

至此就可以实现音频的播放了.

看懂以上代码后再看read函数就没什么难度了,整个read的流程如下:


如果第一次调用,那么建立8个用于dma的fragment,并且加入到dma队列里面开始接收数据
读取一个fragment开始,读取里面的数据
重新将该fragment提交给dma模块用于接收数据


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
五大标准轻松搞定4K超高清电视选购