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 "); return -ENOENT; } //2410iis的虚拟地址,不知道什么时候ioremap过来的 iis_base = (void *)S3C24XX_VA_IIS ; if (iis_base == 0) { printk(KERN_INFO PFX "failed to ioremap() region "); return -EINVAL; } //clk相关的操作,见后文的分析 iis_clock = clk_get(dev, "iis"); if (iis_clock == NULL) { printk(KERN_INFO PFX "failed to find clock source "); 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(); //初始化uda1341 init_uda1341(); //初始化缓冲用的stream //输出用dma的channel2 output_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 " ); return -EBUSY; } //输入用dma的channel1 input_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 " ); 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 "); 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; else idno = 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) ", 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) ", 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=1 s3c2410_gpio_setpin(S3C2410_GPB4,1);//L3CLOCK=1 local_irq_restore(flags); uda1341_l3_address(UDA1341_REG_STATUS); uda1341_l3_data(0x40 | STAT0_SC_384FS | STAT0_IF_MSB|STAT0_DC_FILTER); // reset uda1341 uda1341_l3_data(STAT1 | STAT1_ADC_ON | STAT1_DAC_ON); uda1341_l3_address(UDA1341_REG_DATA0); uda1341_l3_data(DATA0 |DATA0_VOLUME(0x0)); // maximum volume uda1341_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还是memory s3c2410_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 sdo dcon = 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 ; } else return 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 ", __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 ", __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的目的地址寄存器DIDST chan->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 ", __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) ", 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 ", __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 ", channel); local_irq_restore(flags); return -EBUSY; } else { printk(KERN_ERR "dma%d: client already has channel ", channel); } } chan->client = client; chan->in_use = 1; if (!chan->irq_claimed) { //安装对应dma通道的中断,可以看出每个dma通道共用了同一个中断服务程序s3c2410_dma_irq err = 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 ", 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 "); 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++; } else return -EINVAL; if (cold) { //第一次open,赋初值 //44100 audio_rate = AUDIO_RATE_DEFAULT; //2 audio_channels = AUDIO_CHANNELS_DEFAULT; //8192,8k audio_fragsize = AUDIO_FRAGSIZE_DEFAULT; //8 audio_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 "); //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指向当前的fragment audio_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 ", 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. "); 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 ", 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是8k if (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个fragment audio_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 ", 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 "); 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) ", __FUNCTION__, sizeof(*buf)); return -ENOMEM; } pr_debug("%s: new buffer %p ", __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的buf if (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 ", 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的流程如下:
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』