LDD3 DMA驅動

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

發佈了29 篇原創文章 · 獲贊 19 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章