alsa架構分析(二)

Linux ALSA聲卡驅動之六:ASoC架構中的Machine

前面一節的內容我們提到,ASoC被分爲Machine、Platform和Codec三大部分,其中的Machine驅動負責Platform和Codec之間的耦合以及部分和設備或板子特定的代碼,再次引用上一節的內容:Machine驅動負責處理機器特有的一些控件和音頻事件(例如,當播放音頻時,需要先行打開一個放大器);單獨的Platform和Codec驅動是不能工作的,它必須由Machine驅動把它們結合在一起才能完成整個設備的音頻處理工作。

ASoC的一切都從Machine驅動開始,包括聲卡的註冊,綁定Platform和Codec驅動等等,下面就讓我們從Machine驅動開始討論吧。


/********************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/********************************************************************************************/

1. 註冊Platform Device

ASoC把聲卡註冊爲Platform Device,我們以裝配有WM8994的一款Samsung的開發板SMDK爲例子做說明,WM8994是一顆Wolfson生產的多功能Codec芯片。

代碼的位於:/sound/soc/samsung/smdk_wm8994.c,我們關注模塊的初始化函數:

  1. static int __init smdk_audio_init(void)  
  2. {  
  3.     int ret;  
  4.   
  5.     smdk_snd_device = platform_device_alloc("soc-audio", -1);  
  6.     if (!smdk_snd_device)  
  7.         return -ENOMEM;  
  8.   
  9.     platform_set_drvdata(smdk_snd_device, &smdk);  
  10.   
  11.     ret = platform_device_add(smdk_snd_device);  
  12.     if (ret)  
  13.         platform_device_put(smdk_snd_device);  
  14.   
  15.     return ret;  
  16. }  


由此可見,模塊初始化時,註冊了一個名爲soc-audio的Platform設備,同時把smdk設到platform_device結構的dev.drvdata字段中,這裏引出了第一個數據結構snd_soc_card的實例smdk,他的定義如下:

  1. static struct snd_soc_dai_link smdk_dai[] = {  
  2.     { /* Primary DAI i/f */  
  3.         .name = "WM8994 AIF1",  
  4.         .stream_name = "Pri_Dai",  
  5.         .cpu_dai_name = "samsung-i2s.0",  
  6.         .codec_dai_name = "wm8994-aif1",  
  7.         .platform_name = "samsung-audio",  
  8.         .codec_name = "wm8994-codec",  
  9.         .init = smdk_wm8994_init_paiftx,  
  10.         .ops = &smdk_ops,  
  11.     }, { /* Sec_Fifo Playback i/f */  
  12.         .name = "Sec_FIFO TX",  
  13.         .stream_name = "Sec_Dai",  
  14.         .cpu_dai_name = "samsung-i2s.4",  
  15.         .codec_dai_name = "wm8994-aif1",  
  16.         .platform_name = "samsung-audio",  
  17.         .codec_name = "wm8994-codec",  
  18.         .ops = &smdk_ops,  
  19.     },  
  20. };  
  21.   
  22. static struct snd_soc_card smdk = {  
  23.     .name = "SMDK-I2S",  
  24.     .owner = THIS_MODULE,  
  25.     .dai_link = smdk_dai,  
  26.     .num_links = ARRAY_SIZE(smdk_dai),  
  27. };  

通過snd_soc_card結構,又引出了Machine驅動的另外兩個個數據結構:

  • snd_soc_dai_link(實例:smdk_dai[] )
  • snd_soc_ops(實例:smdk_ops )

其中,snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字,稍後Machine驅動將會利用這些名字去匹配已經在系統中註冊的platform,codec,dai,這些註冊的部件都是在另外相應的Platform驅動和Codec驅動的代碼文件中定義的,這樣看來,Machine驅動的設備初始化代碼無非就是選擇合適Platform和Codec以及dai,用他們填充以上幾個數據結構,然後註冊Platform設備即可。當然還要實現連接Platform和Codec的dai_link對應的ops實現,本例就是smdk_ops,它只實現了hw_params函數:smdk_hw_params。

2. 註冊Platform Driver

按照Linux的設備模型,有platform_device,就一定會有platform_driver。ASoC的platform_driver在以下文件中定義:sound/soc/soc-core.c。

還是先從模塊的入口看起:

  1. static int __init snd_soc_init(void)  
  2. {  
  3.     ......  
  4.     return platform_driver_register(&soc_driver);  
  5. }  

soc_driver的定義如下:

  1. /* ASoC platform driver */  
  2. static struct platform_driver soc_driver = {  
  3.     .driver     = {  
  4.         .name       = "soc-audio",  
  5.         .owner      = THIS_MODULE,  
  6.         .pm     = &soc_pm_ops,  
  7.     },  
  8.     .probe      = soc_probe,  
  9.     .remove     = soc_remove,  
  10. };  

我們看到platform_driver的name字段爲soc-audio,正好與platform_device中的名字相同,按照Linux的設備模型,platform總線會匹配這兩個名字相同的device和driver,同時會觸發soc_probe的調用,它正是整個ASoC驅動初始化的入口。

3. 初始化入口soc_probe()

soc_probe函數本身很簡單,它先從platform_device參數中取出snd_soc_card,然後調用snd_soc_register_card,通過snd_soc_register_card,爲snd_soc_pcm_runtime數組申請內存,每一個dai_link對應snd_soc_pcm_runtime數組的一個單元,然後把snd_soc_card中的dai_link配置複製到相應的snd_soc_pcm_runtime中,最後,大部分的工作都在snd_soc_instantiate_card中實現,下面就看看snd_soc_instantiate_card做了些什麼:

該函數首先利用card->instantiated來判斷該卡是否已經實例化,如果已經實例化則直接返回,否則遍歷每一對dai_link,進行codec、platform、dai的綁定工作,下只是代碼的部分選節,詳細的代碼請直接參考完整的代碼樹。

  1. /* bind DAIs */  
  2. for (i = 0; i < card->num_links; i++)  
  3.     soc_bind_dai_link(card, i);  

ASoC定義了三個全局的鏈表頭變量:codec_list、dai_list、platform_list,系統中所有的Codec、DAI、Platform都在註冊時連接到這三個全局鏈表上。soc_bind_dai_link函數逐個掃描這三個鏈表,根據card->dai_link[]中的名稱進行匹配,匹配後把相應的codec,dai和platform實例賦值到card->rtd[]中(snd_soc_pcm_runtime)。經過這個過程後,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驅動的信息。

snd_soc_instantiate_card接着初始化Codec的寄存器緩存,然後調用標準的alsa函數創建聲卡實例: 

  1. /* card bind complete so register a sound card */  
  2. ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,  
  3.         card->owner, 0, &card->snd_card);  
  4. card->snd_card->dev = card->dev;  
  5.   
  6. card->dapm.bias_level = SND_SOC_BIAS_OFF;  
  7. card->dapm.dev = card->dev;  
  8. card->dapm.card = card;  
  9. list_add(&card->dapm.list, &card->dapm_list);  


然後,依次調用各個子結構的probe函數:

  1. /* initialise the sound card only once */  
  2. if (card->probe) {  
  3.     ret = card->probe(card);  
  4.     if (ret < 0)  
  5.         goto card_probe_error;  
  6. }  
  7.   
  8. /* early DAI link probe */  
  9. for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;  
  10.         order++) {  
  11.     for (i = 0; i < card->num_links; i++) {  
  12.         ret = soc_probe_dai_link(card, i, order);  
  13.         if (ret < 0) {  
  14.             pr_err("asoc: failed to instantiate card %s: %d\n",  
  15.                card->name, ret);  
  16.             goto probe_dai_err;  
  17.         }  
  18.     }  
  19. }  
  20.   
  21. for (i = 0; i < card->num_aux_devs; i++) {  
  22.     ret = soc_probe_aux_dev(card, i);  
  23.     if (ret < 0) {  
  24.         pr_err("asoc: failed to add auxiliary devices %s: %d\n",  
  25.                card->name, ret);  
  26.         goto probe_aux_dev_err;  
  27.     }  
  28. }  
}

在上面的soc_probe_dai_link()函數中做了比較多的事情,把他展開繼續討論:


  1. static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)  
  2. {  
  3.         ......  
  4.     /* set default power off timeout */  
  5.     rtd->pmdown_time = pmdown_time;  
  6.   
  7.     /* probe the cpu_dai */  
  8.     if (!cpu_dai->probed &&  
  9.             cpu_dai->driver->probe_order == order) {  
  10.   
  11.         if (cpu_dai->driver->probe) {  
  12.             ret = cpu_dai->driver->probe(cpu_dai);  
  13.         }  
  14.         cpu_dai->probed = 1;  
  15.         /* mark cpu_dai as probed and add to card dai list */  
  16.         list_add(&cpu_dai->card_list, &card->dai_dev_list);  
  17.     }  
  18.   
  19.     /* probe the CODEC */  
  20.     if (!codec->probed &&  
  21.             codec->driver->probe_order == order) {  
  22.         ret = soc_probe_codec(card, codec);  
  23.     }  
  24.   
  25.     /* probe the platform */  
  26.     if (!platform->probed &&  
  27.             platform->driver->probe_order == order) {  
  28.         ret = soc_probe_platform(card, platform);  
  29.     }  
  30.   
  31.     /* probe the CODEC DAI */  
  32.     if (!codec_dai->probed && codec_dai->driver->probe_order == order) {  
  33.         if (codec_dai->driver->probe) {  
  34.             ret = codec_dai->driver->probe(codec_dai);  
  35.         }  
  36.   
  37.         /* mark codec_dai as probed and add to card dai list */  
  38.         codec_dai->probed = 1;  
  39.         list_add(&codec_dai->card_list, &card->dai_dev_list);  
  40.     }  
  41.   
  42.     /* complete DAI probe during last probe */  
  43.     if (order != SND_SOC_COMP_ORDER_LAST)  
  44.         return 0;  
  45.   
  46.     ret = soc_post_component_init(card, codec, num, 0);  
  47.     if (ret)  
  48.         return ret;  
  49.         ......  
  50.     /* create the pcm */  
  51.     ret = soc_new_pcm(rtd, num);  
  52.         ........  
  53.     return 0;  
  54. }  

該函數出了挨個調用了codec,dai和platform驅動的probe函數外,在最後還調用了soc_new_pcm()函數用於創建標準alsa驅動的pcm邏輯設備。現在把該函數的部分代碼也貼出來:


 

  1. /* create a new pcm */  
  2. int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)  
  3. {  
  4.     ......  
  5.     struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;  
  6.   
  7.     soc_pcm_ops->open    = soc_pcm_open;  
  8.     soc_pcm_ops->close   = soc_pcm_close;  
  9.     soc_pcm_ops->hw_params   = soc_pcm_hw_params;  
  10.     soc_pcm_ops->hw_free = soc_pcm_hw_free;  
  11.     soc_pcm_ops->prepare = soc_pcm_prepare;  
  12.     soc_pcm_ops->trigger = soc_pcm_trigger;  
  13.     soc_pcm_ops->pointer = soc_pcm_pointer;  
  14.   
  15.     ret = snd_pcm_new(rtd->card->snd_card, new_name,  
  16.             num, playback, capture, &pcm);  
  17.   
  18.     /* DAPM dai link stream work */  
  19.     INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);  
  20.   
  21.     rtd->pcm = pcm;  
  22.     pcm->private_data = rtd;  
  23.     if (platform->driver->ops) {  
  24.         soc_pcm_ops->mmap = platform->driver->ops->mmap;  
  25.         soc_pcm_ops->pointer = platform->driver->ops->pointer;  
  26.         soc_pcm_ops->ioctl = platform->driver->ops->ioctl;  
  27.         soc_pcm_ops->copy = platform->driver->ops->copy;  
  28.         soc_pcm_ops->silence = platform->driver->ops->silence;  
  29.         soc_pcm_ops->ack = platform->driver->ops->ack;  
  30.         soc_pcm_ops->page = platform->driver->ops->page;  
  31.     }  
  32.   
  33.     if (playback)  
  34.         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, soc_pcm_ops);  
  35.   
  36.     if (capture)  
  37.         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, soc_pcm_ops);  
  38.   
  39.     if (platform->driver->pcm_new) {  
  40.         ret = platform->driver->pcm_new(rtd);  
  41.         if (ret < 0) {  
  42.             pr_err("asoc: platform pcm constructor failed\n");  
  43.             return ret;  
  44.         }  
  45.     }  
  46.   
  47.     pcm->private_free = platform->driver->pcm_free;  
  48.     return ret;  
  49. }  
 

該函數首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成員,例如open,close,hw_params等,緊接着調用標準alsa驅動中的創建pcm的函數snd_pcm_new()創建聲卡的pcm實例,pcm的private_data字段設置爲該runtime變量rtd,然後用platform驅動中的snd_pcm_ops替換部分pcm中的snd_pcm_ops字段,最後,調用platform驅動的pcm_new回調,該回調實現該platform下的dma內存申請和dma初始化等相關工作。到這裏,聲卡和他的pcm實例創建完成。

回到snd_soc_instantiate_card函數,完成snd_card和snd_pcm的創建後,接着對dapm和dai支持的格式做出一些初始化合設置工作後,調用了 card->late_probe(card)進行一些最後的初始化合設置工作,最後則是調用標準alsa驅動的聲卡註冊函數對聲卡進行註冊:

  1. if (card->late_probe) {  
  2.     ret = card->late_probe(card);  
  3.     if (ret < 0) {  
  4.         dev_err(card->dev, "%s late_probe() failed: %d\n",  
  5.             card->name, ret);  
  6.         goto probe_aux_dev_err;  
  7.     }  
  8. }  
  9.   
  10. snd_soc_dapm_new_widgets(&card->dapm);  
  11.   
  12. if (card->fully_routed)  
  13.     list_for_each_entry(codec, &card->codec_dev_list, card_list)  
  14.         snd_soc_dapm_auto_nc_codec_pins(codec);  
  15.   
  16. ret = snd_card_register(card->snd_card);  
  17. if (ret < 0) {  
  18.     printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);  
  19.     goto probe_aux_dev_err;  
  20. }  
 至此,整個Machine驅動的初始化已經完成,通過各個子結構的probe調用,實際上,也完成了部分Platfrom驅動和Codec驅動的初始化工作,整個過程可以用一下的序列圖表示:


                                                                               圖3.1  基於3.0內核  soc_probe序列圖


下面的序列圖是本文章第一個版本,基於內核2.6.35,大家也可以參考一下兩個版本的差異:

                                                                               圖3.2  基於2.6.35  soc_probe序列圖

Linux ALSA聲卡驅動之七:ASoC架構中的Codec

1.  Codec簡介

在移動設備中,Codec的作用可以歸結爲4種,分別是:

  • 對PCM等信號進行D/A轉換,把數字的音頻信號轉換爲模擬信號
  • 對Mic、Linein或者其他輸入源的模擬信號進行A/D轉換,把模擬的聲音信號轉變CPU能夠處理的數字信號
  • 對音頻通路進行控制,比如播放音樂,收聽調頻收音機,又或者接聽電話時,音頻信號在codec內的流通路線是不一樣的
  • 對音頻信號做出相應的處理,例如音量控制,功率放大,EQ控制等等

ASoC對Codec的這些功能都定義好了一些列相應的接口,以方便地對Codec進行控制。ASoC對Codec驅動的一個基本要求是:驅動程序的代碼必須要做到平臺無關性,以方便同一個Codec的代碼不經修改即可用在不同的平臺上。以下的討論基於wolfson的Codec芯片WM8994,kernel的版本3.3.x。


/*****************************************************************************************************/
聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/

2.  ASoC中對Codec的數據抽象
描述Codec的最主要的幾個數據結構分別是:snd_soc_codec,snd_soc_codec_driver,snd_soc_dai,snd_soc_dai_driver,其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驅動中也會使用到,Platform和Codec的DAI通過snd_soc_dai_link結構,在Machine驅動中進行綁定連接。下面我們先看看這幾個結構的定義,這裏我只貼出我要關注的字段,詳細的定義請參照:/include/sound/soc.h。
snd_soc_codec:
  1. /* SoC Audio Codec device */  
  2. struct snd_soc_codec {  
  3.     const char *name;  /* Codec的名字*/  
  4.     struct device *dev; /* 指向Codec設備的指針 */  
  5.     const struct snd_soc_codec_driver *driver; /* 指向該codec的驅動的指針 */  
  6.     struct snd_soc_card *card;    /* 指向Machine驅動的card實例 */  
  7.     int num_dai; /* 該Codec數字接口的個數,目前越來越多的Codec帶有多個I2S或者是PCM接口 */  
  8.     int (*volatile_register)(...);  /* 用於判定某一寄存器是否是volatile */  
  9.     int (*readable_register)(...);  /* 用於判定某一寄存器是否可讀 */  
  10.     int (*writable_register)(...);  /* 用於判定某一寄存器是否可寫 */  
  11.   
  12.     /* runtime */  
  13.     ......  
  14.     /* codec IO */  
  15.     void *control_data; /* 該指針指向的結構用於對codec的控制,通常和read,write字段聯合使用 */  
  16.     enum snd_soc_control_type control_type;/* 可以是SND_SOC_SPI,SND_SOC_I2C,SND_SOC_REGMAP中的一種 */  
  17.     unsigned int (*read)(struct snd_soc_codec *, unsigned int);  /* 讀取Codec寄存器的函數 */  
  18.     int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);  /* 寫入Codec寄存器的函數 */  
  19.     /* dapm */  
  20.     struct snd_soc_dapm_context dapm;  /* 用於DAPM控件 */  
  21. };  
/* SoC Audio Codec device */ struct snd_soc_codec { const char *name; /* Codec的名字*/ struct device *dev; /* 指向Codec設備的指針 */ const struct snd_soc_codec_driver *driver; /* 指向該codec的驅動的指針 */ struct snd_soc_card *card; /* 指向Machine驅動的card實例 */ int num_dai; /* 該Codec數字接口的個數,目前越來越多的Codec帶有多個I2S或者是PCM接口 */ int (*volatile_register)(...); /* 用於判定某一寄存器是否是volatile */ int (*readable_register)(...); /* 用於判定某一寄存器是否可讀 */ int (*writable_register)(...); /* 用於判定某一寄存器是否可寫 */ /* runtime */ ...... /* codec IO */ void *control_data; /* 該指針指向的結構用於對codec的控制,通常和read,write字段聯合使用 */ enum snd_soc_control_type control_type;/* 可以是SND_SOC_SPI,SND_SOC_I2C,SND_SOC_REGMAP中的一種 */ unsigned int (*read)(struct snd_soc_codec *, unsigned int); /* 讀取Codec寄存器的函數 */ int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); /* 寫入Codec寄存器的函數 */ /* dapm */ struct snd_soc_dapm_context dapm; /* 用於DAPM控件 */ };
snd_soc_codec_driver:
  1. /* codec driver */  
  2. struct snd_soc_codec_driver {  
  3.     /* driver ops */  
  4.     int (*probe)(struct snd_soc_codec *);  /* codec驅動的probe函數,由snd_soc_instantiate_card回調 */  
  5.     int (*remove)(struct snd_soc_codec *);    
  6.     int (*suspend)(struct snd_soc_codec *);  /* 電源管理 */  
  7.     int (*resume)(struct snd_soc_codec *);  /* 電源管理 */  
  8.   
  9.     /* Default control and setup, added after probe() is run */  
  10.     const struct snd_kcontrol_new *controls;  /* 音頻控件指針 */  
  11.     const struct snd_soc_dapm_widget *dapm_widgets;  /* dapm部件指針 */  
  12.     const struct snd_soc_dapm_route *dapm_routes;  /* dapm路由指針 */  
  13.   
  14.     /* codec wide operations */  
  15.     int (*set_sysclk)(...);  /* 時鐘配置函數 */  
  16.     int (*set_pll)(...);  /* 鎖相環配置函數 */  
  17.   
  18.     /* codec IO */  
  19.     unsigned int (*read)(...);  /* 讀取codec寄存器函數 */  
  20.     int (*write)(...);  /* 寫入codec寄存器函數 */  
  21.     int (*volatile_register)(...);  /* 用於判定某一寄存器是否是volatile */  
  22.     int (*readable_register)(...);  /* 用於判定某一寄存器是否可讀 */  
  23.     int (*writable_register)(...);  /* 用於判定某一寄存器是否可寫 */  
  24.   
  25.     /* codec bias level */  
  26.     int (*set_bias_level)(...);  /* 偏置電壓配置函數 */  
  27.   
  28. };  
/* codec driver */ struct snd_soc_codec_driver { /* driver ops */ int (*probe)(struct snd_soc_codec *); /* codec驅動的probe函數,由snd_soc_instantiate_card回調 */ int (*remove)(struct snd_soc_codec *); int (*suspend)(struct snd_soc_codec *); /* 電源管理 */ int (*resume)(struct snd_soc_codec *); /* 電源管理 */ /* Default control and setup, added after probe() is run */ const struct snd_kcontrol_new *controls; /* 音頻控件指針 */ const struct snd_soc_dapm_widget *dapm_widgets; /* dapm部件指針 */ const struct snd_soc_dapm_route *dapm_routes; /* dapm路由指針 */ /* codec wide operations */ int (*set_sysclk)(...); /* 時鐘配置函數 */ int (*set_pll)(...); /* 鎖相環配置函數 */ /* codec IO */ unsigned int (*read)(...); /* 讀取codec寄存器函數 */ int (*write)(...); /* 寫入codec寄存器函數 */ int (*volatile_register)(...); /* 用於判定某一寄存器是否是volatile */ int (*readable_register)(...); /* 用於判定某一寄存器是否可讀 */ int (*writable_register)(...); /* 用於判定某一寄存器是否可寫 */ /* codec bias level */ int (*set_bias_level)(...); /* 偏置電壓配置函數 */ };snd_soc_dai:
  1. /*  
  2.  * Digital Audio Interface runtime data.  
  3.  *  
  4.  * Holds runtime data for a DAI.  
  5.  */  
  6. struct snd_soc_dai {  
  7.     const char *name;  /* dai的名字 */  
  8.     struct device *dev;  /* 設備指針 */  
  9.   
  10.     /* driver ops */  
  11.     struct snd_soc_dai_driver *driver;  /* 指向dai驅動結構的指針 */  
  12.   
  13.     /* DAI runtime info */  
  14.     unsigned int capture_active:1;      /* stream is in use */  
  15.     unsigned int playback_active:1;     /* stream is in use */  
  16.   
  17.     /* DAI DMA data */  
  18.     void *playback_dma_data;  /* 用於管理playback dma */  
  19.     void *capture_dma_data;  /* 用於管理capture dma */  
  20.   
  21.     /* parent platform/codec */  
  22.     union {  
  23.         struct snd_soc_platform *platform;  /* 如果是cpu dai,指向所綁定的平臺 */  
  24.         struct snd_soc_codec *codec;  /* 如果是codec dai指向所綁定的codec */  
  25.     };  
  26.     struct snd_soc_card *card;  /* 指向Machine驅動中的crad實例 */  
  27. };  
/* * Digital Audio Interface runtime data. * * Holds runtime data for a DAI. */ struct snd_soc_dai { const char *name; /* dai的名字 */ struct device *dev; /* 設備指針 */ /* driver ops */ struct snd_soc_dai_driver *driver; /* 指向dai驅動結構的指針 */ /* DAI runtime info */ unsigned int capture_active:1; /* stream is in use */ unsigned int playback_active:1; /* stream is in use */ /* DAI DMA data */ void *playback_dma_data; /* 用於管理playback dma */ void *capture_dma_data; /* 用於管理capture dma */ /* parent platform/codec */ union { struct snd_soc_platform *platform; /* 如果是cpu dai,指向所綁定的平臺 */ struct snd_soc_codec *codec; /* 如果是codec dai指向所綁定的codec */ }; struct snd_soc_card *card; /* 指向Machine驅動中的crad實例 */ };snd_soc_dai_driver:
  1. /*  
  2.  * Digital Audio Interface Driver.  
  3.  *  
  4.  * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97  
  5.  * operations and capabilities. Codec and platform drivers will register this  
  6.  * structure for every DAI they have.  
  7.  *  
  8.  * This structure covers the clocking, formating and ALSA operations for each  
  9.  * interface.  
  10.  */  
  11. struct snd_soc_dai_driver {  
  12.     /* DAI description */  
  13.     const char *name;  /* dai驅動名字 */  
  14.   
  15.     /* DAI driver callbacks */  
  16.     int (*probe)(struct snd_soc_dai *dai);  /* dai驅動的probe函數,由snd_soc_instantiate_card回調 */  
  17.     int (*remove)(struct snd_soc_dai *dai);    
  18.     int (*suspend)(struct snd_soc_dai *dai);  /* 電源管理 */  
  19.     int (*resume)(struct snd_soc_dai *dai);    
  20.   
  21.     /* ops */  
  22.     const struct snd_soc_dai_ops *ops;  /* 指向本dai的snd_soc_dai_ops結構 */  
  23.   
  24.     /* DAI capabilities */  
  25.     struct snd_soc_pcm_stream capture;  /* 描述capture的能力 */  
  26.     struct snd_soc_pcm_stream playback;  /* 描述playback的能力 */  
  27. };  
/* * Digital Audio Interface Driver. * * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97 * operations and capabilities. Codec and platform drivers will register this * structure for every DAI they have. * * This structure covers the clocking, formating and ALSA operations for each * interface. */ struct snd_soc_dai_driver { /* DAI description */ const char *name; /* dai驅動名字 */ /* DAI driver callbacks */ int (*probe)(struct snd_soc_dai *dai); /* dai驅動的probe函數,由snd_soc_instantiate_card回調 */ int (*remove)(struct snd_soc_dai *dai); int (*suspend)(struct snd_soc_dai *dai); /* 電源管理 */ int (*resume)(struct snd_soc_dai *dai); /* ops */ const struct snd_soc_dai_ops *ops; /* 指向本dai的snd_soc_dai_ops結構 */ /* DAI capabilities */ struct snd_soc_pcm_stream capture; /* 描述capture的能力 */ struct snd_soc_pcm_stream playback; /* 描述playback的能力 */ };snd_soc_dai_ops用於實現該dai的控制盒參數配置:
  1. struct snd_soc_dai_ops {  
  2.     /*  
  3.      * DAI clocking configuration, all optional.  
  4.      * Called by soc_card drivers, normally in their hw_params.  
  5.      */  
  6.     int (*set_sysclk)(...);  
  7.     int (*set_pll)(...);  
  8.     int (*set_clkdiv)(...);  
  9.     /*  
  10.      * DAI format configuration  
  11.      * Called by soc_card drivers, normally in their hw_params.  
  12.      */  
  13.     int (*set_fmt)(...);  
  14.     int (*set_tdm_slot)(...);  
  15.     int (*set_channel_map)(...);  
  16.     int (*set_tristate)(...);  
  17.     /*  
  18.      * DAI digital mute - optional.  
  19.      * Called by soc-core to minimise any pops.  
  20.      */  
  21.     int (*digital_mute)(...);  
  22.     /*  
  23.      * ALSA PCM audio operations - all optional.  
  24.      * Called by soc-core during audio PCM operations.  
  25.      */  
  26.     int (*startup)(...);  
  27.     void (*shutdown)(...);  
  28.     int (*hw_params)(...);  
  29.     int (*hw_free)(...);  
  30.     int (*prepare)(...);  
  31.     int (*trigger)(...);  
  32.     /*  
  33.      * For hardware based FIFO caused delay reporting.  
  34.      * Optional.  
  35.      */  
  36.     snd_pcm_sframes_t (*delay)(...);  
  37. };  
struct snd_soc_dai_ops { /* * DAI clocking configuration, all optional. * Called by soc_card drivers, normally in their hw_params. */ int (*set_sysclk)(...); int (*set_pll)(...); int (*set_clkdiv)(...); /* * DAI format configuration * Called by soc_card drivers, normally in their hw_params. */ int (*set_fmt)(...); int (*set_tdm_slot)(...); int (*set_channel_map)(...); int (*set_tristate)(...); /* * DAI digital mute - optional. * Called by soc-core to minimise any pops. */ int (*digital_mute)(...); /* * ALSA PCM audio operations - all optional. * Called by soc-core during audio PCM operations. */ int (*startup)(...); void (*shutdown)(...); int (*hw_params)(...); int (*hw_free)(...); int (*prepare)(...); int (*trigger)(...); /* * For hardware based FIFO caused delay reporting. * Optional. */ snd_pcm_sframes_t (*delay)(...); };
3.  Codec的註冊
因爲Codec驅動的代碼要做到平臺無關性,要使得Machine驅動能夠使用該Codec,Codec驅動的首要任務就是確定snd_soc_codec和snd_soc_dai的實例,並把它們註冊到系統中,註冊後的codec和dai才能爲Machine驅動所用。以WM8994爲例,對應的代碼位置:/sound/soc/codecs/wm8994.c,模塊的入口函數註冊了一個platform driver:
  1. static struct platform_driver wm8994_codec_driver = {  
  2.     .driver = {  
  3.            .name = "wm8994-codec",  
  4.            .owner = THIS_MODULE,  
  5.            },  
  6.     .probe = wm8994_probe,  
  7.     .remove = __devexit_p(wm8994_remove),  
  8. };  
  9.   
  10. module_platform_driver(wm8994_codec_driver);  
static struct platform_driver wm8994_codec_driver = { .driver = { .name = "wm8994-codec", .owner = THIS_MODULE, }, .probe = wm8994_probe, .remove = __devexit_p(wm8994_remove), }; module_platform_driver(wm8994_codec_driver); 有platform driver,必定會有相應的platform device,這個platform device的來源後面再說,顯然,platform driver註冊後,probe回調將會被調用,這裏是wm8994_probe函數:
  1. static int __devinit wm8994_probe(struct platform_device *pdev)  
  2. {  
  3.     return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,  
  4.             wm8994_dai, ARRAY_SIZE(wm8994_dai));  
  5. }  
static int __devinit wm8994_probe(struct platform_device *pdev) { return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994, wm8994_dai, ARRAY_SIZE(wm8994_dai)); } 其中,soc_codec_dev_wm8994和wm8994_dai的定義如下(代碼中定義了3個dai,這裏只列出第一個):
  1. static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {  
  2.     .probe =    wm8994_codec_probe,  
  3.     .remove =   wm8994_codec_remove,  
  4.     .suspend =  wm8994_suspend,  
  5.     .resume =   wm8994_resume,  
  6.     .set_bias_level = wm8994_set_bias_level,  
  7.     .reg_cache_size = WM8994_MAX_REGISTER,  
  8.     .volatile_register = wm8994_soc_volatile,  
  9. };  
static struct snd_soc_codec_driver soc_codec_dev_wm8994 = { .probe = wm8994_codec_probe, .remove = wm8994_codec_remove, .suspend = wm8994_suspend, .resume = wm8994_resume, .set_bias_level = wm8994_set_bias_level, .reg_cache_size = WM8994_MAX_REGISTER, .volatile_register = wm8994_soc_volatile, };
  1. static struct snd_soc_dai_driver wm8994_dai[] = {  
  2.     {  
  3.         .name = "wm8994-aif1",  
  4.         .id = 1,  
  5.         .playback = {  
  6.             .stream_name = "AIF1 Playback",  
  7.             .channels_min = 1,  
  8.             .channels_max = 2,  
  9.             .rates = WM8994_RATES,  
  10.             .formats = WM8994_FORMATS,  
  11.         },  
  12.         .capture = {  
  13.             .stream_name = "AIF1 Capture",  
  14.             .channels_min = 1,  
  15.             .channels_max = 2,  
  16.             .rates = WM8994_RATES,  
  17.             .formats = WM8994_FORMATS,  
  18.          },  
  19.         .ops = &wm8994_aif1_dai_ops,  
  20.     },  
  21.     ......  
  22. }  
static struct snd_soc_dai_driver wm8994_dai[] = { { .name = "wm8994-aif1", .id = 1, .playback = { .stream_name = "AIF1 Playback", .channels_min = 1, .channels_max = 2, .rates = WM8994_RATES, .formats = WM8994_FORMATS, }, .capture = { .stream_name = "AIF1 Capture", .channels_min = 1, .channels_max = 2, .rates = WM8994_RATES, .formats = WM8994_FORMATS, }, .ops = &wm8994_aif1_dai_ops, }, ...... } 可見,Codec驅動的第一個步驟就是定義snd_soc_codec_driver和snd_soc_dai_driver的實例,然後調用snd_soc_register_codec函數對Codec進行註冊。進入snd_soc_register_codec函數看看:
首先,它申請了一個snd_soc_codec結構的實例:
  1. codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);  
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); 確定codec的名字,這個名字很重要,Machine驅動定義的snd_soc_dai_link中會指定每個link的codec和dai的名字,進行匹配綁定時就是通過和這裏的名字比較,從而找到該Codec的!
  1. /* create CODEC component name */  
  2.     codec->name = fmt_single_name(dev, &codec->id);  
/* create CODEC component name */ codec->name = fmt_single_name(dev, &codec->id);
然後初始化它的各個字段,多數字段的值來自上面定義的snd_soc_codec_driver的實例soc_codec_dev_wm8994:
  1. codec->write = codec_drv->write;  
  2. codec->read = codec_drv->read;  
  3. codec->volatile_register = codec_drv->volatile_register;  
  4. codec->readable_register = codec_drv->readable_register;  
  5. codec->writable_register = codec_drv->writable_register;  
  6. codec->dapm.bias_level = SND_SOC_BIAS_OFF;  
  7. codec->dapm.dev = dev;  
  8. codec->dapm.codec = codec;  
  9. codec->dapm.seq_notifier = codec_drv->seq_notifier;  
  10. codec->dapm.stream_event = codec_drv->stream_event;  
  11. codec->dev = dev;  
  12. codec->driver = codec_drv;  
  13. codec->num_dai = num_dai;  
codec->write = codec_drv->write; codec->read = codec_drv->read; codec->volatile_register = codec_drv->volatile_register; codec->readable_register = codec_drv->readable_register; codec->writable_register = codec_drv->writable_register; 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; codec->driver = codec_drv; codec->num_dai = num_dai; 在做了一些寄存器緩存的初始化和配置工作後,通過snd_soc_register_dais函數對本Codec的dai進行註冊:
  1. /* register any DAIs */  
  2. if (num_dai) {  
  3.     ret = snd_soc_register_dais(dev, dai_drv, num_dai);  
  4.     if (ret < 0)  
  5.         goto fail;  
  6. }  
/* register any DAIs */ if (num_dai) { ret = snd_soc_register_dais(dev, dai_drv, num_dai); if (ret < 0) goto fail; } 最後,它把codec實例鏈接到全局鏈表codec_list中,並且調用snd_soc_instantiate_cards是函數觸發Machine驅動進行一次匹配綁定操作:
  1. list_add(&codec->list, &codec_list);  
  2. snd_soc_instantiate_cards();  
list_add(&codec->list, &codec_list); snd_soc_instantiate_cards(); 上面的snd_soc_register_dais函數其實也是和snd_soc_register_codec類似,顯示爲每個snd_soc_dai實例分配內存,確定dai的名字,用snd_soc_dai_driver實例的字段對它進行必要初始化,最後把該dai鏈接到全局鏈表dai_list中,和Codec一樣,最後也會調用snd_soc_instantiate_cards函數觸發一次匹配綁定的操作。

               圖3.1 dai的註冊
關於snd_soc_instantiate_cards函數,請參閱另一篇博文:Linux音頻驅動之六:ASoC架構中的Machine
4.  mfd設備

前面已經提到,codec驅動把自己註冊爲一個platform driver,那對應的platform device在哪裏定義?答案是在以下代碼文件中:/drivers/mfd/wm8994-core.c。

WM8994本身具備多種功能,除了codec外,它還有作爲LDO和GPIO使用,這幾種功能共享一些IO和中斷資源,linux爲這種設備提供了一套標準的實現方法:mfd設備。其基本思想是爲這些功能的公共部分實現一個父設備,以便共享某些系統資源和功能,然後每個子功能實現爲它的子設備,這樣既共享了資源和代碼,又能實現合理的設備層次結構,主要利用到的API就是:mfd_add_devices(),mfd_remove_devices(),mfd_cell_enable(),mfd_cell_disable(),mfd_clone_cell()。

回到wm8994-core.c中,因爲WM8994使用I2C進行內部寄存器的存取,它首先註冊了一個I2C驅動:

  1. static struct i2c_driver wm8994_i2c_driver = {  
  2.     .driver = {  
  3.         .name = "wm8994",  
  4.         .owner = THIS_MODULE,  
  5.         .pm = &wm8994_pm_ops,  
  6.         .of_match_table = wm8994_of_match,  
  7.     },  
  8.     .probe = wm8994_i2c_probe,  
  9.     .remove = wm8994_i2c_remove,  
  10.     .id_table = wm8994_i2c_id,  
  11. };  
  12.   
  13. static int __init wm8994_i2c_init(void)  
  14. {  
  15.     int ret;  
  16.   
  17.     ret = i2c_add_driver(&wm8994_i2c_driver);  
  18.     if (ret != 0)  
  19.         pr_err("Failed to register wm8994 I2C driver: %d\n", ret);  
  20.   
  21.     return ret;  
  22. }  
  23. module_init(wm8994_i2c_init);  
static struct i2c_driver wm8994_i2c_driver = { .driver = { .name = "wm8994", .owner = THIS_MODULE, .pm = &wm8994_pm_ops, .of_match_table = wm8994_of_match, }, .probe = wm8994_i2c_probe, .remove = wm8994_i2c_remove, .id_table = wm8994_i2c_id, }; static int __init wm8994_i2c_init(void) { int ret; ret = i2c_add_driver(&wm8994_i2c_driver); if (ret != 0) pr_err("Failed to register wm8994 I2C driver: %d\n", ret); return ret; } module_init(wm8994_i2c_init); 進入wm8994_i2c_probe()函數,它先申請了一個wm8994結構的變量,該變量被作爲這個I2C設備的driver_data使用,上面已經講過,codec作爲它的子設備,將會取出並使用這個driver_data。接下來,本函數利用regmap_init_i2c()初始化並獲得一個regmap結構,該結構主要用於後續基於regmap機制的寄存器I/O,關於regmap我們留在後面再講。最後,通過wm8994_device_init()來添加mfd子設備:

  1. static int wm8994_i2c_probe(struct i2c_client *i2c,  
  2.                 const struct i2c_device_id *id)  
  3. {  
  4.     struct wm8994 *wm8994;  
  5.     int ret;  
  6.     wm8994 = devm_kzalloc(&i2c->dev, sizeof(struct wm8994), GFP_KERNEL);  
  7.     i2c_set_clientdata(i2c, wm8994);  
  8.     wm8994->dev = &i2c->dev;  
  9.     wm8994->irq = i2c->irq;  
  10.     wm8994->type = id->driver_data;  
  11.     wm8994->regmap = regmap_init_i2c(i2c, &wm8994_base_regmap_config);  
  12.   
  13.     return wm8994_device_init(wm8994, i2c->irq);  
  14. }  
static int wm8994_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct wm8994 *wm8994; int ret; wm8994 = devm_kzalloc(&i2c->dev, sizeof(struct wm8994), GFP_KERNEL); i2c_set_clientdata(i2c, wm8994); wm8994->dev = &i2c->dev; wm8994->irq = i2c->irq; wm8994->type = id->driver_data; wm8994->regmap = regmap_init_i2c(i2c, &wm8994_base_regmap_config); return wm8994_device_init(wm8994, i2c->irq); } 繼續進入wm8994_device_init()函數,它首先爲兩個LDO添加mfd子設備:
  1. /* Add the on-chip regulators first for bootstrapping */  
  2. ret = mfd_add_devices(wm8994->dev, -1,  
  3.               wm8994_regulator_devs,  
  4.               ARRAY_SIZE(wm8994_regulator_devs),  
  5.               NULL, 0);  
/* Add the on-chip regulators first for bootstrapping */ ret = mfd_add_devices(wm8994->dev, -1, wm8994_regulator_devs, ARRAY_SIZE(wm8994_regulator_devs), NULL, 0); 因爲WM1811,WM8994,WM8958三個芯片功能類似,因此這三個芯片都使用了WM8994的代碼,所以wm8994_device_init()接下來根據不同的芯片型號做了一些初始化動作,這部分的代碼就不貼了。接着,從platform_data中獲得部分配置信息:
  1. if (pdata) {  
  2.     wm8994->irq_base = pdata->irq_base;  
  3.     wm8994->gpio_base = pdata->gpio_base;  
  4.   
  5.     /* GPIO configuration is only applied if it's non-zero */  
  6.     ......  
  7. }  
if (pdata) { wm8994->irq_base = pdata->irq_base; wm8994->gpio_base = pdata->gpio_base; /* GPIO configuration is only applied if it's non-zero */ ...... } 最後,初始化irq,然後添加codec子設備和gpio子設備:
  1. wm8994_irq_init(wm8994);  
  2.   
  3. ret = mfd_add_devices(wm8994->dev, -1,  
  4.               wm8994_devs, ARRAY_SIZE(wm8994_devs),  
  5.               NULL, 0);  
wm8994_irq_init(wm8994); ret = mfd_add_devices(wm8994->dev, -1, wm8994_devs, ARRAY_SIZE(wm8994_devs), NULL, 0); 經過以上這些處理後,作爲父設備的I2C設備已經準備就緒,它的下面掛着4個子設備:ldo-0,ldo-1,codec,gpio。其中,codec子設備的加入,它將會和前面所講codec的platform driver匹配,觸發probe回調完成下面所說的codec驅動的初始化工作。

5.  Codec初始化
Machine驅動的初始化,codec和dai的註冊,都會調用snd_soc_instantiate_cards()進行一次聲卡和codec,dai,platform的匹配綁定過程,這裏所說的綁定,正如Machine驅動一文中所描述,就是通過3個全局鏈表,按名字進行匹配,把匹配的codec,dai和platform實例賦值給聲卡每對dai的snd_soc_pcm_runtime變量中。一旦綁定成功,將會使得codec和dai驅動的probe回調被調用,codec的初始化工作就在該回調中完成。對於WM8994,該回調就是wm8994_codec_probe函數:

                                                                   圖5.1  wm8994_codec_probe

  • 取出父設備的driver_data,其實就是上一節的wm8994結構變量,取出其中的regmap字段,複製到codec的control_data字段中;
  • 申請一個wm8994_priv私有數據結構,並把它設爲codec設備的driver_data;
  • 通過snd_soc_codec_set_cache_io初始化regmap io,完成這一步後,就可以使用API:snd_soc_read(),snd_soc_write()對codec的寄存器進行讀寫了;
  • 把父設備的driver_data(struct wm8994)和platform_data保存到私有結構wm8994_priv中;
  • 因爲要同時支持3個芯片型號,這裏要根據芯片的型號做一些特定的初始化工作;
  • 申請必要的幾個中斷;
  • 設置合適的偏置電平;
  • 通過snd_soc_update_bits修改某些寄存器;
  • 根據父設備的platform_data,完成特定於平臺的初始化配置;
  • 添加必要的control,dapm部件進而dapm路由信息;

至此,codec驅動的初始化完成。

5.  regmap-io
我們知道,要想對codec進行控制,通常都是通過讀寫它的內部寄存器完成的,讀寫的接口通常是I2C或者是SPI接口,不過每個codec芯片寄存器的比特位組成都有所不同,寄存器地址的比特位也有所不同。例如WM8753的寄存器地址是7bits,數據是9bits,WM8993的寄存器地址是8bits,數據也是16bits,而WM8994的寄存器地址是16bits,數據也是16bits。在kernel3.1版本,內核引入了一套regmap機制和相關的API,這樣就可以用統一的操作來實現對這些多樣的寄存器的控制。regmap使用起來也相對簡單:
  • 爲codec定義一個regmap_config結構實例,指定codec寄存器的地址和數據位等信息;
  • 根據codec的控制總線類型,調用以下其中一個函數,得到一個指向regmap結構的指針:

    • struct regmap *regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config);
    • struct regmap *regmap_init_spi(struct spi_device *dev, const struct regmap_config *config);
  • 把獲得的regmap結構指針賦值給codec->control_data;
  • 調用soc-io的api:snd_soc_codec_set_cache_io使得soc-io和regmap進行關聯;

完成以上步驟後,codec驅動就可以使用諸如snd_soc_read、snd_soc_write、snd_soc_update_bits等API對codec的寄存器進行讀寫了

Linux ALSA聲卡驅動之八:ASoC架構中的Platform

1.  Platform驅動在ASoC中的作用

前面幾章內容已經說過,ASoC被分爲Machine,Platform和Codec三大部件,Platform驅動的主要作用是完成音頻數據的管理,最終通過CPU的數字音頻接口(DAI)把音頻數據傳送給Codec進行處理,最終由Codec輸出驅動耳機或者是喇叭的音信信號。在具體實現上,ASoC有把Platform驅動分爲兩個部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver負責管理音頻數據,把音頻數據通過dma或其他操作傳送至cpu dai中,dai_driver則主要完成cpu一側的dai的參數配置,同時也會通過一定的途徑把必要的dma等參數與snd_soc_platform_driver進行交互。

/*****************************************************************************************************/

聲明:本博內容均由http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/
2.  snd_soc_platform_driver的註冊
通常,ASoC把snd_soc_platform_driver註冊爲一個系統的platform_driver,不要被這兩個相像的術語所迷惑,前者只是針對ASoC子系統的,後者是來自Linux的設備驅動模型。我們要做的就是:
  • 定義一個snd_soc_platform_driver結構的實例;
  • 在platform_driver的probe回調中利用ASoC的API:snd_soc_register_platform()註冊上面定義的實例;
  • 實現snd_soc_platform_driver中的各個回調函數;
以kernel3.3中的/sound/soc/samsung/dma.c爲例:
  1. static struct snd_soc_platform_driver samsung_asoc_platform = {  
  2.     .ops        = &dma_ops,  
  3.     .pcm_new    = dma_new,  
  4.     .pcm_free   = dma_free_dma_buffers,  
  5. };  
  6.   
  7. static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)  
  8. {  
  9.     return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);  
  10. }  
  11.   
  12. static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)  
  13. {  
  14.     snd_soc_unregister_platform(&pdev->dev);  
  15.     return 0;  
  16. }  
  17.   
  18. static struct platform_driver asoc_dma_driver = {  
  19.     .driver = {  
  20.         .name = "samsung-audio",  
  21.         .owner = THIS_MODULE,  
  22.     },  
  23.   
  24.     .probe = samsung_asoc_platform_probe,  
  25.     .remove = __devexit_p(samsung_asoc_platform_remove),  
  26. };  
  27.   
  28. module_platform_driver(asoc_dma_driver);  
static struct snd_soc_platform_driver samsung_asoc_platform = { .ops = &dma_ops, .pcm_new = dma_new, .pcm_free = dma_free_dma_buffers, }; static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform); } static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); return 0; } static struct platform_driver asoc_dma_driver = { .driver = { .name = "samsung-audio", .owner = THIS_MODULE, }, .probe = samsung_asoc_platform_probe, .remove = __devexit_p(samsung_asoc_platform_remove), }; module_platform_driver(asoc_dma_driver);snd_soc_register_platform() 該函數用於註冊一個snd_soc_platform,只有註冊以後,它纔可以被Machine驅動使用。它的代碼已經清晰地表達了它的實現過程:
  • 爲snd_soc_platform實例申請內存;
  • 從platform_device中獲得它的名字,用於Machine驅動的匹配工作;
  • 初始化snd_soc_platform的字段;
  • 把snd_soc_platform實例連接到全局鏈表platform_list中;
  • 調用snd_soc_instantiate_cards,觸發聲卡的machine、platform、codec、dai等的匹配工作;
3.  cpu的snd_soc_dai driver驅動的註冊
dai驅動通常對應cpu的一個或幾個I2S/PCM接口,與snd_soc_platform一樣,dai驅動也是實現爲一個platform driver,實現一個dai驅動大致可以分爲以下幾個步驟:
  • 定義一個snd_soc_dai_driver結構的實例;
  • 在對應的platform_driver中的probe回調中通過API:snd_soc_register_dai或者snd_soc_register_dais,註冊snd_soc_dai實例;
  • 實現snd_soc_dai_driver結構中的probe、suspend等回調;
  • 實現snd_soc_dai_driver結構中的snd_soc_dai_ops字段中的回調函數;
snd_soc_register_dai  這個函數在上一篇介紹codec驅動的博文中已有介紹,請參考:Linux ALSA聲卡驅動之七:ASoC架構中的Codec
snd_soc_dai  該結構在snd_soc_register_dai函數中通過動態內存申請獲得, 簡要介紹一下幾個重要字段:
  • driver  指向關聯的snd_soc_dai_driver結構,由註冊時通過參數傳入;
  • playback_dma_data  用於保存該dai播放stream的dma信息,例如dma的目標地址,dma傳送單元大小和通道號等;
  • capture_dma_data  同上,用於錄音stream;
  • platform  指向關聯的snd_soc_platform結構;

snd_soc_dai_driver  該結構需要自己根據不同的soc芯片進行定義,關鍵字段介紹如下:

  • probe、remove  回調函數,分別在聲卡加載和卸載時被調用;
  • suspend、resume  電源管理回調函數;
  • ops  指向snd_soc_dai_ops結構,用於配置和控制該dai;
  • playback  snd_soc_pcm_stream結構,用於指出該dai支持的聲道數,碼率,數據格式等能力;
  • capture  snd_soc_pcm_stream結構,用於指出該dai支持的聲道數,碼率,數據格式等能力;
4.  snd_soc_dai_driver中的ops字段

ops字段指向一個snd_soc_dai_ops結構,該結構實際上是一組回調函數的集合,dai的配置和控制幾乎都是通過這些回調函數來實現的,這些回調函數基本可以分爲3大類,驅動程序可以根據實際情況實現其中的一部分:


工作時鐘配置函數  通常由machine驅動調用:

  • set_sysclk  設置dai的主時鐘;
  • set_pll  設置PLL參數;
  • set_clkdiv  設置分頻係數;
  • dai的格式配置函數  通常由machine驅動調用:
  • set_fmt   設置dai的格式;
  • set_tdm_slot  如果dai支持時分複用,用於設置時分複用的slot;
  • set_channel_map 聲道的時分複用映射設置;
  • set_tristate  設置dai引腳的狀態,當與其他dai並聯使用同一引腳時需要使用該回調;

標準的snd_soc_ops回調  通常由soc-core在進行PCM操作時調用:

  • startup
  • shutdown
  • hw_params
  • hw_free
  • prepare
  • trigger

抗pop,pop聲  由soc-core調用:

  • digital_mute 

以下這些api通常被machine驅動使用,machine驅動在他的snd_pcm_ops字段中的hw_params回調中使用這些api:

  • snd_soc_dai_set_fmt()  實際上會調用snd_soc_dai_ops或者codec driver中的set_fmt回調;
  • snd_soc_dai_set_pll() 實際上會調用snd_soc_dai_ops或者codec driver中的set_pll回調;
  • snd_soc_dai_set_sysclk()  實際上會調用snd_soc_dai_ops或者codec driver中的set_sysclk回調;
  • snd_soc_dai_set_clkdiv()  實際上會調用snd_soc_dai_ops或者codec driver中的set_clkdiv回調;

snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)的第二個參數fmt在這裏特別說一下,ASoC目前只是用了它的低16位,並且爲它專門定義了一些宏來方便我們使用:

bit 0-3 用於設置接口的格式:

  1. #define SND_SOC_DAIFMT_I2S      1 /* I2S mode */   
  2. #define SND_SOC_DAIFMT_RIGHT_J      2 /* Right Justified mode */   
  3. #define SND_SOC_DAIFMT_LEFT_J       3 /* Left Justified mode */   
  4. #define SND_SOC_DAIFMT_DSP_A        4 /* L data MSB after FRM LRC */   
  5. #define SND_SOC_DAIFMT_DSP_B        5 /* L data MSB during FRM LRC */   
  6. #define SND_SOC_DAIFMT_AC97     6 /* AC97 */   
  7. #define SND_SOC_DAIFMT_PDM      7 /* Pulse density modulation */  
#define SND_SOC_DAIFMT_I2S 1 /* I2S mode */ #define SND_SOC_DAIFMT_RIGHT_J 2 /* Right Justified mode */ #define SND_SOC_DAIFMT_LEFT_J 3 /* Left Justified mode */ #define SND_SOC_DAIFMT_DSP_A 4 /* L data MSB after FRM LRC */ #define SND_SOC_DAIFMT_DSP_B 5 /* L data MSB during FRM LRC */ #define SND_SOC_DAIFMT_AC97 6 /* AC97 */ #define SND_SOC_DAIFMT_PDM 7 /* Pulse density modulation */

bit 4-7 用於設置接口時鐘的開關特性:

  1. #define SND_SOC_DAIFMT_CONT     (1 << 4) /* continuous clock */   
  2. #define SND_SOC_DAIFMT_GATED        (2 << 4) /* clock is gated */  
#define SND_SOC_DAIFMT_CONT (1 << 4) /* continuous clock */ #define SND_SOC_DAIFMT_GATED (2 << 4) /* clock is gated */

bit 8-11 用於設置接口時鐘的相位:

  1. #define SND_SOC_DAIFMT_NB_NF        (1 << 8) /* normal bit clock + frame */   
  2. #define SND_SOC_DAIFMT_NB_IF        (2 << 8) /* normal BCLK + inv FRM */   
  3. #define SND_SOC_DAIFMT_IB_NF        (3 << 8) /* invert BCLK + nor FRM */   
  4. #define SND_SOC_DAIFMT_IB_IF        (4 << 8) /* invert BCLK + FRM */  
#define SND_SOC_DAIFMT_NB_NF (1 << 8) /* normal bit clock + frame */ #define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */ #define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */ #define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM */

bit 12-15 用於設置接口主從格式:

  1. #define SND_SOC_DAIFMT_CBM_CFM      (1 << 12) /* codec clk & FRM master */   
  2. #define SND_SOC_DAIFMT_CBS_CFM      (2 << 12) /* codec clk slave & FRM master */   
  3. #define SND_SOC_DAIFMT_CBM_CFS      (3 << 12) /* codec clk master & frame slave */   
  4. #define SND_SOC_DAIFMT_CBS_CFS      (4 << 12) /* codec clk & FRM slave */  
#define SND_SOC_DAIFMT_CBM_CFM (1 << 12) /* codec clk & FRM master */ #define SND_SOC_DAIFMT_CBS_CFM (2 << 12) /* codec clk slave & FRM master */ #define SND_SOC_DAIFMT_CBM_CFS (3 << 12) /* codec clk master & frame slave */ #define SND_SOC_DAIFMT_CBS_CFS (4 << 12) /* codec clk & FRM slave */ 5.  snd_soc_platform_driver中的ops字段

該ops字段是一個snd_pcm_ops結構,實現該結構中的各個回調函數是soc platform驅動的主要工作,他們基本都涉及dma操作以及dma buffer的管理等工作。下面介紹幾個重要的回調函數:

ops.open 

當應用程序打開一個pcm設備時,該函數會被調用,通常,該函數會使用snd_soc_set_runtime_hwparams()設置substream中的snd_pcm_runtime結構裏面的hw_params相關字段,然後爲snd_pcm_runtime的private_data字段申請一個私有結構,用於保存該平臺的dma參數。

ops.hw_params 

驅動的hw_params階段,該函數會被調用。通常,該函數會通過snd_soc_dai_get_dma_data函數獲得對應的dai的dma參數,獲得的參數一般都會保存在snd_pcm_runtime結構的private_data字段。然後通過snd_pcm_set_runtime_buffer函數設置snd_pcm_runtime結構中的dma buffer的地址和大小等參數。要注意的是,該回調可能會被多次調用,具體實現時要小心處理多次申請資源的問題。

ops.prepare

正式開始數據傳送之前會調用該函數,該函數通常會完成dma操作的必要準備工作。

ops.trigger

數據傳送的開始,暫停,恢復和停止時,該函數會被調用。

ops.pointer

該函數返回傳送數據的當前位置。

 

6.  音頻數據的dma操作

soc-platform驅動的最主要功能就是要完成音頻數據的傳送,大多數情況下,音頻數據都是通過dma來完成的。

 6.1.  申請dma buffer

因爲dma的特殊性,dma buffer是一塊特殊的內存,比如有的平臺規定只有某段地址範圍的內存纔可以進行dma操作,而多數嵌入式平臺還要求dma內存的物理地址是連續的,以方便dma控制器對內存的訪問。在ASoC架構中,dma buffer的信息保存在snd_pcm_substream結構的snd_dma_buffer *buf字段中,它的定義如下

  1. struct snd_dma_buffer {  
  2.     struct snd_dma_device dev;  /* device type */  
  3.     unsigned char *area;    /* virtual pointer */  
  4.     dma_addr_t addr;    /* physical address */  
  5.     size_t bytes;       /* buffer size in bytes */  
  6.     void *private_data; /* private for allocator; don't touch */  
  7. };  
struct snd_dma_buffer { struct snd_dma_device dev; /* device type */ unsigned char *area; /* virtual pointer */ dma_addr_t addr; /* physical address */ size_t bytes; /* buffer size in bytes */ void *private_data; /* private for allocator; don't touch */ };

那麼,在哪裏完成了snd_dam_buffer結構的初始化賦值操作呢?答案就在snd_soc_platform_driver的pcm_new回調函數中,還是以/sound/soc/samsung/dma.c爲例:

  1. static struct snd_soc_platform_driver samsung_asoc_platform = {  
  2.     .ops        = &dma_ops,  
  3.     .pcm_new    = dma_new,  
  4.     .pcm_free   = dma_free_dma_buffers,  
  5. };  
  6.   
  7. static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)  
  8. {  
  9.     return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);  
  10. }  
static struct snd_soc_platform_driver samsung_asoc_platform = { .ops = &dma_ops, .pcm_new = dma_new, .pcm_free = dma_free_dma_buffers, }; static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform); }

pcm_new字段指向了dma_new函數,dma_new函數進一步爲playback和capture分別調用preallocate_dma_buffer函數,我們看看preallocate_dma_buffer函數的實現:

  1. static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)  
  2. {  
  3.     struct snd_pcm_substream *substream = pcm->streams[stream].substream;  
  4.     struct snd_dma_buffer *buf = &substream->dma_buffer;  
  5.     size_t size = dma_hardware.buffer_bytes_max;  
  6.   
  7.     pr_debug("Entered %s\n", __func__);  
  8.   
  9.     buf->dev.type = SNDRV_DMA_TYPE_DEV;  
  10.     buf->dev.dev = pcm->card->dev;  
  11.     buf->private_data = NULL;  
  12.     buf->area = dma_alloc_writecombine(pcm->card->dev, size,  
  13.                        &buf->addr, GFP_KERNEL);  
  14.     if (!buf->area)  
  15.         return -ENOMEM;  
  16.     buf->bytes = size;  
  17.     return 0;  
  18. }  
static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream) { struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_dma_buffer *buf = &substream->dma_buffer; size_t size = dma_hardware.buffer_bytes_max; pr_debug("Entered %s\n", __func__); buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->area = dma_alloc_writecombine(pcm->card->dev, size, &buf->addr, GFP_KERNEL); if (!buf->area) return -ENOMEM; buf->bytes = size; return 0; }

該函數先是獲得事先定義好的buffer大小,然後通過dma_alloc_weitecombine函數分配dma內存,然後完成substream->dma_buffer的初始化賦值工作。上述的pcm_new回調會在聲卡的建立階段被調用,調用的詳細的過程請參考Linux ALSAs聲卡驅動之六:ASoC架構中的Machine中的圖3.1。

在聲卡的hw_params階段,snd_soc_platform_driver結構的ops->hw_params會被調用,在該回調用,通常會使用api:snd_pcm_set_runtime_buffer()把substream->dma_buffer的數值拷貝到substream->runtime的相關字段中(.dma_area, .dma_addr,  .dma_bytes),這樣以後就可以通過substream->runtime獲得這些地址和大小信息了。

dma buffer獲得後,即是獲得了dma操作的源地址,那麼目的地址在哪裏?其實目的地址當然是在dai中,也就是前面介紹的snd_soc_dai結構的playback_dma_data和capture_dma_data字段中,而這兩個字段的值也是在hw_params階段,由snd_soc_dai_driver結構的ops->hw_params回調,利用api:snd_soc_dai_set_dma_data進行設置的。緊隨其後,snd_soc_platform_driver結構的ops->hw_params回調利用api:snd_soc_dai_get_dma_data獲得這些dai的dma信息,其中就包括了dma的目的地址信息。這些dma信息通常還會被保存在substream->runtime->private_data中,以便在substream的整個生命週期中可以隨時獲得這些信息,從而完成對dma的配置和操作。

6.2  dma buffer管理
播放時,應用程序把音頻數據源源不斷地寫入dma buffer中,然後相應platform的dma操作則不停地從該buffer中取出數據,經dai送往codec中。錄音時則正好相反,codec源源不斷地把A/D轉換好的音頻數據經過dai送入dma buffer中,而應用程序則不斷地從該buffer中讀走音頻數據。

                                                                                 圖6.2.1   環形緩衝區

環形緩衝區正好適合用於這種情景的buffer管理,理想情況下,大小爲Count的緩衝區具備一個讀指針和寫指針,我們期望他們都可以閉合地做環形移動,但是實際的情況確實:緩衝區通常都是一段連續的地址,他是有開始和結束兩個邊界,每次移動之前都必須進行一次判斷,當指針移動到末尾時就必須人爲地讓他回到起始位置。在實際應用中,我們通常都會把這個大小爲Count的緩衝區虛擬成一個大小爲n*Count的邏輯緩衝區,相當於理想狀態下的圓形繞了n圈之後,然後把這段總的距離拉平爲一段直線,每一圈對應直線中的一段,因爲n比較大,所以大多數情況下不會出現讀寫指針的換位的情況(如果不對buffer進行擴展,指針到達末端後,回到起始端時,兩個指針的前後相對位置會發生互換)。擴展後的邏輯緩衝區在計算剩餘空間可條件判斷是相對方便。alsa driver也使用了該方法對dma buffer進行管理:

                                                                       圖6.2.2  alsa driver緩衝區管理

snd_pcm_runtime結構中,使用了四個相關的字段來完成這個邏輯緩衝區的管理:
  • snd_pcm_runtime.hw_ptr_base  環形緩衝區每一圈的基地址,當讀寫指針越過一圈後,它按buffer size進行移動;
  • snd_pcm_runtime.status->hw_ptr  硬件邏輯位置,播放時相當於讀指針,錄音時相當於寫指針;
  • snd_pcm_runtime.control->appl_ptr  應用邏輯位置,播放時相當於寫指針,錄音時相當於讀指針;
  • snd_pcm_runtime.boundary  擴展後的邏輯緩衝區大小,通常是(2^n)*size;
通過這幾個字段,我們可以很容易地獲得緩衝區的有效數據,剩餘空間等信息,也可以很容易地把當前邏輯位置映射回真實的dma buffer中。例如,獲得播放緩衝區的空閒空間:
  1. static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)  
  2. {  
  3.     snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;  
  4.     if (avail < 0)  
  5.         avail += runtime->boundary;  
  6.     else if ((snd_pcm_uframes_t) avail >= runtime->boundary)  
  7.         avail -= runtime->boundary;  
  8.     return avail;  
  9. }  
static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime) { snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr; if (avail < 0) avail += runtime->boundary; else if ((snd_pcm_uframes_t) avail >= runtime->boundary) avail -= runtime->boundary; return avail; }
要想映射到真正的緩衝區位置,只要減去runtime->hw_ptr_base即可。下面的api用於更新這幾個指針的當前位置:
  1. int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)  
int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
所以要想通過snd_pcm_playback_avail等函數獲得正確的信息前,應該先要調用這個api更新指針位置。
以播放(playback)爲例,我現在知道至少有3個途徑可以完成對dma buffer的寫入:
  • 應用程序調用alsa-lib的snd_pcm_writei、snd_pcm_writen函數;
  • 應用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
  • 應用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;
以上幾種方式最終把數據寫入dma buffer中,然後修改runtime->control->appl_ptr的值。
播放過程中,通常會配置成每一個period size生成一個dma中斷,中斷處理函數最重要的任務就是:
  • 更新dma的硬件的當前位置,該數值通常保存在runtime->private_data中;
  • 調用snd_pcm_period_elapsed函數,該函數會進一步調用snd_pcm_update_hw_ptr0函數更新上述所說的4個緩衝區管理字段,然後喚醒相應的等待進程;
  1. <SPAN style="FONT-FAMILY: Arial, Verdana, sans-serif"><SPAN style="WHITE-SPACE: normal"><PRE class=cpp name="code">void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)  
  2. {  
  3.     struct snd_pcm_runtime *runtime;  
  4.     unsigned long flags;  
  5.   
  6.     if (PCM_RUNTIME_CHECK(substream))  
  7.         return;  
  8.     runtime = substream->runtime;  
  9.   
  10.     if (runtime->transfer_ack_begin)  
  11.         runtime->transfer_ack_begin(substream);  
  12.   
  13.     snd_pcm_stream_lock_irqsave(substream, flags);  
  14.     if (!snd_pcm_running(substream) ||  
  15.         snd_pcm_update_hw_ptr0(substream, 1) < 0)  
  16.         goto _end;  
  17.   
  18.     if (substream->timer_running)  
  19.         snd_timer_interrupt(substream->timer, 1);  
  20.  _end:  
  21.     snd_pcm_stream_unlock_irqrestore(substream, flags);  
  22.     if (runtime->transfer_ack_end)  
  23.         runtime->transfer_ack_end(substream);  
  24.     kill_fasync(&runtime->fasync, SIGIO, POLL_IN);  
  25. }  
  26. 如果設置了transfer_ack_begin和transfer_ack_end回調,snd_pcm_period_elapsed還會調用這兩個回調函數。
      

  27.   
  28.   
  29.   
  30.   
  1. void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)  
  2. {  
  3.     struct snd_pcm_runtime *runtime;  
  4.     unsigned long flags;  
  5.   
  6.     if (PCM_RUNTIME_CHECK(substream))  
  7.         return;  
  8.     runtime = substream->runtime;  
  9.   
  10.     if (runtime->transfer_ack_begin)  
  11.         runtime->transfer_ack_begin(substream);  
  12.   
  13.     snd_pcm_stream_lock_irqsave(substream, flags);  
  14.     if (!snd_pcm_running(substream) ||  
  15.         snd_pcm_update_hw_ptr0(substream, 1) < 0)  
  16.         goto _end;  
  17.   
  18.     if (substream->timer_running)  
  19.         snd_timer_interrupt(substream->timer, 1);  
  20.  _end:  
  21.     snd_pcm_stream_unlock_irqrestore(substream, flags);  
  22.     if (runtime->transfer_ack_end)  
  23.         runtime->transfer_ack_end(substream);  
  24.     kill_fasync(&runtime->fasync, SIGIO, POLL_IN);  
  25. }  
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; unsigned long flags; if (PCM_RUNTIME_CHECK(substream)) return; runtime = substream->runtime; if (runtime->transfer_ack_begin) runtime->transfer_ack_begin(substream); snd_pcm_stream_lock_irqsave(substream, flags); if (!snd_pcm_running(substream) || snd_pcm_update_hw_ptr0(substream, 1) < 0) goto _end; if (substream->timer_running) snd_timer_interrupt(substream->timer, 1); _end: snd_pcm_stream_unlock_irqrestore(substream, flags); if (runtime->transfer_ack_end) runtime->transfer_ack_end(substream); kill_fasync(&runtime->fasync, SIGIO, POLL_IN); } 如果設置了transfer_ack_begin和transfer_ack_end回調,snd_pcm_period_elapsed還會調用這兩個回調函數。

7.  圖說代碼

最後,反正圖也畫了,好與不好都傳上來供參考一下,以下這張圖表達了 ASoC中Platform驅動的幾個重要數據結構之間的關係:


                                                                                  圖7.1   ASoC Platform驅動

一堆的private_data,很重要但也很容易搞混,下面的圖不知對大家有沒有幫助:


                                                              圖7.2  private_data

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章