基於uda34x的ALSA音效卡驅動之s3c24xx-pcm.c

來源:互聯網
上載者:User

最近在調試音頻,雖然AC97 但是其實思路還是一樣的。轉篇別人寫的文章記錄下

 

原文地址:

 

http://chxxxyg.blog.163.com/blog/static/150281193201033105123937/

 

 

#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>

#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>

#include <asm/dma.h>
#include <mach/hardware.h>
#include <mach/dma.h>
#include <mach/audio.h>

#include "s3c24xx-pcm.h"

#define S3C24XX_PCM_DEBUG 0
#if S3C24XX_PCM_DEBUG
#define DBG(x...) printk(KERN_DEBUG "s3c24xx-pcm: " x)
#else
#define DBG(x...)
#endif
//定義了一些緩衝區資訊,在函數s3c24xx_pcm_open中用於初始化結構體substream->runtime->hw
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 |
        SNDRV_PCM_INFO_PAUSE |
        SNDRV_PCM_INFO_RESUME,
 .formats  = SNDRV_PCM_FMTBIT_S16_LE |
        SNDRV_PCM_FMTBIT_U16_LE |
        SNDRV_PCM_FMTBIT_U8 |
        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,
};
//緩衝管理結構體在函數s3c24xx_pcm_open中掛到runtime->private_data上
//該結構體在函數s3c24xx_pcm_hw_params中被初始化
//所管理的快取區域的分配在函數s3c24xx_pcm_preallocate_dma_buffer中
struct s3c24xx_runtime_data {
 spinlock_t lock;
 int state;
 unsigned int dma_loaded;//已插入DMA備用儲存鏈的儲存段數
 unsigned int dma_limit;//緩衝段的最大數字
 unsigned int dma_period;//每段緩衝的最大儲存資料量
 dma_addr_t dma_start;//緩衝區的開始地址
 dma_addr_t dma_pos;//第一個未被插入DMA備用緩衝鏈的儲存段地址
 dma_addr_t dma_end;//緩衝區的尾地址
 //結構params在函數s3c24xx_pcm_hw_params中被初始化prtd->params = dma;
 struct s3c24xx_pcm_dma_params *params;
};

/* 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;
 int ret;

 DBG("Entered %s/n", __func__);

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

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

  if ((pos + len) > prtd->dma_end) {
   len  = prtd->dma_end - pos;
   DBG(KERN_DEBUG "%s: corrected dma len %ld/n",
          __func__, len);
  }
//將緩衝插入DMA備用緩衝鏈,pos必須是物理地址,它將被寫入DMA的初始化目的或源寄存器
  ret = s3c2410_dma_enqueue(prtd->params->channel,
   substream, pos, len);

  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 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;

 DBG("Entered %s/n", __func__);

 if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR)
  return;

 prtd = substream->runtime->private_data;

 if (substream)
  snd_pcm_period_elapsed(substream);

 spin_lock(&prtd->lock);
 if (prtd->state & ST_RUNNING) {
  prtd->dma_loaded--;//用完一段緩衝,將該緩衝插入DMA備用鏈表尾。整個緩衝區為一環形緩衝區
  s3c24xx_pcm_enqueue(substream);
 }

 spin_unlock(&prtd->lock);
}

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;
 //從結構體params->intervals中擷取緩衝區大小
 unsigned long totbytes = params_buffer_bytes(params);
 int ret = 0;

 DBG("Entered %s/n", __func__);

 /* 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 -HW */
 if (prtd->params == NULL) {
  /* prepare DMA */
  //結構體dma為s3c24xx_i2s_pcm_stereo_out或s3c24xx_i2s_pcm_stereo_in
  //在檔案s3c24xx-i2s.c中定義
  //結構體params為s3c24xx_runtime_data
  prtd->params = dma;

  DBG("params %p, client %p, channel %d/n", prtd->params,
   prtd->params->client, prtd->params->channel);
      //申請一DMA通道
  ret = s3c2410_dma_request(prtd->params->channel,
       prtd->params->client, NULL);

  if (ret < 0) {
   DBG(KERN_ERR "failed to get dma channel/n");
   return ret;
  }
 }
//該函數主要實現chan->callback_fn = rtn;即讓chan->callback_fn指向函數s3c24xx_audio_buffdone
 s3c2410_dma_set_buffdone_fn(prtd->params->channel,
        s3c24xx_audio_buffdone);
//用已指派的緩衝區substream->dma_buffer區初始化substream->runtime的一些變數
 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;
 prtd->dma_period = params_period_bytes(params);//擷取一段緩衝的大小
 prtd->dma_start = runtime->dma_addr;
 prtd->dma_pos = prtd->dma_start;
 prtd->dma_end = prtd->dma_start + totbytes;
 spin_unlock_irq(&prtd->lock);

 return 0;
}

static int s3c24xx_pcm_hw_free(struct snd_pcm_substream *substream)
{
 struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;

 DBG("Entered %s/n", __func__);

 /* TODO - do we need to ensure DMA flushed */
 snd_pcm_set_runtime_buffer(substream, NULL);

 if (prtd->params) {
  s3c2410_dma_free(prtd->params->channel, prtd->params->client);
  prtd->params = NULL;
 }

 return 0;
}

static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream)
{
 struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
 int ret = 0;

 DBG("Entered %s/n", __func__);

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

 /* channel needs configuring for mem=>device, increment memory addr,
  * sync to pclk, half-word transfers to the IIS-FIFO. */
 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,//配置DMA控制寄存器,將配置值存於chan->dcon = dcon;
    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);
 }

 /* flush the DMA channel */
 s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH);
 prtd->dma_loaded = 0;
 prtd->dma_pos = prtd->dma_start;

 /* enqueue dma buffers */
 s3c24xx_pcm_enqueue(substream);//將緩衝鏈插入DMA備用緩衝鏈

 return ret;
}

static int s3c24xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
 struct s3c24xx_runtime_data *prtd = substream->runtime->private_data;
 int ret = 0;

 DBG("Entered %s/n", __func__);

 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;//裝載DMA緩衝,開始DMA資料轉送
  s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);
  s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STARTED);
  break;

 case SNDRV_PCM_TRIGGER_STOP:
 case SNDRV_PCM_TRIGGER_SUSPEND:
 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  prtd->state &= ~ST_RUNNING;//停止DMA傳輸
  s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP);
  break;

 default:
  ret = -EINVAL;
  break;
 }

 spin_unlock(&prtd->lock);

 return ret;
}

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;

 DBG("Entered %s/n", __func__);

 spin_lock(&prtd->lock);//讀取DMA的當前目的寄存器和當前源寄存器
 s3c2410_dma_getposition(prtd->params->channel, &src, &dst);

 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
  res = dst - prtd->dma_start;//計算已傳輸的資料
 else
  res = src - prtd->dma_start;

 spin_unlock(&prtd->lock);

 DBG("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);//將緩衝中的資料轉換成幀
}

static int s3c24xx_pcm_open(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct s3c24xx_runtime_data *prtd;

 DBG("Entered %s/n", __func__);
//用s3c24xx_pcm_hardware初始化結構體substream->runtime->hw
 snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware);
//為結構體s3c24xx_runtime_data分配記憶體
 prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL);
 if (prtd == NULL)
  return -ENOMEM;

 spin_lock_init(&prtd->lock);

 runtime->private_data = prtd;//
 return 0;
}

static int s3c24xx_pcm_close(struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct s3c24xx_runtime_data *prtd = runtime->private_data;

 DBG("Entered %s/n", __func__);

 if (!prtd)
  DBG("s3c24xx_pcm_close called with prtd == NULL/n");

 kfree(prtd);

 return 0;
}

static int s3c24xx_pcm_mmap(struct snd_pcm_substream *substream,
 struct vm_area_struct *vma)
{
 struct snd_pcm_runtime *runtime = substream->runtime;

 DBG("Entered %s/n", __func__);
//關聯一些使用者空間地址到裝置記憶體. 無論何時程式在給定範圍內讀或寫, 它實際上是在存取裝置
 return dma_mmap_writecombine(substream->pcm->card->dev, vma,
         runtime->dma_area,
         runtime->dma_addr,
         runtime->dma_bytes);
}
//播放和錄音的資料流操作函數
static struct snd_pcm_ops s3c24xx_pcm_ops = {
 .open  = s3c24xx_pcm_open,
 .close  = s3c24xx_pcm_close,
 .ioctl  = snd_pcm_lib_ioctl,
 .hw_params = s3c24xx_pcm_hw_params,
 .hw_free = s3c24xx_pcm_hw_free,
 .prepare = s3c24xx_pcm_prepare,
 .trigger = s3c24xx_pcm_trigger,
 .pointer = s3c24xx_pcm_pointer,
 .mmap  = s3c24xx_pcm_mmap,
};
//為資料緩衝區分配一段記憶體
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;

 DBG("Entered %s/n", __func__);

 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);
 if (!buf->area)
  return -ENOMEM;
 buf->bytes = size;
 return 0;
}

static void s3c24xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
{
 struct snd_pcm_substream *substream;
 struct snd_dma_buffer *buf;
 int stream;

 DBG("Entered %s/n", __func__);

 for (stream = 0; stream < 2; stream++) {
  substream = pcm->streams[stream].substream;
  if (!substream)
   continue;

  buf = &substream->dma_buffer;
  if (!buf->area)
   continue;

  dma_free_writecombine(pcm->card->dev, buf->bytes,
          buf->area, buf->addr);
  buf->area = NULL;
 }
}

static u64 s3c24xx_pcm_dmamask = DMA_32BIT_MASK;
//分配播放和錄音緩衝區
static int s3c24xx_pcm_new(struct snd_card *card,
 struct snd_soc_dai *dai, struct snd_pcm *pcm)
{
 int ret = 0;

 DBG("Entered %s/n", __func__);

 if (!card->dev->dma_mask)
  card->dev->dma_mask = &s3c24xx_pcm_dmamask;
 if (!card->dev->coherent_dma_mask)
  card->dev->coherent_dma_mask = 0xffffffff;

 if (dai->playback.channels_min) {
  ret = s3c24xx_pcm_preallocate_dma_buffer(pcm,
   SNDRV_PCM_STREAM_PLAYBACK);
  if (ret)
   goto out;
 }

 if (dai->capture.channels_min) {
  ret = s3c24xx_pcm_preallocate_dma_buffer(pcm,
   SNDRV_PCM_STREAM_CAPTURE);
  if (ret)
   goto out;
 }
 out:
 return ret;
}
//pcm執行個體的初始化函數和資料流操作函數
struct snd_soc_platform s3c24xx_soc_platform = {
 .name  = "s3c24xx-audio",
 .pcm_ops  = &s3c24xx_pcm_ops,
 .pcm_new = s3c24xx_pcm_new,
 .pcm_free = s3c24xx_pcm_free_dma_buffers,
};
EXPORT_SYMBOL_GPL(s3c24xx_soc_platform);

static int __init s3c24xx_soc_platform_init(void)
{//將s3c24xx_soc_platform 掛到鏈表platform_list上,調用函數snd_soc_instantiate_card,
 //如果本音效卡沒有初始化,則從各鏈表上取下相應結構體,並調用各結構體中的初始化函數,
 return snd_soc_register_platform(&s3c24xx_soc_platform);
}
module_init(s3c24xx_soc_platform_init);

static void __exit s3c24xx_soc_platform_exit(void)
{
 snd_soc_unregister_platform(&s3c24xx_soc_platform);
}
module_exit(s3c24xx_soc_platform_exit);

MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("Samsung S3C24XX PCM DMA module");
MODULE_LICENSE("GPL");

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.