ALSA架構分析

主要分爲以下幾類:
pcmC0D0p —— Playback
pcmC0D0c —— Capture
controlC0 —— Control,比如各種音頻控件開關、音量增益等

一套嵌入式硬件平臺(Machine)包含了平臺AP(Platform)和音頻CODEC芯片(Codec),對應ASoC的三個設備驅動。這三個設備分別註冊各自功能的dev設備,但都是以內核platform設備模型來創建.

https://blog.csdn.net/RadianceBlau/article/details/79432661
https://blog.csdn.net/u014310046/article/details/53671853
https://blog.csdn.net/longwang155069/article/details/53392769


ASOC音頻架構
爲了實現上述的新feature,ASOC將嵌入式音頻系統分爲三大類可重複使用的驅動程序:  Platform,  Machine,  Codec。

Codec類:     Codec即編解碼芯片的驅動,此Codec驅動是和平臺無關,包含的功能有:  音頻的控制接口,音頻讀寫IO接口,以及DAPM的定義等。如果需要的話,此Codec類可以在BT,FM,MODEM模塊中不做修改的使用。因此Codec就是一個可重複使用的模塊,同一個Codec在不同的SOC中可以使用。

Platform類:  可以理解爲某款SOC平臺,平臺驅動中包括音頻DMA引擎驅動,數字接口驅動(I2S, AC97, PCM)以及該平臺相關的任何音頻DSP驅動。同樣此Platform也可以重用,在不同的Machine中可以直接重複使用。

Machine類:  Machine可以理解爲是一個橋樑,用於在Codec和Platform之間建立聯繫。此Machine指定了該機器使用那個Platform,那個Codec,最終會通過Machine建立兩者之間的聯繫。

ASoC把音頻系統同樣分爲3大部分:Machine,Platform/CPU和Codec。 
Platform :
一般是指某一個SoC平臺,比如MT6582, MT6595, MT6752等等,與音頻相關的通常包含該SoC中的Clock、AFE、I2S、DMA等等。
Codec :  字面上的意思就是編解碼器,Codec裏面包含了I2S接口、DAC、ADC、Mixer、PA(功放),通常包含多種輸入(Mic、Line-in、I2S、PCM)和多個輸出(耳機、喇叭、聽筒,Line-out),Codec和Platform一樣,是可重用的部件。
Machine :
綁定platform driver和codec driver 。 

一、Machine設備驅動 :
Machine設備可以看成是一塊嵌入式主板(Board) 或者一塊聲卡。machine設備驅動是ASoC驅動框架的入口, 主要功能是負責platform/cpu和codec之間的連接和控制,或者響應獨立於Platform功能和Codec功能之外的特殊音頻事件,如平臺GPIO控制外置功放等,這些屬於machine本身的特定操作代碼,都會放到machine驅動裏。
Machine設備驅動的主要功能是定義各種DAI(Digital Audio Interface) links,它的作用是把platform/cpu和codec設備驅動連接起來,形成完整的音頻通路。

machine設備的初始化是整個ASoC驅動的入口。 machine設備的probe()會調用snd_soc_register_card()去註冊聲卡,然後在snd_soc_instantiate_card()裏實例化聲卡設備的時候,調用Platform和Codec設備各自的probe(),完成這兩個設備的初始化。
sound/soc/mediatek/common_int/mtk-soc-machine.c
"mediatek,audio"
.probe = mt_soc_snd_init,
    platform_set_drvdata(pdev, &mt_snd_soc_card_mt);
        static struct snd_soc_card mt_snd_soc_card_mt = {
            .name       = "mt-snd-card",
            .dai_link   = mt_soc_dai_common,
            .num_links  = ARRAY_SIZE(mt_soc_dai_common),
        };
    snd_soc_register_card
        snd_soc_instantiate_card
            1.   根據num_links的值,進行DAIs的bind工作。第一步先bind cpu側的dai
            snd_soc_find_dai    此函數會在component_list鏈表中先找到相同的name,然後在component->dai_list中查找是否有相同的dai_name。此處的component_list是在註冊codec和platform中的時候設置的。會在codec和platform的時候會詳細介紹。在此處找到註冊的cpu_dai之後,存在snd_soc_pcm_runtime中的cpu_dai中。
            
            2.  然後根據codec的數據,尋找codec側的dai。
            snd_soc_find_dai
            然後將找到的codec側的dai也同樣賦值給snd_soc_pcm_runtime中的codec_dai中。
            3.在platform_list鏈表中查找platfrom,根據dai_link中的platform_name域。如果沒有platform_name,則設置爲"snd-soc-dummy"
            這樣查找完畢之後,snd_soc_pcm_runtime中存儲了查找到的codec, dai,  platform。
            
            4.  接着初始化註冊的codec cache,cache_init代表是否已經初始化過。
            
            5.  然後調用ALSA中的創建card的函數:  snd_card_new創建一個card
            snd_card_new
            
            6.  然後依次調用各個子部件的probe函數
            soc_probe_link_components
            soc_probe_link_dais
            
            7.  在soc_probe_link_dais函數中依次調用了cpu_dai, codec_dai側的probe函數
            cpu_dai->driver->probe
            soc_probe_codec_dai
            
            8.   最終調用到soc_new_pcm函數創建pcm設備
            soc_new_pcm
            最中此函數會調用ALSA的標準創建pcm設備的接口:  snd_pcm_new,然後會設置pcm相應的ops操作函數集合。然後調用到platform->driver->pcm_new的函數。此處不帖函數了。

            9.  接着會在dapm和dai widget做相應的操作,後期會設置control參數,最終會調用到ALSA的註冊card的函數snd_card_regist。
            snd_card_register
            
            總結:  經過Machine的驅動的註冊,Machine會根據註冊以"soc_audio"爲名字的平臺設備,然後在同名的平臺的驅動的probe函數中,會根據snd_soc_dai_link結構體中的name,進行匹配查找相應的codec, codec_dai,platform, cpu_dai。找到之後將這些值全部放入結構體snd_soc_pcm_runtime的相應位置,然後註冊card,依次調用codec, platform,cpu_dai側相應的probe函數進行初始化,接着創建pcm設備,註冊card到系統中。其實ASOC也就是在ALSA的基礎上又再次封裝了一次,讓寫驅動更方便,簡便。

            這樣封裝之後,就可以大大簡化驅動的編寫,關於Machine驅動需要做的:
            1.   註冊名爲"soc-audio"的平臺設備。
            2.   分配一個struct snd_soc_card結構體,然後設置其中的dai_link。對其中的dai_link再次設置。
            3.   將struct snd_soc_card結構放入到平臺設備的dev的私有數據中。
            4.   註冊平臺設備。
                        
        
其中dai_link結構就是用作連接platform和codec的,指明到底用那個codec,那個platfrom。那是通過什麼指定的?  如果有興趣可以詳細看snd_soc_dai_link的註釋,此註釋寫的非常清楚。
    {
        .name = "I2S0DL1OUTPUT",
        .stream_name = MT_SOC_I2SDL1_STREAM_NAME,
        .cpu_dai_name   = MT_SOC_I2S0DL1_NAME,
        .platform_name  = MT_SOC_I2S0DL1_PCM,
        .codec_dai_name = MT_SOC_CODEC_I2S0TXDAI_NAME,
        .codec_name = MT_SOC_CODEC_NAME,
    },    
        
二、Platform 設備驅動:
Platform設備可看作是平臺AP(SoC主控,或CPU)。 它負責提供嵌入式平臺端的音頻功能, 如播放、錄音、 Voice通話等。
Platform設備驅動主要有兩個作用:
(1) transfer:負責平臺AP端的audio/voice數據流(stream)和DSP之間的傳輸;
(2) routing:將stream數據流按照特定的路線對應到其他音頻模塊中。
ASoC會註冊多個Platform設備來負責不同功能的音頻模塊:
(1) msm-pcm-dsp: 負責audio回放/錄音
代碼路徑: kernel/sound/msm/msm-pcm-q6.c
(2) msm-pcm-voice: 負責voice通話
代碼路徑: kernel/sound/msm/msm-pcm-voice-v2.c
(3) msm-voip-dsp: 負責VoIP通話
代碼路徑: kernel/sound/msm/msm-pcm-voip-v2.c
(4) msm-compress-dsp: 負責壓縮格式的audio播放
代碼路徑: kernel/sound/msm/msm-compressed-q6-v2.c
(5) msm-pcm-routing:負責stream數據流的路線指定
代碼路徑: kernel/sound/msm/msm-pcm-routing-v2.c
代碼路徑: kernel/sound/msm/msm-pcm-routing-v2.c
kernel-4.4/sound/soc/mediatek/mt6739/mt6739-sound.c

Plateform Dai:
kernel-4.4/sound/soc/mediatek/common_int/mtk-soc-dai-stub.c

在ASOC在Platform部分,主要是平臺相關的DMA操作和音頻管理。大概流程先將音頻數據從內存通過DMA方式傳輸到CPU側的dai接口,然後通過CPU的dai接口(通過I2S總線)將數據從達到Codec中,數據會在Codec側會解碼的操作,最終輸出到耳機/音箱中。

在platfrom側的主要功能有:  音頻數據管理,音頻數據傳輸通過dma; 數據如何通過cpudai傳入到codec dai,已經cpu測dai的配置。
而上述的兩大類功能在ASOC中使用兩個結構體表示:
snd_soc_dai_driver代表cpu側的dai驅動,其中包括dai的配置(音頻格式,clock,音量等)。
snd_soc_platform_driver代表平臺使用的dma驅動,主要是數據的傳輸等。
和Machine一樣,使用snd_soc_platform結構對所有platform設備進行統一抽象。

如何找到Machine對應的Platform呢? 答案也是通過Machine中的snd_soc_dai_link中的platform_name。在內核中搜素platform_name所對應的name。
通常還有另一種方式,會將cpu側dai的驅動和平臺相關的dma驅動分離的。也就是machine中的snd_soc_dai_link的platform_name和cpu_dai_name不相同。而上述的samsung的例子則是platform_name和cpu_dai_name是相同的。不過原理都是相同的最後都會調用snd_soc_add_platform函數註冊platform到AOSC core的。

在內核中搜素platform_name所對應的name。
sound/soc/mediatek/common_int/mtk-soc-pcm-dl1-i2s0.c
static const struct of_device_id mt_soc_pcm_dl1_i2s0_of_ids[] = {
    {.compatible = "mediatek,mt_soc_pcm_dl1_i2s0",},
    {}
};
static struct platform_driver mtk_i2s0_driver = {
    .driver = {
           .name = "mt-soc-i2s0-pcm",
           .owner = THIS_MODULE,
#ifdef CONFIG_OF
           .of_match_table = mt_soc_pcm_dl1_i2s0_of_ids,
#endif
           },
    .probe = mtk_i2s0_probe,
    .remove = mtk_i2s0_remove,
};
static struct snd_soc_platform_driver mtk_i2s0_soc_platform = {
    .ops = &mtk_i2s0_ops,
    .pcm_new = mtk_asoc_pcm_i2s0_new,
    .probe = mtk_afe_i2s0_probe,
};

mtk_i2s0_probe
    snd_soc_register_platform //mtk_i2s0_soc_platform
        調用snd_soc_add_platform函數註冊platformd到ASOC core。
        snd_soc_add_platform
        
    初始化platform的component, 設置probe, remove回調,最終將platform添加到platform_list中,將platform->component添加到component_list鏈表中。    
        

在內核中搜素cpu_dai_name所對應的name。    
kernel-4.4/sound/soc/mediatek/common_int/mtk-soc-dai-stub.c    
static const struct of_device_id mt_soc_dai_stub_of_ids[] = {
    { .compatible = "mediatek,mt_soc_dai_stub", },
    {}
};
static struct platform_driver mtk_dai_stub_driver = {
    .probe  = mtk_dai_stub_dev_probe,
    .remove = mtk_dai_stub_dev_remove,
    .driver = {
        .name = "mt-soc-dai-driver",
        .owner = THIS_MODULE,
#ifdef CONFIG_OF
        .of_match_table = mt_soc_dai_stub_of_ids,
#endif
    },
};
static struct snd_soc_dai_driver mtk_dai_stub_dai[] = {
    {
        .playback = {
            .stream_name = MT_SOC_I2SDL1_STREAM_NAME,
            .rates = SNDRV_PCM_RATE_8000_192000,
            .formats = SND_SOC_ADV_MT_FMTS,
            .channels_min = 1,
            .channels_max = 2,
            .rate_min = 8000,
            .rate_max = 192000,
        },
        .name = "mt-soc-i2s0dl1dai-driver",
        .ops = &mtk_dai_stub_ops,
    },    
    
mtk_dai_stub_dev_probe
    snd_soc_register_component(&pdev->dev, &mt_dai_component,
                    mtk_dai_stub_dai, ARRAY_SIZE(mtk_dai_stub_dai));
    通過snd_soc_register_component註冊一個component組件。傳入的參數分別是snd_soc_component_driver和snd_soc_dai_driver
    static const struct snd_soc_component_driver mt_dai_component = {
    .name       = MT_SOC_DAI_NAME,
    };
    static struct snd_soc_dai_driver mtk_dai_stub_dai[] = {
    {
        .playback = {
            .stream_name = MT_SOC_I2SDL1_STREAM_NAME,
            .rates = SNDRV_PCM_RATE_8000_192000,
            .formats = SND_SOC_ADV_MT_FMTS,
            .channels_min = 1,
            .channels_max = 2,
            .rate_min = 8000,
            .rate_max = 192000,
        },
        .name = MT_SOC_I2S0DL1_NAME,
        .ops = &mtk_dai_stub_ops,
    },
    根據傳入參數,進入到devm_snd_soc_register_component函數分析。其中devm是一種資源管理的方式,不用考慮資源釋放,內核會內部做好資源回收。然後進入snd_soc_register_component函數。
    這裏估計沒有使用devm
    
    此函數和snd_soc_register_codec的大體流程一致,都是初始化snd_soc_component的實例,然後註冊dai,最終將註冊的dai放入到component->dai_list中,然後將分配的component放入到component_list鏈表中。
    上述的步驟只是完成platform的一部分,關於cpu_dai側的設置,配置。還需要平臺相關的dma操作。
    
    總結:  通過machine中的snd_soc_dai_link中的platform_name和cpu_dai_name分別查找平臺的dma設備驅動和cpu側的dai驅動。最終會將這dai保存到component->dai_list中,platform保存到platform_list當中。然後將component放入到component_list鏈表中。這些數據會在Machine代碼的開始過程中進行匹配操作。

    關於cpu側的驅動總結:
    1.   分配一個cpu_dai_name的平臺驅動,註冊。
    2.   分配一個struct snd_soc_dai_driver結構,然後設置相應數據。
    3.   調用snd_soc_register_component函數註冊cpu側的dai結構。
    4.   分配一個struct snd_soc_platform_driver結構,設置相應的數據。
    5.   最終調用snd_soc_add_platform函數添加snd_soc_platform_driver結構。
    
三:CODEC設備驅動:
對於一塊嵌入式設備的主板來說,一般會集成一顆音頻CODEC芯片。 ASoC架構下的CODEC設備功能和物理CODEC對應, 其在machine的控制下和platform設備連接,負責音頻的實際處理,如聲音播放(D/A)、聲音採集(A/D) 和各種音頻control控件的設置。
平臺一般會集成一個CODEC單元,也會有添加外部獨立CODEC芯片,已達到更好的音質。如Wolfson的WM8998芯片,它是一顆獨立的CODEC,基於I2S接口從平臺獲取音頻數據,在其內部經過DAC輸出到耳機或speaker。高通有自己的外置CODEC芯片,如WCD9326/9335等,和平臺AP的音頻數據接口叫Slimbus,其實是和I2S複用的GPIO口。
CODEC芯片可能需要I2C或SPI控制。
COCEC設備支持耳機插拔及按鍵檢測功能。
CODEC設備驅動中定義了大量的mixer、 mux和各種開關的kcontrol,以及DAPM使用的widget和route。
kernel-4.4\sound\soc\mediatek\codec\mt6357\mtk-soc-codec-6357.c
static const struct snd_kcontrol_new mt6357_snd_controls[] = {
    SOC_ENUM_EXT("Audio_Amp_R_Switch", Audio_DL_Enum[0], Audio_AmpR_Get,
             Audio_AmpR_Set),
             
並無snd_soc_dapm_widget

CODEC Dai:
kernel-4.4/sound/soc/mediatek/codec/mt6357/mtk-soc-codec-6357.c

在Machine中已經知道,snd_soc_dai_link結構就指明瞭該Machine所使用的Platform和Codec。在Codec這邊通過codec_dai和Platform側的cpu_dai相互通信,既然相互通信,就需要遵守一定的規則,其中codec_dai和cpu_dai統一抽象爲struct snd_soc_dai結構,而將dai的相關操作使用snd_soc_dai_driver抽象。同時也需要對所有的codec設備進行抽象封裝,linux使用snd_soc_codec進行所有codec設備的抽象,而將codec的驅動抽象爲snd_soc_codec_driver結構。
所有簡單來說,Codec側有四個重要的數據結構:
struct snd_soc_dai,struct snd_soc_dai_driver,struct snd_soc_codec,struct snd_soc_codec_driver。

如何找到codec的代碼呢?  答案是通過machine中的snd_soc_dai_link結構:
.codec_name = "mt-soc-codec",

在內核中搜索codec_name="mt-soc-codec",會在kernel-4.4\sound\soc\mediatek\codec\mt6357\mtk-soc-codec-6357.c中發現
static struct platform_driver mtk_codec_6357_driver = {
    .driver = {
           .name = "mt-soc-codec",
           .owner = THIS_MODULE,
#ifdef CONFIG_OF
           .of_match_table = mt_soc_codec_63xx_of_ids,
#endif
           },
    .probe = mtk_mt6357_codec_dev_probe,
    .remove = mtk_mt6357_codec_dev_remove,
};

mtk_mt6357_codec_dev_probe
    snd_soc_register_codec(&pdev->dev,&soc_mtk_codec, mtk_6357_dai_codecs,ARRAY_SIZE(mtk_6357_dai_codecs));
    此出通過snd_soc_register_codec函數註冊了uda134x的codec,同時傳入了snd_soc_codec_driver和snd_soc_dai_driver結構。
    static struct snd_soc_codec_driver soc_mtk_codec = {
        .probe = mt6357_codec_probe,
        .remove = mt6357_codec_remove,
        .read = mt6357_read,
        .write = mt6357_write,
    };
    static struct snd_soc_dai_driver mtk_6357_dai_codecs[] = {
        {
         .name = MT_SOC_CODEC_I2S0TXDAI_NAME,
         .ops = &mt6323_aif1_dai_ops,
         .playback = {
                  .stream_name = MT_SOC_I2SDL1_STREAM_NAME,
                  .channels_min = 1,
                  .channels_max = 2,
                  .rate_min = 8000,
                  .rate_max = 192000,
                  .rates = SNDRV_PCM_RATE_8000_192000,
                  .formats = SND_SOC_ADV_MT_FMTS,
                  }
         },    
    static const struct snd_soc_dai_ops mt6323_aif1_dai_ops = {
        .prepare = mt63xx_codec_prepare,
    };
    
        繼續進入snd_soc_register_codec函數分析。
        1.  分配一個snd_soc_codec結構,設置component參數。
        2.  調用snd_soc_component_initialize函數進行component部件的初始化,會根據snd_soc_codec_driver中的struct snd_soc_component_driver結構設置snd_soc_codec中的component組件,詳細代碼不在貼出。
        snd_soc_component_initialize
        3.  根據snd_soc_codec_driver的參數,進行一系列的初始化,
        4.  根據snd_soc_dai_driver中的參數設置Format
        fixup_codec_formats(&dai_drv[i].playback);
        fixup_codec_formats(&dai_drv[i].capture);
        5.  調用snd_soc_register_dais接口註冊dai,傳入參數有dai的驅動,以及dai的參數,因爲一個codec不止一個dai接口
        snd_soc_register_dais
            根據dai的數目,分配snd_soc_dai結構,根據dai的數目設置dai的名字,這是dai的傳入參數驅動,然後將此dai加入到component的dai_list中。
        6.   根據component的dai_list,對所有的dai設置其所屬的codec。
        7.   將所有的組間加入到component_list中,然後將註冊的codec存入codec_list中。
    
    至此,codec的註冊就分析完畢。

    總結:  通過調用snd_soc_register_codec函數之後,分配的codec最終放入了codec_list鏈表中,codec下的component組件全部放入component_list鏈表中,codec下的dai全部存放入code->component.dai_list中。 可以在上節的Machine中看到,machine匹配codec_dai和cpu_dai也是從code->component.dai_list中獲取的。

    關於codec側驅動總結:
    1.   分配名字爲"codec_name"的平臺驅動,註冊。
    2.   定義struct snd_soc_codec_driver結構,設置,初始化。
    3.   定義struct snd_soc_dai_driver結構,設置,初始化。
    4.   調用snd_soc_register_codec函數註冊codec。
    

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