我们已经大体知道与平台相关并且知道内核音频子系统所需要的数据结构.接下来我们将根据函数调用流程看一下平台相关的代码是如何和内核音频子系统交互的.
1.平台相关端入口函数:static int s3c24xx_uda134x_probe(struct platform_device *pdev)
MINI2440音频驱动的入口在sound/soc/s3c24xx/s3c24xx_uda134x.c.关于平台总线上设备与驱动如何匹配的可自行参考内核源码或相关网络资料.大体流程如下:
module_init(s3c24xx_uda134x_init);
-->
platform_driver_register(&s3c24xx_uda134x_driver);
-->
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
因此,我们直接看函数static int s3c24xx_uda134x_probe(struct platform_device *pdev).此函数主要分两部分功能.一是L3的配置;二是与内核音频子系统的交互.
1-2.L3
S3C2440和UDA1341TS的通讯通过L3总线协议来完成.static int s3c24xx_uda134x_probe(struct platform_device *pdev)函数中,下面代码关于L3的操作:
s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;
s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,"data")
s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,"clk")
s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,"mode")
1-3.static struct platform_device *s3c24xx_uda134x_snd_device;
下面我们动态创建一个动态平台设备s3c24xx_uda134x_snd_device,并用我们平台相关的音频结构体struct snd_soc_device s3c24xx_uda134x_snd_devdata来初始化这个平台设备.见下面代码:
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
if (!s3c24xx_uda134x_snd_device) {
printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
"Unable to register\n");
return -ENOMEM;
}
platform_set_drvdata(s3c24xx_uda134x_snd_device,
&s3c24xx_uda134x_snd_devdata);
s3c24xx_uda134x_snd_devdata.dev = &s3c24xx_uda134x_snd_device->dev;
ret = platform_device_add(s3c24xx_uda134x_snd_device);
2.走进音频子系统.
音频子系统入口函数:static struct platform_driver soc_driver
在1-3里面创建了一个平台设备,相应的得有一个平台设备的驱动.平台总线上的设备和驱动的匹配规则是它们的name域.在1-3里面创建的平台设备的名字为"soc-audio".其对应的平台驱动是static struct platform_driver soc_driver.音频这个平台驱动源码的入口位于sound/soc/soc-core.c.可见,它是位于音频子系统的核心部分,这部分代码是平台无关性的.此时,我们开始进入了伟大的音频子系统!!!如下(sound/soc/soc-core.c):
module_init(snd_soc_init);
->
platform_driver_register(&soc_driver);
->
static int soc_probe(struct platform_device *pdev)
2-1.static int soc_probe(struct platform_device *pdev)
看内核函数,一定要明确其参数,当然,无论是什么语言函数,也是必须明确其函数参数的.平台总线的驱动端一般通过总线这个"中介"去获取其相应的设备端的资源实现设备的操作的.
函数static int soc_probe(struct platform_device *pdev)参数pdev即为在1-3动态分配并初始化的平台设备s3c24xx_uda134x_snd_device.如下(sound/soc/soc-core.c):
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
int ret = 0;
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_card *card = socdev->card;
/* Bodge while we push things out of socdev */
card->socdev = socdev;
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
return 0;
}
下面语句:
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
socdev则指向struct snd_soc_device s3c24xx_uda134x_snd_devdata.因此,接下来的代码语句:
struct snd_soc_card *card = socdev->card;
card则指向的是:struct snd_soc_card snd_soc_s3c24xx_uda134x.
再接下来的代码:
/* Bodge while we push things out of socdev */
card->socdev = socdev;
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
主要方便后续从card这个参数获取到其相应的socdev和pdev.
2-2.static int snd_soc_register_card(struct snd_soc_card *card)
2-1中接下来的便是通过下面的函数向内核注册一个声卡实例:
ret = snd_soc_register_card(card);
此函数同样属于音频子系统的核心层,位于sound/soc/soc-core.c:
/**
* snd_soc_register_card - Register a card with the ASoC core
*
* @card: Card to register
*
* Note that currently this is an internal only function: it will be
* exposed to machine drivers after further backporting of ASoC v2
* registration APIs.
*/
static int snd_soc_register_card(struct snd_soc_card *card)
{
if (!card->name || !card->dev)
return -EINVAL;
INIT_LIST_HEAD(&card->list);
card->instantiated = 0;
mutex_lock(&client_mutex);
list_add(&card->list, &card_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "Registered card '%s'\n", card->name);
return 0;
}
这里的函数snd_soc_instantiate_cards()参数为空?!其实是通过全局链表card_list来索引到我们当前这张声卡实例的.因此,下面语句完成了当前声卡实例链进全局链表头:
list_add(&card->list, &card_list);
接下来函数snd_soc_instantiate_cards();开始了我们当前声卡实例的初始化:
/*
* Attempt to initialise any uninitalised cards. Must be called with
* client_mutex.
*/
static void snd_soc_instantiate_cards(void)
{
struct snd_soc_card *card;
list_for_each_entry(card, &card_list, list)
snd_soc_instantiate_card(card);
}
函数snd_soc_instantiate_card(card)的参数card还是原来struct snd_soc_card snd_soc_s3c24xx_uda134x.
2-3.static void snd_soc_instantiate_card(struct snd_soc_card *card)
函数static void snd_soc_instantiate_card(struct snd_soc_card *card)在整个音频子系统里面非常重要,音频子系统的初始化便是由其负责完成的.此函数调用完成.整个音频驱动就安静沉眠了.静静地等待着上层APP的调用.这个函数比较庞大.如下:
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct platform_device *pdev = container_of(card->dev,
struct platform_device,
dev);
struct snd_soc_codec_device *codec_dev = card->socdev->codec_dev;
struct snd_soc_platform *platform;
struct snd_soc_dai *dai;
int i, found, ret, ac97;
if (card->instantiated)
return;
found = 0;
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
if (!found) {
dev_dbg(card->dev, "Platform %s not registered\n",
card->platform->name);
return;
}
ac97 = 0;
for (i = 0; i < card->num_links; i++) {
found = 0;
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
if (!found) {
dev_dbg(card->dev, "DAI %s not registered\n",
card->dai_link[i].cpu_dai->name);
return;
}
if (card->dai_link[i].cpu_dai->ac97_control)
ac97 = 1;
}
for (i = 0; i < card->num_links; i++) {
if (!card->dai_link[i].codec_dai->ops)
card->dai_link[i].codec_dai->ops = &null_dai_ops;
}
/* If we have AC97 in the system then don't wait for the
* codec. This will need revisiting if we have to handle
* systems with mixed AC97 and non-AC97 parts. Only check for
* DAIs currently; we can't do this per link since some AC97
* codecs have non-AC97 DAIs.
*/
if (!ac97)
for (i = 0; i < card->num_links; i++) {
found = 0;
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].codec_dai == dai) {
found = 1;
break;
}
if (!found) {
dev_dbg(card->dev, "DAI %s not registered\n",
card->dai_link[i].codec_dai->name);
return;
}
}
/* Note that we do not current check for codec components */
dev_dbg(card->dev, "All components present, instantiating\n");
/* Found everything, bring it up */
if (card->probe) {
ret = card->probe(pdev);
if (ret < 0)
return;
}
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
if (cpu_dai->probe) {
ret = cpu_dai->probe(pdev, cpu_dai);
if (ret < 0)
goto cpu_dai_err;
}
}
if (codec_dev->probe) {
ret = codec_dev->probe(pdev);
if (ret < 0)
goto cpu_dai_err;
}
if (platform->probe) {
ret = platform->probe(pdev);
if (ret < 0)
goto platform_err;
}
/* DAPM stream work */
INIT_DELAYED_WORK(&card->delayed_work, close_delayed_work);
#ifdef CONFIG_PM
/* deferred resume work */
INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif
card->instantiated = 1;
return;
platform_err:
if (codec_dev->remove)
codec_dev->remove(pdev);
cpu_dai_err:
for (i--; i >= 0; i--) {
struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
if (cpu_dai->remove)
cpu_dai->remove(pdev, cpu_dai);
}
if (card->remove)
card->remove(pdev);
}
函数参数card依旧是struct snd_soc_card snd_soc_s3c24xx_uda134x.下面分各个细节详细分析此函数:
2-3-1.
struct platform_device *pdev = container_of(card->dev, struct platform_device,dev);
pdev指向的是在函数s3c24xx_uda134x_probe()动态分配的s3c24xx_uda134x_snd_device.
2-3-2.
struct snd_soc_codec_device *codec_dev = card->socdev->codec_dev;
codec_dev指向的是snd_soc_s3c24xx_uda134x下的socdev域下的codec_dev域.我们在snd_soc_s3c24xx_uda134x里面并没有实现对socdev域的初始化.暂且不管.
2-3-3.
struct snd_soc_platform *platform;
platform在下面的代码初始化用到:
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
platform是链接进去platform_list链表的一个元素.那么,platfom_list是何时初始化的呢?在sound/soc/s3c24xx/s3c24xx-pcm.c里面初始化.如下:
module_init(s3c24xx_soc_platform_init);
-->
snd_soc_register_platform(&s3c24xx_soc_platform);
-->
list_add(&platform->list, &platform_list);
此处,函数snd_soc_register_platform()是音频核心层的函数.pcm驱动进入音频核心层便是通过此函数以"插件"的形式插入音频子系统.
因此,此处的platform指向的是struct snd_soc_platform s3c24xx_soc_platform:
struct snd_soc_platform s3c24xx_soc_platform = {
.name = "s3c24xx-audio",
.pcm_ops = &s3c24xx_pcm_ops,
.pcm_new = s3c24xx_pcm_new,
.pcm_free = s3c24xx_pcm_free_dma_buffers,
};
为了连续性,暂时不对pcm平台驱动展开.继续回到函数snd_soc_instantiate_card().
2-3-4.
struct snd_soc_dai *dai;
和2-3-3的platform类似,在下面代码中将用到这个临时变量:
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
同样,链表dai_list是在sound/soc/s3c24xx/s3c24xx-i2s.c里面初始化:
module_init(s3c24xx_i2s_init);
-->
snd_soc_register_dai(&s3c24xx_i2s_dai);
-->
list_add(&dai->list, &dai_list);
其中,函数snd_soc_register_dai()是音频子系统核心层的函数.DAI(Digital Audio Interface)便是通过此函数以"插件"的形式插入音频子系统的核心层.
因此,此处的dai指向的是struct snd_soc_dai s3c24xx_i2s_dai:
struct snd_soc_dai s3c24xx_i2s_dai = {
.name = "s3c24xx-i2s",
.id = 0,
.probe = s3c24xx_i2s_probe,
.suspend = s3c24xx_i2s_suspend,
.resume = s3c24xx_i2s_resume,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &s3c24xx_i2s_dai_ops,
};
其实,s3c24xx_i2s_dai同时被存放在s3c24xx_uda134x_snd_devdata->snd_soc_s3c24xx_uda134x->s3c24xx_uda134x_dai_link->s3c24xx_i2s_dai(见sound/soc/s3c24xx/s3c24xx_uda134x.c).
3.走出音频子系统
弄明白上述的几个临时变量pdev、codec_dev、platform和dai.我们准备走出音频子系统.来来回回,重重复复.子系统只是软件上一的种组织手段,最终还是会落到平台相关的操作上.继续在函数static void snd_soc_instantiate_card(struct snd_soc_card *card)里面逗留.
3-1.card->probe:
在函数static void snd_soc_instantiate_card(struct snd_soc_card *card)中见下面代码:
if (card->probe) {
ret = card->probe(pdev);
if (ret < 0)
return;
}
只是在我们在S3C2440这个平台上这颗UDA1341TS CODEC并没有对probe域进行赋值.见struct snd_soc_card snd_soc_s3c24xx_uda134x.
3-2.cpu_dai->probe:
在函数static void snd_soc_instantiate_card(struct snd_soc_card *card)中见下面代码:
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
if (cpu_dai->probe) {
ret = cpu_dai->probe(pdev, cpu_dai);
if (ret < 0)
goto cpu_dai_err;
}
}
cpu_dai其实指向了平台相关的函数static int s3c24xx_i2s_probe(struct platform_device *pdev,struct snd_soc_dai *dai).展开此函数,可以看到一系列平台相关的关于IIS总线的寄存器配置信息.如下(sound/soc/s3c24xx/s3c24xx-i2s.c):
static int s3c24xx_i2s_probe(struct platform_device *pdev,
struct snd_soc_dai *dai)
{
pr_debug("Entered %s\n", __func__);
s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
if (s3c24xx_i2s.regs == NULL)
return -ENXIO;
s3c24xx_i2s.iis_clk = clk_get(&pdev->dev, "iis");
if (s3c24xx_i2s.iis_clk == NULL) {
pr_err("failed to get iis_clock\n");
iounmap(s3c24xx_i2s.regs);
return -ENODEV;
}
clk_enable(s3c24xx_i2s.iis_clk);
/* Configure the I2S pins in correct mode */
s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
s3c24xx_snd_txctrl(0);
s3c24xx_snd_rxctrl(0);
return 0;
}
3-3.codec_dev->probe
函数static void snd_soc_instantiate_card(struct snd_soc_card *card)接下来代码:
if (codec_dev->probe) {
ret = codec_dev->probe(pdev);
if (ret < 0)
goto cpu_dai_err;
}
由上述2-1的函数static int soc_probe(struct platform_device *pdev).见如下(sound/soc/soc-core.c)代码:
static int soc_probe(struct platform_device *pdev)
{
int ret = 0;
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_card *card = socdev->card;
/* Bodge while we push things out of socdev */
card->socdev = socdev;
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
return 0;
}
参数pdev由上述2-3-1可知,是s3c24xx_uda134x_snd_device.
在card->socdev域记录了struct snd_soc_codec_device soc_codec_dev_uda134x.展开结构体soc_codec_dev_uda134x:
struct snd_soc_codec_device soc_codec_dev_uda134x = {
.probe = uda134x_soc_probe,
.remove = uda134x_soc_remove,
.suspend = uda134x_soc_suspend,
.resume = uda134x_soc_resume,
};
因此,此probe域是函数static int uda134x_soc_probe(struct platform_device *pdev).参数pdev由上述2-3-1可知,是s3c24xx_uda134x_snd_device.展开函数(sound/soc/codecs/uda134x.c)static intuda134x_soc_probe(struct platform_device *pdev):
static int uda134x_soc_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
struct uda134x_priv *uda134x;
void *codec_setup_data = socdev->codec_data;
int ret = -ENOMEM;
struct uda134x_platform_data *pd;
printk(KERN_INFO "UDA134X SoC Audio Codec\n");
if (!codec_setup_data) {
printk(KERN_ERR "UDA134X SoC codec: "
"missing L3 bitbang function\n");
return -ENODEV;
}
pd = codec_setup_data;
switch (pd->model) {
case UDA134X_UDA1340:
case UDA134X_UDA1341:
case UDA134X_UDA1344:
break;
default:
printk(KERN_ERR "UDA134X SoC codec: "
"unsupported model %d\n",
pd->model);
return -EINVAL;
}
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (socdev->card->codec == NULL)
return ret;
codec = socdev->card->codec;
uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
if (uda134x == NULL)
goto priv_err;
codec->private_data = uda134x;
codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg),
GFP_KERNEL);
if (codec->reg_cache == NULL)
goto reg_err;
mutex_init(&codec->mutex);
codec->reg_cache_size = sizeof(uda134x_reg);
codec->reg_cache_step = 1;
codec->name = "UDA134X";
codec->owner = THIS_MODULE;
codec->dai = &uda134x_dai;
codec->num_dai = 1;
codec->read = uda134x_read_reg_cache;
codec->write = uda134x_write;
#ifdef POWER_OFF_ON_STANDBY
codec->set_bias_level = uda134x_set_bias_level;
#endif
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
codec->control_data = codec_setup_data;
if (pd->power)
pd->power(1);
uda134x_reset(codec);
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
printk(KERN_ERR "UDA134X: failed to register pcms\n");
goto pcm_err;
}
switch (pd->model) {
case UDA134X_UDA1340:
case UDA134X_UDA1344:
ret = snd_soc_add_controls(codec, uda1340_snd_controls,
ARRAY_SIZE(uda1340_snd_controls));
break;
case UDA134X_UDA1341:
ret = snd_soc_add_controls(codec, uda1341_snd_controls,
ARRAY_SIZE(uda1341_snd_controls));
break;
default:
printk(KERN_ERR "%s unkown codec type: %d",
__func__, pd->model);
return -EINVAL;
}
if (ret < 0) {
printk(KERN_ERR "UDA134X: failed to register controls\n");
goto pcm_err;
}
ret = snd_soc_init_card(socdev);
if (ret < 0) {
printk(KERN_ERR "UDA134X: failed to register card\n");
goto card_err;
}
return 0;
card_err:
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
pcm_err:
kfree(codec->reg_cache);
reg_err:
kfree(codec->private_data);
priv_err:
kfree(codec);
return ret;
}
大体浏览了一下这个函数,主要是完成这颗codec表征结构体struct snd_soc_codec *codec的动态分配并初始化,然后以"插件"的形式插入到内核的音频子系统.
首先我们来了解一下此函数里面几个重要的临时变量:socdev、codec、uda134x、codec_setup_data和pd.如下:
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
struct uda134x_priv *uda134x;
void *codec_setup_data = socdev->codec_data;
int ret = -ENOMEM;
struct uda134x_platform_data *pd;
临时变量socdev指向的是struct snd_soc_device s3c24xx_uda134x_snd_devdata;
临时变量codec即为临时指针,方便用来实现codec的初始时代码上的操作.如下:
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (socdev->card->codec == NULL)
return ret;
codec = socdev->card->codec;
临时变量uda134x的作用只是分配一段内存并记录在codec->private_data域.如下:
uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
if (uda134x == NULL)
goto priv_err;
codec->private_data = uda134x;
临时变量pd指向的是codec_setup_data,而codec_setup_data指向的即是s3c24xx_uda134x_snd_devdata下面的codec_data域,即struct uda134x_platform_data s3c24xx_uda134x.这里主要是完成L3总线协议.
了解完这五个变量之后,下面我们继续函数static int uda134x_soc_probe(struct platform_device *pdev)的代码浏览.这里可以说是codec端的代码,即设备端(相对于SOC而言).SOC端需要和音频子系统打交道,自然,codec也是需要插入到音频子系统里面的.
3-3-1.codec设备相关层:
这codec设备密切相关的至少会包括寄存器的操作.如下:
codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg),GFP_KERNEL);
codec->dai = &uda134x_dai;
codec->read = uda134x_read_reg_cache;
codec->write = uda134x_write;
#ifdef POWER_OFF_ON_STANDBY
codec->set_bias_level = uda134x_set_bias_level;
#endif
codec->control_data = codec_setup_data;//L3接口
uda134x_reset(codec);
注意上述有codec的寄存器读写.还有语句
codec->control_data = codec_setup_data;
由上述3-3分析可知,codec_setup_data指向的是s3c24xx_uda134x_snd_devdata.主要完成其寄存器读写的总线协议,这里是L3.
3-3-2.int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
此函数是音频子系统的核心,实现音频控制的设备节点/dev/snd/controlCn,播放音频的设备节点/dev/snd/pcmCiDjp,录制音频的设备节点/dev/snd/pcmCiDjc都是在此函数生成,并在此函数在直接绑定了对应上层用户空间的系统调用.
在函数static int uda134x_soc_probe(struct platform_device *pdev)往下看有如下代码:
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
printk(KERN_ERR "UDA134X: failed to register pcms\n");
goto pcm_err;
}
向音频子系统注册一个声卡和PCM设备,主要完成AD/DA转换.参数说明如下:
socdev:struct snd_soc_device s3c24xx_uda134x_snd_devdata;
SNDRV_DEFAULT_IDX1:-1;
SNDRV_DEFAULT_STR1:NULL.
展开音频子系统里面的函数int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid):
/*
* snd_soc_new_pcms - create new sound card and pcms
* @socdev: the SoC audio device
* @idx: ALSA card index
* @xid: card identification
*
* Create a new sound card based upon the codec and interface pcms.
*
* Returns 0 for success, else error.
*/
int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
{
struct snd_soc_card *card = socdev->card;
struct snd_soc_codec *codec = card->codec;
int ret, i;
mutex_lock(&codec->mutex);
/* register a sound card */
ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create sound card for codec %s\n",
codec->name);
mutex_unlock(&codec->mutex);
return ret;
}
codec->socdev = socdev;
codec->card->dev = socdev->dev;
codec->card->private_data = codec;
strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver));
/* create the pcms */
for (i = 0; i < card->num_links; i++) {
ret = soc_new_pcm(socdev, &card->dai_link[i], i);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm %s\n",
card->dai_link[i].stream_name);
mutex_unlock(&codec->mutex);
return ret;
}
}
mutex_unlock(&codec->mutex);
return ret;
}
此函数里面有两大重要函数snd_card_create()和soc_new_pcm()函数.这两个函数完成了用户空间设备节点的创建及最直接面向用户空间的系统调用的操作集.
此函数参数在展开之前已经交代过了.按照惯例,我们先来看函数开头的几个临时变量:
struct snd_soc_card *card = socdev->card;
临时变量card指向的是struct snd_soc_card snd_soc_s3c24xx_uda134x.
struct snd_soc_codec *codec = card->codec;
临时变量codec指向的是在3-3动态分配并初始化的codec.
接下来的函数便是将声卡注册进ALSA框架:
/* register a sound card */
ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create sound card for codec %s\n",
codec->name);
mutex_unlock(&codec->mutex);
return ret;
}
为了不影响流程,函数int snd_card_create(int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)只讲述其重要函数:
int snd_card_create(int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)
->
err = snd_ctl_create(card);
->
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}
->
sprintf(name, "controlC%i", cardnum);
if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, name)) < 0)
->
return snd_register_device_for_dev(type, card, dev, f_ops,
private_data, name,
snd_card_get_device_link(card));
->
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
在函数snd_card_create()在调用snd_ctl_create()给用户空间开辟了一个控制音频的接口,如音量控制等.并且函数snd_ctl_create()有一个域dev_register是一个回调函数.在后续的分析中会看到它是如何被回调的.
在函数snd_ctl_create()里面还调用了函数snd_device_new().如下:
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
展开函数:
int snd_device_new(struct snd_card *card, snd_device_type_t type,
void *device_data, struct snd_device_ops *ops)
{
struct snd_device *dev;
if (snd_BUG_ON(!card || !device_data || !ops))
return -ENXIO;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
snd_printk(KERN_ERR "Cannot allocate device\n");
return -ENOMEM;
}
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
list_add(&dev->list, &card->devices); /* add to the head of list */
return 0;
}
在此函数中,下面语句比较重要,后面会用到:
dev->device_data = device_data;
例如这里的device_data是传进来的card.
我们知道设备节点的生成及操作集的绑定都是在回调函数snd_ctl_dev_register()里面完成的.那么,啥时候实现回调,和下面代码息息相关:
list_add(&dev->list, &card->devices);
因此,此函数最大的意义在于,创建了/dev/snd/controlCn(n = 0,1,2,...)设备节点.并且在此设备节点上绑定了操作集snd_ctl_f_ops,来最直接对应用户空间的系统调用,储如read、write、ioctl等.
另一重要函数static int soc_new_pcm(struct snd_soc_device *socdev,struct snd_soc_dai_link *dai_link, int num)其设计模式与刚才的函数int snd_card_create(int idx, const char *xid, struct module *module, int extra_size,struct snd_card **card_ret)的设计模式相仿.snd_card_create()生成了音频设备的音频控制接口/dev/snd/controlCn,而soc_new_pcm()则生成了音频里面的另外两个设备节点:
/dev/snd/pcmCiDjp(i = 0,1,2,...; j = 0,1,2,...)//播音
/dev/snd/pcmCiDjc(i = 0,1,2,...; j = 0,1,2,...)//录音
下面看函数soc_new_pcm()是如何生成播音及录音的设备节点的呢?大体流程如下:
int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
->
ret = snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback,capture, &pcm);
->
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register, //回调函数
.dev_disconnect = snd_pcm_dev_disconnect,
};
->
static int snd_pcm_dev_register(struct snd_device *device)
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);//设备节点命名/dev/snd/pcmCiDjp(i = 0,1,2,...; j = 0,1,2,...)
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);//设备节点命名/dev/snd/pcmCiDjc(i = 0,1,2,...; j = 0,1,2,...)
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
->
err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);//绑定PCM操作集
->
preg->f_ops = f_ops;//绑定设备节点的操作集
preg->dev = device_create(sound_class, device, MKDEV(major, minor),private_data, "%s", name);//创建设备节点
与此设备节点绑定的操作集是const struct file_operations snd_pcm_f_ops[2].如下:
/*
* Register section
*/
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = dummy_get_unmapped_area,
},
{
.owner = THIS_MODULE,
.read = snd_pcm_read,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = dummy_get_unmapped_area,
}
};
因此,函数soc_new_pcm()最大的意义在于生成播音和放音这两个设备节点:/dev/snd/pcmCiDjp和/dev/snd/pcmCiDic.并且绑定了最直接面向用户空间的系统调用的操作集snd_pcm_f_ops.
和函数snd_ctl_create()相仿,内部都调用了函数snd_device_new().如下:
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
snd_pcm_free(pcm);
return err;
}
同样要注意到函数snd_device_new()下面两行代码:
dev->device_data = device_data;
list_add(&dev->list, &card->devices);
注意到这里的device_data是pcm,在函数snd_pcm_new()动态分配分配的.
在上述的回调函数snd_pcm_dev_register()中:
static int snd_pcm_dev_register(struct snd_device *device)
{
pcm = device->device_data;
err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);
}
注意到有下面语句代码:
pcm = device->device_data;
这里取出来的便是在函数snd_pcm_new()里面动态分配的pcm.
有一个函数特别重要,是下面分析用户空间到硬件IIS的一个节点:
err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);
注意到参数pcm和次设备号关联,后续的操作就是通过设备的次设备号来索引其pcm的.如下:
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data,const char *name, struct device *device)
{
int minor;
struct snd_minor *preg;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops;
preg->private_data = private_data;
snd_minors[minor] = preg;
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
}
注意到参数private_data便是传进来的pcm.
关于S3C2440和UDA1341TS的音频数据交互是通过IIS总线来实现的.下面看一下上层用户空间是如何路由到硬件层的IIS的?
为了更好地理解用户空间如何实现底层IIS音频数据的交互,下面分三步走:
一、struct snd_pcm_substream *substream;
struct snd_pcm *pcm;
ret = snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback,capture, &pcm);//为PCM分配内存
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
在这里注意一个全局变量的操作集--soc_pcm_ops.注意,这个操作集将是上层到IIS硬件层中间层的第二"逻辑层"(第一层是上述的snd_pcm_f_ops).并且,在函数snd_pcm_set_ops()暂存在动态分配的pcm的streams域.如下:
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)
{
struct snd_pcm_str *stream = &pcm->streams[direction];
struct snd_pcm_substream *substream;
for (substream = stream->substream; substream != NULL; substream = substream->next)
substream->ops = ops;
}
二、static int snd_pcm_playback_open(struct inode *inode, struct file *file)
以播放声音为例,当用户空间对设备节点/dev/sdn/pcmD0C0p进行open时,第一响应的是snd_pcm_f_ops.如下:
.open = snd_pcm_playback_open,
open函数里面一般来说其内容是最丰富的,比如通过file->private_data来暂存一些设备相关信息,后续的read()、write()等动作可以通过file->private_data来提取这些设备信息.函数static int snd_pcm_playback_open(struct inode *inode, struct file *file)也不例外:
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
struct snd_pcm *pcm;
pcm = snd_lookup_minor_data(iminor(inode),
SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
}
函数snd_lookup_minor_data()返回的是pcm,其实这个pcm是在上述函数snd_pcm_new()和snd_register_device_for_dev()这两个函数.大体流程如下:
snd_pcm_open()
->
err = snd_pcm_open_file(file, pcm, stream, &pcm_file);
->
snd_pcm_open_substream()
->
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
->
for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) {
if (SUBSTREAM_BUSY(substream))
return -EAGAIN;
}
*rsubstream = substream;
pcm_file->substream = substream;
file->private_data = pcm_file;
pcm还是那个pcm,播音录音音频流的操作集(pcm->stream->ops)在函数snd_pcm_set_ops()都被赋值为soc_pcm_ops.并且在open函数里面被保存在file->private_data.
三、static ssize_t snd_pcm_write(struct file *file, const char __user *buf,size_t count, loff_t * offset)
以write为例,看用户空间的音频数据流是如何流窜到硬件IIS的.大体流程如下:
.write = snd_pcm_write,
->
pcm_file = file->private_data;//见open()
substream = pcm_file->substream;//见open()
result = snd_pcm_lib_write(substream, buf, count);
->
return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock,snd_pcm_lib_write_transfer);
->
err = snd_pcm_start(substream);
->
return snd_pcm_action(&snd_pcm_action_start, substream,SNDRV_PCM_STATE_RUNNING);
static struct action_ops snd_pcm_action_start = {
.pre_action = snd_pcm_pre_start,
.do_action = snd_pcm_do_start,
.undo_action = snd_pcm_undo_start,
.post_action = snd_pcm_post_start
};
->
res = snd_pcm_action_single(ops, substream, state);
->
res = ops->do_action(substream, state);
即static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
->
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
即static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
->
if (cpu_dai->ops->trigger) {
ret = cpu_dai->ops->trigger(substream, cmd, cpu_dai);
if (ret < 0)
return ret;
}
->
static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,struct snd_soc_dai *dai)
因此,用户空间到底层硬件的IIS的核心流程如下:
UserSpace -> snd_pcm_f_ops -> snd_pcm_action_start -> soc_pcm_ops -> s3c24xx_i2s_trigger
一共包含了四个"逻辑层".
3-3-3.int snd_soc_add_controls(struct snd_soc_codec *codec,const struct snd_kcontrol_new *controls, int num_controls):
结束了函数snd_soc_new_pcms()之旅,继续回到函数uda134x_soc_probe()往下看:
case UDA134X_UDA1341:
ret = snd_soc_add_controls(codec, uda1341_snd_controls,ARRAY_SIZE(uda1341_snd_controls));
以上述函数snd_ctl_create()为例,其创建的设备节点/dev/snd/controlC0对应的是对codec的一些控制,比如音量.其直接面向用户空间的操作集为snd_ctl_f_ops.此函数位于sound/core/control.c,是平台无关的.那么,snd_ctl_f_ops又是如何重载到具体codec相关的呢?我们将在此函数里面找出此关联点.下面导出其简单的流程:
int snd_soc_add_controls(struct snd_soc_codec *codec,
->
err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL));
->
struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
->
snd_ctl_new1()
kctl.put = ncontrol->put;
最后的参数ncontrol即为uda1340_snd_controls:
static const struct snd_kcontrol_new uda1340_snd_controls[] = {
SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
};
展开宏SOC_SINGLE:
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
由函数put域看是如何关联到具体codec的寄存器操作的呢?下面给出流程:
.put = snd_soc_put_volsw, \
->
return snd_soc_update_bits(codec, reg, val_mask, val);
->
snd_soc_write(codec, reg, new);
->
return codec->write(codec, reg, val);
在函数static int uda134x_soc_probe(struct platform_device *pdev)我们看到以下代码:
codec->write = uda134x_write;
uda1341TS是通过L3总线接口完成其内部寄存器操作的.如下:
ret = l3_write(&pd->l3,addr, &data, 1);
因此,当用户空间通过设备节点/dev/snd/controlCn(n = 0,1,2,...).进行系统调用,以设置播放声音音量为例.简要流程如下:
==> /dev/snd/controlC 绑定 snd_ctl_f_ops //从设备节点到底层寄存器
->
static const struct file_operations snd_ctl_f_ops
->
snd_ctl_ioctl()
->
snd_ctl_elem_write_user()
->
snd_ctl_elem_write()
->
result = kctl->put(kctl, control);??疑点在这,kctl是如何跟uda1341_snd_controls关联起来的呢?见附注一:
->
uda1341_snd_controls
SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1)
->
put = snd_soc_put_volsw()
->
snd_soc_update_bits()
->
snd_soc_write()
->
codec->write()
->
codec->write = uda134x_write()
[附注一:]
static int uda134x_soc_probe(struct platform_device *pdev)
->
int snd_soc_add_controls(struct snd_soc_codec *codec,
->
err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL));
->
struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
->
snd_ctl_new1()
kctl.put = ncontrol->put;
->
.put = snd_soc_put_volsw, \
->
return snd_soc_update_bits(codec, reg, val_mask, val);
->
snd_soc_write(codec, reg, new);
->
return codec->write(codec, reg, val);
上述是用户空间通过ioctl设置音量的流程,其路由的设备节点是/dev/snd/conctolCn(n = 0,1,2,3...).最大体的流程如下:
UserSpace-->snd_ctl_f_ops-->[kctl->put]-->uda1340_snd_controls->put
因此,从上层开始到底层操作.中间路由了两层平台无关的"逻辑层".
3-3-4.int snd_soc_init_card(struct snd_soc_device *socdev)
上面我们分析了用户空间实现音频音量设置用户空间如何通过设备节点/dev/snd/controlC0到底层L3硬件总线的流程;以播放声音为例也分析了用户空间如何通过设备节点/dev/snd/pcmC0D0p到底层IIS硬件总线的流程.我们知道这两者设备节点的生成和绑定最直接面向对应用户空间的操作集的回调函数分别是snd_ctl_dev_register()和snd_pcm_dev_register()来实现.下面看这两个函数是什么时候被怎么样执行的.函数snd_ctl_dev_register()和snd_pcm_dev_register()里面都调用了snd_device_new().展开此函数见下面代码:
int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops)
{
list_add(&dev->list, &card->devices);
}
回到函数uda134x_soc_probe(),看到下面代码:
ret = snd_soc_init_card(socdev);
if (ret < 0) {
printk(KERN_ERR "UDA134X: failed to register card\n");
goto card_err;
}
回调函数便是在此函数里面回调的.大体流程如下:
snd_soc_init_card()
->
ret = snd_card_register(codec->card);
->
ret = snd_card_register(codec->card);
->
if ((err = snd_device_register_all(card)) < 0)
->
list_for_each_entry(dev, &card->devices, list) {
if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
if ((err = dev->ops->dev_register(dev)) < 0)
return err;
dev->state = SNDRV_DEV_REGISTERED;
}
}
3-4.platform->probe
static void snd_soc_instantiate_card(struct snd_soc_card *card)见下面代码:
if (platform->probe) {
ret = platform->probe(pdev);
if (ret < 0)
goto platform_err;
}
platform见上述2-3-3分析可知,是指向s3c24xx_soc_platform.如下(sound/soc/s3c24xx/s3c24xx-pcm.c):
struct snd_soc_platform s3c24xx_soc_platform = {
.name = "s3c24xx-audio",
.pcm_ops = &s3c24xx_pcm_ops,
.pcm_new = s3c24xx_pcm_new,
.pcm_free = s3c24xx_pcm_free_dma_buffers,
};
同样,probe域为空.
4.上述几个probe函数分析完毕后,回到函数snd_soc_instantiate_card(),也即将执行完毕.下面代码是防止多次初始化的:
if (card->instantiated)
return;
.
.
.
.
.
.
card->instantiated = 1;
5.platform_list
在函数static void snd_soc_instantiate_card(struct snd_soc_card *card)里面有以下代码:
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
这里用到了内核链表platform_list.其实它是在s3c24xx-pcm驱动里面初始化的.见代码sound/soc/s3c24xx/s3c24xx-pcm.c:
module_init(s3c24xx_soc_platform_init);
static int __init s3c24xx_soc_platform_init(void)
{
return snd_soc_register_platform(&s3c24xx_soc_platform);
}
函数snd_soc_register_platform()其实和上述的snd_soc_register_card()有很大的相似度.都在内部调用了函数 snd_soc_instantiate_cards().对比如下:
static int snd_soc_register_card(struct snd_soc_card *card)
{
if (!card->name || !card->dev)
return -EINVAL;
INIT_LIST_HEAD(&card->list);
card->instantiated = 0;
mutex_lock(&client_mutex);
list_add(&card->list, &card_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "Registered card '%s'\n", card->name);
return 0;
}
int snd_soc_register_platform(struct snd_soc_platform *platform)
{
if (!platform->name)
return -EINVAL;
INIT_LIST_HEAD(&platform->list);
mutex_lock(&client_mutex);
list_add(&platform->list, &platform_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
pr_debug("Registered platform '%s'\n", platform->name);
return 0;
}
6.dai_list
在函数static void snd_soc_instantiate_card(struct snd_soc_card *card)里面有以下代码:
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
这里用到内核链表dai_list,其实它是在s3c24xx-i2s驱动里面初始化的.见代码sound/soc/s3c24xx/s3c24xx-i2s.c:
module_init(s3c24xx_i2s_init);
static int __init s3c24xx_i2s_init(void)
{
return snd_soc_register_dai(&s3c24xx_i2s_dai);
}
函数snd_soc_register_dai()和函数snd_soc_register_platform()、snd_soc_register_card()都是高度相仿的.如下:
/**
* snd_soc_register_dai - Register a DAI with the ASoC core
*
* @dai: DAI to register
*/
int snd_soc_register_dai(struct snd_soc_dai *dai)
{
if (!dai->name)
return -EINVAL;
/* The device should become mandatory over time */
if (!dai->dev)
printk(KERN_WARNING "No device for DAI %s\n", dai->name);
if (!dai->ops)
dai->ops = &null_dai_ops;
INIT_LIST_HEAD(&dai->list);
mutex_lock(&client_mutex);
list_add(&dai->list, &dai_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
pr_debug("Registered DAI '%s'\n", dai->name);
return 0;
}
s3c24xx-uda1341ts驱动、s3c24xx-i2s驱动、s3c24xx-pcm驱动都会对整个内核音频子系统实行平台初始化.但是一旦一个初始化了,下面就不会再进行其初始化.见上述点4.