DMA主要是用来协助其他设备驱动做数据快速传输的,其具体协议这里就不写了,网上一大堆。下面以2440的音频驱动为例结合理解dma传输。
1、音频驱动的初始化:
int __init s3c2440_uda1341_init(void)
{
unsigned long flags;
local_irq_save(flags);//关中断
//对复用引脚及电平配置
/* GPB 4: L3CLOCK, OUTPUT */
set_gpio_ctrl(GPIO_L3CLOCK);
/* GPB 3: L3DATA, OUTPUT */
set_gpio_ctrl(GPIO_L3DATA);
/* GPB 2: L3MODE, OUTPUT */
set_gpio_ctrl(GPIO_L3MODE);
/* GPE 3: I2SSDI */
set_gpio_ctrl(GPIO_E3 | GPIO_PULLUP_EN | GPIO_MODE_I2SSDI);
/* GPE 0: I2SLRCK */
set_gpio_ctrl(GPIO_E0 | GPIO_PULLUP_EN | GPIO_MODE_I2SSDI);
/* GPE 1: I2SSCLK */
set_gpio_ctrl(GPIO_E1 | GPIO_PULLUP_EN | GPIO_MODE_I2SSCLK);
/* GPE 2: CDCLK */
set_gpio_ctrl(GPIO_E2 | GPIO_PULLUP_EN | GPIO_MODE_CDCLK);
/* GPE 4: I2SSDO */
set_gpio_ctrl(GPIO_E4 | GPIO_PULLUP_EN | GPIO_MODE_I2SSDO);
local_irq_restore(flags);//恢复中断状态
init_uda1341();//使用主控配置设备邋uda1341
init_s3c2440_iis_bus();//配置邋主控iisbus
output_stream.dma_ch = DMA_CH2;//由datasheet可知audioout使用第二个通道
if (audio_init_dma(&output_stream, "UDA1341 out")) {//为输出配置dma通道
audio_clear_dma(&output_stream);
printk( KERN_WARNING AUDIO_NAME_VERBOSE
": unable to get DMA channels\n" );
return -EBUSY;
}
audio_dev_dsp = register_sound_dsp(&smdk2440_audio_fops, -1);//注册声卡
audio_dev_mixer = register_sound_mixer(&smdk2440_mixer_fops, -1);//注册混音器
printk(AUDIO_NAME_VERBOSE " initialized\n");
return 0;
}
2、看看如何注册dma的
static int __init audio_init_dma(audio_stream_t * s, char *desc)
{
return s3c2440_request_dma("I2SSDO", s->dma_ch, audio_dmaout_done_callback, NULL);
}//想dma申请使用dma功能参数一 设备名 参数二 通道号 参数三输出dma完成后的callback函数参数四dma输入callback函数,这里没有第四个参数
3、进一步看,这部分就进入了dma模块了:
int s3c2440_request_dma(const char *device_id, dmach_t channel,
dma_callback_t write_cb, dma_callback_t read_cb)
{
s3c2440_dma_t *dma;
int err;
if ((channel < 0) || (channel >= MAX_S3C2440_DMA_CHANNELS)) {
printk(KERN_ERR "%s: not support #%d DMA channel\n", device_id, channel);
return -ENODEV;
}
err = 0;
spin_lock(&dma_list_lock);
dma = &dma_chan[channel];
if (dma->in_use) {
printk(KERN_ERR "%s: DMA channel is busy\n", device_id);
err = -EBUSY;
} else {
dma->in_use = 1;
}
spin_unlock(&dma_list_lock);
if (err)
return err;
//在可以使用dma功能的设备或memeory中查询是否有这个设备,后两个
//参数为写地址和读地址
err = fill_dma_source(channel, device_id, &dma->write, &dma->read);
if (err < 0) {
printk(KERN_ERR "%s: can not found this devcie\n", device_id);
dma->in_use = 0;
return err;
}
//注册中断号
err = request_irq(dma->irq, dma_irq_handler, 0 * SA_INTERRUPT,
device_id, (void *)dma);
if (err) {
printk( KERN_ERR
"%s: unable to request IRQ %d for DMA channel\n",
device_id, dma->irq);
dma->in_use = 0;
return err;
}
//传递参数给dma
dma->device_id = device_id;//设备id
dma->head = dma->tail = dma->curr = NULL;//buffer清空
dma->write.callback = write_cb;//callback
dma->read.callback = read_cb;//callback
DPRINTK("write cb = %p, read cb = %p\n", dma->write.callback, dma->read.callback);
DPRINTK("requested\n");
return 0;
}
int s3c2440_dma_queue_buffer(dmach_t channel, void *buf_id,
dma_addr_t data, int size, int write)
{
s3c2440_dma_t *dma;
dma_buf_t *buf;
int flags;
dma = &dma_chan[channel];
if ((channel >= MAX_S3C2440_DMA_CHANNELS) || (!dma->in_use))
return -EINVAL;
buf = kmalloc(sizeof(*buf), GFP_ATOMIC);
if (!buf)
return -ENOMEM;
buf->next = NULL;
buf->ref = 0;
buf->dma_start = data;
buf->size = size;
buf->id = buf_id;
buf->write = write;
DPRINTK("queueing b=%#x, a=%#x, s=%d, w=%d\n", (int) buf_id, data, size, write);
local_irq_save(flags);
if (dma->tail)
dma->tail->next = buf;
else
dma->head = buf;
dma->tail = buf;
buf->next = NULL;
dma->queue_count++;
DPRINTK("number of buffers in queue: %ld\n", dma->queue_count);
process_dma(dma);//执行dma传输启动动作
local_irq_restore(flags);
return 0;
}
4、进入fill_dma_source查看如何dma与设备之间的关联
static int fill_dma_source(int channel, const char *dev_name,
dma_device_t *write, dma_device_t *read)
{
int source;
dma_type_t *dma_type = dma_types[channel];//在头文件中定义了所有的设备及其寄存器等
for(source=0;source<4;source++) {
if (strcmp(dma_type[source].name, dev_name) == 0)
break;
}
if (source >= 4) return -1;
dma_type += source;
//找到后给申请到的通道配置相关寄存器
write->src = dma_type->write_src;
write->dst = dma_type->write_dst;
write->ctl = dma_type->write_ctl;
write->src_ctl = dma_type->write_src_ctl;
write->dst_ctl = dma_type->write_dst_ctl;
read->src = dma_type->read_src;
read->dst = dma_type->read_dst;
read->ctl = dma_type->read_ctl;
read->src_ctl = dma_type->read_src_ctl;
read->dst_ctl = dma_type->read_dst_ctl;
return 0;
}
5,在dma.h中看到dma_types的dma设备的定义
{ "I2SSDO", I2SSDO_WR_SRC, I2SSDO_WR_DST, I2SSDO_WR_CTL, \
I2SSDO_WR_SRC_CTL, I2SSDO_WR_DST_CTL, \
I2SSDO_RD_SRC, I2SSDO_RD_DST, I2SSDO_RD_CTL, \
I2SSDO_RD_SRC_CTL, I2SSDO_RD_DST_CTL },
6、下面再来看open这个设备会做哪些动作,其实关键在初始化内存的那一部分难以理解
static int smdk2440_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++;
} else
return -EINVAL;
if (cold) {
audio_rate = AUDIO_RATE_DEFAULT;
audio_channels = AUDIO_CHANNELS_DEFAULT;
audio_fragsize = AUDIO_FRAGSIZE_DEFAULT;
audio_nbfrags = AUDIO_NBFRAGS_DEFAULT;
audio_clear_buf(&output_stream);
// audio_power_on();
// 加上以下这行代码
if (!output_stream .buffers && audio_setup_buf(&output_stream)) //申请设备数据结构内存及dma buffer
return -ENOMEM;
}
MOD_INC_USE_COUNT;
return 0;
}
7、进入设备相关内存及dma内存分配
static int audio_setup_buf(audio_stream_t * s)
{
int frag;
int dmasize = 0;
char *dmabuf = 0;
dma_addr_t dmaphys = 0;
if (s->buffers)//防止未初始化内存区
return -EBUSY;
s->nbfrags = audio_nbfrags;
s->fragsize = audio_fragsize;
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++) {
audio_buf_t *b = &s->buffers[frag];
if (!dmasize) {
dmasize = (s->nbfrags - frag) * s->fragsize;
do {
dmabuf = consistent_alloc(GFP_KERNEL|GFP_DMA,
dmasize, &dmaphys);
if (!dmabuf)
dmasize -= s->fragsize;
} while (!dmabuf && dmasize);
if (!dmabuf)
goto err;
b->master = dmasize;
}
b->start = dmabuf;
b->dma_addr = dmaphys;
sema_init(&b->sem, 1);
DPRINTK("buf %d: start %p dma %p\n", frag, b->start,
b->dma_addr);
dmabuf += s->fragsize;
dmaphys += s->fragsize;
dmasize -= s->fragsize;
}
//for循环的作用是申请dma所要的buffer,写的如此晦涩的原因是为了
//防止万一第一次申请不到所有的连续内存的时候
//可以分次少量的申请连续内存,每一段连续的内存的第一个
//第一个换存快的master为这段连续内存的大小
8、在linux下要播放声音,首先需要设置声卡的比特率、采样率等等,这里与dma无关就不会说了,做了设置后,就可以直接往设备写数据了,下面我们看看write函数:
static ssize_t smdk2440_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;
DPRINTK("audio_write : start count=%d\n", count);
switch (file->f_flags & O_ACCMODE) {
case O_WRONLY:
case O_RDWR:
break;
default:
return -EPERM;
}
if (!s->buffers && audio_setup_buf(s))
return -ENOMEM;
count &= ~0x03;//dma需要四字节对齐传输
while (count > 0) {//从用户空间把数据copy到内核空间
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 = s->fragsize - b->size;
if (chunksize > count)
chunksize = count;
DPRINTK("write %d to %d\n", chunksize, s->buf_idx);
if (copy_from_user(b->start + b->size, buffer, chunksize)) {
up(&b->sem);
return -EFAULT;
}
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);
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) {
up(&b->sem);
break;
}
//请求进行dma处理数据传输
s3c2440_dma_queue_buffer(s->dma_ch, (void *) b,
b->dma_addr, b->size, DMA_BUF_WR);
b->size = 0;
NEXT_BUF(s, buf);
}
if ((buffer - buffer0))
ret = buffer - buffer0;
DPRINTK("audio_write : end count=%d\n\n", ret);
return ret;
}
基本上也就这样了,其他设备在使用dma时大概也差不多。