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時大概也差不多。