linux4.9下alsa架构,[Alsa]4, wm8524 Kernel音频子系统入口

上篇说到音频子系统的环境搭建和ASoC,我们会发现这样一个问题,对于已有的,已驱动的音频Codec,我们可以很方便地用aplayer、arecorder来录放音频,但是这表象背后到底隐藏了什么不为人知的PY(朋友)交易,确实是值得我们深究的,本篇从设备树作为突破口,一层一层的揭开这一谜题,欢迎收看本期走近科学。。。

额,以下都是我自己的一些理解,主要是以DroidPhone的专栏 – Linux音频子系统为参考,如果大家觉得哪里说得有问题,欢迎指正,以期共同进步。

1, 内核版本和Codec型号

本文Codec基于wm8524。

2, 设备树

设备树是音频子系统的入口吗?暂且认为它是吧,这样理解起来方便一些,我们从设备树里找到音频的设置:

wm8524: wm8524 {

compatible = "wlf,wm8524";

clocks = ;

clock-names = "mclk";

wlf,mute-gpios = ;

};

sound-wm8524 {

compatible = "fsl,imx-audio-wm8524";

model = "wm8524-audio";

audio-cpu = ;

audio-codec = ;

audio-routing =

"Line Out Jack", "LINEVOUTL",

"Line Out Jack", "LINEVOUTR";

};

里面主要有两部分内容,一部分是Codec(wm8524),另一部分就叫Mechine(sound-wm8524)吧.

从这里面我们了看到几个已经定义好的属性,从Machine入手,

compatible: 用于匹配设备树对应的驱动。

model:设备模型,暂时不是很清楚作用。

audio-cpu: 音频控制单元

audio-codec: 这个其实就是指上面的Codec

audio-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

今后的几篇会详细的分门别类的来介绍这几个部分。

标签:Kernel,snd,struct,soc,dev,dai,wm8524,card,Alsa

来源: https://blog.csdn.net/wangyijieonline/article/details/88037544

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值