AC97之DMA(s3c6410)

来源:互联网 发布:虚拟变量数据案例 编辑:程序博客网 时间:2024/05/18 01:16

AC97之DMA(S3C6410)

在AC97的s3c-pcm.c文件中即platform驱动中,提供了ac97的DMA的使用。现在按照下面的过程一一讲述。
在设备打开时会调用s3c24xx_pcm_open,其参数为打开的子流数据
static const struct snd_pcm_hardware s3c24xx_pcm_hardware = {
 .info   = SNDRV_PCM_INFO_INTERLEAVED |
        SNDRV_PCM_INFO_BLOCK_TRANSFER |
        SNDRV_PCM_INFO_MMAP |
        SNDRV_PCM_INFO_MMAP_VALID,
 .formats  = SNDRV_PCM_FMTBIT_S16_LE |
        SNDRV_PCM_FMTBIT_U16_LE |
        SNDRV_PCM_FMTBIT_U8 |
        SNDRV_PCM_FMTBIT_S24_LE |
        SNDRV_PCM_FMTBIT_S8,
 .channels_min  = 2,
 .channels_max  = 2,
 .buffer_bytes_max = 128*1024,
 .period_bytes_min = PAGE_SIZE,
 .period_bytes_max = PAGE_SIZE*2,
 .periods_min  = 2,
 .periods_max  = 128,
 .fifo_size  = 32,
};

struct s3c24xx_runtime_data {
 spinlock_t lock;
 int state;
 unsigned int dma_loaded;
 unsigned int dma_limit;
 unsigned int dma_period;
 dma_addr_t dma_start;
 dma_addr_t dma_pos;
 dma_addr_t dma_end;
 struct s3c24xx_pcm_dma_params *params;
};
static int s3c24xx_pcm_open(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct s3c24xx_runtime_data *prtd;

 s3cdbg("Entered %s/n", __FUNCTION__);

 snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware); 设置运行硬件参数

 prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
 if (prtd == NULL)
  return -ENOMEM;

 spin_lock_init(&prtd->lock);

 runtime->private_data = prtd;    将DMA的信息保存在运行的私有数据中
 return 0;
}

/**
 * snd_soc_set_runtime_hwparams - set the runtime hardware parameters
 * @substream: the pcm substream
 * @hw: the hardware parameters
 *
 * Sets the substream runtime hardware parameters.
 */
int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,const struct snd_pcm_hardware *hw)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 runtime->hw.info = hw->info;
 runtime->hw.formats = hw->formats;
 runtime->hw.period_bytes_min = hw->period_bytes_min;
 runtime->hw.period_bytes_max = hw->period_bytes_max;
 runtime->hw.periods_min = hw->periods_min;
 runtime->hw.periods_max = hw->periods_max;
 runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
 runtime->hw.fifo_size = hw->fifo_size;
 return 0;
}

static struct s3c2410_dma_client s3c6400_dma_client_out = {
 .name = "AC97 PCM Stereo out"
};

static struct s3c24xx_pcm_dma_params s3c6400_ac97_pcm_stereo_out = {
 .client  = &s3c6400_dma_client_out,
 .channel = DMACH_AC97_PCM_OUT,
 .dma_addr = S3C6400_PA_AC97 + S3C_AC97_PCM_DATA,
 .dma_size = 4,
};

#ifdef CONFIG_SOUND_WM9713_INPUT_STREAM_MIC
static struct s3c2410_dma_client s3c6400_dma_client_micin = {
 .name = "AC97 Mic Mono in"
};

static struct s3c24xx_pcm_dma_params s3c6400_ac97_mic_mono_in = {
 .client  = &s3c6400_dma_client_micin,
 .channel = DMACH_AC97_MIC_IN,
 .dma_addr = S3C6400_PA_AC97 + S3C_AC97_MIC_DATA,
 .dma_size = 4,
};
#else /* Input Stream is LINE-IN */
static struct s3c2410_dma_client s3c6400_dma_client_in = {
 .name = "AC97 PCM Stereo Line in"
};

static struct s3c24xx_pcm_dma_params s3c6400_ac97_pcm_stereo_in = {
 .client  = &s3c6400_dma_client_in,
 .channel = DMACH_AC97_PCM_IN,
 .dma_addr = S3C6400_PA_AC97 + S3C_AC97_PCM_DATA,
 .dma_size = 4,
};
#endif
在调用下面的函数时,会先调用,对dma_data设定,在调用s3c24xx_pcm_hw_params时,记住substream->private_data->->dai->cpu_dai->dma_data被赋值了。
其中针对不同的流格式不同的流,其dma_data不同。
static int s3c6400_ac97_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params)
{
 struct snd_soc_pcm_runtime *rtd = substream->private_data;
 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
 
 s3cdbg("Entered %s/n", __FUNCTION__);

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  cpu_dai->dma_data = &s3c6400_ac97_pcm_stereo_out; --播放流
 else
#ifdef CONFIG_SOUND_WM9713_INPUT_STREAM_MIC           --录音流分mono和stereo(单声道和立体声),这个是因为所使用的DMA通道不同。
  cpu_dai->dma_data = &s3c6400_ac97_mic_mono_in;
#else /* Input Stream is LINE-IN */
  cpu_dai->dma_data = &s3c6400_ac97_pcm_stereo_in;
#endif
 return 0;
}
在系统启动,设备加载的时候,会调用以下函数实现DMA缓冲区的提前开辟,避免了在使用的时候的反复开辟和注销
static int s3c24xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
{
 struct snd_pcm_substream *substream = pcm->streams[stream].substream;
 struct snd_dma_buffer *buf = &substream->dma_buffer;
 size_t size = s3c24xx_pcm_hardware.buffer_bytes_max;  -- 128*1024

 s3cdbg("Entered %s/n", __FUNCTION__);

 buf->dev.type = SNDRV_DMA_TYPE_DEV;
 buf->dev.dev = pcm->card->dev;
 buf->private_data = NULL;
 buf->area = dma_alloc_writecombine(pcm->card->dev, size,&buf->addr, GFP_KERNEL); --分配出来的内存不使用缓存,但是会使用写缓冲区,
 参数为所属设备,缓冲大小,DMA地址(物理地址),标识符,返回值为内核地址,因为DMA只认识物理地址的,并且这段地址是连续的。
 if (!buf->area)
  return -ENOMEM;
 buf->bytes = size;
 return 0;
}
static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct s3c24xx_runtime_data *prtd = runtime->private_data;
 struct snd_soc_pcm_runtime *rtd = substream->private_data;
 struct s3c24xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data;   --这个值在上述函数赋值
 unsigned long totbytes = params_buffer_bytes(params);               --   最小的缓冲区大小
 int ret=0;

 s3cdbg("Entered %s, params = %p /n", __FUNCTION__, prtd->params);

 /* return if this is a bufferless transfer e.g.
  * codec <--> BT codec or GSM modem -- lg FIXME */
 if (!dma)
  return 0;

 /* this may get called several times by oss emulation
  * with different params */
 if (prtd->params == NULL) {                --第一调用时应该是NULL,因为在open的时候仅仅开辟了ptrd的空间,并没有对其中的部分参数赋值。
  prtd->params = dma;                      --将ptrd->params指向rtd->dai->cpu_dai->dma_data
  s3cdbg("params %p, client %p, channel %d/n", prtd->params,prtd->params->client, prtd->params->channel);
  /* prepare DMA */
  ret = s3c2410_dma_request(prtd->params->channel,prtd->params->client, NULL);  --申请DMA通道

  if (ret) {
   printk(KERN_ERR "failed to get dma channel/n");
   return ret;
  }
 } else if (prtd->params != dma) {         
  s3c2410_dma_free(prtd->params->channel, prtd->params->client);
  prtd->params = dma;
  s3cdbg("params %p, client %p, channel %d/n", prtd->params,prtd->params->client, prtd->params->channel);


  /* prepare DMA */
  ret = s3c2410_dma_request(prtd->params->channel,prtd->params->client, NULL);

  if (ret) {
   printk(KERN_ERR "failed to get dma channel/n");
   return ret;
  }
 }

 /* channel needs configuring for mem=>device, increment memory addr, sync to pclk, half-word transfers to the IIS-FIFO. */
 下面是对DMA的设置,关于下面的设置什么含义下面说
#if !defined (CONFIG_CPU_S3C6400) && !defined (CONFIG_CPU_S3C6410)  && !defined(CONFIG_CPU_S5PC100) && !defined (CONFIG_CPU_S5P6440)
 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 {
  s3c2410_dma_devconfig(prtd->params->channel,S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC |S3C2410_DISRCC_APB, prtd->params->dma_addr);
  s3c2410_dma_config(prtd->params->channel,prtd->params->dma_size,S3C2410_DCON_SYNC_PCLK | S3C2410_DCON_HANDSHAKE);
 }
 else
 {
  s3c2410_dma_config(prtd->params->channel,prtd->params->dma_size,S3C2410_DCON_HANDSHAKE | S3C2410_DCON_SYNC_PCLK);
  s3c2410_dma_devconfig(prtd->params->channel,S3C2410_DMASRC_HW, 0x3,prtd->params->dma_addr);
 }

#else
 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 {
  s3c2410_dma_devconfig(prtd->params->channel,S3C2410_DMASRC_MEM, 0,prtd->params->dma_addr);
  s3c2410_dma_config(prtd->params->channel,prtd->params->dma_size, 0);
 }
 else
 {
  s3c2410_dma_devconfig(prtd->params->channel,S3C2410_DMASRC_HW, 0,prtd->params->dma_addr);  
  s3c2410_dma_config(prtd->params->channel,prtd->params->dma_size, 0);
 }
#endif
 设置dma完成后的回调函数
 s3c2410_dma_set_buffdone_fn(prtd->params->channel,s3c24xx_audio_buffdone);
 设置运行时的buffer参数,即是dma的参数,如DMA分配出来的物理地址以及虚拟地址
 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
 
 runtime->dma_bytes = totbytes;   --缓冲区大小

 spin_lock_irq(&prtd->lock);
 prtd->dma_loaded = 0;
 prtd->dma_limit = runtime->hw.periods_min;  -- 2
 prtd->dma_period = params_period_bytes(params); --dma周期
 prtd->dma_start = runtime->dma_addr;    --dma的起始地址
 prtd->dma_pos = prtd->dma_start;        --dma的现在地址
 prtd->dma_end = prtd->dma_start + totbytes;  --dma的结束地址
 spin_unlock_irq(&prtd->lock);

 return 0;
}
s3c24xx_pcm_hw_params主要是完成DMA的一些设置,同时对子流的中关于DMA的参数进行设置。
设置运行时的buffer参数,其实也是一个指针的赋值
static inline void snd_pcm_set_runtime_buffer(struct snd_pcm_substream *substream,struct snd_dma_buffer *bufp)
{
 struct snd_pcm_runtime *runtime = substream->runtime;  --记住bufp是有值的,在设备加载中就分配了
 if (bufp) {
  runtime->dma_buffer_p = bufp;
  runtime->dma_area = bufp->area;
  runtime->dma_addr = bufp->addr;
  runtime->dma_bytes = bufp->bytes;
 } else {
  runtime->dma_buffer_p = NULL;
  runtime->dma_area = NULL;
  runtime->dma_addr = 0;
  runtime->dma_bytes = 0;
 }
}

在硬件参数设置完成后,就应该实现mmap,将物理地址映射到用户的虚拟空间。在这儿采用了dma的函数,进行mmap。
static int s3c24xx_pcm_mmap(struct snd_pcm_substream *substream,struct vm_area_struct *vma)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 s3cdbg("Entered %s/n", __FUNCTION__);

 return dma_mmap_writecombine(substream->pcm->card->dev, vma,runtime->dma_area,runtime->dma_addr, runtime->dma_bytes);
}

在内存映射后,就是播放或录音的准备
static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream)
{
 struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
 int ret = 0;

 s3cdbg("Entered %s/n", __FUNCTION__);
#if !defined (CONFIG_CPU_S3C6400) && !defined (CONFIG_CPU_S3C6410)
 /* return if this is a bufferless transfer e.g.
  * codec <--> BT codec or GSM modem -- lg FIXME */
 if (!prtd->params)
   return 0;
#endif

 /* flush the DMA channel */
 s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);  --刷新dma通道
 prtd->dma_loaded = 0;               --ptrd的关于dma运行时的参数初始化,如dma加载次数和,dma的起始地址

 prtd->dma_pos = prtd->dma_start;  

 /* enqueue dma buffers */
 s3c24xx_pcm_enqueue(substream);

 return ret;
}

/* s3c24xx_pcm_enqueue
 *
 * place a dma buffer onto the queue for the dma system to handle.
*/
static void s3c24xx_pcm_enqueue(struct snd_pcm_substream *substream)
{
 struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
 dma_addr_t pos = prtd->dma_pos;  --dma的当前位置
 int ret;

 s3cdbg("Entered %s/n", __FUNCTION__);

 while (prtd->dma_loaded < prtd->dma_limit) {
  unsigned long len = prtd->dma_period;   --步进值

  s3cdbg("dma_loaded: %d/n",prtd->dma_loaded);

  if ((pos + len) > prtd->dma_end) {     --如果pos+len大于dma的尾位置,则len等于剩余值
   len  = prtd->dma_end - pos;
   s3cdbg(KERN_DEBUG "%s: corrected dma len %ld/n", __FUNCTION__, len);
  }

  ret = s3c2410_dma_enqueue(prtd->params->channel, substream, pos, len);  --将buffer加入dma队列中,留给DMA处理

  if (ret == 0) {
   prtd->dma_loaded++;
   pos += prtd->dma_period;
   if (pos >= prtd->dma_end)  --如果位置大了,则从头开始
    pos = prtd->dma_start;
  } else
   break;
 }

 prtd->dma_pos = pos;     --将位置赋值
}

static snd_pcm_uframes_t  s3c24xx_pcm_pointer(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct s3c24xx_runtime_data *prtd = runtime->private_data;
 unsigned long res;
 dma_addr_t src, dst;
 int i;
 s3cdbg("Entered %s/n", __FUNCTION__);

 spin_lock(&prtd->lock);

 s3c2410_dma_getposition(prtd->params->channel, &src, &dst);  --读取当前DMA的目的地址和源地址

 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)   --计算已经传输的数据
  res = dst - prtd->dma_start;
 else
  res = src - prtd->dma_start;

 spin_unlock(&prtd->lock);

 s3cdbg("Pointer %x %x/n",src,dst);
 /* we seem to be getting the odd error from the pcm library due
  * to out-of-bounds pointers. this is maybe due to the dma engine
  * not having loaded the new values for the channel before being
  * callled... (todo - fix )
  */

 if (res >= snd_pcm_lib_buffer_bytes(substream)) {
  if (res == snd_pcm_lib_buffer_bytes(substream))
   res = 0;
 }
 return bytes_to_frames(substream->runtime, res); 将缓存中的数据转换成帧
}

DMA传输完成后回调函数
static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel,void *dev_id, int size,enum s3c2410_dma_buffresult result)
{
 struct snd_pcm_substream *substream = dev_id;
 struct s3c24xx_runtime_data *prtd;

 s3cdbg("Entered %s/n", __FUNCTION__);

 if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR){ 
  return;
 }
 else {
  
  if (!substream)
   return;

  prtd = substream->runtime->private_data;
  snd_pcm_period_elapsed(substream);  --update the pcm status for the next period

  spin_lock(&prtd->lock);
  if (prtd->state & ST_RUNNING) {
   prtd->dma_loaded--;                   --将DMA的加载自减
   s3c24xx_pcm_enqueue(substream);       --重新入队
  }

  prtd->dma_loaded--;             
  spin_unlock(&prtd->lock);
 }
}

下面函数会在开始播放和开始录音,以及挂起以及暂停时调用
static int s3c24xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
 struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
 int ret = 0;

 s3cdbg("Entered %s/n", __FUNCTION__);

 spin_lock(&prtd->lock);

 switch (cmd) {
 case SNDRV_PCM_TRIGGER_START:
 case SNDRV_PCM_TRIGGER_RESUME:
 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  prtd->state |= ST_RUNNING;                                      --将状态置为运行
  s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);   --开始DMA传输
#if !defined (CONFIG_CPU_S3C6400) && !defined (CONFIG_CPU_S3C6410) && !defined (CONFIG_CPU_S5P6440)
  s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STARTED);
#endif  
  break;

 case SNDRV_PCM_TRIGGER_STOP:
 case SNDRV_PCM_TRIGGER_SUSPEND:
 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  prtd->state &= ~ST_RUNNING;
  s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP);
  break;

 default:
  ret = -EINVAL;
  break;
 }

 spin_unlock(&prtd->lock);

 return ret;
}