1. 程式人生 > >Linux ASoC音訊驅動架構 及 Machine驅動程式碼分析

Linux ASoC音訊驅動架構 及 Machine驅動程式碼分析

【軟體框架】

    在對要做的事情一無所知的時候,從全域性看看系統的拓撲圖對我們認識新事物有很大的幫助。Audio 部分的驅動程式框架如下圖所示:

    這幅圖明顯地分為 3 級。

    上方藍色系的 ALSA Kernel 整體屬於Linux Kernel,是原生Linux 作業系統的一部分,其中又分出 ASoC Core 和 PCM Core 兩級,和她們相關的程式碼都可以直接在 Linux 原始碼中找到。

    中間淡紅色的部分看名字就知道和驅動相關,分為左右 2 條支線。需要注意的是左側支線由 ASoC 派生而來,而 ASoC 雖本質上屬於 ALSA,但在程式碼上將各部分驅動進行分離設計,也就是這裡看到的 Platform Driver、Machine Driver、Codec Driver,分別對應 CPU 驅動、板驅動、編解碼晶片驅動。這種架構增強了 CPU 晶片驅動和編解碼晶片驅動的可移植性,讓我們在開發音訊驅動時只需要重新編寫電路板相關的板驅動即可。進一步分析,緊接 Platform Driver 後面是 SST Driver,這個 SST 即 Smart Sound Technology,是 Intel 自研的技術,所以這部分結構不一定適用於其它 CPU 晶片,但一旁的 DMA Driver 卻是所有 CPU 驅動都必須包含的,因為音訊的實質是資料;Codec Driver 之後是 I2C 驅動,對於編解碼晶片暫存器的讀寫都是通過 I2C 匯流排實現。右側支線從 PCM Core 引出,這部分我目前還不熟悉,從框圖和字面意思上看是和高清音訊介面相關的,直接通過 ALSA 的 PCM 機制進行操作並且在硬體上使用專門的 HDMI 控制器而非編解碼晶片。

    下方灰色一級是最底層的硬體部分,依次為 CPU 內嵌的 DSP 模組、獨立的編解碼晶片、獨立的 HDMI 控制器。在 DSP 與 Codec 之間存在音訊資料傳輸,雖然在圖中沒有註明,但我們知道這是通過 I2S 匯流排實現的。在我的實際專案中,DSP 模組整合在編解碼晶片中。

    無疑,編解碼晶片驅動,也即 Codec Driver 部分是我們編寫音訊驅動的核心。負責對內驅動晶片實現裝置/驅動正常註冊,對外溝通 DSP 進行音訊資料交換。所以下文以此為中心進行程式碼分析。

【原始碼檔案架構】

    按照 ASoC 框架的設計理念,原始碼檔案應該分為 3 個部分,分別是 Platform Driver、Machine Driver、Codec Driver,這 3 者為並行關係,各對應一份原始碼。其中,Platform Driver 相關的原始碼主要實現 DMA 功能和 DAI,即 DSP 模組的 I2S 資料傳輸功能,並匯出相應變數或操作函式介面;Codec Driver 相關的原始碼主要實現 I/O 控制、DAPM 和 PCM 配置,並匯出相應變數或操作函式介面;Machine Driver 相關的原始碼則將前 2 個檔案中匯出的介面繫結在一起。核心啟動後,以模組的形式載入 3 個驅動。

    在我的實際專案中,檔案 rt5677.c 中是 Codec Driver 部分的原始碼,檔案 cht_rt5677.c 中則為 Machine Driver 部分的原始碼。前者實現編解碼晶片的暫存器配置、裝置/驅動建立與註冊,後者實現將 Machine 與 Platform 繫結。

【Machine Driver 程式碼分析】

    既然驅動是以模組的形式載入的,那麼我們就從模組初始化函式開始閱讀程式碼。

    模組初始化函式部分的程式碼為 late_initcall(snd_cht_driver_init);

 檢視 snd_cht_driver_init() 函式:

static int __init snd_cht_driver_init(void)
{
    …
    return platform_driver_register(&snd_cht_mc_driver);
}

    原來是註冊 platform 驅動,接下來的流程應該就比較熟悉了。

    結構體snd_cht_mc_driver 的定義如下:

static struct platform_driver snd_cht_mc_driver = {
         .driver = {
                   .owner = THIS_MODULE,
                   .name = "cht_rt5677",
                   .pm = &snd_cht_mc_pm_ops,
         },
         .probe = snd_cht_mc_probe,    // 繫結 probe() 函式
         .remove = snd_cht_mc_remove,
         .shutdown = snd_cht_mc_shutdown,
};

        這裡的 probe 函式 snd_cht_mc_probe() 會在平臺驅動註冊過程中被呼叫。檢視該函式程式碼:

static int snd_cht_mc_probe(struct platform_device *pdev)
{
        …
         /* register the soc card */
         snd_soc_card_cht.dev = &pdev->dev;
         ret_val = snd_soc_register_card(&snd_soc_card_cht);    // 註冊音效卡
        …
         platform_set_drvdata(pdev,&snd_soc_card_cht);    // 設定音效卡資訊
         codec = cht_get_codec(&snd_soc_card_cht);
        // use gpio GPIO_SPK_EN to enable/disable ext boost pa
         gpio_request(GPIO_SPK_EN, "speaker boost pa ctl");
         gpio_direction_output(GPIO_SPK_EN, 0);
        …
         return ret_val;
}

        其中 snd_soc_register_card() 是ASoC Core 中的函式,由Linux 核心實現,功能是建立和註冊一個音效卡裝置。而函式 platform_set_drvdata() 的功能是將預設的音效卡結構體值配置到 machine driver。追蹤 snd_soc_card_cht 這個結構體,其定義如下:

/* SoCcard */
static struct snd_soc_card snd_soc_card_cht = {
         .name = "cherrytrailaud",
         .dai_link = cht_dailink,    // 繫結 dai_link
         .num_links = ARRAY_SIZE(cht_dailink),
         .set_bias_level = cht_set_bias_level,
         .dapm_widgets = cht_dapm_widgets,    // 繫結 dapm_widgets
         .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets),
         .dapm_routes = cht_audio_map,
         .num_dapm_routes = ARRAY_SIZE(cht_audio_map),
         .controls = cht_mc_controls,    // 繫結 controls
         .num_controls = ARRAY_SIZE(cht_mc_controls),
};

        在這裡我們看到了 cht_dailink 陣列、cht_dapm_widgets() 陣列、cht_mc_controls 陣列。後 2 者主要實現 DAPM 相關操作,我們重點檢視 cht_dailink,該陣列核心部分程式碼如下:

static struct snd_soc_dai_link cht_dailink[] = {
         [CHT_DPCM_AUDIO] = {
                   .name = "Cherrytrail Audio Port",
                   .stream_name = "Cherrytrail Audio",
                   .cpu_dai_name = "Headset-cpu-dai",
                   .codec_name = "snd-soc-dummy",
                   .codec_dai_name = "snd-soc-dummy-dai",
                   .platform_name = "sst-platform",
                   .init = cht_audio_init,    // 繫結 init() 函式
                   .ignore_suspend = 1,
                   .dynamic = 1,
                   .ops = &cht_aif1_ops,
                   .dpcm_playback = 1,
         },
        …
};

        初始化函式 cht_audio_init() 在第 1 個 cht_dailink 元素中被繫結。至此,Machine Driver 相關程式碼的分析就完成了。