ASoC驱动框架简介

  1. 简介
    ASoC驱动框架简介_第1张图片

    ASoC框架是一种linux系统声卡驱动框架,是针对嵌入式设备在linux alsa声卡驱动框架基础上进行了一层封装,意在将声卡和cpu两部分的控制代码分离开来;
    如上图是音频系统的硬件示意图,声卡通过I2S接口与cpu进行音频数据传输,通过I2C接口与cpu进行控制通讯(数据接口和控制接口也可以是其他);
    录音数据通路:麦克风---->声卡–(I2S)->DMA---->内存;
    播放数据通路:内存---->DMA–(I2S)->声卡---->扬声器;
    如图所示,cpu部分的代码包括DMA控制器部分的代码和I2S控制器接口部分的代码;声卡部分的代码包括声卡寄存器控制部分(I2C接口进行控制)和I2S接口数据格式控制部分;
    在ASoC驱动框架中cpu部分称作platform,声卡部分被称作codec,两者通过machine进行匹配连接;machine可以理解为对开发板的抽象,开发板可能包括多个声卡,对应machine部分包含多个link;platform、codec和machine三者的关系在下面段落中会说明;

  2. ASoC驱动框架
    ASoC驱动框架简介_第2张图片

如上图:
codec: codec是声卡部分的控制代码,会抽象出两个结构体分别为snd_soc_dai_driversnd_soc_codec_driver

snd_soc_dai_driver为声卡接口部分的控制代码,如设置该声卡支持播放和录音参数,如采样率(如SNDRV_PCM_RATE_8000_48000),数据格式(如SNDRV_PCM_FMTBIT_S16_LE)、通道数等,以及提供声卡相关的操作函数,如snd_soc_dai_ops->set_fmt设置数据格式、snd_soc_dai_ops->set_sysclk系统时钟设置等等;

snd_soc_codec_driver为声卡注册相关控件(controls)、部件(widgets)以及routes snd_soc_dapm_route
controls 为控件抽象的结构体为snd_kcontrol_new,可以为单独的一个控件,控制声卡某一功能,如声卡的播放声音,也可以用于连接两个部件(widget)形成一条通路(route),controls的注册函数snd_soc_add_codec_controls
widgets 为部件,抽象的结构体为snd_soc_dapm_widget,个人感觉部件就是声卡内部的节点的抽象,也是对某些controls做了一层封装,如录音输入引脚、输出引脚以及中间控制多路声音混合的混音器,widgets注册函数snd_soc_dapm_new_controls
route 表示的是两个节点之间的通路,抽象的结构体为snd_soc_dapm_route,用于连接两个widget,两个widget中间通过controls连接形成一条route(貌似controls可以为空),routes注册函数snd_soc_dapm_add_routes
snd_soc_dai_driversnd_soc_codec_driver是同时注册的,注册函数为snd_soc_register_platform(struct device *dev, const struct snd_soc_platform_driver *platform_drv)

platform: platform为cpu部分的控制代码,抽象出两个结构体snd_soc_dai_driversnd_soc_platform_driver

snd_soc_dai_driver 和codec的的dai是同类型结构体,功能一样,只是在platform中的dai针对的对象是cpu部分的I2S接口;
snd_soc_dai_driver的注册函数为devm_snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *cmpnt_drv,struct snd_soc_dai_driver *dai_drv, int num_dai);参数cmpnt_drv需要自己定义(暂时不知道作用), dai_drv可以包含多个snd_soc_dai_driver即可以为结构体指针,num_dai表示结构体数组成员格式;

snd_soc_platform_driver 是控制cpu部分音频数据传输的,一般音频数据在I2S接口与内存之间传输会使用DMA,所以snd_soc_platform_driver一般为cpu DMA的控制代码;结构体主要成员:
name:platform driver的名称,
probe:probe函数,platform和codec匹配成功后调用;
remove:remove函数,与probe函数做相反的操作;
pcm_new:DMA内存申请等操作,可使用dma_alloc_writecombine申请DMA内存;
pcm_free:DMA内存释放等操作,可使用dma_free_writecombine释放DMA内存;
ops:pcm数据流操作函数,即操作DMA来进行数据搬运;
ops.open:open函数
a 使用snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,const struct snd_pcm_hardware *hw)函数设置snd_pcm_runtime硬件属性,其中snd_pcm_hardware需要用户自己定义,规定一些buffer大小、最大最小周期等等,貌似这些参数用户空间也需要访问,然后根据区间设定合适的period、period_size等等再进行数据传输;
b 使用snd_pcm_hw_constraint_integer(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var)确保snd_pcm_runtime确保缓冲区(buffer)大小是周期(period)大小的倍数;
c 根据当前snd_pcm_runtime->pstr->streamSNDRV_PCM_STREAM_PLAYBACK还是SNDRV_PCM_STREAM_CAPTURE申请对应的DMA中断等相关操作;
ops.close:close函数,和open操作相反;
ops.ioctl:这个可以不用自己写,使用内核的snd_pcm_lib_ioctl函数;
ops.prepare:启动播放或录音开始前调用,初始化相关状态参数,比如播放或录音的offset
ops.trigger:音频传输触发函数,根据传入的命令进行开始\停止\暂停等等操作(如SNDRV_PCM_TRIGGER_START要设置DMA源、目的地址、传输长度并开始DMA传输);音频的DMA传输是一段一段传输的(DMA每次触发传输的大小为snd_pcm_substream->snd_pcm_runtime->buffer_size),应用程序每次发起SNDRV_PCM_TRIGGER_START传输的数据为snd_pcm_substream->snd_pcm_runtime->dma_bytes大小(即申请的DMA buffer大小,在设定snd_pcm_hardware的时候就被指定),在DMA中断处理函数中需要做的事情:
a 更新dma buffer指针offset += substream->runtime->buffer_size,判断如果已经读到了DMA buffer尾部()则offset=0(注意:offset变量需要驱动自己定义维护,在ops.pointer中被返回应用层);
b 调用snd_pcm_period_elapsed更新pcm状态为下一个周期(period),该函数会调用ops.pointer函数,在ops.pointer函数中需要利用dma buffer当前偏移量offset调用bytes_to_frames(struct snd_pcm_runtime *runtime, ssize_t size)返回frames;
c 驱动还需要自己维护一个变量streaming保存当前的状态,如果streaming=1那么再次设置DMA源、目的地址、传输长度并开始DMA传输(注意streaming状态根据用户传进来的命令在ops.trigger中被改变,比如用户调用SNDRV_PCM_TRIGGER_STOP那streaming应该被设为0);
d return IRQ_HANDLED;
ops.pointer:要利用dma buffer当前偏移量offset调用bytes_to_frames(struct snd_pcm_runtime *runtime, ssize_t size)返回frames;
后面的成员可以不实现:
ops.get_time_info
ops.fill_silence
ops.copy_user
ops.copy_kernel
ops.fill_silence
ops.page
ops.mmap
ops.ack
snd_soc_platform_driver的注册函数为snd_soc_register_platform(struct device *dev,const struct snd_soc_platform_driver *platform_drv)

machine: machine部分控制管理platform和codec之间的连接匹配,管理控件(controls)、部件(widgets)以及routes,其抽象的结构体为snd_soc_card,结构体成员简介:
namelong_namedriver_namedmi_longname:snd_soc_card名字相关
snd_card:alsa声卡层抽象的结构体;
probe:probe函数
remove:remove函数
dai_link:预定义描述platform和codec连接的结构体指针,实现两者绑定配对,匹配方式可以是名字或者设备树节点,可以预定义多个;
num_links:预定义连接结构体的个数;
dai_link_list:所有的link最终会添加到这里,内核自动操作;
num_dai_links:添加进dai_link_list的link的计数器;
rtd_list:创建的snd_soc_pcm_runtime结构体会挂载这个链表,该结构体在snd_soc_register_card()->snd_soc_instantiate_card()->soc_bind_dai_link->soc_new_pcm_runtime()中被创建,每个link都会创建一个;
num_rtd:链表内snd_soc_pcm_runtime个数计数器
controls:控件;
num_controls:控件的个数;
dapm_widgets:widgets组件;
num_dapm_widgets:widgets组件个数;
dapm_routes:routes路线;
num_dapm_routes:数目
of_dapm_widgets:设备树中定义的widgets
num_of_dapm_widgets:数目;
of_dapm_routes:设备树中定义的routes;
num_of_dapm_routes:数目
component_dev_list
widgets
paths
dapm_list
dapm_dirty
dapm
dapm_stats
update

simple-card: simple-card框架主要用于设置machine部分参数,需要在设备树中添加相应的sound {
compatible = “simple-audio-card”;…}设备节点;

head_list: 这里的head_list是指在sound/soc/soc-core.c源文件中的platform_listcodec_list、和component_list结构体,在machine部分的snd_soc_card通过snd_soc_register_card()注册时会到这三个链表中找相应的设备;
如图所示:
component_list:该链表挂着snd_soc_component结构体,在platform_driver、platform_dai、codec_driver和codec_dai(其中codec 的dai 和 driver通过同一个函数一起注册的)注册的时候都会直接或间接的分配一个snd_soc_component结构体成员(其中platform_driver注册时分配了snd_soc_platform结构体里面包含snd_soc_component成员,codec部分注册时分配了snd_soc_codec结构体里面也包含snd_soc_component成员,platform_dai注册时时直接申请的snd_soc_component),然后将关键结构体(platform_driver、platform_dai、codec_driver和codec_daiplatform_dai、codec_driver和codec_dai)直接添加或以某种关系关联到snd_soc_component中,并把snd_soc_component注册到component_list中;

platform_list:链表上挂结构体是在snd_soc_register_platform()中创建的snd_soc_platform,通过该结构体可以找到platform_driver部分;
codec_list:链表挂的结构体是在snd_soc_register_codec创建的snd_soc_codec

注:
a 由于水平有限,文章中可能有部分每完善的,后续水平够了再作说明;
b 在machine部分的snd_soc_card注册时(调用snd_soc_register_card()),会根据名字或设备树节点到component_list匹配相应的组件,通过匹配的组件再去找platform_dai、codec_dai、codec_driver和platform_driver;platform_driver的匹配过程中先到component_list找到相应的组件,找到了再到platform_list中去取到platform_driver;

你可能感兴趣的:(#,linux,ASoC驱动框架)