先從幾個重要的structure 看起,並以wm8580為例,這些屬於platform driver,請見smdkc100_wm8580.c:

platform device註冊

snd_soc_device: audio subsystem

這個結構是platform device註冊最主要的,註冊就是利用這個結構來填platform drvdata。

/* SoC Device - the audio subsystem */
struct snd_soc_device {
    struct device *dev;
    struct snd_soc_card *card;
    struct snd_soc_codec_device *codec_dev;
    void *codec_data;

}; 

在該platform driver是這樣宣告的:

static struct snd_soc_device smdkc100_snd_devdata = {
    .card = &smdkc100,
    .codec_dev = &soc_codec_dev_wm8580,
    .codec_data = &smdkc100_wm8580_setup,
};

一個一個來看。

snd_soc_card

就是這張卡的描述,主要成員為

1. 名字 (name),即user看到這張卡的名字

2. 所使用的platform interface (snd_soc_platform,這個結構實作了pcm creation/destruction)

3. CPU與codec間的dai_link(snd_soc_dai_link),包括cpu_dai (I2S/PCM/AC97)及codec_dai (playback/capture)

struct snd_soc_card {
    char *name;
    struct device *dev;
    struct list_head list;

    int instantiated;
    int lp_mode; /* if this card is operating in LowPower mode, where SUSPEND/RESUME doesn't matter */
    int (*probe)(struct platform_device *pdev);
    int (*remove)(struct platform_device *pdev);
    /* the pre and post PM functions are used to do any PM work before and
     * after the codec and DAI's do any PM work. */
    int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);
    int (*suspend_post)(struct platform_device *pdev, pm_message_t state);
    int (*resume_pre)(struct platform_device *pdev);
    int (*resume_post)(struct platform_device *pdev);

    /* callbacks */smdkc100

    int (*set_bias_level)(struct snd_soc_card *,
                  enum snd_soc_bias_level level); 


    /* CPU <--> Codec DAI links  */
    struct snd_soc_dai_link *dai_link;
    int num_links;

    struct snd_soc_device *socdev;
    struct snd_soc_codec *codec;

    struct snd_soc_platform *platform;

    struct delayed_work delayed_work;
    struct work_struct deferred_resume_work;
};

在該platform driver是這樣宣告的

static struct snd_soc_card smdkc100 = {
    .name = "smdkc100",
    .lp_mode = 0,
    .platform = &s3c_pcm_pdat.pcm_pltfm,
    .dai_link = smdkc100_dai,
    .num_links = ARRAY_SIZE(smdkc100_dai),
};

name可看到是smdkc100,來看看platform和dai_link這兩個結構

snd_soc_platform

/* SoC platform interface */
struct snd_soc_platform {
    char *name;
    struct list_head list;

    int (*probe)(struct platform_device *pdev);
    int (*remove)(struct platform_device *pdev);
    int (*suspend)(struct snd_soc_dai *dai);
    int (*resume)(struct snd_soc_dai *dai);

    /* pcm creation and destruction */
    int (*pcm_new)(struct snd_card *, struct snd_soc_dai *,
        struct snd_pcm *);
codec_data

    void (*pcm_free)(struct snd_pcm *);

    /* platform stream ops */
    struct snd_pcm_ops *pcm_ops;
};

在該platform driver是設為s3c_pcm_pdat.pcm_pltfm,這個interface是宣告在s3c-pcm-lp.c。這應該是samsung audio device所共同使用的platform interface,所有的音效卡都註冊到這個platform list。其中pcm_ops定義了pcm的open/close。這是userspace每次在播放音效時都會呼叫到的。

struct s5p_pcm_pdata s3c_pcm_pdat = {
    .lp_mode   = 0,
    .lp_buffs = {
        .dma_addr[SNDRV_PCM_STREAM_PLAYBACK] = LP_TXBUFF_ADDR,
        //.dma_addr[SNDRV_PCM_STREAM_CAPTURE]  = LP_RXBUFF_ADDR,
    },
    .set_mode = s3c_pcm_setmode,
    .pcm_pltfm = {
        .name        = "s3c-audio",
        .pcm_ops     = &s5p_pcm_ops,
        .pcm_new    = s5p_pcm_new,
        .pcm_free    = s5p_pcm_free_dma_buffers,
    },

..........

再來是dai_link

snd_soc_dai_link

/* SoC machine DAI configuration, glues a codec and cpu DAI together */
struct snd_soc_dai_link  {
    char *name;            /* Codec name */
    char *stream_name;        /* Stream name */

    /* DAI */
    struct snd_soc_dai *codec_dai;
    struct snd_soc_dai *cpu_dai;


    /* machine stream operations */
    struct snd_soc_ops *ops;

    /* codec/machine specific init - e.g. add machine controls */
    int (*init)(struct snd_soc_codec *codec);

    /* Symmetry requirements */
    unsigned int symmetric_rates:1;

    /* Symmetry data - only valid if symmetry is being enforced */
    unsigned int rate;

    /* DAI pcm */
    struct snd_pcm *pcm;
};

其中cpu_dai是cpu與codec之間的interface,如I2S,PCM及AC97。這就看hardware 如何設計。而codec_dai宣告在 codec driver (wm8580.c)裡,指定playback及capture的相關資訊.

以上完成了snd_soc_card的宣告。再來是snd_soc_codec_device

snd_soc_codec_device

這是將此codec註冊在codec_dai list,將evi來dce/driver match時,會來呼叫其probe。這個probe會呼叫snd_soc_new_pcms,註冊一個pcm

/* codec device */
struct snd_soc_codec_device {
    int (*probe)(struct platform_device *pdev);
    int (*remove)(struct platform_device *pdev);
    int (*suspend)(struct platform_device *pdev, pm_message_t state);
    int (*resume)(struct platform_device *pdev);
};

這屬於codec,因此定義在 codec driver wm8580.c

struct snd_soc_codec_device soc_codec_dev_wm8580 = {
    .probe =     wm8580_probe,
    .remove =     wm8580_remove,
};

再來是codec_data。

codec_data

這邊定義了有關i2c address的資訊,直接看其設定

static struct wm8580_setup_data smdkc100_wm8580_setup = {
    .i2c_address = 0x1b,
};

這是有關i2c command的address,設定需看其spec的定義,通常是0x1a或0x1b。

 

以上有了snd_soc_card,snd_soc_codec_device和 codec_data就有足夠的資訊可以來註冊platform_device。

 

platform driver要init了...

static int __init smdkc100_audio_init(void)
{
    int ret;

..........


    smdkc100_snd_device = platform_device_alloc("soc-audio", 0);

    if (!smdkc100_snd_device)
        return -ENOMEM;

    platform_set_drvdata(smdkc100_snd_device, &smdkc100_snd_devdata);

    smdkc100_snd_devdata.dev = &smdkc100_snd_device->dev;
    ret = platform_device_add(smdkc100_snd_device);

先從platform_device_alloc("soc-audio", 0)看起,其allocate了memory,然後將snd_soc_device這個結構整個塞進去這個platform device。最後執行platform_device_add(smdkc100_snd_device);

這邊要注意的是soc-audio這個關鍵字,device/ driver在 match時是用這個name。

Platform driver註冊

因此來看一下platform driver的註冊,在soc-core.c

先看看soc-core做了什麼事:

soc-core platform driver

sound/soc/soc-core.c

/* ASoC platform driver */
static struct platform_driver soc_driver = {
    .driver        = {
        .name        = "soc-audio",
        .owner        = THIS_MODULE,
        .pm        = &soc_pm_ops,
    },
    .probe        = soc_probe,
    .remove        = soc_remove,
};

這是一個名為soc-audio的platform driver,如前篇所提,此時platform bus會以name來做match。將來若有以此名字註冊進platform bus,則會match成功。注意其宣告了probe function,將來match成功是會被觸發的。

 

Match

match的流程主要是確認這張卡是否該註冊的地方都有註冊,包括platform list, cpu_dai list,codec_dai list。然後就是各別執行每個list上所定義的probe (有定義才執行)。一樣先從soc-core的probe:

static int soc_probe(struct platform_device *pdev)
{
    int ret = 0;
    struct snd_soc_device *socdev = platform_get_drvdata(pdev);
    struct snd_soc_card *card = socdev->card;


    /* Bodge while we push things out of socdev */
    card->socdev = socdev;

    /* 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");
        return ret;
    }

    return 0;
}

此時傳入的pdev就是wm8580這個platform_device,取得platform_set_drvdata(smdkc100_snd_device, &smdkc100_snd_devdata)填進去的東西。然後指定card,最後snd_soc_register_card(card):

static int snd_soc_register_card(struct snd_soc_card *card)
{
    if (!card->name || !card->dev)
        return -EINVAL;

    INIT_LIST_HEAD(&card->list);
    card->instantiated = 0;

    mutex_lock(&client_mutex);
    list_add(&card->list, &card_list); //加入card list
    snd_soc_instantiate_cards();
    mutex_unlock(&client_mutex);

    dev_dbg(card->dev, "Registered card '%s'\n", card->name);

    return 0;
}

然後是

static void snd_soc_instantiate_cards(void)
{
    struct snd_soc_card *card;
    list_for_each_entry(card, &card_list, list)
        snd_soc_instantiate_card(card);

}

static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
    struct platform_device *pdev = container_of(card->dev,
                            struct platform_device,
                            dev);
    struct snd_soc_codec_device *codec_dev = card->socdev->codec_dev;
    struct snd_soc_codec *codec;
    struct snd_soc_platform *platform;
    struct snd_soc_dai *dai;
    int i, found, ret, ac97;

    if (card->instantiated)
        return;

    found = 0;
    list_for_each_entry(platform, &platform_list, list) //檢查platform_list是否有這張卡,此例並沒有定義
        if (card->platform == platform) {
            found = 1;
            break;
        }
    if (!found) {
        dev_dbg(card->dev, "Platform %s not registered\n",
            card->platform->name);

 

        return;
    }

    ac97 = 0;
    for (i = 0; i < card->num_links; i++) {
        found = 0;
        list_for_each_entry(dai, &dai_list, list)  //檢查 dai_list是否有cpu_dai
            if (card->dai_link[i].cpu_dai == dai) {
                found = 1;
                break;
            }
        if (!found) {
            dev_dbg(card->dev, "DAI %s not registered\n",
                card->dai_link[i].cpu_dai->name);
            return;
        }

        if (card->dai_link[i].cpu_dai->ac97_control)
            ac97 = 1;
    }

    for (i = 0; i < card->num_links; i++) {
        if (!card->dai_link[i].codec_dai->ops)
            card->dai_link[i].codec_dai->ops = &null_dai_ops;
    }

    /* If we have AC97 in the system then don't wait for the
     * codec.  This will need revisiting if we have to handle
     * systems with mixed AC97 and non-AC97 parts.  Only check for
     * DAIs currently; we can't do this per link since some AC97
     * codecs have non-AC97 DAIs.
     */
    if (!ac97)
        for (i = 0; i < card->num_links; i++) {
            found = 0;
            list_for_each_entry(dai, &dai_list, list) //檢查 dai_list是否有codec_dai
                if (card->dai_link[i].codec_dai == dai) {
                    found = 1;
                    break;
                }
            if (!found) {
                dev_dbg(card->dev, "DAI %s not registered\n",
                    card->dai_link[i].codec_dai->name);
                return;
            }
        }

    /* Note that we do not current check for codec components */

    dev_dbg(card->dev, "All components present, instantiating\n");

    /* Found everything, bring it up */
    card->pmdown_time = pmdown_time;

    if (card->probe) { //wm8580並沒有實作probe
        ret = card->probe(pdev);
        if (ret < 0)
            return;
    }

    for (i = 0; i < card->num_links; i++) {
        struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
        if (cpu_dai->probe) {  //cpu_dai有實作,即sound/soc/s3c/s3c-i2s.c的s3c_i2s_probe
            ret = cpu_dai->probe(pdev, cpu_dai);
            if (ret < 0)
                goto cpu_dai_err;
        }
    }

    if (codec_dev->probe) {
        ret = codec_dev->probe(pdev);  //此處就是呼叫codec driver的probe,執行ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
                                                         //並建立pcm instance

        if (ret < 0)
            goto cpu_dai_err;
    }
    codec = card->codec;

    if (platform->probe) {
        ret = platform->probe(pdev);
        if (ret < 0)
            goto platform_err;
    }

   /*.....以下有關dapm省略....*/


card_err:
    if (platform->remove)
        platform->remove(pdev);

platform_err:
    if (codec_dev->remove)
        codec_dev->remove(pdev);

cpu_dai_err:
    for (i--; i >= 0; i--) {, 此例並沒有定義

        struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;
        if (cpu_dai->remove)
            cpu_dai->remove(pdev, cpu_dai);
    }

    if (card->remove)
        card->remove(pdev);
}

 

Reference

ASoC subsystem

linux2.6.30中uda134x声卡源码解读

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 kezeodsnx 的頭像
    kezeodsnx

    心的距離

    kezeodsnx 發表在 痞客邦 留言(0) 人氣()