简介:
本文主要对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\n" );
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\n" );
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_request,
int 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)\n", 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\n", __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分析就讲完了。