DAPM(Dynamic Audio Power Management):动态音频电源管理,用于Linux设备使用音频子系统内的最低电量,它独立于其他内核PM,DAPM对所有的用户空间应用程序来说也是完全透明的,因为所有电源切换都是在ASoC核心内完成的,对于用户空间应用程序,不需要更改代码,DAPM根据当前激活的音频流和声卡中的Mixer等的配置来决定哪些音频控件的电源开关打开或关闭
Control是音频驱动中用来表示用户可操作的音频参数或功能的抽象设备,它可以是音量控制、Mixer(混音控制)、Mux(开关控制)等,Control提供了一个统一的接口,用于能够通过音频设备驱动程序来管理和调整音频参数,ALSA core层已经实现了Control中间层,在include\sound\control.h中定义了所有的Control API
/* 为ASoC声卡设备的每个pcm runtime会分配一个编号,从0开始 */ unsignedint num; /* 0-based and monotonic increasing */ /* 将当前的pcm runtime链接到ASoC sound card的rtd_list中 */ structlist_headlist;/* rtd list of the soc card */
/* function mark */ structsnd_pcm_substream *mark_startup; structsnd_pcm_substream *mark_hw_params; structsnd_pcm_substream *mark_trigger; structsnd_compr_stream *mark_compr_startup;
/* bit field */ unsignedint pop_wait:1; unsignedint fe_compr:1; /* for Dynamic PCM */
int num_components; /* 指向存储当前dai_link上的platform以及codec的component */ structsnd_soc_component *components[];/* CPU/Codec/Platform */ };
PCM设备创建过程
DAPM相关
DAPM是Dynamic Audio Power Management的缩写,即动态音频电源管理,是独立于内核其他PM的一套音频电源管理系统,DAPM对所有用户应用程序来说是完全透明的,电源切换的过程都在ASoC核心内完成,DAPM根据当前激活的音频流对声卡中的Mixer等进行配置,来决定音频控件的电源打开和关闭,达到省电的目的
void *priv; /* widget specific data */ structregulator *regulator;/* attached regulator */ structpinctrl *pinctrl;/* attached pinctrl */
/* dapm control */ /* 用于dapm控制的寄存器地址 */ int reg; /* negative reg = no direct dapm */ unsignedchar shift; /* bits to shift */ unsignedint mask; /* non-shifted mask */ unsignedint on_val; /* on state value */ unsignedint off_val; /* off state value */ /* 表示widget的上电状态 */ unsignedchar power:1; /* block power status */ unsignedchar active:1; /* active stream on DAC, ADC's */ unsignedchar connected:1; /* connected codec pin */ unsignedcharnew:1; /* cnew complete */ unsignedchar force:1; /* force state */ unsignedchar ignore_suspend:1; /* kept enabled over suspend */ unsignedchar new_power:1; /* power from this run */ unsignedchar power_checked:1; /* power checked this run */ unsignedchar is_supply:1; /* Widget is a supply type widget */ unsignedchar is_ep:2; /* Widget is a endpoint type widget */ int subseq; /* sort within widget type */
int (*power_check)(struct snd_soc_dapm_widget *w);
/* external events */ unsignedshort event_flags; /* flags to specify event types */ int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
/* kcontrols that relate to this widget */ int num_kcontrols; conststructsnd_kcontrol_new *kcontrol_news; structsnd_kcontrol **kcontrols; structsnd_soc_dobjdobj;
// include/sound/soc_dapm.h /* DAPM context */ structsnd_soc_dapm_context { enumsnd_soc_bias_levelbias_level; unsignedint idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */ /* Go to BIAS_OFF in suspend if the DAPM context is idle */ unsignedint suspend_bias_off:1;
es8336的示例,通过es8336的datasheet可知,Left Hp Mixer通过ES8336_HPMIX_PDN_REG15的bit4来控制Left Hp Mixer的电源状态,而es8336_out_left_mixer是通过ES8336_HPMIX_SWITCH_REG14寄存器的bit7来控制Left DAC的开关
// include/sound/soc_dapm.h /* dapm audio path between two widgets */ structsnd_soc_dapm_path { constchar *name;
/* * source (input) and sink (output) widgets * The union is for convience, since it is a lot nicer to type * p->source, rather than p->node[SND_SOC_DAPM_DIR_IN] */ union { struct { /* 输入端widget */ structsnd_soc_dapm_widget *source; /* 输出端widget */ structsnd_soc_dapm_widget *sink; }; structsnd_soc_dapm_widget *node[2]; };
/* status */ /* * connect用来表示source widget和sink widget之间的连接状态 * 如果在source widget和sink widget中间有kcontrol可以控制通断, * 那么connect表示的就是kcontrol的通断状态,对于两个直连的widget, * connect值始终为1。 */ u32 connect:1; /* source and sink widgets are connected */ u32 walking:1; /* path is in the process of being walked */ u32 weak:1; /* path ignored for power management */ u32 is_supply:1; /* At least one of the connected widgets is a supply */
int (*connected)(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink);
/* * DAPM audio route definition. * * Defines an audio route originating at source via control and finishing * at sink. */ structsnd_soc_dapm_route { /* 指向目标端widget的名称 */ constchar *sink; /* 指向负责控制该连接所对应的kcontrol名称 */ constchar *control; /* 指向起始端widget的名称 */ constchar *source;
/* Note: currently only supported for links where source is a supply */ int (*connected)(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink);
/* Mutex for PCM operations */ structmutexpcm_mutex; enumsnd_soc_pcm_subclasspcm_subclass;
int (*probe)(struct snd_soc_card *card); int (*late_probe)(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);
/* CPU <--> Codec DAI links */ /* 存储所有的dai link */ structsnd_soc_dai_link *dai_link;/* predefined links only */ /* dai_link的数量 */ int num_links; /* predefined links only */ /* 链接所有的snd_soc_pcm_runtime,一个dai_link对应一个runtime */ structlist_headrtd_list; /* pcm runtime的数量 */ int num_rtd;
/* optional codec specific configuration */ structsnd_soc_codec_conf *codec_conf; int num_configs;
/* * optional auxiliary devices such as amplifiers or codecs with DAI * link unused */ structsnd_soc_aux_dev *aux_dev; int num_aux_devs; structlist_headaux_comp_list;
conststructsnd_kcontrol_new *controls; int num_controls;
/* * Card-specific routes and widgets. * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in. */ conststructsnd_soc_dapm_widget *dapm_widgets; int num_dapm_widgets; conststructsnd_soc_dapm_route *dapm_routes; int num_dapm_routes; conststructsnd_soc_dapm_widget *of_dapm_widgets; int num_of_dapm_widgets; conststructsnd_soc_dapm_route *of_dapm_routes; int num_of_dapm_routes;
/* lists of probed devices belonging to this card */ structlist_headcomponent_dev_list; structlist_headlist;
// include/sound/soc.h structsnd_soc_dai_link { /* config - must be set by machine driver */ constchar *name; /* Codec name */ constchar *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. */ /* 保存当前dai_link上所有的cpu components */ structsnd_soc_dai_link_component *cpus; /* cpu components的数量,一般来说1个dai_link只对应1个cpu和1个codec */ unsignedint 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 */ structsnd_soc_dai_link_component *codecs; unsignedint 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. */ structsnd_soc_dai_link_component *platforms; unsignedint num_platforms;
int id; /* optional ID for machine driver link identification */
/* Do not create a PCM for this DAI link (Backend link) */ unsignedint no_pcm:1;
/* This DAI link can route to other DAI links at runtime (Frontend)*/ unsignedint dynamic:1;
/* DPCM capture and Playback support */ unsignedint dpcm_capture:1; unsignedint dpcm_playback:1;
/* DPCM used FE & BE merged format */ unsignedint dpcm_merged_format:1; /* DPCM used FE & BE merged channel */ unsignedint dpcm_merged_chan:1; /* DPCM used FE & BE merged rate */ unsignedint dpcm_merged_rate:1;
/* pmdown_time is ignored at stop */ unsignedint ignore_pmdown_time:1;
/* Do not create a PCM for this DAI link (Backend link) */ unsignedint 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 */ unsignedint stop_dma_first:1;
#ifdef CONFIG_SND_SOC_TOPOLOGY structsnd_soc_dobjdobj;/* For topology */ #endif };
structlink_info { /* 表示音频数据链路的数量 */ int link; /* number of link */ /* 表示当前正在处理的是第几个cpu */ int cpu; /* turn for CPU / Codec */ /* 保存每条音频数据链路所需的信息 */ structprop_numsnum[SNDRV_MAX_LINKS]; };
structprop_nums { /* 分别表示该dai_link下cpu codec platform的数量 */ int cpus; int codecs; int platforms; };
structazx { structi2s_busbus; structsnd_card *card; structpci_dev *pci; int dev_index;
/* playback stream数量,默认是1 */ int playback_streams; /* playback索引偏移,默认为0 */ int playback_index_offset; /* capture stream数量,默认是1 */ int capture_streams; /* capture索引偏移,默认在playback索引之后 */ int capture_index_offset; int num_streams;
unsignedint bufsize; /* size of the play buffer in bytes */ unsignedint period_bytes; /* size of the period in bytes */ unsignedint frags; /* number for period in the play buffer */ unsignedint fifo_size; /* FIFO size */
/* pcm support */ structsnd_pcm_substream *substream;/* assigned substream, * set in PCM open */ unsignedint format_val; /* format value to be set in the * controller and the codec */ /* stream的标记,意义应该不大 */ unsignedchar stream_tag; /* assigned stream */ /* stream的索引号 */ unsignedchar index; /* stream index */ int assigned_key; /* last device# key assigned to */
/* * 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() */ structsnd_soc_dapm_contextdapm;
/* machine specific init */ int (*init)(struct snd_soc_component *component);
/* function mark */ structsnd_pcm_substream *mark_module; structsnd_pcm_substream *mark_open; structsnd_pcm_substream *mark_hw_params; structsnd_pcm_substream *mark_trigger; structsnd_compr_stream *mark_compr_open; void *mark_pm;
/* 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, unsignedint freq, int dir); int (*set_pll)(struct snd_soc_component *component, int pll_id, int source, unsignedint freq_in, unsignedint freq_out); int (*set_jack)(struct snd_soc_component *component, struct snd_soc_jack *jack, void *data);
/* DT */ int (*of_xlate_dai_name)(struct snd_soc_component *component, const struct of_phandle_args *args, constchar **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, unsignedint 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, unsignedlong pos, void __user *buf, unsignedlong bytes); structpage *(*page)(structsnd_soc_component *component, structsnd_pcm_substream *substream, unsignedlongoffset); 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);
conststructsnd_compress_ops *compress_ops;
/* probe ordering - for components with runtime dependencies */ /* probe的顺序,一共有5个顺序,驱动初始化的过程中根据顺序进行component的probe */ 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. */ unsignedint module_get_upon_open:1;
/* bits */ unsignedint idle_bias_on:1; unsignedint suspend_bias_off:1; unsignedint use_pmdown_time:1; /* care pmdown_time at stop */ unsignedint endianness:1; unsignedint non_legacy_dai_naming:1;
/* this component uses topology and ignore machine driver FEs */ constchar *ignore_machine; constchar *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 */ };
// 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. */ structsnd_soc_dai_driver { /* DAI description */ constchar *name; /* dai标识符 */ unsignedint id; unsignedint base; structsnd_soc_dobjdobj;
/* 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);
// include/sound/soc_dai.h structsnd_soc_dai_ops { /* * DAI clocking configuration, all optional. * Called by soc_card drivers, normally in their hw_params. */ /* 配置dai的时钟 */ int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id, unsignedint freq, int dir); int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source, unsignedint freq_in, unsignedint freq_out); int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div); int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsignedint ratio);
/* * DAI format configuration * Called by soc_card drivers, normally in their hw_params. */ int (*set_fmt)(struct snd_soc_dai *dai, unsignedint fmt); int (*xlate_tdm_slot_mask)(unsignedint slots, unsignedint *tx_mask, unsignedint *rx_mask); int (*set_tdm_slot)(struct snd_soc_dai *dai, unsignedint tx_mask, unsignedint rx_mask, int slots, int slot_width); int (*set_channel_map)(struct snd_soc_dai *dai, unsignedint tx_num, unsignedint *tx_slot, unsignedint rx_num, unsignedint *rx_slot); int (*get_channel_map)(struct snd_soc_dai *dai, unsignedint *tx_num, unsignedint *tx_slot, unsignedint *rx_num, unsignedint *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 *); /* 配置PCM音频的硬件参数 */ int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *, struct snd_soc_dai *); /* 释放PCM音频的硬件资源 */ int (*hw_free)(struct snd_pcm_substream *, struct snd_soc_dai *); /* 准备PCM音频操作 */ 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. */ /* 触发PCM音频操作 */ 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;