arm平台:IMX6Q
内核:Linux4.1.15
系统:Android6.0
codec:wm8960
主要内容为Machine、Platform和Codec三大部分之间的关系和实现。文章中的举例和代码均为imx6q-Android6.0源码和文件。
文章中如有错误和不严谨内容,欢迎指正。
一、注册platform device
kernel_imx/sound/soc/fsl/imx-wm8960.c
在文件中包含关于wm8960相关的设备结构体。设备结构体重包含snd_soc_card结构体。
struct imx_wm8960_data {
struct snd_soc_card card; //主要内容
struct clk *codec_clk;
unsigned int clk_frequency;
bool is_codec_master;
bool is_stream_in_use[2];
bool is_stream_opened[2];
struct regmap *gpr;
unsigned int hp_det[2];
u32 asrc_rate;
u32 asrc_format;
};
在imx-wm8960.c文件中注册platform driver和设备树中的Platform Device进行匹配。然后执行imx_wm8960_probe函数,注册声卡。
static struct snd_soc_ops imx_hifi_ops = {
.hw_params = imx_hifi_hw_params,
.hw_free = imx_hifi_hw_free,
.startup = imx_hifi_startup,
.shutdown = imx_hifi_shutdown,
};
static struct snd_soc_dai_link imx_wm8960_dai[] = {
{
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai_name = "wm8960-hifi",
.ops = &imx_hifi_ops,
},
{
.name = "HiFi-ASRC-FE",
.stream_name = "HiFi-ASRC-FE",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "HiFi-ASRC-BE",
.stream_name = "HiFi-ASRC-BE",
.codec_dai_name = "wm8960-hifi",
.platform_name = "snd-soc-dummy",
.no_pcm = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &imx_hifi_ops,
.be_hw_params_fixup = be_hw_params_fixup,
},
};
struct imx_wm8960_data *data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
data->card.dai_link = imx_wm8960_dai;
/*把snd_soc_card结构体放到platform_device的dev.drvdata字段*/
platform_set_drvdata(pdev, &data->card);
/*把data结构体保存到snd_soc_card结构体下的drvdata字段*/
snd_soc_card_set_drvdata(&data->card, data);
/*注册声卡到asoc核心*/
devm_snd_soc_register_card(&pdev->dev, &data->card);
machine部分的平台设备驱动代码主要是snd_soc_card结构体。有引出了machine驱动的另外两个数据结构(snd_soc_dai_link、snd_soc_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实现。
二、snd_soc_dai_link
在snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字。
codec_dai_name、cpu_dai_name两个名字必须指定。其他可以没有。
struct snd_soc_dai_link {
/* config - must be set by machine driver */
const char *name; /* Codec name */
const char *stream_name; /* Stream name */
const char *cpu_name; /*cpu控制接口name*/
const char *cpu_dai_name; /*cpu数据传输接口name*/
const char *codec_name; /*codec设备name*/
const char *codec_dai_name; /*codec数据传输接口name*/
/*
* You MAY specify the link's platform/PCM/DMA driver, either by
* device name, or by DT/OF node, but not both. Some forms of link
* do not need a platform.
*/
const char *platform_name;
...
};
/*在wm8960的machine部分中的定义*/
static struct snd_soc_dai_link imx_wm8960_dai[] = {
...
{
.name = "HiFi-ASRC-BE",
.stream_name = "HiFi-ASRC-BE",
.codec_dai_name = "wm8960-hifi",
.platform_name = "snd-soc-dummy",
.no_pcm = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &imx_hifi_ops,
.be_hw_params_fixup = be_hw_params_fixup,
},
};
/*在imx_wm8960_probe中*/
imx_wm8960_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);
三、snd_soc_register_card
kernel_imx/sound/soc/soc-core.c
目的:
- 检测snd_soc_dai_link结构体中的codec_dai_name、cpu_dai_name是否存在。
- 初始化有关链表
- 创建snd_soc_pcm_runtime结构体
- 初始化声卡(重要)
int snd_soc_register_card(struct snd_soc_card *card)
{
int i, j, ret;
if (!card->name || !card->dev)
return -EINVAL;
/*遍历snd_soc_card下的dai_link,确保codec_dai_name、cpu_dai_name存在*/
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
ret = snd_soc_init_multicodec(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init multicodec\n");
return ret;
}
for (j = 0; j < link->num_codecs; j++) {
/*
* Codec must be specified by 1 of name or OF node,
* not both or neither.
*/
if (!!link->codecs[j].name ==
!!link->codecs[j].of_node) {
dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
/* Codec DAI name must be specified */
if (!link->codecs[j].dai_name) {
dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n",
link->name);
return -EINVAL;
}
}
/*
* Platform may be specified by either name or OF node, but
* can be left unspecified, and a dummy platform will be used.
*/
if (link->platform_name && link->platform_of_node) {
dev_err(card->dev,
"ASoC: Both platform name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
/*
* CPU device may be specified by either name or OF node, but
* can be left unspecified, and will be matched based on DAI
* name alone..
*/
if (link->cpu_name && link->cpu_of_node) {
dev_err(card->dev,
"ASoC: Neither/both cpu name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
/*
* At least one of CPU DAI name or CPU device name/node must be
* specified
*/
if (!link->cpu_dai_name &&
!(link->cpu_name || link->cpu_of_node)) {
dev_err(card->dev,
"ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n",
link->name);
return -EINVAL;
}
}
dev_set_drvdata(card->dev, card);
/*内联函数
INIT_LIST_HEAD(&card->codec_dev_list);
INIT_LIST_HEAD(&card->widgets);
INIT_LIST_HEAD(&card->paths);
INIT_LIST_HEAD(&card->dapm_list);
初始化各个部件的链表
*/
snd_soc_initialize_card_lists(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];
/*把平台数据snd_soc_card结构体中的dai_link拷贝到snd_soc_pcm_runtime结构体的对应成员中*/
for (i = 0; i < card->num_links; i++) {
card->rtd[i].card = card;
card->rtd[i].dai_link = &card->dai_link[i];
card->rtd[i].codec_dais = devm_kzalloc(card->dev,
sizeof(struct snd_soc_dai *) *
(card->rtd[i].dai_link->num_codecs),
GFP_KERNEL);
if (card->rtd[i].codec_dais == NULL)
return -ENOMEM;
}
for (i = 0; i < card->num_aux_devs; i++)
card->rtd_aux[i].card = card;
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)
return ret;
/* deactivate pins to sleep state */
...
...
return ret;
}
四、snd_soc_instantiate_card
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_codec *codec;
...
/*
ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、
Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据
card->dai_link[]中的名称(name)进行匹配,匹配成功后把相应的codec、dai和platform实例赋值到card->rtd[]
中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime结构体(card->rtd)中保存了本Machine中
使用的Codec,DAI和Platform驱动的信息。
*/
/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
ret = soc_bind_dai_link(card, i);
}
/* bind aux_devs too */
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_bind_aux_dev(card, i);
}
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
snd_soc_init_codec_cache(codec);
}
/*创建声卡实例*/
snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, card->num_dapm_widgets);
/* probe all components used by DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_components(card, i, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
/* probe all DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_dais(card, i, order);//关键函数
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_probe_aux_dev(card, i);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to add auxiliary devices %d\n",
ret);
goto probe_aux_dev_err;
}
}
snd_soc_dapm_link_dai_widgets(card);
snd_soc_dapm_connect_dai_link_widgets(card);
if (card->controls)
snd_soc_add_card_controls(card, card->controls, card->num_controls);
if (card->dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);
if (card->of_dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
card->num_of_dapm_routes);
snd_soc_dapm_new_widgets(card);
snd_card_register(card->snd_card);
...
...
}
五、soc_probe_link_dais
static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
/*执行cpu_dai下的probe函数*/
ret = soc_probe_dai(cpu_dai, order);
if (ret)
return ret;
/* probe the CODEC DAI */
/*执行codec_dai下的prove函数*/
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_probe_dai(rtd->codec_dais[i], order);
if (ret)
return ret;
}
/* 调用标准的alsa的接口函数snd_pcm_new()函数用于创建标准alsa驱动的pcm逻辑设备 */
soc_new_pcm(rtd, num);
}
六、soc_new_pcm
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
ret = snd_pcm_new(rtd->card->snd_card, new_name,
num, playback, capture, &pcm); // 创建标准alsa的pcm逻辑设备
rtd->pcm = pcm; // 让snd_soc_pcm_runtime中的pcm指向刚刚创建的pcm
pcm->private_data = rtd;
/* 设置真正操作底层声卡的操作函数!!一共有3条路径 */
(1):用platform中的驱动层的ops来填充
if (platform->driver->ops) {
soc_pcm_ops.mmap = platform->driver->ops->mmap;
soc_pcm_ops.pointer = platform->driver->ops->pointer;
soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
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;
}
(2):用标准的alsa的api接口来创建ops
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
(3):调用pcm_new函数,来创建一个
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
}
}