[Alsa]4, wm8524 Kernel音频子系统入口

上篇说到音频子系统的环境搭建和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: 这个其实就是指上面的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()


终于写完了这个流程了,读者应该也发现了,里面出现了几个东西不太好理解

  1. dapm
  2. widgets
  3. routes
  4. kcontrol
  5. dai_link

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

上一篇:【转】区块链上的稳定币:理论支持和技术实现


下一篇:Linux声卡驱动(3)——DAPM