Linux ALSA音頻驅動(二)


根據一我們發現創建聲卡的全過程基本都在snd_soc_instantiate_cards()函數實現。我們要了解聲卡的創建過程,就必須了解ASoC的軟件架構(詳細http://blog.csdn.net/droidphone/article/details/7165482);

在軟件層面,ASoC也把嵌入式設備的音頻系統同樣分為3大部分,Machine,Platform和Codec

Codec驅動  ASoC中的一個重要設計原則就是要求Codec驅動是平台無關的,它包含了一些音頻的控件(Controls),音頻接口,DAMP(動態音頻電源管理)的定義和某些Codec IO功能。
為了保證硬件無關性,任何特定於平台和機器的代碼都要移到Platform和Machine驅動中。所有的Codec驅動都要提供以下特性:
   Codec DAI 和 PCM的配置信息;
   Codec的IO控制方式(I2C,SPI等);
   Mixer和其他的音頻控件;
   Codec的ALSA音頻操作接口;
必要時,也可以提供以下功能:
   DAPM描述信息;
   DAPM事件處理程序;
   DAC數字靜音控制
Platform驅動  它包含了該SoC平台的音頻DMA和音頻接口的配置和控制(I2S,PCM,AC97等等);它也不能包含任何與板子或機器相關的代碼。
Machine驅動  Machine驅動負責處理機器特有的一些控件和音頻事件(例如,當播放音頻時,需要先行打開一個放大器);單獨的Platform和Codec驅動是不能工作的,它必須由Machine驅動把它們結合在一起才能完成整個設備的音頻處理工作。

整個ASoC是由一些列數據結構組成,要搞清楚ASoC的工作機理,必須要理解這一系列數據結構之間的關系和作用,下面的關系圖展示了ASoC中重要的數據結構之間的關聯方式(我的內核是2.6.37版本 與2.6.35版本已經有區別了):

由上圖我們可以看出,2.6.37中的數據結構更為合理和清晰,取消了snd_soc_device結構,直接用snd_soc_card取代了它,並且強化了snd_soc_pcm_runtime的作用,同時還增加了另外兩個數據結構snd_soc_codec_driver和snd_soc_platform_driver,用於明確代表Codec驅動和Platform驅動。到這里可能會問pltform和codec是這么關聯的?看下圖:

通過下面的代碼結構體的成員不難發現ASoC的各個成員(Platform, codec, dai)通過名字聯系在一起:

static struct snd_soc_dai_link ti81xx_mcasp_dai[] = {
    {
        .name = "TVP5158AUDIO",
        .stream_name = "TVP-PCM",
        .cpu_dai_name= "davinci-mcasp.0",
        .codec_dai_name = "tvp5158-hifi",
        .platform_name ="davinci-pcm-audio",
        .codec_name = "tvp5158-audio",
        .ops = &ti81xx_dvr_ops,
    },
    {
        .name = "TLV320AIC3X",
        .stream_name = "AIC3X",
        .cpu_dai_name= "davinci-mcasp.2",
        .codec_dai_name = "tlv320aic3x-hifi",
        .codec_name = "tlv320aic3x-codec.1-0018",
        .platform_name = "davinci-pcm-audio",
        .init = ti81xx_dvr_aic3x_init,
        .ops = &ti81xx_dvr_ops,
    }
};

#ifdef CONFIG_SND_SOC_TI81XX_HDMI static struct snd_soc_dai_link ti81xx_hdmi_dai = {
    .name = "HDMI_SOC_LINK",
    .stream_name = "hdmi",
    .cpu_dai_name = "hdmi-dai",
    .platform_name = "davinci-pcm-audio",
    .codec_dai_name = "HDMI-DAI-CODEC",     /* DAI name */
    .codec_name = "hdmi-dummy-codec",
};
#endif

static struct snd_soc_card ti81xx_dvr_snd_card0 = {
    .name = "TI81XX SOUND0",
    .dai_link = ti81xx_mcasp_dai,
    .num_links = ARRAY_SIZE(ti81xx_mcasp_dai),
};

#ifdef CONFIG_SND_SOC_TI81XX_HDMI static struct snd_soc_card ti81xx_dvr_snd_card1 = {
    .name = "TI81XX SOUND1",
    .dai_link = &ti81xx_hdmi_dai,
    .num_links = 1,
};
#endif

從上面看兩個snd_coc_card兩個結構體通過平台設備私有數據的方式傳遞給平台驅動,並且通過snd_soc_register_card創建了兩個聲卡card0 card1。
我們已經知道了ALSA音頻的軟件架構分為Machine,Platform和Codec三個部分,從上面的代碼可以清楚的看出:
Codec:聲卡0對應的Codec是tvp5158和TLV320AIC3X;聲卡1則對應的Codec是HDMI。
Platform:TI的davinci系列。
Machine:TI81xx。

這些部分怎么連接起來的?接下來我們要分析ALSA架構中一個非常重要的函數snd_soc_instantiate_card,先貼下整個代碼:

static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
    struct platform_device *pdev = to_platform_device(card->dev);
    int ret, i;

    mutex_lock(&card->mutex);

    if (card->instantiated) {
        mutex_unlock(&card->mutex);
        return;
    }

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

    /* bind completed ? */
    if (card->num_rtd != card->num_links) {
        mutex_unlock(&card->mutex);
        return;
    }

    /* 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) {
        printk(KERN_ERR "asoc: can't create sound card for card %s\n",
            card->name);
        mutex_unlock(&card->mutex);
        return;
    }
    card->snd_card->dev = card->dev;

#ifdef CONFIG_PM
    /* deferred resume work */
    INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif

    /* initialise the sound card only once */
    if (card->probe) {
        ret = card->probe(pdev);
        if (ret < 0)
            goto card_probe_error;
    }

    for (i = 0; i < card->num_links; i++) {
        ret = soc_probe_dai_link(card, i);
        if (ret < 0) {
            pr_err("asoc: failed to instantiate card %s: %d\n",
                   card->name, ret);
            goto probe_dai_err;
        }
    }

    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->name);

    ret = snd_card_register(card->snd_card);
    if (ret < 0) {
        printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
        goto probe_dai_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) {
            printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
            while (--i >= 0)
                soc_unregister_ac97_dai_link(&card->rtd[i]);
            goto probe_dai_err;
        }
    }
#endif

    card->instantiated = 1;
    mutex_unlock(&card->mutex);
    return;

probe_dai_err:
    for (i = 0; i < card->num_links; i++)
        soc_remove_dai_link(card, i);

card_probe_error:
    if (card->remove)
        card->remove(pdev);

    snd_card_free(card->snd_card);

    mutex_unlock(&card->mutex);
}

從上面的標記為紅色的代碼則是這個函數中要重點分析的部分:
1.card->instantiated 來判斷該卡是否已經實例化,如果已經實例化則直接返回。
2.card->num_links   cpu<->code DAI鏈接數,聲卡0為2個link(snd_soc_dai_link), 聲卡1為1個link(snd_soc_dai_link);在soc_probe的時候從platform_device參數中取出num_links。
3.soc_bind_dai_link 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驅動的信息。
             那么,這個鏈表的元素從哪里來的呢?這些元素的名字則是由設備的名字(device->name)和對應的驅動的名字(device->driver->name)組成。
             dai是通過snd_soc_register_dai函數添加到dai_list鏈表中(davinci-hdmi.c("hdmi-dai"), davinci-mcasp.c("davinci-mcasp.0", "davinci-mcasp.1", 注意平台設備的時候,如果id不等於-1的時候,設備的名字有name和id組成  (platform_device_add)),rtd->cpu_dai = cpu_dai;//填充snd_soc_pcm_runtime結構體中的cpu_dai。
                          codec是通過snd_soc_register_codec函數添加到codec_list鏈表中(tvp5158-audio.c("tvp5158-audio"), ti81xx_hdmi.c("hdmi-dummy-codec"), tlv320aic3x.c("tlv320aic3x-codec.1-0018" 注意i2c子系統中的client設備的名字由I2C適配器的ID和client的地址組成1-0018(i2c_new_device)))
             rtd->codec = codec;//填充snd_soc_pcm_runtime結構體中的codec
                          rtd->codec_dai = codec_dai;//填充snd_soc_pcm_runtime結構體中的codec_dai
                          platform是通過snd_soc_register_platform函數添加到platform_list(davinci-pcm.c(""davinci-pcm-audio"")) 
                          rtd->platform = platform;//填充snd_soc_pcm_runtime結構體中的platform
4.snd_card_create   創建一個聲卡的實例,其代碼如下: 

/**
 *  snd_card_create - create and initialize a soundcard structure
 *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
 *  @xid: card identification (ASCII string)
 *  @module: top level module for locking
 *  @extra_size: allocate this extra size after the main soundcard structure
 *  @card_ret: the pointer to store the created card instance
 *
 *  Creates and initializes a soundcard structure.
 *
 *  The function allocates snd_card instance via kzalloc with the given
 *  space for the driver to use freely.  The allocated struct is stored
 *  in the given card_ret pointer.
 *
 *  Returns zero if successful or a negative error code.
 */
int snd_card_create(int idx, const char *xid,
            struct module *module, int extra_size,
            struct snd_card **card_ret)
{
    struct snd_card *card;
    int err, idx2;

    if (snd_BUG_ON(!card_ret))
        return -EINVAL;
    *card_ret = NULL;

    if (extra_size < 0)
        extra_size = 0;
    card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
    if (!card)
        return -ENOMEM;
    if (xid)
        strlcpy(card->id, xid, sizeof(card->id));
    err = 0;
    mutex_lock(&snd_card_mutex);
    if (idx < 0) {
        for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
            /* idx == -1 == 0xffff means: take any free slot */
            if (~snd_cards_lock & idx & 1<<idx2) {
                if (module_slot_match(module, idx2)) {
                    idx = idx2;
                    break;
                }
            }
    }
    if (idx < 0) {
        for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
            /* idx == -1 == 0xffff means: take any free slot */
            if (~snd_cards_lock & idx & 1<<idx2) {
                if (!slots[idx2] || !*slots[idx2]) {
                    idx = idx2;
                    break;
                }
            }
    }
    if (idx < 0)
        err = -ENODEV;
    else if (idx < snd_ecards_limit) {
        if (snd_cards_lock & (1 << idx))
            err = -EBUSY;    /* invalid */
    } else if (idx >= SNDRV_CARDS)
        err = -ENODEV;
    if (err < 0) {
        mutex_unlock(&snd_card_mutex);
        snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i), error: %d\n",
             idx, snd_ecards_limit - 1, err);
        goto __error;
    }
    snd_cards_lock |= 1 << idx;        /* lock it */
    if (idx >= snd_ecards_limit)
        snd_ecards_limit = idx + 1; /* increase the limit */
    mutex_unlock(&snd_card_mutex);
    card->number = idx;
    card->module = module;
 INIT_LIST_HEAD(&card->devices);
    init_rwsem(&card->controls_rwsem);
    rwlock_init(&card->ctl_files_rwlock);
    INIT_LIST_HEAD(&card->controls);
    INIT_LIST_HEAD(&card->ctl_files);
    spin_lock_init(&card->files_lock);
    INIT_LIST_HEAD(&card->files_list);
    init_waitqueue_head(&card->shutdown_sleep);
#ifdef CONFIG_PM
    mutex_init(&card->power_lock);
    init_waitqueue_head(&card->power_sleep);
#endif
    /* the control interface cannot be accessed from the user space until */
    /* snd_cards_bitmask and snd_cards are set with snd_card_register */
    err = snd_ctl_create(card);
    if (err < 0) {
        snd_printk(KERN_ERR "unable to register control minors\n");
        goto __error;
    }
    err = snd_info_card_create(card);
    if (err < 0) {
        snd_printk(KERN_ERR "unable to create card info\n");
        goto __error_ctl;
    }
    if (extra_size > 0)
        card->private_data = (char *)card + sizeof(struct snd_card);
    *card_ret = card;
    return 0;

__error_ctl:
    snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
      __error:
kfree(card);
      return err;
}

  通過紅色標記的代碼發現,這個函數主要做的初始化聲卡的一些設備鏈表devices,讀寫信號量(rw_semaphore)讀寫鎖(rwlock),鎖(spinlock_t),鏈表,等待隊列(wait_queue_head_t);調用snd_ctl_create函數初始化snd_device_ops並且添加聲卡控制的邏輯設備(SNDRV_DEV_CONTROL)到聲卡的設備鏈表card->devices中;snd_info_card_create函數創建是聲卡proc的入口;最后是聲卡的私有數據。

5.soc_probe_dai_link cpu_dai,codec,platform,codec_dai順序執行其驅動的probe函數並且添加的聲卡下code_dev_list,platform_dev_list,dai_dev_list中;
               並且每個dail_link(這個定義在平台設備(machine))創建一個設備,並且創建sys文件接口,即/sys/devices/platform/soc-aduio.0目錄下創建兩個目錄TLV320AIC3X,TVP5158AUDIO和soc-aduio.1目錄下創建目錄HDMI_SOC_LINK,並且在這三個目錄下創建codec_reg,dapm_widget,pmdown_time三個sys文件接口rtd->dev.parent = card->dev; //其父設備card-dev
               (snd_pcm)pcm組件的創建:首先調用snd_pcm_new函數初始化snd_device_ops,創建回放(SNDRV_PCM_STREAM_PLAYBACK)和采集(SNDRV_PCM_STREAM_CAPTURE)子流,調用snd_device_new將邏輯PCM設備添加到聲卡的設備鏈表card->devices中;
                              然后初始化全局變量soc_pcm_ops,將聲卡的邏輯平台設備("davinci-pcm-audio")驅動的一些操作函數賦值給soc_pcm_ops,這個結構體主要系統調用時候用到;
                              最后是調用邏輯平台設備("davinci-pcm-audio")驅動的pcm_new函數去申請DMA。
6.snd_card_register 首先在/sys/class/sound目錄下創建card0,card1兩個目錄;
                              然后通過snd_device_register_all->snd_device_ops->dev_register將聲卡下面的邏輯設備全部注冊到snd_minors[]數組(這個數組很重要,所有的聲卡相關系統調用都是它),當調用到pcm->dev_register函數時候,次函數調用snd_pcm_timer_init()注冊snd_minors[]數組中,添加到聲卡的邏輯設備鏈表中;當系統調用的時候在open的時候,會從snd_minors[]數組獲取
                              接着在/proc/asound/下創建card0,card1, 並且創建SOUND0,SOUND1分別軟連接card0,card1(proc_symlink軟連接函數);
                              最后在card0,card1目錄下創建id,number兩個文件。

    直此,聲卡在底層初始化的分析已經結束,下一篇將分析聲卡的系統調用過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM