Yuhang Zheng

基于i.MX6X的音频驱动分析(三)音频驱动ASoC的机器层

N 人看过

前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分。

其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:

Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧

注册Platform Device

ASoC把声卡注册为Platform Device,以WM8960为例 ,涉及文件:sound/soc/imx/imx-wm8960.c

其模块初始化函数为:module_init(imx_asoc_init);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static int __init imx_asoc_init(void)
{
int ret;

ret = platform_driver_register(&imx_wm8960_driver);
if (ret < 0)
goto exit;

if (machine_is_mx6q_sabresd())
imx_dai[0].codec_name = "wm8960-codec.0-001a";
else if (machine_is_mx6sl_arm2() machine_is_mx6sl_evk())
imx_dai[0].codec_name = "wm8960.1-001a";

imx_snd_device = platform_device_alloc("soc-audio", 6);
if (!imx_snd_device)
goto err_device_alloc;

platform_set_drvdata(imx_snd_device, &snd_soc_card_imx);

ret = platform_device_add(imx_snd_device);

if (0 == ret)
goto exit;

platform_device_put(imx_snd_device);

err_device_alloc:
platform_driver_unregister(&imx_wm8960_driver);
exit:
return ret;
}

由此可见,模块初始化时,注册了一个名为soc-audio的Platform设备,同时把snd_soc_card_imx设到platform_device结构的dev.drvdata字段中。

这里引出了第一个数据结构snd_soc_card的实例snd_soc_card_imx,它的定义如下(数据结构snd_soc_card的定义在include/sound/soc.h):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static struct snd_soc_dai_link imx_dai[] = {
{
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai_name = "wm8960-hifi",//指定codec dai的名字
.codec_name = "wm8960-codec.0-001a",//指定codec的名字
.cpu_dai_name = "imx-ssi.1", //指定cpu dai的名字
.platform_name = "imx-pcm-audio.1",//指定platform的名字
.init = imx_wm8960_init,
.ops = &imx_hifi_ops,
},
};

static struct snd_soc_card snd_soc_card_imx = {
.name = "wm8960-audio",
.dai_link = imx_dai,
.num_links = ARRAY_SIZE(imx_dai),
};

通过snd_soc_card结构,又引出了Machine驱动的另外两个数据结构:

  • snd_soc_dai_link(实例:imx_dai[] )
  • snd_soc_ops(实例:imx_hifi_ops )

snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的,这样看来,Machine驱动的设备初始化代码无非就是选择合适Platform和Codec以及dai,用他们填充以上几个数据结构,然后注册Platform设备即可。当然还要实现连接Platform和Codec的dai_link对应的ops实现,本例中就是imx_hifi_ops:

1
2
3
4
5
6
7
static struct snd_soc_ops imx_hifi_ops = {
.startup = imx_hifi_startup,
.shutdown = imx_hifi_shutdown,
.hw_params = imx_hifi_hw_params,
.hw_free = imx_hifi_hw_free,
.trigger = imx_hifi_trigger,
};

注册Platform Driver

按照Linux的设备模型,有platform_device,就一定会有platform_driver。ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。

还是先从模块的入口看起:

1
2
3
4
5
6
static int __init snd_soc_init(void)
{
...
return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);

soc_driver的定义如下:

1
2
3
4
5
6
7
8
9
10
/* 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,
};

我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照Linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。

初始化入口soc_probe()

soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);//从platform_device参数中取出snd_soc_card
int ret = 0;

/*
* no card, so machine driver should be registering card
* we should not be here in that case so ret error
*/
if (!card)
return -EINVAL;

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

ret = snd_soc_register_card(card);//为snd_soc_pcm_runtime数组申请内存
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 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;

if (!card->name !card->dev)
return -EINVAL;

dev_set_drvdata(card->dev, card);

snd_soc_initialize_card_lists(card);

soc_init_card_debugfs(card);

card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);
if (card->rtd == NULL)
return -ENOMEM;
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];

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

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;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);
1
2
3
4
5
6
7
8
9
10
/*
* Attempt to initialise any uninitialised 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中实现,下面就看看snd_soc_instantiate_card做了些什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_codec *codec;
struct snd_soc_codec_conf *codec_conf;
enum snd_soc_compress_type compress_type;
int ret, i;

mutex_lock(&card->mutex);

if (card->instantiated) {
mutex_unlock(&card->mutex);
return;
}

/* bind DAIs */
for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i);

/* bind completed ? */
if (card->num_rtd != card->num_links) {
mutex_unlock(&card->mutex);
return;
}

/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init)
continue;
/* by default we don't override the compress_type */
compress_type = 0;
/* check to see if we need to override the compress_type */
for (i = 0; i < card->num_configs; ++i) {
codec_conf = &card->codec_conf[i];
if (!strcmp(codec->name, codec_conf->dev_name)) {
compress_type = codec_conf->compress_type;
if (compress_type && compress_type
!= codec->compress_type)
break;
}
}
ret = snd_soc_init_codec_cache(codec, compress_type);
if (ret < 0) {
mutex_unlock(&card->mutex);
return;
}
}

/* card bind complete so register a sound card */
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create sound card for card %s\n",
card->name);
mutex_unlock(&card->mutex);
return;
}
card->snd_card->dev = card->dev;

card->dapm.bias_level = SND_SOC_BIAS_OFF;
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);

/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}

for (i = 0; i < card->num_links; i++) {
ret = soc_probe_dai_link(card, i);
if (ret < 0) {
pr_err("asoc: failed to instantiate card %s: %d\n",
card->name, ret);
goto probe_dai_err;
}
}

for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_probe_aux_dev(card, i);
if (ret < 0) {
pr_err("asoc: failed to add auxiliary devices %s: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}
}

/* We should have a non-codec control add function but we don't */
if (card->controls)
snd_soc_add_controls(list_first_entry(&card->codec_dev_list,
struct snd_soc_codec,
card_list),
card->controls,
card->num_controls);

if (card->dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);

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);
if (ret < 0) {
dev_err(card->dev, "%s late_probe() failed: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}
}

ret = snd_card_register(card->snd_card);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
goto probe_aux_dev_err;
}

#ifdef CONFIG_SND_SOC_AC97_BUS
/* register any AC97 codecs */
for (i = 0; i < card->num_rtd; i++) {
ret = soc_register_ac97_dai_link(&card->rtd[i]);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
while (--i >= 0)
soc_unregister_ac97_dai_link(card->rtd[i].codec);
goto probe_aux_dev_err;
}
}
#endif

card->instantiated = 1;
mutex_unlock(&card->mutex);
return;

probe_aux_dev_err:
for (i = 0; i < card->num_aux_devs; i++)
soc_remove_aux_dev(card, i);

probe_dai_err:
soc_remove_dai_links(card);

card_probe_error:
if (card->remove)
card->remove(card);

snd_card_free(card->snd_card);

mutex_unlock(&card->mutex);
}

该函数首先利用card->instantiated来判断该卡是否已经实例化,如果已经实例化则直接返回,否则遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。

1
2
3
/* bind DAIs */
for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i);

ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。

snd_soc_instantiate_card接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* card bind complete so register a sound card */
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create sound card for card %s\n",
card->name);
mutex_unlock(&card->mutex);
return;
}
card->snd_card->dev = card->dev;

card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list);

然后,依次调用各个子结构的probe函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}

for (i = 0; i < card->num_links; i++) {
ret = soc_probe_dai_link(card, i);
if (ret < 0) {
pr_err("asoc: failed to instantiate card %s: %d\n",
card->name, ret);
goto probe_dai_err;
}
}

for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_probe_aux_dev(card, i);
if (ret < 0) {
pr_err("asoc: failed to add auxiliary devices %s: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}
}

在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
static int soc_probe_dai_link(struct snd_soc_card *card, int num)
{
...
/* set default power off timeout */
rtd->pmdown_time = pmdown_time;

/* probe the cpu_dai */
if (!cpu_dai->probed) {
if (cpu_dai->driver->probe) {
ret = cpu_dai->driver->probe(cpu_dai);
}
cpu_dai->probed = 1;
/* mark cpu_dai as probed and add to card cpu_dai list */
list_add(&cpu_dai->card_list, &card->dai_dev_list);
}

/* probe the CODEC */
if (!codec->probed) {
ret = soc_probe_codec(card, codec);
}

/* probe the platform */
if (!platform->probed) {
if (platform->driver->probe) {
ret = platform->driver->probe(platform);
}
/* mark platform as probed and add to card platform list */
platform->probed = 1;
list_add(&platform->card_list, &card->platform_dev_list);
}

/* probe the CODEC DAI */
if (!codec_dai->probed) {
if (codec_dai->driver->probe) {
ret = codec_dai->driver->probe(codec_dai);
}

/* mark cpu_dai as probed and add to card cpu_dai list */
codec_dai->probed = 1;
list_add(&codec_dai->card_list, &card->dai_dev_list);
}

/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

ret = soc_post_component_init(card, codec, num, 0);
if (ret)
return ret;

ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);

/* create the pcm */
ret = soc_new_pcm(rtd, num);

/* add platform data for AC97 devices */
if (rtd->codec_dai->driver->ac97_control)
snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);

return 0;
}

该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。现在把该函数的部分代码也贴出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* create a new pcm */
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_pcm_ops *soc_pcm_ops;

rtd->pcm = pcm;
pcm->private_data = rtd;

soc_pcm_ops->open = soc_pcm_open;
soc_pcm_ops->close = soc_codec_close;
soc_pcm_ops->hw_params = soc_pcm_hw_params;
soc_pcm_ops->hw_free = soc_pcm_hw_free;
soc_pcm_ops->prepare = soc_pcm_prepare;
soc_pcm_ops->pointer = soc_pcm_pointer;
soc_pcm_ops->trigger = soc_pcm_trigger;
soc_pcm_ops->ioctl = soc_pcm_ioctl;

if (platform->driver->ops) {
soc_pcm_ops->mmap = platform->driver->ops->mmap;
soc_pcm_ops->copy = platform->driver->ops->copy;
soc_pcm_ops->silence = platform->driver->ops->silence;
soc_pcm_ops->ack = platform->driver->ops->ack;
soc_pcm_ops->page = platform->driver->ops->page;
}

rtd->ops = soc_pcm_ops;

if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);

if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);

if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd->card->snd_card,
codec_dai, pcm);
if (ret < 0) {
pr_err("asoc: platform pcm constructor failed\n");
kfree(rtd->ops);
return ret;
}
}

pcm->private_free = platform->driver->pcm_free;
printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,
cpu_dai->name);
return ret;
}

该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。

回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (card->late_probe) {
ret = card->late_probe(card);
if (ret < 0) {
dev_err(card->dev, "%s late_probe() failed: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}
}

ret = snd_card_register(card->snd_card);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
goto probe_aux_dev_err;
}

至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,整个过程可以用以下的序列图表示:

基于3.0内核的soc probe序列图