参考: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 写的很详细