嵌入式Linux——音频设备驱动(2):uda341中DMA的分析

2019-07-12 23:24发布

简介:     本文主要对uda341中DMA相关部分进行分析,所以本文将不在讲解基础知识,而是直接分析代码。 Linux内核:linux-2.6.22.6 所用开发板:JZ2440 V3(S3C2440A) 音频芯片:uda1341 总线:DMA     我们直接进入代码的分析,从总体上看,DMA的代码为: output_stream.dma_ch = DMACH_I2S_OUT; 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; } input_stream.dma_ch = DMACH_I2S_IN; 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; }     上面为DMA的输出和输入的函数,由于两个函数十分相似,我们只讲解其中的一个,我们讲解输出的DMA。而从上面看他最主要的就是audio_init_dma函数,我们进去看他做了什么: if(s->dma_ch == DMACH_I2S_OUT){ channel = DMACH_I2S_OUT; source = S3C2410_DMASRC_MEM; hwcfg = BUF_ON_APB; devaddr = 0x55000010; dcon = S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_INTREQ|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH2_I2SSDO|S3C2410_DCON_HWTRIG; // VAL: 0xa0800000; flags = S3C2410_DMAF_AUTOSTART; ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_out, NULL); s3c2410_dma_devconfig(channel, source, hwcfg, devaddr); s3c2410_dma_config(channel, 2, dcon); s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback); s3c2410_dma_setflags(channel, flags); s->dma_ok = 1; return ret; } else if(s->dma_ch == DMACH_I2S_IN){ channel = DMACH_I2S_IN; source = S3C2410_DMASRC_HW; hwcfg = BUF_ON_APB; devaddr = 0x55000010; dcon = S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_INTREQ|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH1_I2SSDI|S3C2410_DCON_HWTRIG; // VAL: 0xa2800000; flags = S3C2410_DMAF_AUTOSTART; ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_in, NULL); 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); s->dma_ok =1; return ret ; }     从上面可以看出他主要还是初始化输入输出通道,那么我们还是用其中的一个分析我们这里还是选择输出通道:     从上面可以看出他分为两部分,第一部分为对DMA中相应参数的设置,而第二部分就是将这些参数写入到相应的寄存器中。我们看第一部分: channel = DMACH_I2S_OUT; /* DMA输出 */ source = S3C2410_DMASRC_MEM; /* DMA的源 */ hwcfg = BUF_ON_APB; /* DMA硬件配置 */ devaddr = 0x55000010; /* 设备地址 */ dcon = S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_INTREQ|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH2_I2SSDO|S3C2410_DCON_HWTRIG; // VAL: 0xa0800000; flags = S3C2410_DMAF_AUTOSTART; /* DMA自动开始 */     这一部分主要为参数设置,大家一看就明白了。我们主要分析第二部分: ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_out, NULL); s3c2410_dma_devconfig(channel, source, hwcfg, devaddr); s3c2410_dma_config(channel, 2, dcon); s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback); s3c2410_dma_setflags(channel, flags);     我们先看第一个函数s3c2410_dma_requestint s3c2410_dma_request(unsigned int channel,struct s3c2410_dma_client *client,void *dev) { struct s3c2410_dma_chan *chan; unsigned long flags; local_irq_save(flags);         /* 获得请求源的通道 */ chan = s3c2410_dma_map_channel(channel); chan->client = client; chan->in_use = 1; if (!chan->irq_claimed) { chan->irq_claimed = 1; local_irq_restore(flags);                 /* 申请DMA中断 */ err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED, client->name, (void *)chan); local_irq_save(flags); chan->irq_enabled = 1; } local_irq_restore(flags); return 0; }     从上面看在该函数中主要做了两件事: 1. 获得请求源的通道 
2. 申请DMA中断
    我们现在一一分析上面这两件事,我们先看:获得请求源的通道 for (ch = 0; ch < dma_channels; ch++) { if (!is_channel_valid(ord->list[ch])) continue; if (s3c2410_chans[ord->list[ch]].in_use == 0) { ch = ord->list[ch] & ~DMA_CH_VALID; goto found; } } found: dmach = &s3c2410_chans[ch]; dma_chan_map[channel] = dmach; /* select the channel */ (dma_sel.select)(dmach, ch_map);     从代码看就是一个一个找看哪个通道可以用,然后对应的可用通道上报。     分析完这个我们就来分析DMA中断请求函数,这里我们主要还是分析中断处理函数,来看看这个中断处理函数中做了什么: s3c2410_dma_irq(int irq, void *devpw) { struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw; struct s3c2410_dma_buf *buf; buf = chan->curr; /* 改变当前的态为下一个态*/ switch (chan->load_state) { case S3C2410_DMALOAD_1RUNNING: chan->load_state = S3C2410_DMALOAD_NONE; break; case S3C2410_DMALOAD_1LOADED: chan->load_state = S3C2410_DMALOAD_NONE; break; case S3C2410_DMALOAD_1LOADED_1RUNNING: chan->load_state = S3C2410_DMALOAD_1LOADED; break; case S3C2410_DMALOAD_NONE: break; default: break; }         /* 判断缓冲的数据是否为空 */ if (buf != NULL) { chan->curr = buf->next; buf->next = NULL; s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK); /* free resouces */ s3c2410_dma_freebuf(buf); } /* 判断是否还有要传输的缓冲区,以及DMA的状态是否空闲 */ if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) { unsigned long flags; switch (chan->load_state) { case S3C2410_DMALOAD_1RUNNING: case S3C2410_DMALOAD_NONE: case S3C2410_DMALOAD_1LOADED: if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { } case S3C2410_DMALOAD_1LOADED_1RUNNING: goto no_load; default: } local_irq_save(flags); s3c2410_dma_loadbuffer(chan, chan->next); /* 传输下一个缓冲区 */ local_irq_restore(flags); } else { s3c2410_dma_lastxfer(chan); /* see if we can stop this channel.. */ if (chan->load_state == S3C2410_DMALOAD_NONE) { s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL, /* 停止DMA */ S3C2410_DMAOP_STOP); } } no_load: return IRQ_HANDLED; }     DMA完成一次传输进入中断处理函数中,将其现有的状态变为下一个状态,然后判断缓冲区中是否还有数据,如果有则继续传输直到传输完。接着再来判断是否还有缓冲的数据要传输,如果有则继续传输,没有则关闭DMA。     而我们主要要看的是上面函数中的:s3c2410_dma_buffdone,s3c2410_dma_loadbuffer以及s3c2410_dma_ctrl这三个函数。我们先分析s3c2410_dma_buffdone static inline void s3c2410_dma_buffdone(struct s3c2410_dma_chan *chan, struct s3c2410_dma_buf *buf, enum s3c2410_dma_buffresult result) { if (chan->callback_fn != NULL) { (chan->callback_fn)(chan, buf->id, buf->size, result); } }     从上面看他主要做的就是调用通道的回调函数。而回调函数我会在后面介绍。 s3c2410_dma_loadbuffer: static inline int s3c2410_dma_loadbuffer(struct s3c2410_dma_chan *chan, struct s3c2410_dma_buf *buf) { unsigned long reload;         /* 向寄存器写入数据 */ writel(buf->data, chan->addr_reg); chan->next = buf->next; /* 更新通道的状态 */ switch (chan->load_state) { case S3C2410_DMALOAD_NONE: chan->load_state = S3C2410_DMALOAD_1LOADED; break; case S3C2410_DMALOAD_1RUNNING: chan->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING; break; default: } }     从上面他就做了两件事: 1. 向寄存器写入数据 
2. 更新通道的状态
s3c2410_dma_ctrl: int s3c2410_dma_ctrl(dmach_t channel, enum s3c2410_chan_op op) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); switch (op) { case S3C2410_DMAOP_START: return s3c2410_dma_start(chan); case S3C2410_DMAOP_STOP: return s3c2410_dma_dostop(chan); case S3C2410_DMAOP_PAUSE: case S3C2410_DMAOP_RESUME: return -ENOENT; case S3C2410_DMAOP_FLUSH: return s3c2410_dma_flush(chan); case S3C2410_DMAOP_STARTED: return s3c2410_dma_started(chan); case S3C2410_DMAOP_TIMEOUT: return 0; } }     从上面看就是根据op 的不同做不同的操作,而操作的详细细节就不讲了,而这些细节包括:开始DMA,结束DMA等。       讲解完s3c2410_dma_request其实我们就讲解了大半了,因为后面的函数主要做的是想寄存器中写参数,我们先看: s3c2410_dma_devconfig: int s3c2410_dma_devconfig(int channel, enum s3c2410_dmasrc source,int hwcfg,unsigned long devaddr) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); chan->source = source; chan->dev_addr = devaddr;         /* 选择请求源 */ switch (source) { case S3C2410_DMASRC_HW: /* 请求源为硬件 */ dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3); /* 将控制信息写入初始化源控制寄存器 */ dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr); /* 将源地址写入初始化源寄存器 */ dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0)); /* 将控制信息写入初始化目的控制寄存器 */ chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST); return 0; case S3C2410_DMASRC_MEM: /* 源是内存 */ 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; }     从上面可以看出主要是对源与目的初始化控制寄存器的设置,以及分别对源或目的初始化寄存器的设置。 s3c2410_dma_config: int s3c2410_dma_config(dmach_t channel,int xferunit,int dcon) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); dcon |= chan->dcon & dma_sel.dcon_mask;         /* 用于传输数据的大小 */ 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; } dcon |= S3C2410_DCON_HWTRIG; /* 硬件触发 */ dcon |= S3C2410_DCON_INTREQ; /* 产生中断请求 */ chan->dcon = dcon; chan->xfer_unit = xferunit; return 0; }     从上面看主要设置的是:   1. 用于传输数据的大小 2. 硬件触发 3. 产生中断请求 4. 以及dcon中相关的设置:S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_INTREQ|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH2_I2SSDO|S3C2410_DCON_HWTRIG; s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback): int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); chan->callback_fn = rtn; }     该函数主要设置的就是上面讲的s3c2410_dma_buffdone中的回调函数。 s3c2410_dma_setflags: int s3c2410_dma_setflags(dmach_t channel, unsigned int flags) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel) chan->flags = flags; }     而最后他设置的是通道的flags。     讲到这里我们的DMA分析就讲完了。