以mini210-wm8960参考,内核版本2.6.35,(3.0内核版本不一样)学习alsa驱动相关注册及流程笔记记录如下
(部分内容参考linux audio(alsa) 驱动注册的简明流程.)
大体结构:
cpu_dai-------指的是I2C通路.
codec_dai-----codec的功能,如录音放音参数。
codec_dev-----解码设备,重点
platform------据我看只是做了pcm的操作函数和分配dma用的
整个ASoC是由一些列数据结构组成,要搞清楚ASoC的工作机理,必须要理解这一系列数据结构之间的关系和作用,下面的关系图展示了ASoC中重要的数据结构之间的关联方式:
图 Kernel-2.6.35-ASoC中各个结构的静态关系
static struct snd_soc_dai_link mini210_dai = {
.name = "MINI210",
.stream_name = "WM8960 HiFi",
//#define MAX_I2SV3 2
.cpu_dai = &s3c64xx_i2s_dai[0], //IIS2
.codec_dai = &wm8960_dai,
.init = mini210_wm8960_init,
.ops = &mini210_wm8960_ops,
};
static struct snd_soc_card mini210_soc_card = {
.name = "mini210",
.platform = &s3c_dma_wrapper, //,
.dai_link = &mini210_dai,
.num_links = 1,
};
static struct snd_soc_device mini210_snd_devdata = {
.card = &mini210_soc_card,
.codec_dev = &soc_codec_dev_wm8960,
};
.name = "MINI210",
.stream_name = "WM8960 HiFi",
//#define MAX_I2SV3 2
.cpu_dai = &s3c64xx_i2s_dai[0], //IIS2
.codec_dai = &wm8960_dai,
.init = mini210_wm8960_init,
.ops = &mini210_wm8960_ops,
};
static struct snd_soc_card mini210_soc_card = {
.name = "mini210",
.platform = &s3c_dma_wrapper, //,
.dai_link = &mini210_dai,
.num_links = 1,
};
static struct snd_soc_device mini210_snd_devdata = {
.card = &mini210_soc_card,
.codec_dev = &soc_codec_dev_wm8960,
};
1. 在文件中注册soc-audio设备,以唤醒soc-core中的probe
我们的注册如下:
mini210_snd_device = platform_device_alloc("soc-audio", -1);
if ( !mini210_snd_device ){
return -ENOMEM;
}
platform_set_drvdata( mini210_snd_device, &mini210_snd_devdata );
将这些结构通过函数platform_set_drvdata放入了platform_device的 private data中;
mini210_snd_device = platform_device_alloc("soc-audio", -1);
if ( !mini210_snd_device ){
return -ENOMEM;
}
platform_set_drvdata( mini210_snd_device, &mini210_snd_devdata );
将这些结构通过函数platform_set_drvdata放入了platform_device的 private data中;
omini210_snd_device 中包括card和codec_dev, card中包括snd_soc_dai_link,
snd_soc_dai_link中包括cpu_dai,和codec_dai, 这些比较简单,不需要赘述了.
按照Linux的设备模型,有platform_device,就一定会有platform_driver。ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。
我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照Linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。
我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照Linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个ASoC驱动初始化的入口。
soc_probe->snd_soc_register_card->snd_soc_instantiate_cards->snd_soc_instantiate_card
在snd_soc_instantiate_card中匹配platform
/*匹配platform*/
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
接下来匹配cpu_dai
found = 0;
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
...
}
再是codec_dai
if (card->dai_link[i].codec_dai == dai)...
如果都找到了,就进行card->probe, 一般都没有。
if (card->probe)
再进行cpu_dai的初始化,就是i2s接口的初始化。
ret = cpu_dai->probe(pdev, cpu_dai);
再进行codec_dev的probe, 就是soc_codec_dev_wm8960
ret = codec_dev->probe(pdev); //codec_dev.probe->mini210_snd_devdata->soc_codec_dev_wm8960->wm8960_probe
这个probe一般也都没有。
ret = platform->probe(pdev);
初始化等待队列
INIT_DELAYED_WORK(&card->delayed_work, close_delayed_work);
重头戏来了, codec_dev的probe
static int wm8960_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
int ret = 0;
if (wm8960_codec == NULL) {
dev_err(&pdev->dev, "Codec device not registered\n");
return -ENODEV;
}
socdev->card->codec = wm8960_codec;
codec = wm8960_codec;
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
goto pcm_err;
}
snd_soc_add_controls(codec, wm8960_snd_controls,
ARRAY_SIZE(wm8960_snd_controls));
wm8960_add_widgets(codec);
return ret;
pcm_err:
return ret;
}
static int wm8960_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
int ret = 0;
if (wm8960_codec == NULL) {
dev_err(&pdev->dev, "Codec device not registered\n");
return -ENODEV;
}
socdev->card->codec = wm8960_codec;
codec = wm8960_codec;
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
goto pcm_err;
}
snd_soc_add_controls(codec, wm8960_snd_controls,
ARRAY_SIZE(wm8960_snd_controls));
wm8960_add_widgets(codec);
return ret;
pcm_err:
return ret;
}
1. snd_soc_new_pcms中调用snd_card_create创建1个基于PCM的声卡设备。
snd_card_create创建声卡,进入snd_card_create中,
snd_ctl_create定义声卡device的操作方法,snd_device_new创建出声卡设备并注册到链表中。
2. 回到snd_soc_new_pcms中,看soc_new_pcm。
将codec与dai建立链接,snd_pcm_new中先创建SNDRV_PCM_STREAM_PLAYBACK和SNDRV_PCM_STREAM_CAPTURE流
然后snd_device_new创建pcm的device.
3. 回到soc_new_pcm中为soc_pcm_ops赋值,其实就是将platform中的ops全部指过来。
4. 然后调用platform中的new, 创建dma.
ret = platform->pcm_new(codec->card, codec_dai, pcm);
至此PCM的数据路就注册好了,下面就要注册控制信息了,就是snd_kcontrol_new结构体。
ARRAY_SIZE(wm8960_snd_controls));
wm8960_add_widgets(codec);