asoc 音频驱动学习笔记3

2024-06-23 05:58
文章标签 音频 学习 驱动 笔记 asoc

本文主要是介绍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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1086319

相关文章

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

如何在Ubuntu上安装NVIDIA显卡驱动? Ubuntu安装英伟达显卡驱动教程

《如何在Ubuntu上安装NVIDIA显卡驱动?Ubuntu安装英伟达显卡驱动教程》Windows系统不同,Linux系统通常不会自动安装专有显卡驱动,今天我们就来看看Ubuntu系统安装英伟达显卡... 对于使用NVIDIA显卡的Ubuntu用户来说,正确安装显卡驱动是获得最佳图形性能的关键。与Windo

嵌入式Linux之使用设备树驱动GPIO的实现方式

《嵌入式Linux之使用设备树驱动GPIO的实现方式》:本文主要介绍嵌入式Linux之使用设备树驱动GPIO的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、设备树配置1.1 添加 pinctrl 节点1.2 添加 LED 设备节点二、编写驱动程序2.1

嵌入式Linux驱动中的异步通知机制详解

《嵌入式Linux驱动中的异步通知机制详解》:本文主要介绍嵌入式Linux驱动中的异步通知机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、异步通知的核心概念1. 什么是异步通知2. 异步通知的关键组件二、异步通知的实现原理三、代码示例分析1. 设备结构

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

使用Python实现文本转语音(TTS)并播放音频

《使用Python实现文本转语音(TTS)并播放音频》在开发涉及语音交互或需要语音提示的应用时,文本转语音(TTS)技术是一个非常实用的工具,下面我们来看看如何使用gTTS和playsound库将文本... 目录什么是 gTTS 和 playsound安装依赖库实现步骤 1. 导入库2. 定义文本和语言 3