Linux音频之ASOC

参考:https://blog.csdn.net/droidphone/article/details/7165482

1.ASOC简介

ASoC--ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系

2.ASOC架构

ASOC在硬件件上被分为 Platform   Machine  codec三大部分。


  • Machine  是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。
  • Platform  一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。
  • Codec  字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。

在软件层面,ASOC也同样分为Platform驱动   Machine驱动  codec驱动三大部分。

  • Codec驱动  ASoC中的一个重要设计原则就是要求Codec驱动是平台无关的,它包含了一些音频的控件(Controls),音频接口,DAMP(动态音频电源管理)的定义和某些Codec IO功能。为了保证硬件无关性,任何特定于平台和机器的代码都要移到Platform和Machine驱动中。所有的Codec驱动都要提供以下特性:
    • Codec DAI 和 PCM的配置信息;
    • Codec的IO控制方式(I2C,SPI等);
    • Mixer和其他的音频控件;
    • Codec的ALSA音频操作接口;

必要时,也可以提供以下功能:

    • DAPM描述信息;
    • DAPM事件处理程序;
    • DAC数字静音控制
  • Platform驱动  它包含了该SoC平台的音频DMA和音频接口的配置和控制(I2S,PCM,AC97等等);它也不能包含任何与板子或机器相关的代码。
  • Machine驱动  Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

以上主要参考:https://blog.csdn.net/droidphone/article/details/7165482

3.代码分析

单独的platform和codec不能够工作,必须要通过machine驱动将两者联系起来,才能正常工作。

下面我们将从代码的角度去分析 machine  platform  codec 三者间的联系。

软件上主要涉及如下几个文件,划分如下:

● Machine驱动:link-owl.c
● Platform驱动:pcm-owl.c dai-owl.c dmaengine-pcm-owl.c

● Codec驱动:atc2603c-audio-codec.c

platform:

我们先从platform驱动入手开始分析:

先来看看pcm-owl.c中干了什么事情。

分配并添加了一个名为“s900-pcm-audio”的设备,随后注册了一个name为 “s900-pcm-audio”的platform driver。

static struct platform_driver s900_pcm_driver = {
	.driver = {
			.name = "s900-pcm-audio",
			.owner = THIS_MODULE,
	},

	.probe = s900_pcm_probe,
	.remove = s900_pcm_remove,
};

	s900_pcm_device = platform_device_alloc("s900-pcm-audio", -1);
	if (!s900_pcm_device) {
		snd_dbg(
				"ASoC: Platform device s900-pcm-audio allocation failed\n");
		ret = -ENOMEM;
		goto err;
	}

	ret = platform_device_add(s900_pcm_device);
	if (ret) {
		snd_dbg(
				"ASoC: Platform device s900-pcm-audio add failed\n");
		goto err_device_add;
	}

	pcm_priv = kzalloc(sizeof(struct s900_pcm_priv), GFP_KERNEL);
	if (NULL == pcm_priv)
		return -ENOMEM;
	pcm_priv->output_mode = O_MODE_I2S;
	pcm_priv->input_mode = O_MODE_I2S;
	platform_set_drvdata(s900_pcm_device, pcm_priv);

	ret = platform_driver_register(&s900_pcm_driver);

平台设备驱动的机制决定了当平台设备的名字匹配上了平台driver的名字之后driver的probe函数将会被调用。

static int s900_pcm_probe(struct platform_device *pdev)
{
	dev_err(&pdev->dev,
			"s900_pcm_probe!!\n");
	pdev->dev.init_name = "s900-pcm-audio";
	return snd_soc_register_platform(&pdev->dev,   
			&s900_soc_platform);
}

分析下去主要工作就是将名为“s900-pcm-audio”的平台设备添加进platform_list

int snd_soc_register_platform(struct device *dev,
		const struct snd_soc_platform_driver *platform_drv)
{
	struct snd_soc_platform *platform;
	int ret;

	dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev));

	platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
	if (platform == NULL)
		return -ENOMEM;

	ret = snd_soc_add_platform(dev, platform, platform_drv);
	if (ret)
		kfree(platform);

	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_platform);
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
		const struct snd_soc_platform_driver *platform_drv)
{
	/* create platform component name */
	platform->name = fmt_single_name(dev, &platform->id);
	if (platform->name == NULL) {
		kfree(platform);
		return -ENOMEM;
	}

	platform->dev = dev;
	platform->driver = platform_drv;
	platform->dapm.dev = dev;
	platform->dapm.platform = platform;
	platform->dapm.stream_event = platform_drv->stream_event;
	mutex_init(&platform->mutex);

	mutex_lock(&client_mutex);
	list_add(&platform->list, &platform_list);
	mutex_unlock(&client_mutex);

	dev_dbg(dev, "ASoC: Registered platform '%s'\n", platform->name);

	return 0;
}

至此pcm-owl.c就分析完了。接着看看dai-owl.c

/* modify compatible to match diff IC */
static const struct of_device_id owl_i2s_of_match[] = {
	{.compatible = "actions,s700-audio-i2s", .data = &ic_s700,},
	{.compatible = "actions,s900-audio-i2s", .data = &ic_s900,},
	{},
};
static struct platform_driver s900_dai_driver = {
	.driver = {
		.name = "owl-audio-i2s",
		.owner = THIS_MODULE,
		.of_match_table = owl_i2s_of_match,
	},

	.probe = s900_dai_probe,
	.remove = s900_dai_remove,
};

/*static struct platform_device *s900_dai_device;*/

static int __init s900_dai_init(void)
{
	int ret = 0;

	ret = platform_driver_register(&s900_dai_driver);
	if (ret) {
		snd_err("ASoC: Platform driver s900-dai register failed\n");
	}

	return ret;
}
干的事情很少,就注册了个s900_dai_driver驱动,其中如果在设备树文件中匹配到
"actions,s700-audio-i2s"

字段的话就那么s900_dai_driver的probe函数就会被调用。看了下dts中果然有

songchong@srv-artek-pad:~/AD700A/android/kernel/arch/arm64/boot/dts$ grep "s700-audio-i2s" -nr .

./s700.dtsi:551:                compatible = "actions,s700-audio-i2s";

因此s900_dai_probe函数会被调用。

static int s900_dai_probe(struct platform_device *pdev)
{

	/* get resource of i2s and hdmi from dts file */
	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
	if (!res) {
		snd_err("no memory resource of i2s!\n");
		return -ENODEV;
	}
	/* get virtual base for i2s */


	dev_warn(&pdev->dev, "s900_dai_probe\n");

	pdev->dev.init_name = "owl-audio-i2s";  //设置pdev->dev.init_name 为“owl-audio-i2s”下文中会用到。最终跟machine中的cpu_dai_name匹配就是靠它

	return snd_soc_register_component(&pdev->dev, &s900_component,
					 &s900_dai, 1);

}
struct snd_soc_dai_driver s900_dai = {
	.name = "owl-audio-i2s",
	.id = S900_AIF_I2S,
	.playback = {
		.stream_name = "s900 dai Playback",
		.channels_min = 1,
		.channels_max = 8,
		.rates = S900_STEREO_PLAYBACK_RATES,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
	},
	.capture = {
		.stream_name = "s900 dai Capture",
		.channels_min = 1,
		.channels_max = 4,
		.rates = S900_STEREO_CAPTURE_RATES,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
	},
	.ops = &s900_dai_dai_ops,
};

主要是做了一些IO内存初始化CLK初始化DMA申请等工作,最后 snd_soc_register_component(&pdev->dev, &s900_component,&s900_dai, 1); 是关键。

int snd_soc_register_component(struct device *dev,
			 const struct snd_soc_component_driver *cmpnt_drv,
			 struct snd_soc_dai_driver *dai_drv,
			 int num_dai)
{
	struct snd_soc_component *cmpnt;
	int ret;

	dev_dbg(dev, "component register %s\n", dev_name(dev));

	cmpnt = devm_kzalloc(dev, sizeof(*cmpnt), GFP_KERNEL);
	if (!cmpnt) {
		dev_err(dev, "ASoC: Failed to allocate memory\n");
		return -ENOMEM;
	}

	cmpnt->name = fmt_single_name(dev, &cmpnt->id);//设置
	if (!cmpnt->name) {
		dev_err(dev, "ASoC: Failed to simplifying name\n");
		return -ENOMEM;
	}

	cmpnt->dev	= dev;
	cmpnt->driver	= cmpnt_drv;
	cmpnt->num_dai	= num_dai;

	/*
	 * snd_soc_register_dai()  uses fmt_single_name(), and
	 * snd_soc_register_dais() uses fmt_multiple_name()
	 * for dai->name which is used for name based matching
	 */
	if (1 == num_dai)
		ret = snd_soc_register_dai(dev, dai_drv);
	else
		ret = snd_soc_register_dais(dev, dai_drv, num_dai);
	if (ret < 0) {
		dev_err(dev, "ASoC: Failed to regster DAIs: %d\n", ret);
		goto error_component_name;
	}

	mutex_lock(&client_mutex);
	list_add(&cmpnt->list, &component_list);
	mutex_unlock(&client_mutex);
}
EXPORT_SYMBOL_GPL(snd_soc_register_component);

其中将s900_component加入到component_list中去,接着看一下snd_soc_register_dais

static int snd_soc_register_dai(struct device *dev,
		struct snd_soc_dai_driver *dai_drv)
{
	struct snd_soc_codec *codec;
	struct snd_soc_dai *dai;

	dev_dbg(dev, "ASoC: dai register %s\n", dev_name(dev));

	dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
	if (dai == NULL)
		return -ENOMEM;

	/* create DAI component name */
	dai->name = fmt_single_name(dev, &dai->id);    //去取dev.init_name出来即owl-audio-i2s, 并设置给dai->name = “owl-audio-i2s”  下文中会用到这个name跟machine驱动中的cpu_dai_name字段去匹配
	dai->dev = dev;
	dai->driver = dai_drv;
	dai->dapm.dev = dev;
	if (!dai->driver->ops)
		dai->driver->ops = &null_dai_ops;

	mutex_lock(&client_mutex);

	list_for_each_entry(codec, &codec_list, list) {
		if (codec->dev == dev) {
			dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\n",
				dai->name, codec->name);
			dai->codec = codec;
			break;
		}
	}

	if (!dai->codec)
		dai->dapm.idle_bias_off = 1;

	list_add(&dai->list, &dai_list);

	mutex_unlock(&client_mutex);

	dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name);

	return 0;
}

主要是将dai_driver添加到dai_list中去。至于有什么作用我们后面会分析到。

codec:

接着我们看看codec中干了什么事情。

static int __init atc2603c_init(void)
{
        /*一些初始化工作省去*/
	ret = platform_driver_register(&atc2603c_platform_driver);

}
static struct platform_driver atc2603c_platform_driver = {
	.probe      = atc2603c_platform_probe,
	.remove     = atc2603c_platform_remove,
	.driver     = {
		.name   = "atc2603c-audio",
		.owner  = THIS_MODULE,
		.of_match_table = atc2603c_audio_of_match,
	},
	.shutdown	= atc2603c_platform_shutdown,
};

static const struct of_device_id atc2603c_audio_of_match[] = {
	{.compatible = "actions,atc2603c-audio",},
	{}
};

也就是注册了个platform driver ,如果dts文件中配置了"actions,atc2603c-audio"字段的话,atc2603c_platform_probe函数就会被调用。

static int atc2603c_platform_probe(struct platform_device *pdev)
{


	codec_res.clk = devm_clk_get(&pdev->dev, "audio_pll");

	codec_res.hdmia_clk = devm_clk_get(&pdev->dev, "hdmia");

	atc260x = dev_get_drvdata(pdev->dev.parent);
	platform_set_drvdata(pdev, atc260x);

	atc2603c_ictype = ATC260X_ICTYPE_2603C;
	pdev->dev.init_name = "atc260x-audio";  //关键点,设置pdev->dev.init_name为“atc260x-audio”下文会用到

	/* we use VMICEXT to detect earphone */
	/* bug fix: have no earphone and do not config earphone detect */
	if ((earphone_gpio_num < 0) && (adc_detect_mode == 0)
			&& (audio_hw_cfg.earphone_detect_method == 1)) {
		earphone_irq = platform_get_irq(pdev, 0);
		printk(KERN_INFO"what's my lucky draw %d\n", earphone_irq);
	}

	return snd_soc_register_codec(&pdev->dev, &soc_codec_atc2603c,
			codec_atc2603c_dai, ARRAY_SIZE(codec_atc2603c_dai));
}
主要是做一些初始化和时钟相关的操作,其中,snd_soc_register_codec(&pdev->dev, &soc_codec_atc2603c,

codec_atc2603c_dai, ARRAY_SIZE(codec_atc2603c_dai));是关键。

static struct snd_soc_codec_driver soc_codec_atc2603c = {
	.probe = atc2603c_probe,
	.remove = atc2603c_remove,

	.suspend = atc2603c_suspend,
	.resume = atc2603c_resume,
	/*.set_bias_level = atc2603c_set_bias_level,*/
	.idle_bias_off = true,

	.reg_cache_size = (ADC_ANALOG1 + 1),
	.reg_word_size = sizeof(u16),
	/*.reg_cache_default = atc2603c_reg,*/
	.volatile_register = atc2603c_volatile_register,
	.readable_register = atc2603c_readable_register,
	.reg_cache_step = 1,

	.controls = atc2603c_snd_controls,
	.num_controls = ARRAY_SIZE(atc2603c_snd_controls),
	.dapm_widgets = atc2603c_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(atc2603c_dapm_widgets),
	.dapm_routes = atc2603c_dapm_routes,
	.num_dapm_routes = ARRAY_SIZE(atc2603c_dapm_routes),
};

struct snd_soc_dai_driver codec_atc2603c_dai[] = {
	{
		.name = "atc2603c-dai",
		.id = ATC2603C_AIF,
		.playback = {
			.stream_name = "AIF Playback",
			.channels_min = 1,
			.channels_max = 8,
			.rates = ATC2603C_RATES,
			.formats = ATC2603C_FORMATS,
		},
		.capture = {
			.stream_name = "AIF Capture",
			.channels_min = 1,
			.channels_max = 4,
			.rates = ATC2603C_RATES,
			.formats = ATC2603C_FORMATS,
		},
		.ops = &atc2603c_aif_dai_ops,
	},
};	
int snd_soc_register_codec(struct device *dev,
			   const struct snd_soc_codec_driver *codec_drv,
			   struct snd_soc_dai_driver *dai_drv,
			   int num_dai)
{
    codec->name = fmt_single_name(dev, &codec->id);    //取出上文中pdev->dev.init_name"atc260x-audio"; 设置codec->name为atc260x-audio
	if (codec_drv->compress_type)
		codec->compress_type = codec_drv->compress_type;
	else
		codec->compress_type = SND_SOC_FLAT_COMPRESSION;

	codec->write = codec_drv->write;
	codec->read = codec_drv->read;
	codec->volatile_register = codec_drv->volatile_register;
	codec->readable_register = codec_drv->readable_register;
	codec->writable_register = codec_drv->writable_register;
	codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;
	codec->dapm.bias_level = SND_SOC_BIAS_OFF;
	codec->dapm.dev = dev;
	codec->dapm.codec = codec;
	codec->dapm.seq_notifier = codec_drv->seq_notifier;
	codec->dapm.stream_event = codec_drv->stream_event;
	codec->dev = dev;
	codec->driver = codec_drv;
	codec->num_dai = num_dai;
	mutex_init(&codec->mutex);

	if (codec_drv->reg_access_size && codec_drv->reg_access_default) {
		if (!codec->volatile_register)
			codec->volatile_register = snd_soc_default_volatile_register;
		if (!codec->readable_register)
			codec->readable_register = snd_soc_default_readable_register;
		if (!codec->writable_register)
			codec->writable_register = snd_soc_default_writable_register;
	}

	for (i = 0; i < num_dai; i++) {
		fixup_codec_formats(&dai_drv[i].playback);
		fixup_codec_formats(&dai_drv[i].capture);
	}

	mutex_lock(&client_mutex);
	list_add(&codec->list, &codec_list);
	mutex_unlock(&client_mutex);

	/* register any DAIs */
	ret = snd_soc_register_dais(dev, dai_drv, num_dai);
	if (ret < 0) {
		dev_err(codec->dev, "ASoC: Failed to regster DAIs: %d\n", ret);
		goto fail_codec_name;
	}
}


主要指定了codec driver 然后将code 加入到codec_list链表中去。在snd_soc_register_dais中将会把codec_atc2603c_dai放入dai_list中去。

static int snd_soc_register_dai(struct device *dev,
		struct snd_soc_dai_driver *dai_drv)
{

	/* create DAI component name */
       dai->name = fmt_multiple_name(dev, &dai_drv[i]);/设置dai->name为dai_drv->name 即"atc2603c-dai"下文会跟machine中的codec_dai_name字段匹配
	if (dai->name == NULL) {
		kfree(dai);
		return -ENOMEM;
	}

	dai->dev = dev;
	dai->driver = dai_drv;
	dai->dapm.dev = dev;
	if (!dai->driver->ops)
		dai->driver->ops = &null_dai_ops;

	list_for_each_entry(codec, &codec_list, list) {
		if (codec->dev == dev) {
			dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\n",
				dai->name, codec->name);
			dai->codec = codec;
			break;
		}
	}

	if (!dai->codec)
		dai->dapm.idle_bias_off = 1;

	list_add(&dai->list, &dai_list);

	return 0;
}

至此codec分析完毕。

machine:

接着我们看一下machine是如何将platform 和codec联系起来的。

首先在machine驱动中做一下相关gpio初始化的工作,然后分配并注册一个名为“soc-audio”的platform device 设备。

s900_link_snd_device = platform_device_alloc("soc-audio", -1);
	if (!s900_link_snd_device) {
		snd_err("ASoC: Platform device allocation failed\n");
		ret = -ENOMEM;
		goto platform_device_alloc_failed;
	}
	platform_set_drvdata(s900_link_snd_device,
			&snd_soc_s900_atc2603c_link);//添加私有数据,其中snd_soc_s900_atc2603c_link结构非常重要

	ret = platform_device_add(s900_link_snd_device);//添加platform设备

插入一段题外话为我们后续分析做准备,在这里我们先来看下snd_soc_s900_atc2603c_link结构的定义

static struct snd_soc_card snd_soc_s900_atc2603c_link = {
	.name = "s900_link",
	.owner = THIS_MODULE,
	.dai_link = s900_atc2603c_link_dai,
	.num_links = ARRAY_SIZE(s900_atc2603c_link_dai),
	.controls = owl_outpa_controls,
	.num_controls = ARRAY_SIZE(owl_outpa_controls),
};

在snd_soc_s900_atc2603c_link中定义了dai_link.其中dai_link就是串联 platform和codec驱动的关键。platform和codec就是靠s900_atc2603c_link_dai结构中定义的各种name字段来结合在一起。我们来看下这个关键的结构体定义吧。


static struct snd_soc_dai_link s900_atc2603c_link_dai[] = {
	{
		.name = "S900 ATC2603C",
		.stream_name = "ATC2603C PCM",
		.cpu_dai_name = "owl-audio-i2s",
		.codec_dai_name = "atc2603c-dai",
		.init = s900_link_snd_init,
		.platform_name = "s900-pcm-audio",
		.codec_name = "atc260x-audio",
		.ops = &s900_link_ops,
	},

	{
		.name = "S900 HDMI AUDIO",
		.stream_name = "HDMI PCM",
		.cpu_dai_name = "owl-audio-i2s",
		.codec_dai_name = "s900-hdmi-dai",
		.init = s900_link_snd_init,
		.platform_name = "s900-pcm-audio",
		.codec_name = "s900-hdmi-audio",
		.ops = &s900_link_ops,
	},

	{
		.name = "S900 PCM AUDIO",
		.stream_name = "BLUETOOTH PCM",
		.cpu_dai_name = "owl-audio-i2s",
		.codec_dai_name = "bluetooth-pcm-dai",
		.init = s900_link_snd_init,
		.platform_name = "s900-pcm-audio",
		.codec_name = "pcm-audio",
		.ops = &s900_link_ops,
	},
	
	{
		.name = "S900 SPDIF AUDIO",
		.stream_name = "SPDIF PCM",
		.cpu_dai_name = "owl-audio-i2s",
		.codec_dai_name = "s900-spdif-dai",
		.init = s900_link_snd_init,
		.platform_name = "s900-pcm-audio",
		.codec_name = "s900-spdif-audio",
		.ops = &s900_link_ops,
	}
	
};

在s900_atc2603c_link_dai中定义了四种设备分别是codec、HDMI、蓝牙、SPDIF。在内部分别制定了各种name字段。

回到machine驱动添加了一个名为“soc-audio”的platform设备。

根据linux平台设备驱动的特性,一定会有一个同名的platform driver会去注册。经过查找在kernel/sound/soc/soc-core.c中注册了“soc-audio”platform driver。

/* ASoC platform driver */
static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.owner		= THIS_MODULE,
		.pm		= &snd_soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};
static int __init snd_soc_init(void)
{
	snd_soc_util_init();

	return platform_driver_register(&soc_driver);//注册soc_driver设备驱动程序
}

至此soc_probe函数会被调用,我们来看一下soc_probe

static int soc_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev); //其实就是获取到了上文中的snd_soc_s900_atc2603c_link
	/*
	 * no card, so machine driver should be registering card
	 * we should not be here in that case so ret error
	 */
	if (!card)
		return -EINVAL;

	dev_warn(&pdev->dev,
		 "ASoC: machine %s should use snd_soc_register_card()\n",
		 card->name);

	/* Bodge while we unpick instantiation */
	card->dev = &pdev->dev;

	return snd_soc_register_card(card); //&snd_soc_s900_atc2603c_link
}
int snd_soc_register_card(struct snd_soc_card *card) //snd_soc_s900_atc2603c_link
{
	dev_set_drvdata(card->dev, card);            

	snd_soc_initialize_card_lists(card);

	soc_init_card_debugfs(card);

	card->rtd = devm_kzalloc(card->dev,
				 sizeof(struct snd_soc_pcm_runtime) *
				 (card->num_links + card->num_aux_devs),
				 GFP_KERNEL);
	if (card->rtd == NULL)
		return -ENOMEM;
	card->num_rtd = 0;
	card->rtd_aux = &card->rtd[card->num_links];

	for (i = 0; i < card->num_links; i++)
		card->rtd[i].dai_link = &card->dai_link[i]; //snd_soc_s900_atc2603c_link->dai_link[i];也就是我们上文中提到的s900_atc2609a_link_dai,一共有四组成员

	INIT_LIST_HEAD(&card->list);
	INIT_LIST_HEAD(&card->dapm_dirty);
	card->instantiated = 0;
	mutex_init(&card->mutex);
	mutex_init(&card->dapm_mutex);

	ret = snd_soc_instantiate_card(card);//实例化声卡
	if (ret != 0)
		soc_cleanup_card_debugfs(card);

	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);

接着看看是如何实例化声卡的,此函数比较长,我截取关键部分

static int snd_soc_instantiate_card(struct snd_soc_card *card)//snd_soc_s900_atc2603c_link
{
	struct snd_soc_codec *codec;
	struct snd_soc_codec_conf *codec_conf;
	enum snd_soc_compress_type compress_type;
	struct snd_soc_dai_link *dai_link;

	/* bind DAIs */
	for (i = 0; i < card->num_links; i++) {
		ret = soc_bind_dai_link(card, i); //绑定dai的关键部分
                     //插入  soc_bind_dai_link函数分析一下
	             
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
	struct snd_soc_dai_link *dai_link = &card->dai_link[num];
	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
	struct snd_soc_codec *codec;
	struct snd_soc_platform *platform;
	struct snd_soc_dai *codec_dai, *cpu_dai;
	const char *platform_name;

	dev_dbg(card->dev, "ASoC: binding %s at idx %d\n", dai_link->name, num);

	/* Find CPU DAI from registered DAIs*/
	list_for_each_entry(cpu_dai, &dai_list, list) {     //遍历dai_list链表
		if (dai_link->cpu_of_node &&
		    (cpu_dai->dev->of_node != dai_link->cpu_of_node))
			continue;
		if (dai_link->cpu_name &&
		    strcmp(dev_name(cpu_dai->dev), dai_link->cpu_name))
			continue;
		if (dai_link->cpu_dai_name &&
		    strcmp(cpu_dai->name, dai_link->cpu_dai_name)) //通过比较dai_link->cpu_dai_name 和cpu_dai->name的设备
			continue;                                           //"owl-audio-i2s"   找不到cpu_dai->name         

		rtd->cpu_dai = cpu_dai;  
	}

	/* Find CODEC from registered CODECs */
	list_for_each_entry(codec, &codec_list, list) {
		if (dai_link->codec_of_node) {
			if (codec->dev->of_node != dai_link->codec_of_node)
				continue;
		} else {
			if (strcmp(codec->name, dai_link->codec_name))// dai_link-codec_name = "atc260x-audio"
				continue;                             // code->name = "atc2603c-audio",  匹配成功
		}                                                     

		rtd->codec = codec;
		/*
		 * CODEC found, so find CODEC DAI from registered DAIs from
		 * this CODEC
		 */
		list_for_each_entry(codec_dai, &dai_list, list) {
			if (codec->dev == codec_dai->dev &&
				!strcmp(codec_dai->name,               //codec_dai->name = "atc2603c-dai"
					dai_link->codec_dai_name)) {   //dai_link->codec_dai_name = "atc2603c-dai" 匹配成功

				rtd->codec_dai = codec_dai;
			}
		}
	}

	/* if there's no platform we match on the empty platform */
	platform_name = dai_link->platform_name;
	if (!platform_name && !dai_link->platform_of_node)
		platform_name = "snd-soc-dummy";

	/* find one from the set of registered platforms */
	list_for_each_entry(platform, &platform_list, list) {
		if (dai_link->platform_of_node) {
			if (platform->dev->of_node !=
			    dai_link->platform_of_node)
				continue;
		} else {
			if (strcmp(platform->name, platform_name))
				continue;
		}

		rtd->platform = platform;
	}
	if (!rtd->platform) {
		dev_err(card->dev, "ASoC: platform %s not registered\n",
			dai_link->platform_name);
		return -EPROBE_DEFER;
	}

	card->num_rtd++;

	return 0;
}

至此machine是如何绑定platform和codec就分析完了,其中有些细节没有涉及到。

推荐参考:https://blog.csdn.net/orz415678659/article/details/8982771  写的很详细





猜你喜欢

转载自blog.csdn.net/u012830148/article/details/80079782