Linux的ALSA声卡驱动较为复杂,它需要注册多个平台设备。在mach-zhaocj2440.c文件中的平台设备数组内一共有四个与ALSA相关的平台设备:
&s3c_device_iis,
&uda1340_codec,
&mini2440_audio,
&samsung_asoc_dma,
mini2440_audio和uda1340_codec的定义在该文件内给出:
/*本开发板所用到的是UDA1341芯片,而且S3C2440是用三个通用IO口来模拟L3总线*/
static struct s3c24xx_uda134x_platform_data mini2440_audio_pins = {
.l3_clk= S3C2410_GPB(4), //定义引脚
.l3_mode= S3C2410_GPB(2),
.l3_data= S3C2410_GPB(3),
.model= UDA134X_UDA1341
};
static struct platform_devicemini2440_audio = {
.name = "s3c24xx_uda134x",
.id = 0,
.dev = {
.platform_data = &mini2440_audio_pins,
},
};
static struct platform_device uda1340_codec= {
.name= "uda134x-codec",
.id= -1,
};
s3c_device_iis和samsung_asoc_dma是在arch/arm/plat-samsung/devs.c内定义的:
static struct resource s3c_iis_resource[] = {
[0]= DEFINE_RES_MEM(S3C24XX_PA_IIS,S3C24XX_SZ_IIS),
};
struct platform_device s3c_device_iis = {
.name = "s3c24xx-iis",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_iis_resource),
.resource = s3c_iis_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
struct platform_device samsung_asoc_dma = {
.name = "samsung-audio",
.id = -1,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
有了平台设备,就一定要有平台驱动才能工作,下面就逐一介绍与之匹配的平台驱动及工作流程。Linux3.6.6版本与之前的版本在ALSA声卡驱动上的结构有一些差异,所以还需要注意。
我们先来看名为“uda134x-codec”的设备驱动,它是在sound/soc/codecs/uda134x.c文件内给出的:
static struct platform_driveruda134x_codec_driver = {
.driver= {
.name= "uda134x-codec",
.owner= THIS_MODULE,
},
.probe= uda134x_codec_probe,
.remove= __devexit_p(uda134x_codec_remove),
};
设备和驱动的名字匹配后,就会调用.probe,这里是uda134x_codec_probe函数:
static int __devinituda134x_codec_probe(struct platform_device *pdev)
{
returnsnd_soc_register_codec(&pdev->dev,
&soc_codec_dev_uda134x,&uda134x_dai, 1);
}
在这个函数中,主要是调用了snd_soc_register_codec函数,并用到了两个全局变量soc_codec_dev_uda134x和uda134x_dai,它们的定义为:
//CODEC的驱动结构
static struct snd_soc_codec_driversoc_codec_dev_uda134x = {
.probe= uda134x_soc_probe,
.remove= uda134x_soc_remove,
.suspend= uda134x_soc_suspend,
.resume= uda134x_soc_resume,
.reg_cache_size= sizeof(uda134x_reg),
.reg_word_size= sizeof(u8),
.reg_cache_default= uda134x_reg,
.reg_cache_step= 1,
.read= uda134x_read_reg_cache,
.write= uda134x_write,
.set_bias_level= uda134x_set_bias_level,
};
//dai驱动结构
static struct snd_soc_dai_driveruda134x_dai = {
.name= "uda134x-hifi",
/*playback capabilities */
.playback= { //放音
.stream_name= "Playback",
.channels_min= 1,
.channels_max= 2,
.rates= UDA134X_RATES,
.formats= UDA134X_FORMATS,
},
/*capture capabilities */
.capture= { //录音
.stream_name= "Capture",
.channels_min= 1,
.channels_max= 2,
.rates= UDA134X_RATES,
.formats= UDA134X_FORMATS,
},
/*pcm operations */
.ops= &uda134x_dai_ops,
};
再来看snd_soc_register_codec函数,它在sound/soc/soc-core.c被定义:
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)
{
size_treg_size;
structsnd_soc_codec *codec;
intret, i;
dev_dbg(dev,"codec register %s\n", dev_name(dev));
//开辟一段内存空间给snd_soc_codec结构
codec= kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if(codec == NULL)
return-ENOMEM;
/*create CODEC component name */
//创建一个唯一的名字
codec->name= fmt_single_name(dev, &codec->id);
if(codec->name == NULL) {
kfree(codec);
return-ENOMEM;
}
//soc_codec_dev_uda134x没有定义compress_type,所以执行else语句
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; // uda134x_write
codec->read= codec_drv->read; // uda134x_read_reg_cache
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; // uda1340_codec平台设备
codec->driver= codec_drv; // soc_codec_dev_uda134x
codec->num_dai= num_dai; //1
mutex_init(&codec->mutex);
/*allocate CODEC register cache */
//soc_codec_dev_uda134x定义了reg_cache_size和reg_word_size,所以执行if内容
if(codec_drv->reg_cache_size && codec_drv->reg_word_size) {
reg_size= codec_drv->reg_cache_size * codec_drv->reg_word_size;
codec->reg_size= reg_size;
/*it is necessary to make a copy of the default register cache
* because in the case of using a compressiontype that requires
* the default register cache to be marked as__devinitconst the
* kernel might have freed the array by thetime we initialize
* the cache.
*/
//soc_codec_dev_uda134x定义了reg_cache_default,所以执行if内容
if(codec_drv->reg_cache_default) {
codec->reg_def_copy= kmemdup(codec_drv->reg_cache_default,
reg_size, GFP_KERNEL);
if(!codec->reg_def_copy) {
ret= -ENOMEM;
gotofail;
}
}
}
//soc_codec_dev_uda134x没有定义了reg_access_size,所以不执行if内容
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++) {
//修正DAI格式的字节顺序
fixup_codec_formats(&dai_drv[i].playback);
fixup_codec_formats(&dai_drv[i].capture);
}
mutex_lock(&client_mutex);
//把该函数定义的CODEC添加到CODEC列表中
list_add(&codec->list,&codec_list);
mutex_unlock(&client_mutex);
/*register any DAIs */
if(num_dai) {
//注册DAI
ret= snd_soc_register_dais(dev, dai_drv, num_dai);
if(ret < 0)
dev_err(codec->dev,"Failed to regster DAIs: %d\n",
ret);
}
pr_debug("Registeredcodec '%s'\n", codec->name);
return0;
fail:
kfree(codec->reg_def_copy);
codec->reg_def_copy= NULL;
kfree(codec->name);
kfree(codec);
returnret;
}
下面我们看注册DAIs函数:
int snd_soc_register_dais(struct device*dev,
structsnd_soc_dai_driver *dai_drv, size_t count)
{
structsnd_soc_codec *codec;
structsnd_soc_dai *dai;
inti, ret = 0;
dev_dbg(dev,"dai register %s #%Zu\n", dev_name(dev), count);
for(i = 0; i < count; i++) {
//为DAI开辟内存空间
dai= kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
if(dai == NULL) {
ret= -ENOMEM;
gotoerr;
}
/*create DAI component name */
//创建一个唯一的名字
dai->name= fmt_multiple_name(dev, &dai_drv[i]);
if(dai->name == NULL) {
kfree(dai);
ret= -EINVAL;
gotoerr;
}
dai->dev= dev; // uda1340_codec平台设备
dai->driver= &dai_drv[i]; // uda134x_dai
//uda134x_dai没有定义id,所以执行else语句
if(dai->driver->id)
dai->id= dai->driver->id;
else
dai->id= i;
dai->dapm.dev= dev;
//uda134x_dai定义了ops,为uda134x_dai_ops,所以不执行if内容
if(!dai->driver->ops)
dai->driver->ops= &null_dai_ops;
mutex_lock(&client_mutex);
/*遍历CODEC列表中的所有CODEC,在snd_soc_register_codec函数内,已经添加了一个CODEC,在这里,就把这个CODEC提取出来*/
list_for_each_entry(codec,&codec_list, list) {
if(codec->dev == dev) { //相同,都是uda1340_codec平台设备
dev_dbg(dev,"Mapped DAI %s to CODEC %s\n",
dai->name,codec->name);
dai->codec= codec; //赋值
break;
}
}
//把该函数定义的DAI添加到DAI列表中
list_add(&dai->list,&dai_list);
mutex_unlock(&client_mutex);
pr_debug("RegisteredDAI '%s'\n", dai->name);
}
return0;
err:
for(i--; i >= 0; i--)
snd_soc_unregister_dai(dev);
returnret;
}
通过上面两个函数,我们在CODEC和DAI列表中分别有了一项内容。
下面我们再来看名为“s3c24xx-iis”的平台驱动,它是在sound/soc/samsung/s3c24xx-i2s.c文件内被定义的:
static struct platform_driver s3c24xx_iis_driver = {
.probe = s3c24xx_iis_dev_probe,
.remove= __devexit_p(s3c24xx_iis_dev_remove),
.driver= {
.name= "s3c24xx-iis",
.owner= THIS_MODULE,
},
};
再来看.probe函数:
static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)
{
returnsnd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
}
在这个函数中,主要是调用了snd_soc_register_dai函数,并用到了一个全局变量s3c24xx_i2s_dai,它的定义为:
static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
.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_iis_dev_probe函数调用的是snd_soc_register_dai函数,而上面介绍的函数是snd_soc_register_dais,之间差了一个“s”,从函数名就可以猜出一个是注册一个DAI,而另一个是注册一群DAIs。snd_soc_register_dai函数也是在sound/soc/soc-core.c被定义:
int snd_soc_register_dai(struct device*dev,
structsnd_soc_dai_driver *dai_drv)
{
structsnd_soc_codec *codec;
structsnd_soc_dai *dai;
dev_dbg(dev,"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);
if(dai->name == NULL) {
kfree(dai);
return-ENOMEM;
}
//这里的dev是s3c_device_iis结构中的dev
dai->dev= dev;
dai->driver= dai_drv; // s3c24xx_i2s_dai
dai->dapm.dev= dev;
//s3c24xx_i2s_dai定义了ops,为s3c24xx_i2s_dai_ops,所以不执行if内容
if(!dai->driver->ops)
dai->driver->ops= &null_dai_ops;
mutex_lock(&client_mutex);
//遍历CODEC列表
list_for_each_entry(codec,&codec_list, list) {
//在这里,两者不相等,所以不能执行if内容
if(codec->dev == dev) {
dev_dbg(dev,"Mapped DAI %s to CODEC %s\n",
dai->name,codec->name);
dai->codec= codec;
break;
}
}
//把该函数内定义的DAI添加进DAI列表中
list_add(&dai->list,&dai_list);
mutex_unlock(&client_mutex);
pr_debug("RegisteredDAI '%s'\n", dai->name);
return0;
}
运行完上面的函数后,DAI列表中一共有了两项内容。
下面给出名为“samsung-audio”的平台驱动,它在sound/soc/samsung/dma.c文件内被定义:
static struct platform_driverasoc_dma_driver = {
.driver= {
.name= "samsung-audio",
.owner= THIS_MODULE,
},
.probe= samsung_asoc_platform_probe,
.remove= __devexit_p(samsung_asoc_platform_remove),
};
再给出它的.probe函数:
static int __devinitsamsung_asoc_platform_probe(struct platform_device *pdev)
{
returnsnd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
}
在这个函数中,主要是调用了snd_soc_register_platform函数,并用到了一个全局变量samsung_asoc_platform,它的定义为:
static struct snd_soc_platform_driversamsung_asoc_platform = {
.ops = &dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
snd_soc_register_platform函数在sound/soc/soc-core.c被定义,为:
int snd_soc_register_platform(struct device*dev,
structsnd_soc_platform_driver *platform_drv)
{
structsnd_soc_platform *platform;
dev_dbg(dev,"platform register %s\n", dev_name(dev));
//开辟内存空间
platform= kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
if(platform == NULL)
return-ENOMEM;
/*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; // 为samsung_asoc_platform
platform->dapm.dev= dev;
platform->dapm.platform= platform;
platform->dapm.stream_event= platform_drv->stream_event;
mutex_init(&platform->mutex);
mutex_lock(&client_mutex);
//把该函数定义的platform添加进列表中
list_add(&platform->list,&platform_list);
mutex_unlock(&client_mutex);
pr_debug("Registeredplatform '%s'\n", platform->name);
return0;
}
这时,在platform列表内也有了一项内容。
我们最后来看名为“s3c24xx_uda134x”的设备驱动,它是在sound/soc/samsung/s3c24xx_uda134x.c文件内给出的。
static struct platform_driver s3c24xx_uda134x_driver = {
.probe = s3c24xx_uda134x_probe,
.remove= s3c24xx_uda134x_remove,
.driver= {
.name= "s3c24xx_uda134x",
.owner= THIS_MODULE,
},
};
设备和驱动的名字匹配后,就会调用.probe,这里是s3c24xx_uda134x_probe:
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
intret;
printk(KERN_INFO"S3C24XX_UDA134X SoC Audiodriver\n");
//提取出定义好的模拟L3总线的2440通用IO口,s3c24xx_uda134x_l3_pins为全局变量
s3c24xx_uda134x_l3_pins =pdev->dev.platform_data;
//如果没有定义,则退出
if(s3c24xx_uda134x_l3_pins ==NULL) {
printk(KERN_ERR"S3C24XX_UDA134X SoC Audio:"
"unable to find platformdata\n");
return-ENODEV;
}
//为s3c24xx_uda134x赋值,s3c24xx_uda134x也是全局变量
s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;
//为L3_DATA、L3_CLK和L3_MODE配置IO
if(s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,
"data") < 0)
return-EBUSY;
if(s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,
"clk") < 0) {
gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
return-EBUSY;
}
if(s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,
"mode") < 0) {
gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
return-EBUSY;
}
//申请一个名为“soc-audio”的平台设备,该设备就是声卡
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;
}
//为声卡赋值,内容为全局变量snd_soc_s3c24xx_uda134x
platform_set_drvdata(s3c24xx_uda134x_snd_device,
&snd_soc_s3c24xx_uda134x);
//为声卡添加数据s3c24xx_uda134x
platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
//把声卡添加进系统内
ret= platform_device_add(s3c24xx_uda134x_snd_device);
if(ret) {
printk(KERN_ERR"S3C24XX_UDA134X SoC Audio:Unable to add\n");
platform_device_put(s3c24xx_uda134x_snd_device);
}
returnret;
}
在该函数内用到了一个很重要的全局变量——snd_soc_s3c24xx_uda134x:
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name= "S3C24XX_UDA134X",
.owner= THIS_MODULE,
.dai_link= &s3c24xx_uda134x_dai_link,
.num_links= 1,
};
s3c24xx_uda134x_dai_link为DAI接口连接结构:
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name= "UDA134X",
.stream_name= "UDA134X",
.codec_name= "uda134x-codec",
.codec_dai_name= "uda134x-hifi",
.cpu_dai_name= "s3c24xx-iis",
.ops= &s3c24xx_uda134x_ops,
.platform_name = "samsung-audio",
};
再回顾一下该函数,它定义了一个名为“soc-audio”声卡平台设备,要想使该声卡真正起作用,需要声卡平台驱动。声卡平台驱动在sound/soc/soc-core.c文件内给出:
static struct platform_driver soc_driver ={
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
该平台驱动已经在系统启动时被注册。这样系统就会调用soc_probe函数:
static int soc_probe(struct platform_device*pdev)
{
structsnd_soc_card *card = platform_get_drvdata(pdev); //得到声卡设备
intret = 0;
/*
* no card, so machine driver should beregistering card
* we should not be here in that case so reterror
*/
if(!card)
return-EINVAL;
dev_warn(&pdev->dev,
"ASoC machine %s should usesnd_soc_register_card()\n",
card->name);
/*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");
returnret;
}
return0;
}
重要的snd_soc_register_card函数:
int snd_soc_register_card(structsnd_soc_card *card)
{
inti, ret;
if(!card->name || !card->dev)
return-EINVAL;
//遍历系统中的所有声卡设备,在这里只有一个声卡
for(i = 0; i < card->num_links; i++) {
//提取出DAI接口连接,即s3c24xx_uda134x_dai_link
structsnd_soc_dai_link *link = &card->dai_link[i];
/*
* Codec must be specified by 1 of name or OFnode,
* not both or neither.
*/
//这里定义了codec_name,为uda134x-codec,所以不执行if的内容
if(!!link->codec_name == !!link->codec_of_node) {
dev_err(card->dev,
"Neither/bothcodec name/of_node are set for %s\n",
link->name);
return-EINVAL;
}
/*Codec DAI name must be specified */
//这里定义了codec_dai_name,为uda134x-hifi,所以不执行if的内容
if(!link->codec_dai_name) {
dev_err(card->dev,"codec_dai_name not set for %s\n",
link->name);
return-EINVAL;
}
/*
* Platform may be specified by either name orOF node, but
* can be left unspecified, and a dummyplatform will be used.
*/
//这里定义了platform_name,为samsung-audio,所以不执行if的内容
if(link->platform_name && link->platform_of_node) {
dev_err(card->dev,
"Bothplatform name/of_node are set for %s\n", link->name);
return-EINVAL;
}
/*
* CPU device may be specified by either nameor OF node, but
* can be left unspecified, and will be matchedbased on DAI
* name alone..
*/
//这里没有定义了cpu_name和cpu_of_node,所以不执行if的内容
if(link->cpu_name && link->cpu_of_node) {
dev_err(card->dev,
"Neither/bothcpu name/of_node are set for %s\n",
link->name);
return-EINVAL;
}
/*
* At least one of CPU DAI name or CPU devicename/node must be
* specified
*/
//这里定义了cpu_dai_name,为s3c24xx-iis,所以不执行if的内容
if(!link->cpu_dai_name &&
!(link->cpu_name ||link->cpu_of_node)) {
dev_err(card->dev,
"Neithercpu_dai_name nor cpu_name/of_node are set for %s\n",
link->name);
return-EINVAL;
}
}
//设置声卡设备驱动信息
dev_set_drvdata(card->dev,card);
//初始化声卡设备列表
snd_soc_initialize_card_lists(card);
soc_init_card_debugfs(card);
//为声卡中的snd_soc_pcm_runtime数据结构分配内存空间
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];
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);
returnret;
}
初始化声卡函数:
static int snd_soc_instantiate_card(structsnd_soc_card *card)
{
structsnd_soc_codec *codec;
structsnd_soc_codec_conf *codec_conf;
enumsnd_soc_compress_type compress_type;
structsnd_soc_dai_link *dai_link;
intret, i, order, dai_fmt;
mutex_lock_nested(&card->mutex,SND_SOC_CARD_CLASS_INIT);
/*bind DAIs */
for(i = 0; i < card->num_links; i++) {
//逐一绑定声卡的各类DAI链接,下面会详细介绍该函数
ret= soc_bind_dai_link(card, i);
if(ret != 0)
gotobase_error;
}
/*check aux_devs too */
//snd_soc_s3c24xx_uda134x没有定义num_aux_devs,所以不执行for内容
for(i = 0; i < card->num_aux_devs; i++) {
ret= soc_check_aux_dev(card, i);
if(ret != 0)
gotobase_error;
}
/*initialize the register cache for each available codec */
//遍历CODEC列表中的所有CODEC
list_for_each_entry(codec,&codec_list, list) {
//CODEC缓存是否已初始化
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;
}
}
/*初始化CODEC缓存,该函数最终调用sound/soc/soc-cache.c文件内的snd_soc_flat_cache_init函数,为缓存(reg_cache)开辟内存空间,并把codec->cache_init置为1*/
ret= snd_soc_init_codec_cache(codec, compress_type);
if(ret < 0)
gotobase_error;
}
/*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) {
pr_err("asoc:can't create sound card for card %s: %d\n",
card->name,ret);
gotobase_error;
}
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 */
//这里没有定义probe的回调函数,所以不执行if内容
if(card->probe) {
ret= card->probe(card);
if(ret < 0)
gotocard_probe_error;
}
/*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++) {
//probe所有声卡上被用来DAI链接的成员,下面会详细介绍
ret= soc_probe_link_components(card, i, order);
if(ret < 0) {
pr_err("asoc:failed to instantiate card %s: %d\n",
card->name, ret);
gotoprobe_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++) {
/*probe所有声卡上的DAI链接,主要执行两个任务:一是调用了s3c24xx_i2s_probe函数,完成i2s控制器时钟启动和s3c24xx的i2s对应的io口配置初始化;二是执行了soc_new_pcm函数来创建PCM*/
ret= soc_probe_link_dais(card, i, order);
if(ret < 0) {
pr_err("asoc:failed to instantiate card %s: %d\n",
card->name, ret);
gotoprobe_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);
gotoprobe_aux_dev_err;
}
}
snd_soc_dapm_link_dai_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);
snd_soc_dapm_new_widgets(&card->dapm);
for(i = 0; i < card->num_links; i++) {
dai_link= &card->dai_link[i];
dai_fmt= dai_link->dai_fmt;
if(dai_fmt) {
//配置DAI硬件音频格式
ret= snd_soc_dai_set_fmt(card->rtd[i].codec_dai,
dai_fmt);
if(ret != 0 && ret != -ENOTSUPP)
dev_warn(card->rtd[i].codec_dai->dev,
"Failed to set DAI format: %d\n",
ret);
}
/*If this is a regular CPU link there will be a platform */
if(dai_fmt &&
(dai_link->platform_name ||dai_link->platform_of_node)) {
ret= snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
dai_fmt);
if(ret != 0 && ret != -ENOTSUPP)
dev_warn(card->rtd[i].cpu_dai->dev,
"Failed to set DAI format: %d\n",
ret);
}else if (dai_fmt) {
/*Flip the polarity for the "CPU" end */
dai_fmt&= ~SND_SOC_DAIFMT_MASTER_MASK;
switch(dai_link->dai_fmt &
SND_SOC_DAIFMT_MASTER_MASK){
caseSND_SOC_DAIFMT_CBM_CFM:
dai_fmt|= SND_SOC_DAIFMT_CBS_CFS;
break;
caseSND_SOC_DAIFMT_CBM_CFS:
dai_fmt|= SND_SOC_DAIFMT_CBS_CFM;
break;
caseSND_SOC_DAIFMT_CBS_CFM:
dai_fmt|= SND_SOC_DAIFMT_CBM_CFS;
break;
caseSND_SOC_DAIFMT_CBS_CFS:
dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
break;
}
ret= snd_soc_dai_set_fmt(card->rtd[i].cpu_dai,
dai_fmt);
if(ret != 0 && ret != -ENOTSUPP)
dev_warn(card->rtd[i].cpu_dai->dev,
"Failed to set DAI format: %d\n",
ret);
}
}
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);
gotoprobe_aux_dev_err;
}
}
snd_soc_dapm_new_widgets(&card->dapm);
if(card->fully_routed)
list_for_each_entry(codec,&card->codec_dev_list, card_list)
snd_soc_dapm_auto_nc_codec_pins(codec);
//注册声卡
ret= snd_card_register(card->snd_card);
if(ret < 0) {
pr_err("asoc:failed to register soundcard for %s: %d\n",
card->name,ret);
gotoprobe_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) {
pr_err("asoc:failed to register AC97 %s: %d\n",
card->name,ret);
while(--i >= 0)
soc_unregister_ac97_dai_link(card->rtd[i].codec);
gotoprobe_aux_dev_err;
}
}
#endif
card->instantiated= 1;
snd_soc_dapm_sync(&card->dapm);
mutex_unlock(&card->mutex);
return0;
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);
base_error:
mutex_unlock(&card->mutex);
returnret;
}
soc_bind_dai_link函数为:
static int soc_bind_dai_link(structsnd_soc_card *card, int num)
{
//这里的dai_link为s3c24xx_uda134x_dai_link
structsnd_soc_dai_link *dai_link = &card->dai_link[num];
structsnd_soc_pcm_runtime *rtd = &card->rtd[num];
structsnd_soc_codec *codec;
structsnd_soc_platform *platform;
structsnd_soc_dai *codec_dai, *cpu_dai;
constchar *platform_name;
dev_dbg(card->dev,"binding %s at idx %d\n", dai_link->name, num);
/*Find CPU DAI from registered DAIs*/
/*在前面介绍过的snd_soc_register_dais函数和snd_soc_register_dai函数内,为DAI列表添加了两项内容,这两项的driver分别为uda134x_dai和s3c24xx_i2s_dai,现在遍历该列表*/
list_for_each_entry(cpu_dai,&dai_list, list) {
//s3c24xx_uda134x_dai_link没有定义cpu_of_node,所以不执行if内容
if(dai_link->cpu_of_node &&
(cpu_dai->dev->of_node !=dai_link->cpu_of_node))
continue;
//s3c24xx_uda134x_dai_link没有定义cpu_name,所以不执行if内容
if(dai_link->cpu_name &&
strcmp(dev_name(cpu_dai->dev),dai_link->cpu_name))
continue;
/*s3c24xx_uda134x_dai_link定义的cpu_dai_name为s3c24xx-iis。DAI列表中两项内容name分别为uda134x-hifi和s3c24xx-iis。所以当DAI列表中的内容name为uda134x-hifi时,执行if语句;为s3c24xx-iis时,不执行if语句
if(dai_link->cpu_dai_name &&
strcmp(cpu_dai->name,dai_link->cpu_dai_name))
continue;
//由上一条if语句分析得到rtd->cpu_dai为设备名为s3c24xx-iis的DAI
rtd->cpu_dai= cpu_dai;
}
if(!rtd->cpu_dai) {
dev_err(card->dev,"CPU DAI %s not registered\n",
dai_link->cpu_dai_name);
return-EPROBE_DEFER;
}
/*Find CODEC from registered CODECs */
/*在前面介绍过的snd_soc_register_codec函数内,为CODEC列表添加了一项内容,它的dev为uda1340_codec,codec_drv为soc_codec_dev_uda134x,现在遍历该列表*/
list_for_each_entry(codec,&codec_list, list) {
//s3c24xx_uda134x_dai_link没有定义codec_of_node,所以执行else内容
if(dai_link->codec_of_node) {
if(codec->dev->of_node != dai_link->codec_of_node)
continue;
}else {
//name都是uda134x-codec,所以不执行if语句
if(strcmp(codec->name, dai_link->codec_name))
continue;
}
//把CODEC列表中唯一的一项赋值给rtd->codec
rtd->codec= codec;
/*
* CODEC found, so find CODEC DAI fromregistered DAIs from
* this CODEC
*/
//再一次遍历DAI列表
list_for_each_entry(codec_dai,&dai_list, list) {
/*dev都是uda1340_codec平台设备,而且name都是uda134x-hifi,所以执行if内容*/
if(codec->dev == codec_dai->dev &&
!strcmp(codec_dai->name,
dai_link->codec_dai_name)){
//把uda134x_dai赋值给rtd->codec_dai
rtd->codec_dai= codec_dai;
}
}
if(!rtd->codec_dai) {
dev_err(card->dev,"CODEC DAI %s not registered\n",
dai_link->codec_dai_name);
return-EPROBE_DEFER;
}
}
if(!rtd->codec) {
dev_err(card->dev,"CODEC %s not registered\n",
dai_link->codec_name);
return-EPROBE_DEFER;
}
/*if there's no platform we match on the empty platform */
platform_name= dai_link->platform_name; //为samsung-audio
if(!platform_name && !dai_link->platform_of_node)
platform_name= "snd-soc-dummy";
/*find one from the set of registered platforms */
/*在前面介绍过的snd_soc_register_platform函数内,为platform列表添加了一项内容,它的dev为samsung_asoc_dma,现在遍历该列表*/
list_for_each_entry(platform,&platform_list, list) {
//s3c24xx_uda134x_dai_link没有定义platform_of_node,所以执行else内容
if(dai_link->platform_of_node) {
if(platform->dev->of_node !=
dai_link->platform_of_node)
continue;
}else {
//名字都是samsung-audio,所以不执行if内容
if(strcmp(platform->name, platform_name))
continue;
}
//把dev为samsung_asoc_dma的内容赋值给rtd->platform
rtd->platform= platform;
}
if(!rtd->platform) {
dev_err(card->dev,"platform %s not registered\n",
dai_link->platform_name);
return-EPROBE_DEFER;
}
card->num_rtd++;
return0;
}
soc_probe_link_components函数为:
static int soc_probe_link_components(structsnd_soc_card *card, int num,
int order)
{
structsnd_soc_pcm_runtime *rtd = &card->rtd[num];
structsnd_soc_dai *cpu_dai = rtd->cpu_dai;
structsnd_soc_dai *codec_dai = rtd->codec_dai;
structsnd_soc_platform *platform = rtd->platform;
intret;
/*probe the CPU-side component, if it is a CODEC */
/*由soc_bind_dai_link函数可知,cup_dai的平台设备是s3c24xx-iis,它没有codec回调函数,所以不执行if内容*/
if(cpu_dai->codec &&
!cpu_dai->codec->probed &&
cpu_dai->codec->driver->probe_order == order) {
ret= soc_probe_codec(card, cpu_dai->codec);
if(ret < 0)
returnret;
}
/*probe the CODEC-side component */
//codec_dai指的是uda134x_dai,它的codec是uda1340_codec
if(!codec_dai->codec->probed &&
codec_dai->codec->driver->probe_order == order) {
//主要是调用uda134x_soc_probe,并为CODEC的控制数组——uda1340_snd_controls
ret= soc_probe_codec(card, codec_dai->codec);
if(ret < 0)
returnret;
}
/*probe the platform */
//没有为samsung_asoc_platform定义probe回调函数
if(!platform->probed &&
platform->driver->probe_order ==order) {
ret= soc_probe_platform(card, platform);
if(ret < 0)
returnret;
}
return0;
}
在soc_new_pcm函数内,通过调用snd_pcm_new函数又调用了_snd_pcm_new函数。_snd_pcm_new函数使用snd_device_new函数创建ALSA声卡,并把它添加进声卡设备列表中,其中该设备的ops为:
static struct snd_device_ops ops = {
.dev_free= snd_pcm_dev_free,
.dev_register= snd_pcm_dev_register,
.dev_disconnect= snd_pcm_dev_disconnect,
};
另外_snd_pcm_new函数还使用snd_pcm_new_stream函数创建了PCM的放音和录音两个流。
在soc_new_pcm函数内,还为PCM的操作符(ops)赋值,并且应用snd_pcm_set_ops函数把该ops赋值给了snd_pcm_substream的ops。
在snd_soc_instantiate_card函数内用到了snd_card_create函数和snd_card_register函数创建和注册声卡。在snd_card_create函数内,主要是使用snd_ctl_create函数,然后再次调用snd_device_new函数把声卡设备添加进列表中。其中设备的ops为:
static struct snd_device_ops ops = {
.dev_free= snd_ctl_dev_free,
.dev_register= snd_ctl_dev_register,
.dev_disconnect= snd_ctl_dev_disconnect,
};
在snd_card_register函数内,它首先使用device_create函数创建声卡设备节点,然后又使用snd_device_register_all函数来真正注册声卡设备:
int snd_device_register_all(struct snd_card*card)
{
structsnd_device *dev;
interr;
if(snd_BUG_ON(!card))
return-ENXIO;
//遍历声卡设备列表,在前面提到的snd_device_new函数已经添加了两个card->devices,现在就把它提取出来
list_for_each_entry(dev,&card->devices, list) {
if(dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
//dev->ops->dev_register(dev)实际上回调了前面介绍过的两个ops的dev_register,分别为snd_ctl_dev_register函数和snd_pcm_dev_register函数
if((err = dev->ops->dev_register(dev)) < 0)
returnerr;
dev->state= SNDRV_DEV_REGISTERED;
}
}
return0;
}
snd_ctl_dev_register函数和snd_pcm_dev_register函数主要的作用都是调用snd_register_device_for_dev函数注册,并把相关信息保存到snd_minors数组内,其中用snd_ctl_dev_register函数保存的Control文件操作为:
static const struct file_operationssnd_ctl_f_ops =
{
.owner= THIS_MODULE,
.read= snd_ctl_read,
.open= snd_ctl_open,
.release= snd_ctl_release,
.llseek= no_llseek,
.poll= snd_ctl_poll,
.unlocked_ioctl= snd_ctl_ioctl,
.compat_ioctl= snd_ctl_ioctl_compat,
.fasync= snd_ctl_fasync,
};
用snd_pcm_dev_register函数保存的PCM文件操作为:
const struct file_operationssnd_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,
.llseek= no_llseek,
.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= snd_pcm_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,
.llseek= no_llseek,
.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= snd_pcm_get_unmapped_area,
}
};
至此,创建声卡的整个过程就完成了。