在上一篇博客我们介绍了ALSA子系统的软件架构,同时介绍了ALSA CORE核心数据结构和相关API。本节我们将会介绍ASoC软件体系中音频三大驱动模块:Codec、Platform 和Machine。
一、ASoC核心数据结构
我们首先来了解Codec、Platform 和Machine驱动中涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。本节介绍的数据结构大部分位于include/sound/soc.h头文件中。
1.1 Machine
Machine driver描述了如何控制platform、codec、cpu dai(Digital Audio Interface,数字音频接口)和codec dai,使得互相配合在一起工作。单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。
ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,描述Machine 的最主要的几个数据结构分别是:snd_soc_card,snd_soc_dai,snd_soc_dai_driver、snd_soc_dai_link;当然了此外还有一些操作集相关的数据结构,比如snd_soc_ops、snd_soc_dai_ops;
- snd_soc_card:ASoC中的核心数据结构,和ASLA CORE中的snd_card地位一样;用于对ASocC中的声卡设备进行统一抽象;
- snd_soc_dai和snd_soc_dai_driver:用于描述dai以及dai驱动,根据codec端和soc端,分为codec_dai 和cpu_dai,在ASoC的Platform驱动和Codec驱动中也会使用到;所以这个我们单独拎出来说;
- snd_soc_dai_link:用来描述音频数据链路以及板级操作函数,在snd_soc_dai_link中,指定了platform、codec、codec_dai、cpu_dai的名字;
1.1.1 struct snd_soc_card
ASoC中使用struct snd_soc_card数据结构来描述SoC声卡的所有信息,需要将该数据结构与我们上一节介绍的ALSA CORE中的struct snd_card区分开来;struct snd_soc_card定义在include/sound/soc.h;
/* SoC card */ struct snd_soc_card { const char *name; const char *long_name; const char *driver_name; const char *components; #ifdef CONFIG_DMI char dmi_longname[80]; #endif /* CONFIG_DMI */ char topology_shortname[32]; struct device *dev; struct snd_card *snd_card; struct module *owner; struct mutex mutex; struct mutex dapm_mutex; /* Mutex for PCM operations */ struct mutex pcm_mutex; enum snd_soc_pcm_subclass pcm_subclass; int (*probe)(struct snd_soc_card *card); int (*late_probe)(struct snd_soc_card *card); void (*fixup_controls)(struct snd_soc_card *card); int (*remove)(struct snd_soc_card *card); /* the pre and post PM functions are used to do any PM work before and * after the codec and DAI's do any PM work. */ int (*suspend_pre)(struct snd_soc_card *card); int (*suspend_post)(struct snd_soc_card *card); int (*resume_pre)(struct snd_soc_card *card); int (*resume_post)(struct snd_soc_card *card); /* callbacks */ int (*set_bias_level)(struct snd_soc_card *, struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level); int (*set_bias_level_post)(struct snd_soc_card *, struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level); int (*add_dai_link)(struct snd_soc_card *, struct snd_soc_dai_link *link); void (*remove_dai_link)(struct snd_soc_card *, struct snd_soc_dai_link *link); long pmdown_time; /* CPU <--> Codec DAI links */ struct snd_soc_dai_link *dai_link; /* predefined links only */ int num_links; /* predefined links only */ struct list_head rtd_list; int num_rtd; /* optional codec specific configuration */ struct snd_soc_codec_conf *codec_conf; int num_configs; /* * optional auxiliary devices such as amplifiers or codecs with DAI * link unused */ struct snd_soc_aux_dev *aux_dev; int num_aux_devs; struct list_head aux_comp_list; const struct snd_kcontrol_new *controls; int num_controls; /* * Card-specific routes and widgets. * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in. */ const struct snd_soc_dapm_widget *dapm_widgets; int num_dapm_widgets; const struct snd_soc_dapm_route *dapm_routes; int num_dapm_routes; const struct snd_soc_dapm_widget *of_dapm_widgets; int num_of_dapm_widgets; const struct snd_soc_dapm_route *of_dapm_routes; int num_of_dapm_routes; /* lists of probed devices belonging to this card */ struct list_head component_dev_list; struct list_head list; struct list_head widgets; struct list_head paths; struct list_head dapm_list; struct list_head dapm_dirty; /* attached dynamic objects */ struct list_head dobj_list; /* Generic DAPM context for the card */ struct snd_soc_dapm_context dapm; struct snd_soc_dapm_stats dapm_stats; struct snd_soc_dapm_update *update; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_card_root; #endif #ifdef CONFIG_PM_SLEEP struct work_struct deferred_resume_work; #endif u32 pop_time; /* bit field */ unsigned int instantiated:1; unsigned int topology_shortname_created:1; unsigned int fully_routed:1; unsigned int disable_route_checks:1; unsigned int probed:1; unsigned int component_chaining:1; void *drvdata; };
这个数据结构的内容比较多,我们只挑一些重点说一下:
- name:声卡的名称,保存解析设备节点label或者simple-audio-card,name得到的信息;
- long_name:更详细的名称;
- driver_name:驱动程序的名称;
- components:组件名称;
- dev:分配给此声卡的设备;一般设置为平台设备的device;
- snd_card:ALSA CORE中的声卡设备;
- owner:指向驱动程序拥有者模块的指针;
- pcm_subclass:PCM子类的枚举类型;
- probe:probe是可选的函数,用于在设备被探测时执行特定的操作;
- late_probe:
- remove:remove是可选的函数,用于在设备被移除时执行特定的操作;
- add_dai_link:
- remove_dai_link:
- dai_link:指向动态分配得到的数组,每个元素都一个struct snd_soc_dai_link,即每一元素描述了一条音频数据链路(解析设备节点simple-audio-card,cpu、simple-audio-card,codec得到的);
- num_links:dai_link指向数组的长度;
- codec_conf:指向动态分配得到的数组,每个元素都是一个struct snd_soc_codec_conf,即每个元素描述一个codec_conf;
- num_configs:codec_conf指向数组的长度;
- aux_dev:指向动态分配得到的数组,每个元素都是一个struct snd_soc_aux_dev,保存解析设备节点simple-audio-card,aux-devs得到的aux_dev;
- num_aux_devs:aux_dev指向的数组的长度;
- controls:指向动态分配得到的数组,每个元素都是一个struct snd_kcontrol_new,保存解析设备节点simple-audio-card,pin-switches得到的control信息;
- num_controls:controls指向的数组的长度;
- dapm_widgets:
- num_dapm_widgets:
- dapm_routes:
- num_dapm_routes:
- of_dapm_widgets:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_widget ,保存解析设备节点simple-audio-card,widgets得到的音频控件信息;
- num_of_dapm_widgets:of_dapm_widgets指向的数组的长度;
- of_dapm_routes:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_route ,保存解析设备节点simple-audio-card,routing得到的音频路由信息;
- num_of_dapm_routes:of_dapm_routes指向的数组的长度;
- component_dev_list:链表;
- widgets:链表;
- paths:链表;
- dapm_list:链表;
- dapm_dirty:链表;
- drvdata:驱动程序的私有数据结构;
1.1.2 struct snd_soc_dai_link
ASoC使用struct snd_soc_dai_link数据结构来描述音频数据链路以及板级操作函数,在snd_soc_dai_link中,指定了platform、codec、codec_dai、cpu_dai的名字,Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的。
struct snd_soc_dai_link定义在include/sound/soc.h:
struct snd_soc_dai_link { /* config - must be set by machine driver */ const char *name; /* Codec name */ const char *stream_name; /* Stream name */ /* * You MAY specify the link's CPU-side device, either by device name, * or by DT/OF node, but not both. If this information is omitted, * the CPU-side DAI is matched using .cpu_dai_name only, which hence * must be globally unique. These fields are currently typically used * only for codec to codec links, or systems using device tree. */ /* * You MAY specify the DAI name of the CPU DAI. If this information is * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node * only, which only works well when that device exposes a single DAI. */ struct snd_soc_dai_link_component *cpus; unsigned int num_cpus; /* * You MUST specify the link's codec, either by device name, or by * DT/OF node, but not both. */ /* You MUST specify the DAI name within the codec */ struct snd_soc_dai_link_component *codecs; unsigned int num_codecs; /* * You MAY specify the link's platform/PCM/DMA driver, either by * device name, or by DT/OF node, but not both. Some forms of link * do not need a platform. In such case, platforms are not mandatory. */ struct snd_soc_dai_link_component *platforms; unsigned int num_platforms; int id; /* optional ID for machine driver link identification */ const struct snd_soc_pcm_stream *params; unsigned int num_params; unsigned int dai_fmt; /* format to set on init */ enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */ /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_pcm_runtime *rtd); /* codec/machine specific exit - dual of init() */ void (*exit)(struct snd_soc_pcm_runtime *rtd); /* optional hw_params re-writing for BE and FE sync */ int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); /* machine stream operations */ const struct snd_soc_ops *ops; const struct snd_soc_compr_ops *compr_ops; /* Mark this pcm with non atomic ops */ unsigned int nonatomic:1; /* For unidirectional dai links */ unsigned int playback_only:1; unsigned int capture_only:1; /* Keep DAI active over suspend */ unsigned int ignore_suspend:1; /* Symmetry requirements */ unsigned int symmetric_rate:1; unsigned int symmetric_channels:1; unsigned int symmetric_sample_bits:1; /* Do not create a PCM for this DAI link (Backend link) */ unsigned int no_pcm:1; /* This DAI link can route to other DAI links at runtime (Frontend)*/ unsigned int dynamic:1; /* DPCM capture and Playback support */ unsigned int dpcm_capture:1; unsigned int dpcm_playback:1; /* DPCM used FE & BE merged format */ unsigned int dpcm_merged_format:1; /* DPCM used FE & BE merged channel */ unsigned int dpcm_merged_chan:1; /* DPCM used FE & BE merged rate */ unsigned int dpcm_merged_rate:1; /* pmdown_time is ignored at stop */ unsigned int ignore_pmdown_time:1; /* Do not create a PCM for this DAI link (Backend link) */ unsigned int ignore:1; /* This flag will reorder stop sequence. By enabling this flag * DMA controller stop sequence will be invoked first followed by * CPU DAI driver stop sequence */ unsigned int stop_dma_first:1; #ifdef CONFIG_SND_SOC_TOPOLOGY struct snd_soc_dobj dobj; /* For topology */ #endif };
这个数据结构的内容比较多,我们只挑一些重点说一下:
- name:指定Codec名称,必须配置;
- stream_name:指定Stream名称,必须配置;
- cpus:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(包括cpu端设备名称/设备树节点(二选一)以及cpu测的数字音频接口的名称),即保存当前音频数据链路上的所有cpu设备;
- num_cpus:cpus指向的数组长度;
- codecs:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(包括codec端设备名称/设备树节点(二选一)以及codec测的数字音频接口名称),即保存当前音频数据链路上的所有codec设备;
- num_codecs:codec指向的数组长度;
- platforms:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(通过设备名称/设备树节点字段指定cpu测的platform驱动名称,通常都是DMA驱动,用于音频数据传输);
- num_platforms:platforms指向的数组长度;
- id:可选的链接 ID,用于识别Machine driver link;
- params:指定PCM流参数;
- num_params:PCM流参数数量;
- dai_fmt:数字音频接口格式;
- trigger:DPCM(Direct Pulse Code Modulation)触发类型;
- init:初始化函数,例如添加Machine controls;
- exit:退出函数;
- be_hw_params_fixup:可选的硬件参数重写函数;
- ops:音频相关的操作集;
- compr_ops:数据压缩操作函数;
- nonatomic:标记 PCM 是否使用非原子操作;
- playback_only:标记 PCM 流是否只支持播放;
- capture_only:标记 PCM 流是否只支持捕获;
- ignore_suspend:标记 PCM 是否在挂起时保持 DAI 活动状态;
- symmetric_rate:标记 PCM 采样率是否对称;
- symmetric_channels:标记 PCM 通道数是否对称;
- symmetric_sample_bits:标记 PCM 采样位数是否对称;
- no_pcm:标记 PCM 流是否需要创建;
- dynamic:标记该 DAI 链接是否可以在运行时路由到其他 DAI 链接;
- dpcm_capture:标记是否支持 DPCM 捕获;
- dpcm_playback:标记是否支持 DPCM 播放;
- dpcm_merged_format:标记是否使用合并格式的 DPCM;
- dpcm_merged_chan:标记是否使用合并通道的 DPCM;
- dpcm_merged_rate:标记是否使用合并采样率的 DPCM;
- ignore_pmdown_time:标记是否忽略 pmdown_time 停止时间;pmdown_time 是一种 PCM 的停止时间戳,用于控制 PCM 流在空闲一段时间后自动停止以降低功耗;
- ignore:标记该 DAI 链接是否需要创建 PCM;
- stop_dma_first:标记是否对停止序列进行排序;
1.1.3 snd_soc_dai_link_component
ASoC使用struct snd_soc_dai_link_component来描述音频数据链路中的设备,定义在include/sound/soc.h;
struct snd_soc_dai_link_component { const char *name; struct device_node *of_node; const char *dai_name; };
其中:
- name:链路设备的名称;
- of_node:链路设备所使用的设备树节点;
- dai_name:链路设备使用的dai名称;
1.1.4 struct snd_soc_ops
ASoC使用struct snd_soc_ops来描述ALSA PCM操作集,定义在include/sound/soc.h;
/* SoC audio ops */ struct snd_soc_ops { int (*startup)(struct snd_pcm_substream *); void (*shutdown)(struct snd_pcm_substream *); int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *); int (*hw_free)(struct snd_pcm_substream *); int (*prepare)(struct snd_pcm_substream *); int (*trigger)(struct snd_pcm_substream *, int); }
其中:
- startup:在 PCM子流上启动音频传输期间调用;
- shutdown:在PCM子流上关闭音频传输期间调用;
- hw_params:更改PCM子流硬件参数期间调用;
- hw_free:释放PCM子流资源期间调用;
- prepare:准备PCM子流传输期间调用;
- trigger:在PCM子流上触发执行操作期间调用;
1.2 Codec
Codec driver它不应包含任何特定于目标平台或设备的代码。所有特定于平台和设备的代码应分别添加到平台和机器驱动程序中,Codec deiver提供了配置编解码器、FM、MODEM、BT或外部DSP,以提供音频捕获和播放功能。
每个Codec driver都必须有一个struct snd_soc_dai_driver 结构体,用于定义其 DAI 和 PCM 的能力和操作。该结构体被导出,以便Machine driver可以将其注册到ASoC CORE中。
描述Codec的最主要的几个数据结构分别是:snd_soc_dai,snd_soc_dai_driver,其中:
- snd_soc_dai:描述codec端的dai;
- snd_soc_dai_driver:描述codec端的dai驱动;
1.3 Platform
Platform driver主要是平台相关的DMA操作以及音频管理。大概流程:
- DMA driver :先通过DMA驱动将dma buffer中的音频数据搬运到 I2S tx FIFO;
- cpu dai driver:然后再通过cpu_da驱动将音频数据从I2S tx FIFO搬运Codesc中,数据会在Codec侧进行解码的操作,最终输出到耳机/音箱中。
而上述的两大类功能在ASoC中主要使用4个数据结构表示:snd_soc_dai、snd_soc_dai_driver、snd_soc_component、snd_soc_component_driver;
- snd_soc_dai:描述cpu端的dai;
- snd_soc_dai_driver:描述cpu端的dai驱动;其中包括dai的配置(音频格式、clock、音量等);
- snd_soc_component:和Machine一样,使用snd_soc_component结构对ASoC中的platform设备进行统一抽象;
- snd_soc_component_driver:代表Platform使用的dma驱动。主要是数据的传输等;
1.3.1 struct snd_soc_component
ASoC使用snd_soc_component来描述platform设备,定义在include/sound/soc-component.h:
struct snd_soc_component { const char *name; int id; const char *name_prefix; struct device *dev; struct snd_soc_card *card; unsigned int active; unsigned int suspended:1; /* is in suspend PM state */ struct list_head list; struct list_head card_aux_list; /* for auxiliary bound components */ struct list_head card_list; const struct snd_soc_component_driver *driver; struct list_head dai_list; int num_dai; struct regmap *regmap; int val_bytes; struct mutex io_mutex; /* attached dynamic objects */ struct list_head dobj_list; /* * DO NOT use any of the fields below in drivers, they are temporary and * are going to be removed again soon. If you use them in driver code * the driver will be marked as BROKEN when these fields are removed. */ /* Don't use these, use snd_soc_component_get_dapm() */ struct snd_soc_dapm_context dapm; /* machine specific init */ int (*init)(struct snd_soc_component *component); /* function mark */ void *mark_module; struct snd_pcm_substream *mark_open; struct snd_pcm_substream *mark_hw_params; struct snd_pcm_substream *mark_trigger; struct snd_compr_stream *mark_compr_open; void *mark_pm; struct dentry *debugfs_root; const char *debugfs_prefix; };
1.3.2 struct snd_soc_component_driver
ASoC使用snd_soc_component_driver来描述platform dma driver,定义在include/sound/soc-component.h:
struct snd_soc_component_driver { const char *name; /* Default control and setup, added after probe() is run */ const struct snd_kcontrol_new *controls; unsigned int num_controls; const struct snd_soc_dapm_widget *dapm_widgets; unsigned int num_dapm_widgets; const struct snd_soc_dapm_route *dapm_routes; unsigned int num_dapm_routes; int (*probe)(struct snd_soc_component *component); void (*remove)(struct snd_soc_component *component); int (*suspend)(struct snd_soc_component *component); int (*resume)(struct snd_soc_component *component); unsigned int (*read)(struct snd_soc_component *component, unsigned int reg); int (*write)(struct snd_soc_component *component, unsigned int reg, unsigned int val); /* pcm creation and destruction */ int (*pcm_construct)(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd); void (*pcm_destruct)(struct snd_soc_component *component, struct snd_pcm *pcm); /* component wide operations */ int (*set_sysclk)(struct snd_soc_component *component, int clk_id, int source, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_component *component, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); int (*set_jack)(struct snd_soc_component *component, struct snd_soc_jack *jack, void *data); int (*get_jack_type)(struct snd_soc_component *component); /* DT */ int (*of_xlate_dai_name)(struct snd_soc_component *component, const struct of_phandle_args *args, const char **dai_name); int (*of_xlate_dai_id)(struct snd_soc_component *comment, struct device_node *endpoint); void (*seq_notifier)(struct snd_soc_component *component, enum snd_soc_dapm_type type, int subseq); int (*stream_event)(struct snd_soc_component *component, int event); int (*set_bias_level)(struct snd_soc_component *component, enum snd_soc_bias_level level); int (*open)(struct snd_soc_component *component, struct snd_pcm_substream *substream); int (*close)(struct snd_soc_component *component, struct snd_pcm_substream *substream); int (*ioctl)(struct snd_soc_component *component, struct snd_pcm_substream *substream, unsigned int cmd, void *arg); int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream); int (*prepare)(struct snd_soc_component *component, struct snd_pcm_substream *substream); int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd); int (*sync_stop)(struct snd_soc_component *component, struct snd_pcm_substream *substream); snd_pcm_uframes_t (*pointer)(struct snd_soc_component *component, struct snd_pcm_substream *substream); int (*get_time_info)(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct timespec64 *system_ts, struct timespec64 *audio_ts, struct snd_pcm_audio_tstamp_config *audio_tstamp_config, struct snd_pcm_audio_tstamp_report *audio_tstamp_report); int (*copy_user)(struct snd_soc_component *component, struct snd_pcm_substream *substream, int channel, unsigned long pos, void __user *buf, unsigned long bytes); struct page *(*page)(struct snd_soc_component *component, struct snd_pcm_substream *substream, unsigned long offset); int (*mmap)(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct vm_area_struct *vma); int (*ack)(struct snd_soc_component *component, struct snd_pcm_substream *substream); snd_pcm_sframes_t (*delay)(struct snd_soc_component *component, struct snd_pcm_substream *substream); const struct snd_compress_ops *compress_ops; /* probe ordering - for components with runtime dependencies */ int probe_order; int remove_order; /* * signal if the module handling the component should not be removed * if a pcm is open. Setting this would prevent the module * refcount being incremented in probe() but allow it be incremented * when a pcm is opened and decremented when it is closed. */ unsigned int module_get_upon_open:1; /* bits */ unsigned int idle_bias_on:1; unsigned int suspend_bias_off:1; unsigned int use_pmdown_time:1; /* care pmdown_time at stop */ /* * Indicates that the component does not care about the endianness of * PCM audio data and the core will ensure that both LE and BE variants * of each used format are present. Typically this is because the * component sits behind a bus that abstracts away the endian of the * original data, ie. one for which the transmission endian is defined * (I2S/SLIMbus/SoundWire), or the concept of endian doesn't exist (PDM, * analogue). */ unsigned int endianness:1; unsigned int legacy_dai_naming:1; /* this component uses topology and ignore machine driver FEs */ const char *ignore_machine; const char *topology_name_prefix; int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */ int be_pcm_base; /* base device ID for all BE PCMs */ unsigned int start_dma_last; #ifdef CONFIG_DEBUG_FS const char *debugfs_prefix; #endif };
1.4 DAI
DAI全称数字音频接口,根据codec端和soc端,分为codec_dai、cpu_dai,描述dai的最主要的几个数据结构分别是:snd_soc_dai、snd_soc_dai_driver、snd_soc_dai_ops;
以cpu dai driver为例,每个cpu dai driver必须提供以下功能:
- DAI描述信息;
- DAI配置信息;
- PCM描述信息;
- 系统时钟(SYSCLK)配置;
- 挂起和恢复(可选);
以codec dai driver为例,每个codec dai driver必须提供以下功能:
- Codec DAI和PCM的配置信息;
- Codec的控制接口,如I2C/SPI;
- Mixer和其它音频控件;
- Codec的音频操作;
1.4.1 struct snd_soc_dai
ALSA中使用struct snd_soc_dai数据结构来描述dai运行时数据,定义在include/sound/soc-dai.h:
/* * Digital Audio Interface runtime data. * * Holds runtime data for a DAI. */ struct snd_soc_dai { const char *name; int id; struct device *dev; /* driver ops */ struct snd_soc_dai_driver *driver; /* DAI runtime info */ struct snd_soc_dai_stream stream[SNDRV_PCM_STREAM_LAST + 1]; /* Symmetry data - only valid if symmetry is being enforced */ unsigned int rate; unsigned int channels; unsigned int sample_bits; /* parent platform/codec */ struct snd_soc_component *component; struct list_head list; /* function mark */ struct snd_pcm_substream *mark_startup; struct snd_pcm_substream *mark_hw_params; struct snd_pcm_substream *mark_trigger; struct snd_compr_stream *mark_compr_startup; /* bit field */ unsigned int probed:1; };
该数据结构包含以下字段:
- name:DAI的名称;
- id:DAI的标识符;
- dev:指向包含DAI的设备的指针;
- driver:指向dai驱动结构的指针;
- stream:采集和播放流的数组,其中包含有关流的信息;
- rate:如果强制对称,则为采样率;
- channels:如果强制对称,则为通道数;
- sample_bits:如果强制对称,则为采样位数;
- component:指向父组件(通常是 platform或codec)的指针;
- list:用于将DAI添加到其父组件的DAI列表中;
- mark_startup:用于标记PCM启动事件的指针;
- mark_hw_params:用于标记PCM硬件参数变化事件的指针;
- mark_trigger:用于标记PCM触发事件的指针;
- mark_compr_startup:用于标记压缩流启动事件的指针;
- probed:标记DAI是否已经探测完成;
1.4.2 struct snd_soc_dai_driver
ALSA中使用struct snd_soc_dai_driver数据结构来描述DAI驱动,包括DAI和PCM的能力和操作,定义在include/sound/soc-dai.h:
/* * Digital Audio Interface Driver. * * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97 * operations and capabilities. Codec and platform drivers will register this * structure for every DAI they have. * * This structure covers the clocking, formating and ALSA operations for each * interface. */ struct snd_soc_dai_driver { /* DAI description */ const char *name; unsigned int id; unsigned int base; struct snd_soc_dobj dobj; /* DAI driver callbacks */ int (*probe)(struct snd_soc_dai *dai); int (*remove)(struct snd_soc_dai *dai); /* compress dai */ int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num); /* Optional Callback used at pcm creation*/ int (*pcm_new)(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); /* ops */ const struct snd_soc_dai_ops *ops; const struct snd_soc_cdai_ops *cops; /* DAI capabilities */ struct snd_soc_pcm_stream capture; struct snd_soc_pcm_stream playback; unsigned int symmetric_rate:1; unsigned int symmetric_channels:1; unsigned int symmetric_sample_bits:1; /* probe ordering - for components with runtime dependencies */ int probe_order; int remove_order; };
该数据结构包含了以下字段:
- name:指定DAI的名称;
- id:可选的 DAI 标识符,用于在注册期间区分多个DAI;
- base:可选的 DAI 寄存器基地址;
- dobj:DAI 对象,包含 DAI 及其父组件的句柄;
- probe:可选的DAI探测回调函数;注册声卡时回调;
- remove:可选的DAI卸载回调函数;
- compress_new:可选的压缩 DAI 创建回调函数;
- pcm_new:可选的 PCM 创建回调函数;
- ops:指向本DAI的snd_soc_dai_ops结构,即DAI的操作集,这个操作集非常重要,用于DAI的时钟配置、格式配置、硬件参数配置;
- cops:DAI 压缩操作函数指针表;
- capture:描述capture的能力;如回放设备所支持的声道数、采样率、音频格式;非常重要的字段;
- playbackk:描述playback的能力;如录制设备所支持声道数、采样率、音频格式;非常重要的字段;
- symmetric_rate:标记 DAI 采样率是否对称;
- symmetric_channels:标记 DAI 通道数是否对称;
- symmetric_sample_bits:标记 DAI 采样位数是否对称;
- probe_order:DAI 探测顺序;
- remove_order:DAI 卸载顺序;
1.4.3 struct snd_soc_dai_ops
ASoC使用struct snd_soc_dai_ops数据结构描述DAI的控制和参数配置,这些函数包括对 DAI 时钟配置、对 DAI 格式配置、对 TDM(时分复用)通道配置以及对 ALSA PCM音频操作的配置等。定义在include/sound/soc-dai.h:struct snd_soc_dai_ops { /* * DAI clocking configuration, all optional. * Called by soc_card drivers, normally in their hw_params. */ int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div); int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio); /* * DAI format configuration * Called by soc_card drivers, normally in their hw_params. */ int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt); int (*xlate_tdm_slot_mask)(unsigned int slots, unsigned int *tx_mask, unsigned int *rx_mask); int (*set_tdm_slot)(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width); int (*set_channel_map)(struct snd_soc_dai *dai, unsigned int tx_num, unsigned int *tx_slot, unsigned int rx_num, unsigned int *rx_slot); int (*get_channel_map)(struct snd_soc_dai *dai, unsigned int *tx_num, unsigned int *tx_slot, unsigned int *rx_num, unsigned int *rx_slot); int (*set_tristate)(struct snd_soc_dai *dai, int tristate); int (*set_stream)(struct snd_soc_dai *dai, void *stream, int direction); void *(*get_stream)(struct snd_soc_dai *dai, int direction); /* * DAI digital mute - optional. * Called by soc-core to minimise any pops. */ int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream); /* * ALSA PCM audio operations - all optional. * Called by soc-core during audio PCM operations. */ int (*startup)(struct snd_pcm_substream *, struct snd_soc_dai *); void (*shutdown)(struct snd_pcm_substream *, struct snd_soc_dai *); int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *, struct snd_soc_dai *); int (*hw_free)(struct snd_pcm_substream *, struct snd_soc_dai *); int (*prepare)(struct snd_pcm_substream *, struct snd_soc_dai *); /* * NOTE: Commands passed to the trigger function are not necessarily * compatible with the current state of the dai. For example this * sequence of commands is possible: START STOP STOP. * So do not unconditionally use refcounting functions in the trigger * function, e.g. clk_enable/disable. */ int (*trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *); int (*bespoke_trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *); /* * For hardware based FIFO caused delay reporting. * Optional. */ snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, struct snd_soc_dai *); /* * Format list for auto selection. * Format will be increased if priority format was * not selected. * see * snd_soc_dai_get_fmt() */ u64 *auto_selectable_formats; int num_auto_selectable_formats; /* bit field */ unsigned int no_capture_mute:1; };
具体函数成员包括:
- set_sysclk: 配置DAI的时钟源;
- set_pll: 配置DAI的PLL(锁相环);
- set_clkdiv: 配置DAI的时钟分频;
- set_bclk_ratio: 配置DAI的BCLK(Bit Clock)比率;
- set_fmt: 配置DAI的数据格式;
- xlate_tdm_slot_mask: 将TDM槽位映射为掩码;
- set_tdm_slot: 配置DAI的TDM槽位;
- set_channel_map: 配置DAI的通道映射;
- get_channel_map: 获取DAI的通道映射;
- set_tristate: 配置DAI的三态(tri-state)设置;
- set_stream / get_stream: 设置/获取DAI的数据流;
- mute_stream: 静音DAI的数据流;
- startup / shutdown: 在PCM音频操作期间启动/关闭DAI;
- hw_params: 配置PCM音频的硬件参数;
- hw_free: 释放PCM音频的硬件资源;
- prepare: 准备PCM音频操作;
- trigger: 触发PCM音频操作;
- bespoke_trigger: 自定义触发PCM音频操作;
- delay: 返回基于硬件FIFO导致的延迟;
- auto_selectable_formats: 自动选择的格式列表;
- num_auto_selectable_formats: 自动选择的格式数量;
- no_capture_mute: 捕获静音标志位;
这些函数指针在dai driver中实现,并通过structsnd_soc_dai_driver的ops成员导出,供上层音频驱动程序使用。
二、PCM介绍
我们在Rockchip RK3399 - ALC5651 & I2S基础中实际上已经介绍过PCM,它是一种音频编码格式,更确切的说是一种将声音从模拟信号转换成数字信号的技术。
音频驱动的两大核心任务就是:
- playback :如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频;
- capture :把mic拾取到得模拟信号,经过采样、量化,转换为PCM数据送回给用户空间的应用程序;
参考文章:Linux ALSA声卡驱动之三:PCM设备的创建。
三、kcontrol介绍
一个kcontrol代表着一个Mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。
Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。对于Mixer来说,Control接口显得尤为重要。
参考文章:Linux ALSA声卡驱动之四:Control设备的创建。
二、ASoC核心API
2.1 注册声卡设备
snd_soc_register_card函数用于向ASoC CORE注册一个声卡设备,函数定义在sound/soc/soc-core.c:
/** * snd_soc_register_card - Register a card with the ASoC core * * @card: Card to register * */ int snd_soc_register_card(struct snd_soc_card *card) { if (!card->name || !card->dev) return -EINVAL; dev_set_drvdata(card->dev, card); INIT_LIST_HEAD(&card->widgets); INIT_LIST_HEAD(&card->paths); INIT_LIST_HEAD(&card->dapm_list); INIT_LIST_HEAD(&card->aux_comp_list); INIT_LIST_HEAD(&card->component_dev_list); INIT_LIST_HEAD(&card->list); INIT_LIST_HEAD(&card->rtd_list); INIT_LIST_HEAD(&card->dapm_dirty); INIT_LIST_HEAD(&card->dobj_list); card->instantiated = 0; mutex_init(&card->mutex); mutex_init(&card->dapm_mutex); mutex_init(&card->pcm_mutex); return snd_soc_bind_card(card); }
该函数接受一个 struct snd_soc_card 结构体作为参数,函数指向流程如下:
- 首先检查传入的 card 结构体是否具有合法的 name 和 dev 成员,如果缺少其中任何一个成员,则返回 -EINVAL 错误代码;
- 接着,函数使用dev_set_drvdata 函数设置card->dev 的driver_data为card。这是方便在后续的操作中可以通过设备句柄来获取到对应的card 结构体;
- 然后,函数使用 INIT_LIST_HEAD 宏初始化card结构体中的各个链表成员,包括 widgets、paths、dapm_list、aux_comp_list、component_dev_list、list、rtd_list、dapm_dirty 和 dobj_list;
- 接下来,函数将 card->instantiated 初始化为 0,表示该音频卡尚未实例化。然后,初始化card中的互斥锁:mutex、dapm_mutex 和 pcm_mutex;
- 最后,函数调用 snd_soc_bind_card 函数来绑定声卡,并返回其结果;
snd_soc_bind_card函数定义在sound/soc/soc-core.c:
static int snd_soc_bind_card(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; struct snd_soc_component *component; struct snd_soc_dai_link *dai_link; int ret, i; mutex_lock(&client_mutex); // 获取互斥锁 mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT); // 获取互斥锁 snd_soc_dapm_init(&card->dapm, card, NULL); /* check whether any platform is ignore machine FE and using topology */ soc_check_tplg_fes(card); /* bind aux_devs too */ ret = soc_bind_aux_dev(card); if (ret < 0) goto probe_end; /* add predefined DAI links to the list */ card->num_rtd = 0; for_each_card_prelinks(card, i, dai_link) { ret = snd_soc_add_pcm_runtime(card, dai_link); if (ret < 0) goto probe_end; } soc_init_card_debugfs(card); soc_resume_init(card); ret = snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, card->num_dapm_widgets); if (ret < 0) goto probe_end; ret = snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets, card->num_of_dapm_widgets); if (ret < 0) goto probe_end; /* initialise the sound card only once */ ret = snd_soc_card_probe(card); if (ret < 0) goto probe_end; /* probe all components used by DAI links on this card */ ret = soc_probe_link_components(card); if (ret < 0) { dev_err(card->dev, "ASoC: failed to instantiate card %d\n", ret); goto probe_end; } /* probe auxiliary components */ ret = soc_probe_aux_devices(card); if (ret < 0) { dev_err(card->dev, "ASoC: failed to probe aux component %d\n", ret); goto probe_end; } /* probe all DAI links on this card */ ret = soc_probe_link_dais(card); if (ret < 0) { dev_err(card->dev, "ASoC: failed to instantiate card %d\n", ret); goto probe_end; } for_each_card_rtds(card, rtd) { ret = soc_init_pcm_runtime(card, rtd); if (ret < 0) goto probe_end; } snd_soc_dapm_link_dai_widgets(card); snd_soc_dapm_connect_dai_link_widgets(card); ret = snd_soc_add_card_controls(card, card->controls, card->num_controls); if (ret < 0) goto probe_end; ret = snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, card->num_dapm_routes); if (ret < 0) { if (card->disable_route_checks) { dev_info(card->dev, "%s: disable_route_checks set, ignoring errors on add_routes\n", __func__); } else { dev_err(card->dev, "%s: snd_soc_dapm_add_routes failed: %d\n", __func__, ret); goto probe_end; } } ret = snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes, card->num_of_dapm_routes); if (ret < 0) goto probe_end; /* try to set some sane longname if DMI is available */ snd_soc_set_dmi_name(card, NULL); soc_setup_card_name(card, card->snd_card->shortname, card->name, NULL); soc_setup_card_name(card, card->snd_card->longname, card->long_name, card->name); soc_setup_card_name(card, card->snd_card->driver, card->driver_name, card->name); if (card->components) { /* the current implementation of snd_component_add() accepts */ /* multiple components in the string separated by space, */ /* but the string collision (identical string) check might */ /* not work correctly */ ret = snd_component_add(card->snd_card, card->components); if (ret < 0) { dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n", card->name, ret); goto probe_end; } } ret = snd_soc_card_late_probe(card); if (ret < 0) goto probe_end; snd_soc_dapm_new_widgets(card); snd_soc_card_fixup_controls(card); ret = snd_card_register(card->snd_card); if (ret < 0) { dev_err(card->dev, "ASoC: failed to register soundcard %d\n", ret); goto probe_end; } card->instantiated = 1; dapm_mark_endpoints_dirty(card); snd_soc_dapm_sync(&card->dapm); /* deactivate pins to sleep state */ for_each_card_components(card, component) if (!snd_soc_component_active(component)) pinctrl_pm_select_sleep_state(component->dev); probe_end: if (ret < 0) soc_cleanup_card_resources(card); mutex_unlock(&card->mutex); // 释放互斥锁 mutex_unlock(&client_mutex); // 释放互斥锁 return ret; }
该函数的主要功能包括初始化音频卡、绑定辅助设备、添加预定义的DAI等。以下是该函数的主要步骤:
- 首先,通过调用 mutex_lock和 mutex_lock_nested函数,获取所需的互斥锁,以确保在进行初始化和绑定操作时不会出现冲突;
- 初始化 DAPM:通过调用 snd_soc_dapm_init函数,对音频卡的 DAPM (Dynamic Audio Power Management) 进行初始化,DAPM 是一种用于管理音频设备功耗的机制;
- 通过调用 soc_check_tplg_fes 函数,检查是否有任何平台忽略机器前端并使用拓扑(topology)配置;
- 通过调用 soc_bind_aux_dev 函数,绑定音频卡的辅助设备;
- 通过调用 snd_soc_add_pcm_runtime 函数,将预定义的 DAI 连接添加到列表中;
- 通过调用 soc_init_card_debugfs 函数,初始化调试文件系统,为音频卡提供调试信息;
- 通过调用 soc_resume_init 函数,初始化恢复机制;
- 通过调用 snd_soc_dapm_new_controls 函数,创建和注册音频卡的DAPM控件;
- 通过调用 snd_soc_card_probe 函数,探测音频卡并初始化其相关组件;
- 通过调用 soc_probe_link_components 函数,探测音频卡上的连线组件;
- 通过调用 soc_probe_aux_devices 函数,探测音频卡的辅助设备;
- 通过调用 soc_probe_link_dais 函数,探测音频卡上的 DAI 连接;
- 通过调用 soc_init_pcm_runtime 函数,初始化 PCM 运行时;
- 通过调用 snd_soc_dapm_link_dai_widgets 函数,链接 DAI 控件;
- 通过调用 snd_soc_dapm_connect_dai_link_widgets 函数,链接 DAI 连接的控件;
- 通过调用 snd_soc_add_card_controls 函数,添加音频卡的控件;
- 通过调用 snd_soc_dapm_add_routes 函数,添加音频卡的 DAPM 路由;
- 通过调用 snd_soc_set_dmi_name 和 soc_setup_card_name 函数,设置音频卡的名称;
- 通过调用 snd_component_add 函数,将组件添加到音频卡;
- 通过调用 snd_soc_card_late_probe 函数,延迟探测音频卡;
- 通过调用 snd_soc_dapm_new_widgets 函数,创建新的 DAPM 控件;
- 通过调用 snd_soc_card_fixup_controls 函数,修复音频卡的控件;
- 通过调用 snd_card_register 函数,注册声卡;
- 通过调用 dapm_mark_endpoints_dirty 函数,标记终端点为脏状态;
- 通过调用 snd_soc_dapm_sync 函数,同步 DAPM 状态;
- 通过调用 pinctrl_pm_select_sleep_state 函数,将非活动的组件设置为睡眠状态;
- 如果有错误发生,通过调用 soc_cleanup_card_resources 函数清理资源。最后,通过调用 mutex_unlock 函数解锁互斥锁;
参考文章
[2] Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)
[3] Linux ALSA声卡驱动之六:ASoC架构中的Machine
[4] Linux ALSA声卡驱动之七:ASoC架构中的Codec
[5] Linux ALSA声卡驱动之八:ASoC架构中的Platform
[9] ALSA SoC Layer
标签:snd,struct,int,Rockchip,Codec,soc,dai,RK3399,card From: https://www.cnblogs.com/zyly/p/17536999.html