通过上小节alsa音频驱动框架的分析,知道如果要去写一个声卡驱动,我们需要分配,设置,注册snd_card结构体:

  1. 定义一个struct snd_card *card;
  2. snd_card_new //里面会创建控制接口
  3. snd_pcm_new //里面会创建playback,capture接口
  4. snd_card_register(card) //

对于嵌入式操作系统,说使用的驱动程序程为ASoC,其是在alsa音频驱动框架上再封装了一套代码。在嵌入式系统中,如下图:

cpu和其他的控制器,都会放在一个芯片上,我们称为soc,其中存在I2S控制器,他会连接不同的音频编解码芯片,这个芯片里面存在I2S接口,以及控制接口(如:I2C)。其涉及两部分硬件,一部分我们称之为平台,一部分称之为编解码芯片,我们需要给两边都写出驱动层。平台有rk3399,rk3288等等。编解码有ALC5651(我们使用的就是该解码器),WM8960等等。所以我们需要给不同的芯片,以及编解码写出不同的驱动程序。
在内核中包括了很多平台的驱动程序,以及多个codec驱动。那么问题来了,对于某个单板,他应该使用那一个platform和codec。在老的版本中,我们可以称machine。所以驱动程序会分为三个部分:

首先我们来看看codec部分。

codec

在我们的开发板rk3399设备树rk3399-excavator-sapphire.dtsi文件可以找到如下:

&i2c1 {status = "okay";i2c-scl-rising-time-ns = <300>;i2c-scl-falling-time-ns = <15>;rt5651: rt5651@1a {status = "okay";#sound-dai-cells = <0>;compatible = "rockchip,rt5651";reg = <0x1a>;clocks = <&cru SCLK_I2S_8CH_OUT>;clock-names = "mclk";pinctrl-names = "default";pinctrl-0 = <&i2s_8ch_mclk>;spk-con-gpio = <&gpio0 11 GPIO_ACTIVE_HIGH>;hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_LOW>;};
};

根据设备树,我们在源码中搜索"rockchip,rt5651",没有找到任何东西,那么我们搜索"rt5651"可以找到rt5651.c文件,我们从该驱动程序的入口函数开始分析:

static int rt5651_i2c_probe(struct i2c_client *i2c,const struct i2c_device_id *id)/*首先都是一些寄存器初始设置*/rt5651->regmap = devm_regmap_init_i2c(i2c,&rt5651_regmap);regmap_read(rt5651->regmap, RT5651_DEVICE_ID, &ret);regmap_write(rt5651->regmap, RT5651_RESET, 0);ret = regmap_register_patch(rt5651->regmap, init_list,ARRAY_SIZE(init_list));/*对声卡进行注册*/snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5651,rt5651_dai, ARRAY_SIZE(rt5651_dai));

我们可以看到其中调用了两个重要的结构体snd_soc_dai_driver与snd_soc_codec_driver。其中snd_soc_dai_driver主要用来描述codec的数据传输接口,snd_soc_codec_driver用来描述控制接口:
snd_soc_dai_driver如下:

/*该结构体中的函数主要用于设置硬件的参数*/
static const struct snd_soc_dai_ops rt5651_aif_dai_ops = {.hw_params = rt5651_hw_params,.set_fmt = rt5651_set_dai_fmt,.set_sysclk = rt5651_set_dai_sysclk,.set_pll = rt5651_set_dai_pll,
};
static struct snd_soc_dai_driver rt5651_dai[] = {{.name = "rt5651-aif1",.id = RT5651_AIF1,.playback = {//代表输出通道1.stream_name = "AIF1 Playback",.channels_min = 1,.channels_max = 2,.rates = RT5651_STEREO_RATES,//代表波特率.formats = RT5651_FORMATS,//},.capture = {//代表输入通道1.stream_name = "AIF1 Capture",.channels_min = 1,.channels_max = 2,.rates = RT5651_STEREO_RATES,//代表波特率.formats = RT5651_FORMATS,//数据格式},.ops = &rt5651_aif_dai_ops,/*主要用于设置硬件的参数*/},{.name = "rt5651-aif2",.id = RT5651_AIF2,.playback = {//代表输出通道2.stream_name = "AIF2 Playback",.channels_min = 1,.channels_max = 2,.rates = RT5651_STEREO_RATES,//代表波特率.formats = RT5651_FORMATS,//数据格式},.capture = {//代表输入通道2.stream_name = "AIF2 Capture",.channels_min = 1,.channels_max = 2,.rates = RT5651_STEREO_RATES,//代表波特率.formats = RT5651_FORMATS,//数据格式},.ops = &rt5651_aif_dai_ops,/*主要用于设置硬件的参数*/},
};

其上的playback代表输出通道,capture 代表输入通道,

snd_soc_codec_driver

static struct snd_soc_codec_driver soc_codec_dev_rt5651 = {.probe = rt5651_probe,.suspend = rt5651_suspend,.resume = rt5651_resume,.set_bias_level = rt5651_set_bias_level,.idle_bias_off = true,.controls = rt5651_snd_controls,.num_controls = ARRAY_SIZE(rt5651_snd_controls),.dapm_widgets = rt5651_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(rt5651_dapm_widgets),.dapm_routes = rt5651_dapm_routes,.num_dapm_routes = ARRAY_SIZE(rt5651_dapm_routes),
};

snd_soc_codec_driver 其主要提供了读写编解码寄存器的函数。在snd_soc_dai_driver 结构体中的.ops = &rt5651_aif_dai_ops中的操作,肯定需要使用snd_soc_codec_driver soc_codec_dev_rt5651中的一些读写函数。
下面我们来讲解一下platform。

platform

Soc/Platform平台驱动有 soc 厂商做好
目录;kernel/sound/soc/soc-core.c:

简介:
Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。

在具体实现上,ASoC又把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。
其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。我们先来看看snd_soc_dai_driver

snd_soc_dai_driver

在源码中我们搜索snd_soc_dai_driver可以找到Rockchip_i2s.c文件,

static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = {/*设置参数*/.hw_params = rockchip_i2s_hw_params,.set_sysclk = rockchip_i2s_set_sysclk,.set_fmt = rockchip_i2s_set_fmt,/*启动数据传输*/.trigger = rockchip_i2s_trigger,
};static struct snd_soc_dai_driver rockchip_i2s_dai = {.probe = rockchip_i2s_dai_probe,.playback = {.stream_name = "Playback",//名字.channels_min = 2,  //参数.channels_max = 8,.rates = SNDRV_PCM_RATE_8000_192000,.formats = (SNDRV_PCM_FMTBIT_S8 |SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_S20_3LE |SNDRV_PCM_FMTBIT_S24_LE |SNDRV_PCM_FMTBIT_S32_LE),},.capture = {.stream_name = "Capture",.channels_min = 2,.channels_max = 2,.rates = SNDRV_PCM_RATE_8000_192000,.formats = (SNDRV_PCM_FMTBIT_S8 |SNDRV_PCM_FMTBIT_S16_LE |SNDRV_PCM_FMTBIT_S20_3LE |SNDRV_PCM_FMTBIT_S24_LE |SNDRV_PCM_FMTBIT_S32_LE),},.ops = &rockchip_i2s_dai_ops,
};
static int rockchip_i2s_probe(struct platform_device *pdev)ret = devm_snd_soc_register_component(&pdev->dev,&rockchip_i2s_component,soc_dai, 1);

可以看到,其中有一个重要的过程,那就是数据传输的过程,一般由DMA完成,即snd_soc_platform_driver结构体相关,那么我们来看看snd_soc_platform_driver

snd_soc_platform_driver

在源码搜索snd_soc_platform_driver可以找到文件Rockchip_multi_dais_pcm.c文件,该文件存在函数snd_dmaengine_mpcm_register函数

static const struct snd_pcm_ops dmaengine_mpcm_ops = {.open     = dmaengine_mpcm_open,.close       = dmaengine_mpcm_close,.ioctl      = snd_pcm_lib_ioctl,.hw_params = dmaengine_mpcm_hw_params,.hw_free    = snd_pcm_lib_free_pages,.trigger  = snd_dmaengine_mpcm_trigger,.pointer  = dmaengine_mpcm_pointer,
};static const struct snd_soc_platform_driver dmaengine_mpcm_platform = {.component_driver = {.probe_order = SND_SOC_COMP_ORDER_LATE,},.ops      = &dmaengine_mpcm_ops,.pcm_new = dmaengine_mpcm_new,
};
int snd_dmaengine_mpcm_register(struct rk_mdais_dev *mdais)ret = snd_soc_add_platform(dev, &pcm->platform,&dmaengine_mpcm_platform);

dmaengine_mpcm_ops中多是一些数据传输的函数(通过DMA)。我们在源码中搜索snd_dmaengine_mpcm_register函数,可以看到他在
Rockchip_multi_dais.c文件中被调用。
那么Rockchip_multi_dais.c文件就是以后我们要分析的启动文件。Rockchip_multi_dais.c该文件主要实现了platform数据之间DMA的操作。

machine

从前面的分析我们知道,
在codec端有两个结构体:snd_soc_dai_driver与snd_soc_codec_driver
在platform也存在两个结构体:snd_soc_dai_driver与snd_soc_platform_driver
这些结构体注册之后他们都会分别被放在两个列表之中,如对于dai接口存在一个dai_list(包括了codec端与platform端的snd_soc_dai_driver),对于控制接口存在一个codes_list,对于传输结构体存在platform_list。

一个内核之中,存在很多的platform与codes,那么对于我们的开发板,他需要选择那一个platform与codes呢?

如,芯片的那个I2S与编码器的I2S连接,这个就是有machine决定,首先就是设备树的匹配。我们的开发板rk3399设备树rk3399-excavator-sapphire.dtsi文件可以找到如下,我们的rt5651的machine为kernelsound\soc\rockchip下的rockchip_rt5651_tc358749x.c,在其中重要等部分如下:

static struct snd_soc_ops rockchip_sound_rt5651_hifi_ops = {.hw_params = rockchip_rt5651_hw_params,
};
static struct  rockchip_dailinks[] = {[DAILINK_RT5651_HIFI] = {.name = "RT5651 HIFI",.stream_name = "RT5651 PCM",.codec_dai_name = "rt5651-aif1",.ops = &rockchip_sound_rt5651_hifi_ops,/* set rt5651 as slave */.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |SND_SOC_DAIFMT_CBS_CFS,},[DAILINK_RT5651_VOICE] = {.name = "RT5651 HDMIIN",.stream_name = "RT5651 PCM",.codec_dai_name = "rt5651-aif2",.ops = &rockchip_sound_rt5651_voice_ops,/* set rt5651 as slave */.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |SND_SOC_DAIFMT_CBS_CFS,},[DAILINK_TC358749_HDMIIN] = {.name = "TC358749 HDMIIN",.stream_name = "TC358749 PCM",.codec_dai_name = "tc358749x-audio",},
};static struct snd_soc_card rockchip_sound_card = {.name = "realtekrt5651codec_hdmiin",.owner = THIS_MODULE,.dai_link = rockchip_dailinks,.num_links =  ARRAY_SIZE(rockchip_dailinks),.dapm_widgets = rockchip_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(rockchip_dapm_widgets),.dapm_routes = rockchip_dapm_routes,.num_dapm_routes = ARRAY_SIZE(rockchip_dapm_routes),.controls = rockchip_controls,.num_controls = ARRAY_SIZE(rockchip_controls),
};
static int rockchip_sound_probe(struct platform_device *pdev)
{struct snd_soc_card *card = &rockchip_sound_card;/*以"rockchip,cpu"为参数,使用of_parse_phandle获取I2S adapter的device node*/cpu_node = of_parse_phandle(pdev->dev.of_node, "rockchip,cpu", 0);/*machine驱动负责platform与code之间的耦合*/for (i = 0; i < DAILINK_ENTITIES; i++) {/*决定使用哪个平台*/rockchip_dailinks[i].platform_of_node = cpu_node;/**/rockchip_dailinks[i].cpu_of_node = cpu_node;/*以"rockchip,codec"为参数,使用of_parse_phandle获取sound codec的device node*//*决定使用哪个编解码芯片*/rockchip_dailinks[i].codec_of_node =of_parse_phandle(pdev->dev.of_node,"rockchip,codec", i);if (!rockchip_dailinks[i].codec_of_node) {dev_err(&pdev->dev,"Property[%d] 'rockchip,codec' failed\n", i);return -EINVAL;}}card->dev = &pdev->dev;platform_set_drvdata(pdev, card);ret = devm_snd_soc_register_card(&pdev->dev, card);if (ret)dev_err(&pdev->dev, "%s register card failed %d\n",__func__, ret);dev_info(&pdev->dev, "snd_soc_register_card successful\n");return ret;
}

可以看到我们需要注册一个snd_soc_card rockchip_sound_card结构体,snd_soc_card结构体中又包含了dai_link成员。dai_link中的.codec_dai_name = "rt5651-aif1"必须与codec中的snd_soc_dai_driver rt5651_dai的.name = "rt5651-aif1"对应。

最后我们可以看到其调用了devm_snd_soc_register_card,把进行注册:

int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)ret = snd_soc_register_card(card);/*在该函数内,会做很多工作,如绑定dai等等*/ret = snd_soc_instantiate_card(card);/*绑定完成之后创建一个声卡,此时没有生成设备节点*/ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);/*调用snd_card_register注册之后,生成设备节点*/ret = snd_card_register(card->snd_card);

到这里,就回到了上小节我们我们分析的ASoC框架了
下面是ASoC音频驱动框架一些总结

08.音频系统:第003课_Linux音频驱动程序:第002节_ASoC音频驱动框架相关推荐

  1. 08.音频系统:第003课_Linux音频驱动程序:第003节_RK3399声卡驱动移植_combine

    该小节我们讲解一下开发板RK3399声卡rt5651的移植,主要分为4个部分,platfrom,codec,machine,dts(设备树). 首先我们从设备树开始讲起,当然在讲解之前,我们先来体验下 ...

  2. 08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架

    在上小节我们分析了Adndroid系统音频的框架,这么一个复杂的系统我们怎么去学习呢?我们从下往上学,先分析音频的驱动程序,看看linux系统中驱动程序是怎么编写的,他的结构是怎么样的,然后在琢磨Ti ...

  3. 08.音频系统:第003课_Linux音频驱动程序:第005节_DAPM_widget_route_path

    DAPM是Dynamic Audio PowerManagement的缩写,译过来就是动态音频电源管理的意思.DAPM是为了使基于linux的移动设备上的音频驱动子系统,在任何时候都工作在最小功耗状态 ...

  4. 08.音频系统:第003课_Linux音频驱动程序:第003节_耳麦拔插事件调用流程分析

    在前面的小节中,我们编写了一个驱动程序,模拟耳机的插拔事件,其可以上报耳机的拔插事件,并且修改了android的源代码,可以根据耳机的拔插事件,在状态栏上现实或者消除耳麦的图标,这节视频我们讲解耳麦插 ...

  5. 08.音频系统:第002节_Android音频系统框架简述

    该小节我们来讲解Android音频系统框架,了解了框架之后,我们才能更加容易的去查看以及分析源码,有了框架才不会遗失方向. 下面是一个大框图,该小节我们将围绕下面的图示进行讲解: 以前总提到,写应用程 ...

  6. Linux ALSA 音频系统:物理链路篇

    原址 1. Overview 硬件平台及软件版本: Kernel - 3.4.5 SoC - Samsung exynos CODEC - WM8994 Machine - goni_wm8994 U ...

  7. (一)Linux ALSA 音频系统:物理链路篇

    物理链路篇 转自:https://me.csdn.net/zyuanyun Linux ALSA 音频系统:物理链路篇 Linux ALSA 音频系统:物理链路篇 原创 zyuanyun 最后发布于2 ...

  8. 逐步攻略:使用Matlab音频系统工具箱创建自己的VST插件,让音乐编程在Nashville崭新绽放

    第一部分:引言与Matlab音频系统工具箱概览 尊敬的读者,欢迎阅读这篇文章.我作为一个热爱音乐和编程的人,十分激动地在这里和大家分享我最近的一次学习经验.正如标题所述,我们将探索一种崭新的方式来创建 ...

  9. Linux ALSA音频系统:platform,machine,codec

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_41965270/arti ...

最新文章

  1. 安装win7根证书_最详细图解Windows7x64更新安装教程
  2. JS中window.showModalDialog()详解
  3. 硬盘容量和计算机容量的换算,t和g的换算(硬盘容量G跟T的换算)
  4. echarts 叠加柱状图柱顶显示百分比
  5. go mod tidy 报错:verifying ...: checksum mismatch
  6. 【Python】Talk Python To Me Podcast播客
  7. indiegogo众筹
  8. POI对word文档中的指定内容添加批注
  9. Fabric交易流程
  10. 使用高级语言编写计算机程序步骤,计算机执行用高级语言编写的程序主要有两种途径解释和编译编译专.doc...
  11. matlab subs什么意思,什么是matlab subs函数?
  12. 安装ms word时需要的正版windows xp序列号
  13. OSChina 周二乱弹 —— 有一种蛋蛋的忧伤
  14. 高群耀:移动电影院2.0四大功能实现了用户“观影社交化”
  15. 用Python架设大型多人在线游戏服务端
  16. 【PowerBI】onmicrosoft.com邮箱已经不能注册 Power BI 账号
  17. ROS自学实践(11):利用map_server功能包创建自己的地图
  18. 打通apk到hal层
  19. 新人必读:区块链实用型技能树
  20. 渗透测试-靶机打靶思路与方法

热门文章

  1. 数据挖掘著名案例——啤酒与尿布
  2. 【POJ No. 1986】 距离查询 Distance Queries
  3. 中级工程师专业有哪些?职称申报有哪些要求?伴德诚
  4. 火箭弹道设计 matlab,航天飞行动力学远程火箭弹道设计大作业 (1)
  5. S60v5 GBA模拟器gpSP4Symbian v0.5安装使用图文教程
  6. 经典的搞笑图片集,让你轻松一下
  7. xshell6 强制更新的问题(转载、亲测有效)
  8. 程序员和黑客的十大本质区别
  9. Qt使用html生成word文档
  10. 用JSP/Servlet应用开发一个简单的考试报名系统