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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值