上篇说到音频子系统的环境搭建和ASoC,我们会发现这样一个问题,对于已有的,已驱动的音频Codec,我们可以很方便地用aplayer、arecorder来录放音频,但是这表象背后到底隐藏了什么不为人知的PY(朋友)交易,确实是值得我们深究的,本篇从设备树作为突破口,一层一层的揭开这一谜题,欢迎收看本期走近科学。。。
额,以下都是我自己的一些理解,主要是以DroidPhone的专栏 – Linux音频子系统为参考,如果大家觉得哪里说得有问题,欢迎指正,以期共同进步。
1, 内核版本和Codec型号
Linux 4.9.123 可从以下地址获得
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
本文Codec基于wm8524。
2, 设备树
设备树是音频子系统的入口吗?暂且认为它是吧,这样理解起来方便一些,我们从设备树里找到音频的设置:
wm8524: wm8524 {
compatible = "wlf,wm8524";
clocks = <&clk IMX8MM_CLK_SAI3_ROOT>;
clock-names = "mclk";
wlf,mute-gpios = <&gpio5 21 GPIO_ACTIVE_LOW>;
};
sound-wm8524 {
compatible = "fsl,imx-audio-wm8524";
model = "wm8524-audio";
audio-cpu = <&sai3>;
audio-codec = <&wm8524>;
audio-routing =
"Line Out Jack", "LINEVOUTL",
"Line Out Jack", "LINEVOUTR";
};
里面主要有两部分内容,一部分是Codec(wm8524),另一部分就叫Mechine(sound-wm8524)吧.
从这里面我们了看到几个已经定义好的属性,从Machine入手,compatible
: 用于匹配设备树对应的驱动。model
:设备模型,暂时不是很清楚作用。audio-cpu
: 音频控制单元audio-codec
: 这个其实就是指上面的Codecaudio-routing
: 音频路径,这个也比较重要,涉及到dapm
从audio-codec
跳到Codec,compatible
: 匹配codec用的clocks
: 用到的时钟clock-names
: 时钟名字wlf,mute-gpios
: 专用的静音用的gpio定义
设备树就到这里了,要注意上面说到的每一个属性,下面都会再次见到。
3, Machine驱动
搜索sound-wm8524的compatible——“fsl,imx-audio-wm8524”,跳转到文件sound/soc/fsl/imx-wm8524.c
,
static const struct of_device_id imx_wm8524_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8524", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8524_dt_ids);
static struct platform_driver imx_wm8524_driver = {
.driver = {
.name = "imx-wm8524",
.pm = &snd_soc_pm_ops,
.of_match_table = imx_wm8524_dt_ids,
},
.probe = imx_wm8524_probe,
};
module_platform_driver(imx_wm8524_driver);
我们可以清楚地看到,compatible
->imx_wm8524_dt_ids
->imx_wm8524_probe
,最终是触发了imx_wm8524_probe,
static int imx_wm8524_probe(struct platform_device *pdev)
{
struct device_node *cpu_np, *codec_np = NULL;
struct platform_device *cpu_pdev;
struct imx_priv *priv;
struct platform_device *codec_pdev = NULL;
int ret;
struct i2c_client * codec_client = NULL;
const char *dma_name;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
{ //根据设备树来注册一些属性,让Linux能在想用的时候找到
priv->pdev = pdev;
cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}
cpu_pdev = of_find_device_by_node(cpu_np);
if (!cpu_pdev) {
dev_err(&pdev->dev, "failed to find SAI platform device\n");
ret = -EINVAL;
goto fail;
}
if (of_property_read_string_index(cpu_np, "dmas", 0, &dma_name))
{
dev_err(&pdev->dev, "failed to find SDMA name\n");
ret = -EINVAL;
goto fail;
}
codec_pdev = of_find_device_by_node(codec_np);
if (!codec_pdev || !codec_pdev->dev.driver) {
dev_err(&pdev->dev, "failed to find codec platform device\n");
ret = -EINVAL;
goto fail;
}
priv->codec_clk = devm_clk_get(&codec_pdev->dev, "mclk");
if (IS_ERR(priv->codec_clk)) {
ret = PTR_ERR(priv->codec_clk);
dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
goto fail;
}
}//end of register
{ //给dai(Dynamic Audio Interface)的指针指定位置,后面改变这个地址的内存可以直接反映到这个指针的位置,imx_wm8524_dai的定义见3.0
priv->card.dai_link = imx_wm8524_dai;
imx_wm8524_dai[0].codec_of_node = codec_np;
imx_wm8524_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
imx_wm8524_dai[0].platform_of_node = cpu_np;
imx_wm8524_dai[0].playback_only = false;
imx_wm8524_dai[0].capture_only= false;
}
{ // 这里是大头,实现了很多功能,见3.1
priv->card.late_probe = imx_wm8524_late_probe;
priv->card.num_links = 1;
priv->card.dev = &pdev->dev;
priv->card.owner = THIS_MODULE;
priv->card.dapm_widgets = imx_wm8524_dapm_widgets;
priv->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8524_dapm_widgets);
}
// 解析sound card name,这里用到的是DTS里的·model·属性,见3.2
ret = snd_soc_of_parse_card_name(&priv->card, "model");
if (ret)
goto fail;
// 解析audio_routing,这里用到的是DTS里的·audio-routing·属性,
这个涉及到dapm的widget和routes部分,见3.3
ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing");
if (ret)
goto fail;
// 设置drvdata,把priv设到priv->card里,见3.4
snd_soc_card_set_drvdata(&priv->card, priv);
// 注册sound_card,见3.5
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
goto fail;
}
}
ret = 0;
fail:
if (cpu_np)
of_node_put(cpu_np);
if (codec_np)
of_node_put(codec_np);
return ret;
}
上面有注释的地方需要注意
3.0 static struct snd_soc_dai_link imx_wm8524_dai[]
static struct snd_soc_dai_link imx_wm8524_dai[] = {
{
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai_name = "wm8524-hifi",
.ops = &imx_hifi_ops,
},
};
imx_hifi_ops
static struct snd_soc_ops imx_hifi_ops = {
.hw_params = imx_hifi_hw_params,
};
函数imx_hifi_hw_params的定义
static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_card *card = rtd->card;
struct device *dev = card->dev;
unsigned int fmt;
int ret = 0;
fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS;
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
if (ret) {
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2,
params_physical_width(params));
...
}
3.1 struct imx_priv *priv;
sound/soc/fsl/imx-wm8524.c
struct imx_priv {
struct platform_device *pdev;
struct snd_soc_card card;
struct clk *codec_clk;
unsigned int clk_frequency;
};
priv是一个私有的数据private,在这个私有数据里定义了一些本驱动会用到的参数。
回到imx_wm8524_probe()
3.1.1 struct platform_device *pdev;
include/linux/platform_device.h
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
我们就不往下深究了,只需要知道这个是一个pdev是一个platform driver。
回到struct imx_priv
3.1.2 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;
struct device *dev;
struct snd_card *snd_card;
struct module *owner;
struct mutex mutex;
struct mutex dapm_mutex;
bool instantiated;
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 */
struct snd_soc_dai_link *dai_link; /* predefined links only */
int num_links; /* predefined links only */
struct list_head dai_link_list; /* all links */
int num_dai_links;
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;
bool fully_routed;
struct work_struct deferred_resume_work;
/* lists of probed devices belonging to this card */
struct list_head codec_dev_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;
struct dentry *debugfs_pop_time;
#endif
u32 pop_time;
void *drvdata;
};
保存了特定sound-card的信息。
回到struct imx_priv
3.1.3 struct clk
struct clk {
struct clk_core *core;
const char *dev_id;
const char *con_id;
unsigned long min_rate;
unsigned long max_rate;
struct hlist_node clks_node;
};
这个结构体比较简单,就是存储了clock的信息,对应于设备树中的mclk。
回到struct imx_priv
3.2 snd_soc_of_parse_card_name
sound/soc-core.c
/* Retrieve a card's name from device tree */
int snd_soc_of_parse_card_name(struct snd_soc_card *card,
const char *propname)
{
struct device_node *np;
int ret;
if (!card->dev) {
pr_err("card->dev is not set before calling %s\n", __func__);
return -EINVAL;
}
np = card->dev->of_node;
ret = of_property_read_string_index(np, propname, 0, &card->name);
/*
* EINVAL means the property does not exist. This is fine providing
* card->name was previously set, which is checked later in
* snd_soc_register_card.
*/
if (ret < 0 && ret != -EINVAL) {
dev_err(card->dev,
"ASoC: Property '%s' could not be read: %d\n",
propname, ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_of_parse_card_name);
回到imx_wm8524_probe()
3.3 snd_soc_of_parse_audio_routing
sound/soc-core.c
int snd_soc_of_parse_audio_routing(struct snd_soc_card *card,
const char *propname)
{
struct device_node *np = card->dev->of_node;
int num_routes;
struct snd_soc_dapm_route *routes;
int i, ret;
num_routes = of_property_count_strings(np, propname);
if (num_routes < 0 || num_routes & 1) {
dev_err(card->dev,
"ASoC: Property '%s' does not exist or its length is not even\n",
propname);
return -EINVAL;
}
num_routes /= 2;
if (!num_routes) {
dev_err(card->dev, "ASoC: Property '%s's length is zero\n",
propname);
return -EINVAL;
}
routes = devm_kzalloc(card->dev, num_routes * sizeof(*routes),
GFP_KERNEL);
if (!routes) {
dev_err(card->dev,
"ASoC: Could not allocate DAPM route table\n");
return -EINVAL;
}
for (i = 0; i < num_routes; i++) {
ret = of_property_read_string_index(np, propname,
2 * i, &routes[i].sink);// sink 就是收音端,音频信号的终点
if (ret) {
dev_err(card->dev,
"ASoC: Property '%s' index %d could not be read: %d\n",
propname, 2 * i, ret);
return -EINVAL;
}
ret = of_property_read_string_index(np, propname,
(2 * i) + 1, &routes[i].source);// source 就是放音端,音频信号的出发点
if (ret) {
dev_err(card->dev,
"ASoC: Property '%s' index %d could not be read: %d\n",
propname, (2 * i) + 1, ret);
return -EINVAL;
}
}
card->num_of_dapm_routes = num_routes;
card->of_dapm_routes = routes;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_routing);
回到imx_wm8524_probe()
3.4 snd_soc_card_set_drvdata(&priv->card, priv);
include/sound/soc.h
/* device driver data */
static inline void snd_soc_card_set_drvdata(struct snd_soc_card *card,
void *data)
{
card->drvdata = data;
}
这个函数功能比较简单,就是把priv的值赋给priv->card
回到imx_wm8524_probe()
3.5 devm_snd_soc_register_card(&pdev->dev, &priv->card);
sound/soc/devres.c
/**
* devm_snd_soc_register_card - resource managed card registration
* @dev: Device used to manage card
* @card: Card to register
*
* Register a card with automatic unregistration when the device is
* unregistered.
*/
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{
struct snd_soc_card **ptr;
int ret;
//为 snd_soc_card 数组申请内存
ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
//这里是真正的注册,见3.5.1
ret = snd_soc_register_card(card);//一个牛B的函数
if (ret == 0) {
*ptr = card;
// 将device resource data @res注册到@dev。 应该使用devres_alloc()分配@res。
// 在驱动程序分离时,将调用关联的释放函数,并自动释放device resource data。
devres_add(dev, ptr);
} else {
// 释放使用devres_alloc()创建的device resource data
devres_free(ptr);
}
return ret;
}
EXPORT_SYMBOL_GPL(devm_snd_soc_register_card);
这个函数的功能很显然主要是注册sound_card.
回到3 imx_wm8524_probe()
3.5.1 snd_soc_register_card(card);//一个牛B的函数
sound/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)
{
int i, ret;
struct snd_soc_pcm_runtime *rtd;
// 判断是否已经解析到正确的 card->name 和 card->dev,正常实例化
if (!card->name || !card->dev)
return -EINVAL;
// 遍历每一个dai_link, 对dai_link,也就是imx_wm8524_dai,进行codec、platform、dai的绑定工作,
struct snd_soc_dai_link的定义见3.5.1.1
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
// 该函数主要检查codec,dai和platform驱动是否初始化成功。代码见sound/soc/soc-core.c
ret = soc_init_dai_link(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init link %s\n",
link->name);
return ret;
}
}
// 把card的值赋给card->dev->driver_data
dev_set_drvdata(card->dev, card);
// 维护链表,分别是
// card->codec_dev_list
// card->widgets
// card->paths
// card->dapm_list
// card->aux_comp_list
snd_soc_initialize_card_lists(card);
// 维护链表 card->dai_link_list
INIT_LIST_HEAD(&card->dai_link_list);
card->num_dai_links = 0;
// 维护链表 card->rtd_list
INIT_LIST_HEAD(&card->rtd_list);
card->num_rtd = 0;
INIT_LIST_HEAD(&card->dapm_dirty); // 维护链表 card->dapm_dirty
INIT_LIST_HEAD(&card->dobj_list); // 维护链表 card->dobj_list
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
// 一切尽在snd_soc_instantiate_card的掌控之中,见3.5.1.2
ret = snd_soc_instantiate_card(card);
if (ret != 0)
return ret;
/* deactivate pins to sleep state */
list_for_each_entry(rtd, &card->rtd_list, list) {
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int j;
for (j = 0; j < rtd->num_codecs; j++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
if (!codec_dai->active)
pinctrl_pm_select_sleep_state(codec_dai->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
}
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);
回到3.5 devm_snd_soc_register_card()
3.5.1.1 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 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.
*/
const char *cpu_dai_name;
/*
* You MUST specify the link's codec, either by device name, or by
* DT/OF node, but not both.
*/
const char *codec_name;
struct device_node *codec_of_node;
/* You MUST specify the DAI name within the codec */
const char *codec_dai_name;
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.
*/
const char *platform_name;
struct device_node *platform_of_node;
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);
...
/* machine stream operations */
const struct snd_soc_ops *ops;
const struct snd_soc_compr_ops *compr_ops;
...
/* Mark this pcm with non atomic ops */
bool nonatomic;
/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;
/* Symmetry requirements */
unsigned int symmetric_rates:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_samplebits: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;
unsigned int dpcm_merged_chan:1;
/* pmdown_time is ignored at stop */
unsigned int ignore_pmdown_time:1;
struct list_head list; /* DAI link list of the soc card */
struct snd_soc_dobj dobj; /* For topology */
};
回到snd_soc_register_card(card);
3.5.1.2 snd_soc_instantiate_card(card);
sound/soc-core.c
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_codec *codec;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai_link *dai_link;
int ret, i, order;
mutex_lock(&client_mutex);
mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);
/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
ret = soc_bind_dai_link(card, &card->dai_link[i]);//绑定codec、platform、cpu-dai
if (ret != 0)
goto base_error;
}
/* bind aux_devs too */
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_bind_aux_dev(card, i);//绑定aux
if (ret != 0)
goto base_error;
}
/* add predefined DAI links to the list */
for (i = 0; i < card->num_links; i++)
/* snd_soc_add_dai_link - Add a DAI link dynamically
* @card: The ASoC card to which the DAI link is added
* @dai_link: The new DAI link to add
* /
snd_soc_add_dai_link(card, card->dai_link+i);
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init)
continue;
ret = snd_soc_init_codec_cache(codec);// 初始化codec_cache
if (ret < 0)
goto base_error;
}
/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
if (ret < 0) {
dev_err(card->dev,
"ASoC: can't create sound card for card %s: %d\n",
card->name, ret);
goto base_error;
}
soc_init_card_debugfs(card);
card->dapm.bias_level = SND_SOC_BIAS_OFF;// dapm 的相关成员初始化
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list);
#ifdef CONFIG_DEBUG_FS
snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
#endif
#ifdef CONFIG_PM_SLEEP
/* deferred resume work */
INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif
if (card->dapm_widgets)
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
card->num_dapm_widgets);// Creates new DAPM controls based upon the templates.
if (card->of_dapm_widgets)
snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
card->num_of_dapm_widgets);// 创建machine级别的widget
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}
/* probe all components used by DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
list_for_each_entry(rtd, &card->rtd_list, list) {
ret = soc_probe_link_components(card, rtd, order);// probe CPU-side Codec-side components
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
/* probe auxiliary components */
ret = soc_probe_aux_devices(card);
if (ret < 0)
goto probe_dai_err;
/* Find new DAI links added during probing components and bind them.
* Components with topology may bring new DAIs and DAI links.
*/
list_for_each_entry(dai_link, &card->dai_link_list, list) {
if (soc_is_dai_link_bound(card, dai_link))
continue;
ret = soc_init_dai_link(card, dai_link);// 初始化dai_link
if (ret)
goto probe_dai_err;
ret = soc_bind_dai_link(card, dai_link);//绑定codec、platform、cpu-dai
if (ret)
goto probe_dai_err;
}
/* probe all DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
list_for_each_entry(rtd, &card->rtd_list, list) {
ret = soc_probe_link_dais(card, rtd, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
snd_soc_dapm_link_dai_widgets(card);// 连接dai widget
snd_soc_dapm_connect_dai_link_widgets(card);
// 建立machine级别的普通kcontrol控件
if (card->controls)
snd_soc_add_card_controls(card, card->controls, card->num_controls);
if (card->dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);// 注册machine级别的路径连接信息
if (card->of_dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
card->num_of_dapm_routes);// 注册machine级别的路径连接信息
snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
"%s", card->name);
snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
"%s", card->long_name ? card->long_name : card->name);
snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),
"%s", card->driver_name ? card->driver_name : card->name);
for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {
switch (card->snd_card->driver[i]) {
case '_':
case '-':
case '\0':
break;
default:
if (!isalnum(card->snd_card->driver[i]))
card->snd_card->driver[i] = '_';
break;
}
}
if (card->late_probe) {
ret = card->late_probe(card);// 调用late_probe, 即imx_wm8524_late_probe,进行一些最后的初始化设置工作
if (ret < 0) {
dev_err(card->dev, "ASoC: %s late_probe() failed: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}
}
snd_soc_dapm_new_widgets(card);// 初始化widget包含的dapm kcontrol、电源状态和连接状态
ret = snd_card_register(card->snd_card);
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
ret);
goto probe_aux_dev_err;
}
card->instantiated = 1;// 置flag,示意这个sound-card已经初始化过
snd_soc_dapm_sync(&card->dapm);// 调用snd_soc_dapm_sync函数触发widget的上电和状态改变扫描
mutex_unlock(&card->mutex);
mutex_unlock(&client_mutex);
return 0;
probe_aux_dev_err:
soc_remove_aux_devices(card);
probe_dai_err:
soc_remove_dai_links(card);
card_probe_error:
if (card->remove)
card->remove(card);
snd_soc_dapm_free(&card->dapm);
soc_cleanup_card_debugfs(card);
snd_card_free(card->snd_card);
base_error:
soc_remove_pcm_runtimes(card);
mutex_unlock(&card->mutex);
mutex_unlock(&client_mutex);
return ret;
}
3.5.2 devres_add(dev, ptr);
drivers/base/devres.c
/**
* devres_add - Register device resource
* @dev: Device to add resource to
* @res: Resource to register
*
* Register devres @res to @dev. @res should have been allocated
* using devres_alloc(). On driver detach, the associated release
* function will be invoked and devres will be freed automatically.
*/
void devres_add(struct device *dev, void *res)
{
struct devres *dr = container_of(res, struct devres, data);
unsigned long flags;
spin_lock_irqsave(&dev->devres_lock, flags);
add_dr(dev, &dr->node);
spin_unlock_irqrestore(&dev->devres_lock, flags);
}
EXPORT_SYMBOL_GPL(devres_add);
回到3.5 devm_snd_soc_register_card()
3.5.3 devres_free(ptr);
drivers/base/devres.c
/**
* devres_free - Free device resource data
* @res: Pointer to devres data to free
*
* Free devres created with devres_alloc().
*/
void devres_free(void *res)
{
if (res) {
struct devres *dr = container_of(res, struct devres, data);
BUG_ON(!list_empty(&dr->node.entry));
kfree(dr);
}
}
EXPORT_SYMBOL_GPL(devres_free);
回到3.5 devm_snd_soc_register_card()
终于写完了这个流程了,读者应该也发现了,里面出现了几个东西不太好理解
- dapm
- widgets
- routes
- kcontrol
- dai_link
今后的几篇会详细的分门别类的来介绍这几个部分。