首要看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模块用于接纳数据