1. 程式人生 > >基於S3C2440的Linux-3.6.6移植——音效卡驅動

基於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