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

简介:

    本文主要对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分析就讲完了。

 

你可能感兴趣的:(驱动)