本文主要是介绍asoc 音频驱动学习笔记3,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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, }, 这里有各种名字,就是根据这些名字进行了匹配
///完毕
这篇关于asoc 音频驱动学习笔记3的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!