基於S3C2440的Linux-3.6.6移植——音效卡驅動
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