5.10.6 kernel sound code学习

一 PCM Flow

snd_soc_dai_link结构

link结构统一为snd_soc_dai_link_component存放,并配合宏SND_SOC_DAILINK_DEF使用

snd_soc_pcm_runtime结构

每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,其中

struct snd_soc_dai **dais:保存dailink关联的已注册dai

struct snd_soc_component *components[]:保存dailink关联的已注册CPU/Codec/Platform

snd_soc_register_card流程

主要调用snd_soc_bind_card函数实现的,函数内的主要步骤包括:

1.snd_soc_add_pcm_runtime:

soc_new_pcm_runtime:snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元

asoc_rtd_to_cpu/snd_soc_rtd_add_component:把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中。

2.snd_card_new:创建ALSA snd_card结构

3.soc_xxx_probe:依次调用各个子结构的probe函数。snd_soc_card_probe调用card的probe函数;soc_probe_link_components调用CPU/Codec/Platform的probe函数;soc_probe_aux_devices调用aux的probe函数;soc_probe_link_dais调用dai的probe函数

4.soc_init_pcm_runtime:调用soc_new_pcm创建标准alsa驱动的pcm逻辑设备,并设置ASoC PCM operations

5. snd_card_register:调用标准alsa驱动的声卡注册函数对声卡进行注册

platform/codec驱动

platform/codec统一用snd_soc_component_driver注册,生成的结构为snd_soc_component,并存放在component_list中

dai仍然用snd_soc_dai_driver注册,生成的结构为snd_soc_dai,并存放在snd_soc_component的中

snd_soc_register_component流程

1.snd_soc_component_initialize:snd_soc_component申请内存并初始化。

2.snd_soc_add_component:初始化snd_soc_dai结构,初始化regmap读写函数,最后注册snd_soc_dai和snd_soc_component。snd_soc_dai注册在component->dai_list,snd_soc_component注册在component_list。

snd_soc_component_driver中的operations函数

1.soc platform驱动操作函数:他们基本都涉及dma操作以及dma buffer的管理等工作

open:当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。

hw_params:驱动的hw_params阶段,该函数会被调用。通常,该函数会通过snd_soc_dai_get_dma_data函数获得对应的dai的dma参数,获得的参数一般都会保存在snd_pcm_runtime结构的private_data字段。然后通过snd_pcm_set_runtime_buffer函数设置snd_pcm_runtime结构中的dma buffer的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。

prepare:正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。

trigger:数据传送的开始,暂停,恢复和停止时,该函数会被调用。

2.soc codec regmap操作函数

read/write:regmap 读写

3.DMA操作回调函数

pcm_construct/pcm_destruct:用于初始化和释放DMA,替代旧版本的pcm_new/pcm_free

snd_soc_dai_driver结构

snd_soc_pcm_stream capture/playback:设置dai的性能参数

snd_soc_dai_ops *ops:该结构实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分

1.工作时钟配置函数  通常由machine驱动调用:

  • set_sysclk  设置dai的主时钟;
  • set_pll  设置PLL参数;
  • set_clkdiv  设置分频系数;
  • dai的格式配置函数  通常由machine驱动调用:
  • set_fmt   设置dai的格式;
  • set_tdm_slot  如果dai支持时分复用,用于设置时分复用的slot;
  • set_channel_map 声道的时分复用映射设置;
  • set_tristate  设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;

2.标准的snd_soc_ops回调  通常由soc-core在进行PCM操作时调用:startup/shutdown/hw_params/hw_free/prepare/trigger

3.抗pop,pop声  由soc-core调用:digital_mute 

二 DAPM

snd_kcontrol_new和snd_kcontrol结构

snd_kcontrol_new结构为驱动注册前声明,主要成员如下:

snd_ctl_elem_iface_t iface:字段指出了control的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是MIXER,当然也可以定义属于全局的CARD类型,也可以定义属于某类设备的类型,例如HWDEP,PCMRAWMIDI,TIMER等,这时需要在device和subdevice字段中指出卡的设备逻辑编号。

 const char *name:name字段是该control的名字,从ALSA 0.9.x开始,control的名字是变得比较重要,因为control的作用是按名字来归类的。ALSA已经预定义了一些control的名字,我们再Control Name一节详细讨论。

unsigned int index:index字段用于保存该control的在该卡中的编号。如果声卡中有不止一个codec,每个codec中有相同名字的control,这时我们可以通过index来区分这些controls。当index为0时,则可以忽略这种区分策略。

unsigned int count:相同控件的个数

unsigned int access:access字段包含了该control的访问类型。每一个bit代表一种访问类型,这些访问类型可以多个“或”运算组合在一起。

unsigned long private_value:private_value字段包含了一个任意的长整数类型值。该值可以通过info,get,put这几个回调函数访问。你可以自己决定如何使用该字段,例如可以把它拆分成多个位域,又或者是一个指针,指向某一个数据结构。

union tlv:tlv字段为该control提供元数据。

info/get/put:回调函数,分别用于获取信息,获取参数和设置参数

snd_kcontrol结构为驱动注册后的存储结构,主要成员如下:

struct snd_ctl_elem_id id:包含snd_kcontrol_new结构的iface和index等字段,其中index为检索id的起始值(可能包括多个相同项) unsigned int count:相同控件的个数 union tlv:存储snd_kcontrol_new的tlv字段。

info/get/put:存储snd_kcontrol_new的回调函数 unsigned long private_value:存储snd_kcontrol_new的private_value void *private_data:存储私有数据指针,一般指向上层结构 struct snd_kcontrol_voatile vd[]:用于存储snd_kcontrol_new的access,每一个控件对应一个

DAPM结构

widget:DAPM的基本单元,指音频系统中的某个部件。由结构体snd_soc_dapm_widget描述。其中,edges用于链接所有的patch,SND_SOC_DAPM_DIR_OUT链表用于链接所有的输入path,SND_SOC_DAPM_DIR_IN链表用于链接所有的输出path。list用于链接到声卡的widgets链表,dirty用于链接到声卡的dapm_dirty链表

path:widget之间的连接器。由结构体snd_soc_dapm_path描述。它的source(node[SND_SOC_DAPM_DIR_IN])字段会指向该连接的起始端widget,而它的sink(node[SND_SOC_DAPM_DIR_OUT])字段会指向该连接的到达端widget。无需我们自己定义,它会在注册snd_soc_dapm_route时动态地生成

route:widget的连接关系。由结构体snd_soc_dapm_route描述。其中,sink指向到达端widget的名字字符串,source指向起始端widget的名字字符串,control指向负责控制该连接所对应的kcontrol名字字符串,connected回调则定义了上一节所提到的自定义连接检查回调函数。

context:dapm的电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上。由结构体snd_soc_dapm_context描述

DAPM初始化

驱动注册方法:

通过snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card结构中的dapm_routes和num_dapm_routes字段;

在codec、platform的的probe回调中主动注册音频路径,machine驱动中则通过snd_soc_dai_link结构的init回调函数来注册音频路径;

snd_soc_dapm_new_controls:

创建widget的第一步,它为每个widget分配内存,初始化必要的字段,然后把这些widget挂在代表声卡的snd_soc_card的widgets链表字段中。函数流程如下:

1.dapm_cnew_widget:为widget分配内存,并拷贝参数中传入的在驱动中定义好的模板

2.为不同类型的widget设置合适的power_check电源状态回调函数

3.list_add_tail(&w->list, &dapm->card->widgets):把该widget加入到声卡的widgets链表中

snd_soc_dapm_new_widgets流程:

根据widget的信息,创建widget所需要的dapm kcontrol,这些dapm kcontol的状态变化,代表着音频路径的变化,从而影响着各个widget的电源状态

1.为widget的kcontrols字段分配内存

2.对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol

3.初始化widget的电源状态,并设置到power字段中

4.dapm_power_widgets:统一处理所有位于dapm_dirty链表上的widget的状态改变

snd_soc_dapm_add_routes流程:

根据route创建path

1.遍历声卡的widgets链表,找出源widget和目的widget的指针

2.snd_soc_dapm_add_path:创建并注册audio path

DAPM电源状态控制

关键结构

dapm_dirty:保存所有状态发生了改变的widget。dapm_mark_dirty用来把一个widget加入dapm_dirty链表

up_list:保存需要上电的widget。

down_list:保存需要下电的widget。

dapm内部使用dapm_seq_insert函数把一个widget加入到上述两个链表中的其中一个

关键函数

power_check回调函数:当widget的状态改变后,dapm会遍历dapm_dirty链表,并通过power_check回调函数,决定该widget是否需要上电。大多数的widget的power_check回调被设置为dapm_generic_check_power

dapm要给一个widget上电的其中一个前提条件是:这个widget位于一条完整的音频路径上。dapm_generic_check_power通过以下两个函数获取路径数用以判断

is_connected_output_ep:返回连接至输出引脚或激活状态的输出音频流的路径数量

is_connected_input_ep: 返回连接至输入引脚或激活状态的输入音频流的路径数量

dapm_power_widgets函数

dapm_power_widgets流程

dapm_power_widgets函数来改变整个音频路径上的电源状态

1.dapm_power_one_widget:把需要上电和需要下电的widget分别加入到up_list和down_list链表中,同时,他还会把受到影响的邻居widget再次加入到dapm_dirty链表的末尾,通过这个动作,声卡中所以受到影响的widget都会被“感染”,依次被加到dapm_dirty链表,然后依次被执行dapm_power_one_widget函数。

2.dapm_seq_check_event:遍历down_list/up_list链表,向其中的widget发出SND_SOC_DAPM_WILL_PMD/SND_SOC_DAPM_WILL_PMU事件,感兴趣该事件的widget的event回调会被调用。

3.dapm_seq_run:处理down_list/up_list中的widget,使它们按定义好的顺序依次下电。

4.dapm_widget_update:切换触发该次状态变化的widget的kcontrol中的寄存器值,对应的结果就是:改变音频路径。

5.async_schedule_domain:对每个dapm context发出状态改变回调。

6.pop_wait:适当的延时,防止pop-pop声。

触发dapm_power_widgets场景

1.声卡初始化阶段,snd_soc_dapm_new_widgets函数创建widget包含的kcontrol后,会触发一次扫描操作。

2.用户空间的应用程序修改了widget中包含的dapm kcontrol的配置值时,会触发一次扫描操作。

3.pcm的打开或关闭,会通过音频流widget触发一次扫描操作。

4.驱动程序在改变了某个widget并把它加入到dapm_dirty链表后,主动调用snd_soc_dapm_sync函数触发扫描操作。

dapm事件机制(dapm event)

通过dapm事件机制,widget可以对它所关心的dapm事件做出反应,这种机制对于扩充widget的能力非常有用

dapm event 的种类

SND_SOC_DAPM_PRE_PMU:widget要上电前发出的事件,dapm_seq_run_coalesced中修改reg之前触发

SND_SOC_DAPM_POST_PMU:widget要上电后发出的事件,dapm_seq_run_coalesced中修改reg之后触发

SND_SOC_DAPM_PRE_PMD:widget要下电前发出的事件,dapm_seq_run_coalesced中修改reg之前触发

SND_SOC_DAPM_POST_PMD:widget要下电后发出的事件,dapm_seq_run_coalesced中修改reg之后触发

SND_SOC_DAPM_PRE_REG:音频路径设置之前发出的事件,dapm_widget_update中修改reg之前触发

SND_SOC_DAPM_POST_REG:音频路径设置之后发出的事件,dapm_widget_update中修改reg之后触发

SND_SOC_DAPM_WILL_PMU:在处理up_list链表之前发出的事件,调用dapm_seq_run之前触发

SND_SOC_DAPM_WILL_PMD:在处理down_list链表之前发出的事件,调用dapm_seq_run之前触发

SND_SOC_DAPM_PRE_POST_PMD:SND_SOC_DAPM_PRE_PMD和SND_SOC_DAPM_POST_PMD的合并

widget的event回调函数

在snd_soc_widget结构中,event字段用于保存该widget的事件回调函数,event_flags字段用于保存该widget需要关心的dapm事件种类

触发dapm event

API函数为dapm_seq_check_event

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值