asoc 音频驱动学习笔记3

ASoC驱动解析
本文详细解析了ASoC驱动中的platform部分,特别是s5pv2xx目录下的i2s驱动实现过程,从平台设备注册到DMA配置,再到dai链表的形成,最后通过machine驱动完成codec、platform及CPU DAI的绑定。

Asoc 驱动中的platform 部分的dai部分 在sound/soc/s5pv2xx目录下,有关于处理器方面的asoc驱动部分,包括dma相关的和i2s部分,先看i2s部分吧,dma部分貌似简单,好找 在s5pc1xx-i2s.c文件中,这里驱动名为s3c64xx,应该是210的i2s部分和6410差不多吧 static struct platform_driver s3c64xx_iis_driver = {  .probe  = s3c64xx_iis_dev_probe,  .remove = s3c64xx_iis_dev_remove,  .driver = {   .name = "samsung-i2s",   .owner = THIS_MODULE,  }, };

static int __init s3c64xx_i2s_init(void) {  return platform_driver_register(&s3c64xx_iis_driver); } static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) {  struct s3c_audio_pdata *i2s_pdata;  struct s3c_i2sv2_info *i2s;  struct snd_soc_dai_driver *dai;  struct resource *res;  struct clk *fout_epll, *mout_epll;  struct clk *mout_audss = NULL;  unsigned long base;  unsigned int  iismod;定义了一些结构,下面会用  int ret = 0;  if (pdev->id >= MAX_I2SV3) {   dev_err(&pdev->dev, "id %d out of range\n", pdev->id);   return -EINVAL;  }

 i2s = &s3c64xx_i2s[pdev->id];根据平台驱动的id号选择结构  i2s->dev = &pdev->dev;  dai = &s3c64xx_i2s_dai_driver[pdev->id];根据平台驱动的id号选择结构

 //dai->dev = &pdev->dev;  dai->id = pdev->id;  s3c64xx_iis_dai_init(dai);初始化dai driver

 i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id];  i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id];

 res = platform_get_resource(pdev, IORESOURCE_DMA, 0);得到i2s平台驱动DMA tx资源  if (!res) {   dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");   return -ENXIO;  }  i2s->dma_playback->channel = res->start;

 res = platform_get_resource(pdev, IORESOURCE_DMA, 1);得到i2s平台驱动DMA rx资源

 if (!res) {   dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");   return -ENXIO;  }  i2s->dma_capture->channel = res->start;capture 采集,

 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);得到I2S寄存器地址资源  if (!res) {   dev_err(&pdev->dev, "Unable to get I2S SFR address\n");   return -ENXIO;  }

 if (!request_mem_region(res->start, resource_size(res),向系统申请一块寄存器资源区     "s3c64xx-i2s")) {   dev_err(&pdev->dev, "Unable to request SFR region\n");   return -EBUSY;  }     为i2s结构赋值  i2s->dma_capture->dma_addr = res->start + S3C2412_IISRXD;  i2s->dma_playback->dma_addr = res->start + S3C2412_IISTXD;

 i2s->dma_capture->client = &s3c64xx_dma_client_in;  i2s->dma_capture->dma_size = 4;  i2s->dma_playback->client = &s3c64xx_dma_client_out;  i2s->dma_playback->dma_size = 4;

 i2s_pdata = pdev->dev.platform_data;

 //dai->private_data = i2s;  dev_set_drvdata(&pdev->dev, i2s);设置i2s结构到平台设备中,方便其他函数使用i2s结构中的信息  base = i2s->dma_playback->dma_addr - S3C2412_IISTXD;

 i2s->regs = ioremap(base, 0x100);内存映射为虚拟地址  if (i2s->regs == NULL) {   dev_err(&pdev->dev, "cannot ioremap registers\n");   return -ENXIO;  }

 /* Configure the I2S pins if MUX'ed */  if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {   dev_err(&pdev->dev, "Unable to configure gpio\n");   return -EINVAL;  }

 /* Get i2s power domain regulator */  i2s->regulator = regulator_get(&pdev->dev, "pd");得到一个叫pd的i2s校准器  if (IS_ERR(i2s->regulator)) {   dev_err(&pdev->dev, "%s: failed to get resource %s\n",     __func__, "i2s");   return PTR_ERR(i2s->regulator);  }

 /* Enable Power domain */  regulator_enable(i2s->regulator);使能这个电源校准器

 /* Audio Clock   * fout_epll >> mout_epll >> sclk_audio   * fout_epll >> mout_audss >> audio-bus(iis_clk)   * fout_epll >> dout_audio_bus_clk_i2s(iis_busclk)   */  fout_epll = clk_get(&pdev->dev, "fout_epll");得到时钟  if (IS_ERR(fout_epll)) {   dev_err(&pdev->dev, "failed to get fout_epll\n");   goto err;  }

 mout_epll = clk_get(&pdev->dev, "mout_epll");  if (IS_ERR(mout_epll)) {   dev_err(&pdev->dev, "failed to get mout_epll\n");   clk_put(fout_epll);   goto err;  }  clk_set_parent(mout_epll, fout_epll);

 i2s->sclk_audio = clk_get(&pdev->dev, "sclk_audio");  if (IS_ERR(i2s->sclk_audio)) {   dev_err(&pdev->dev, "failed to get sclk_audio\n");   ret = PTR_ERR(i2s->sclk_audio);   clk_put(i2s->sclk_audio);   goto err;  }  clk_set_parent(i2s->sclk_audio, mout_epll);  /* Need not to enable in general */  clk_enable(i2s->sclk_audio);

 /* When I2S V5.1 used, initialize audio subsystem clock */  /* CLKMUX_ASS */  if (pdev->id == 0) {   mout_audss = clk_get(NULL, "mout_audss");   if (IS_ERR(mout_audss)) {    dev_err(&pdev->dev, "failed to get mout_audss\n");    goto err1;   }   clk_set_parent(mout_audss, fout_epll);   /*MUX-I2SA*/   i2s->iis_clk = clk_get(&pdev->dev, "audio-bus");   if (IS_ERR(i2s->iis_clk)) {    dev_err(&pdev->dev, "failed to get audio-bus\n");    clk_put(mout_audss);    goto err2;   }   clk_set_parent(i2s->iis_clk, mout_audss);   /*getting AUDIO BUS CLK*/   i2s->iis_busclk = clk_get(NULL, "dout_audio_bus_clk_i2s");   if (IS_ERR(i2s->iis_busclk)) {    printk(KERN_ERR "failed to get audss_hclk\n");    goto err3;   }   i2s->iis_ipclk = clk_get(&pdev->dev, "i2s_v50");   if (IS_ERR(i2s->iis_ipclk)) {    dev_err(&pdev->dev, "failed to get i2s_v50_clock\n");    goto err4;   }  }

#if defined(CONFIG_PLAT_S5P)  writel(((1<<0)|(1<<31)), i2s->regs + S3C2412_IISCON); #endif 设置i2s模式。为txrx  /* Mark ourselves as in TXRX mode so we can run through our cleanup   * process without warnings. */  iismod = readl(i2s->regs + S3C2412_IISMOD);  iismod |= S3C2412_IISMOD_MODE_TXRX;  writel(iismod, i2s->regs + S3C2412_IISMOD);

#ifdef CONFIG_S5P_INTERNAL_DMA  s5p_i2s_sec_init(i2s->regs, base); #endif

 ret = s5p_i2sv5_register_dai(&pdev->dev, dai);  if (ret != 0)   goto err_i2sv5;

 clk_put(i2s->iis_ipclk);  clk_put(i2s->iis_busclk);  clk_put(i2s->iis_clk);  clk_put(mout_audss);  clk_put(mout_epll);  clk_put(fout_epll);  return 0; err4:  clk_put(i2s->iis_busclk); err3:  clk_put(i2s->iis_clk); err2:  clk_put(mout_audss); err1:  clk_put(mout_epll);  clk_put(fout_epll); err_i2sv5:  /* Not implemented for I2Sv5 core yet */ err:  iounmap(i2s->regs);

 return ret; }

进入s5p_i2sv5_register_dai int s5p_i2sv5_register_dai(struct device *dev, struct snd_soc_dai_driver *dai) {  struct snd_soc_dai_ops *ops = dai->ops;

 ops->trigger = s5p_i2s_wr_trigger;  ops->hw_params = s5p_i2s_wr_hw_params;  ops->set_fmt = s5p_i2s_set_fmt;  ops->set_clkdiv = s5p_i2s_set_clkdiv;  ops->set_sysclk = s5p_i2s_set_sysclk;  ops->startup   = s5p_i2s_wr_startup;  ops->shutdown = s5p_i2s_wr_shutdown;  /* suspend/resume are not necessary due to Clock/Pwer gating scheme */  dai->suspend = s5p_i2s_suspend;  dai->resume = s5p_i2s_resume;  return snd_soc_register_dai(dev, dai);

} 注册soc dai int snd_soc_register_dai(struct device *dev,   struct snd_soc_dai_driver *dai_drv) {  struct snd_soc_dai *dai;

 dev_dbg(dev, "dai register %s\n", dev_name(dev));

 dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);  if (dai == NULL)   return -ENOMEM;

 /* create DAI component name */  dai->name = fmt_single_name(dev, &dai->id);  if (dai->name == NULL) {   kfree(dai);   return -ENOMEM;  }

 dai->dev = dev;  dai->driver = dai_drv;  if (!dai->driver->ops)   dai->driver->ops = &null_dai_ops;

 mutex_lock(&client_mutex);  list_add(&dai->list, &dai_list);  snd_soc_instantiate_cards();  mutex_unlock(&client_mutex);

 pr_debug("Registered DAI '%s'\n", dai->name);

 return 0; } 这样成功的加入了dai链表,然后会匹配链表中的名字,执行一些回调 再看看DMA怎么搞的吧

static struct platform_driver asoc_dma_driver = {  .driver = {   .name = "samsung-audio",   .owner = THIS_MODULE,  },

 .probe = samsung_asoc_platform_probe,  .remove = __devexit_p(samsung_asoc_platform_remove), };

static int __init samsung_asoc_init(void) {  return platform_driver_register(&asoc_dma_driver); } struct snd_soc_platform_driver samsung_asoc_platform = {  .ops  = &dma_ops,  .pcm_new = dma_new,  .pcm_free = dma_free_dma_buffers, }; EXPORT_SYMBOL_GPL(samsung_asoc_platform);

#ifndef CONFIG_S5P_INTERNAL_DMA static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev) {  return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform); } 注册了一个平台类型的soc驱动,此平台不同于彼平台, static struct snd_pcm_ops dma_ops = {  .open  = dma_open,  .close  = dma_close,  .ioctl  = snd_pcm_lib_ioctl,  .hw_params = dma_hw_params,  .hw_free = dma_hw_free,  .prepare = dma_prepare,  .trigger = dma_trigger,  .pointer = dma_pointer,  .mmap  = dma_mmap, }; 这些函数需要一一实现,内核里都搞好了的, static int dma_new(struct snd_card *card,   struct snd_soc_dai *dai, struct snd_pcm *pcm) {  int ret = 0;

 pr_debug("Entered %s\n", __func__);

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

#ifndef CONFIG_S5P_INTERNAL_DMA  if (dai->driver->playback.channels_min) {

为PCM流分配dma buffer

ret = preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);   if (ret)    goto out;  } #endif  if (dai->driver->capture.channels_min) {   ret = preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);   if (ret)    goto out;  }

out:  return ret; }

static int soc_bind_dai_link(struct snd_soc_card *card, int num)分析    Struct snd_soc_dai_link *dai_link = &card->dai_link[num];从card结构取出dai_link    struct snd_soc_pcm_runtime *rtd = &card->rtd[num];从card取出rtd结构  /* do we already have the CPU DAI for this link ? */  if (rtd->cpu_dai) {   goto find_codec;如果rtd中已经有cpu_dai,则直接跳转  }  /* no, then find CPU DAI from registered DAIs*/  list_for_each_entry(cpu_dai, &dai_list, list) {遍历结构   if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {如果遍历出来的cpu_dai的名字和dai_link 中的名字一样,那么就把遍历出来的结构赋到rtd结构中    rtd->cpu_dai = cpu_dai;    goto find_codec;   }  }

find_codec:  /* do we already have the CODEC for this link ? */  if (rtd->codec) {   goto find_platform;  }

 /* no, then find CODEC from registered CODECs*/  list_for_each_entry(codec, &codec_list, list) {遍历codec链表,   if (!strcmp(codec->name, dai_link->codec_name)) {与dai_link中的codec_name做比较    rtd->codec = codec;

   /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/    list_for_each_entry(codec_dai, &dai_list, list) {如果发现了codec,然后就再查找codec_dai     if (codec->dev == codec_dai->dev &&如果codec的dev和codec_dai的dev相等,同时dai_link的名字也相等,就把codec_dai结构赋给rtd的codec_dai       !strcmp(codec_dai->name, dai_link->codec_dai_name)) {      rtd->codec_dai = codec_dai;      goto find_platform;     }    }    dev_dbg(card->dev, "CODEC DAI %s not registered\n",      dai_link->codec_dai_name);

   goto find_platform;   }  }  dev_dbg(card->dev, "CODEC %s not registered\n",    dai_link->codec_name);

find_platform:  /* do we need a platform? */  if (rtd->platform)如果rtd的platform不为空,就跳转到out   goto out;

 /* if there's no platform we match on the empty platform */  platform_name = dai_link->platform_name;把dai_link的platform_name赋给platform  if (!platform_name)   platform_name = "snd-soc-dummy";

 /* no, then find one from the set of registered platforms */  list_for_each_entry(platform, &platform_list, list) {遍历platform链表,   if (!strcmp(platform->name, platform_name)) {名字比较,相同的话复制到rtd结构    rtd->platform = platform;    goto out;   }  }

 dev_dbg(card->dev, "platform %s not registered\n",    dai_link->platform_name);  return 0; out:  /* mark rtd as complete if we found all 4 of our client devices */  if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {如果上面所有的都匹配成功,那么rtd的complete字段赋值1   rtd->complete = 1;   card->num_rtd++;card的rtd数加1  }  return 1; 上面的分析中到处都是dai_link->,那么dai_link是哪来的,它的各种名字是从哪来的, Asoc驱动分为codec ,platform,machine三部分,codec负责声卡芯片那端,platform负责处理器那端的dma数据传输以及与codec的数据接口部分,而machine负责把两部分绑定起来, dai_link的各种name就是在machine部分定义的,以machine驱动smdk_wm8580.c为例 static struct snd_soc_card smdk = {  .name = "SMDK-I2S",  .dai_link = smdk_dai,  .num_links = 2, }; static struct snd_soc_dai_link smdk_dai[] = {  [PRI_PLAYBACK] = { /* Primary Playback i/f */   .name = "WM8580 PAIF RX",   .stream_name = "Playback",   .cpu_dai_name = "samsung-i2s.0",   .codec_dai_name = "wm8580-hifi-playback",   .platform_name = "samsung-audio",   .codec_name = "wm8580-codec.0-001b",   .init = smdk_wm8580_init_paifrx,   .ops = &smdk_ops,  },  [PRI_CAPTURE] = { /* Primary Capture i/f */   .name = "WM8580 PAIF TX",   .stream_name = "Capture",   .cpu_dai_name = "samsung-i2s.0",   .codec_dai_name = "wm8580-hifi-capture",   .platform_name = "samsung-audio",   .codec_name = "wm8580-codec.0-001b",   .init = smdk_wm8580_init_paiftx,   .ops = &smdk_ops,  }, 这里有各种名字,就是根据这些名字进行了匹配

///////////////////////////////////////////////////完毕////////////////////////////////////////////////////////////

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值