基於imx6q-Android6.0的ASOC架構 -- Machine部分(一)

arm平臺:IMX6Q
內核:Linux4.1.15
系統:Android6.0
codec:wm8960
主要內容爲Machine、Platform和Codec三大部分之間的關係和實現。文章中的舉例和代碼均爲imx6q-Android6.0源碼和文件。
文章中如有錯誤和不嚴謹內容,歡迎指正。

一、註冊platform device

kernel_imx/sound/soc/fsl/imx-wm8960.c

在文件中包含關於wm8960相關的設備結構體。設備結構體重包含snd_soc_card結構體。

struct imx_wm8960_data {
        struct snd_soc_card card;  //主要內容
        struct clk *codec_clk;
        unsigned int clk_frequency;
        bool is_codec_master;
        bool is_stream_in_use[2];
        bool is_stream_opened[2];
        struct regmap *gpr;
        unsigned int hp_det[2];
        u32 asrc_rate;
        u32 asrc_format;
};

在imx-wm8960.c文件中註冊platform driver和設備樹中的Platform Device進行匹配。然後執行imx_wm8960_probe函數,註冊聲卡。

static struct snd_soc_ops imx_hifi_ops = {
        .hw_params = imx_hifi_hw_params,
        .hw_free = imx_hifi_hw_free,
        .startup   = imx_hifi_startup,
        .shutdown  = imx_hifi_shutdown,
};
static struct snd_soc_dai_link imx_wm8960_dai[] = {
        {
                .name = "HiFi",
                .stream_name = "HiFi",
                .codec_dai_name = "wm8960-hifi",
                .ops = &imx_hifi_ops,
        },
        {
                .name = "HiFi-ASRC-FE",
                .stream_name = "HiFi-ASRC-FE",
                .codec_name = "snd-soc-dummy",
                .codec_dai_name = "snd-soc-dummy-dai",
                .dynamic = 1,
                .ignore_pmdown_time = 1,
                .dpcm_playback = 1,
                .dpcm_capture = 1,
        },
        {
                .name = "HiFi-ASRC-BE",
                .stream_name = "HiFi-ASRC-BE",
                .codec_dai_name = "wm8960-hifi",
                .platform_name = "snd-soc-dummy",
                .no_pcm = 1,
                .ignore_pmdown_time = 1,
                .dpcm_playback = 1,
                .dpcm_capture = 1,
                .ops = &imx_hifi_ops,
                .be_hw_params_fixup = be_hw_params_fixup,
        },
};

struct imx_wm8960_data *data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);

data->card.dai_link = imx_wm8960_dai;

/*把snd_soc_card結構體放到platform_device的dev.drvdata字段*/
platform_set_drvdata(pdev, &data->card);
/*把data結構體保存到snd_soc_card結構體下的drvdata字段*/
snd_soc_card_set_drvdata(&data->card, data);
/*註冊聲卡到asoc核心*/
devm_snd_soc_register_card(&pdev->dev, &data->card);

machine部分的平臺設備驅動代碼主要是snd_soc_card結構體。有引出了machine驅動的另外兩個數據結構(snd_soc_dai_link、snd_soc_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實現。

二、snd_soc_dai_link

在snd_soc_dai_link中,指定了Platform、Codec、codec_dai、cpu_dai的名字。
codec_dai_name、cpu_dai_name兩個名字必須指定。其他可以沒有。

struct snd_soc_dai_link {
        /* config - must be set by machine driver */
        const char *name;                       /* Codec name */
        const char *stream_name;                /* Stream name */
        const char *cpu_name;					/*cpu控制接口name*/
        const char *cpu_dai_name;				/*cpu數據傳輸接口name*/
        const char *codec_name;					/*codec設備name*/
        const char *codec_dai_name;				/*codec數據傳輸接口name*/
        /*
         * You MAY specify the link's platform/PCM/DMA driver, either by
         * device name, or by DT/OF node, but not both. Some forms of link
         * do not need a platform.
         */
        const char *platform_name;

        ...
};

/*在wm8960的machine部分中的定義*/
static struct snd_soc_dai_link imx_wm8960_dai[] = {
      ...
        {
                .name = "HiFi-ASRC-BE",
                .stream_name = "HiFi-ASRC-BE",
                .codec_dai_name = "wm8960-hifi",
                .platform_name = "snd-soc-dummy",
                .no_pcm = 1,
                .ignore_pmdown_time = 1,
                .dpcm_playback = 1,
                .dpcm_capture = 1,
                .ops = &imx_hifi_ops,
                .be_hw_params_fixup = be_hw_params_fixup,
        },
};
/*在imx_wm8960_probe中*/
imx_wm8960_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);

三、snd_soc_register_card

kernel_imx/sound/soc/soc-core.c

目的:

  1. 檢測snd_soc_dai_link結構體中的codec_dai_name、cpu_dai_name是否存在。
  2. 初始化有關鏈表
  3. 創建snd_soc_pcm_runtime結構體
  4. 初始化聲卡(重要)
int snd_soc_register_card(struct snd_soc_card *card)
{
        int i, j, ret;

        if (!card->name || !card->dev)
                return -EINVAL;
		/*遍歷snd_soc_card下的dai_link,確保codec_dai_name、cpu_dai_name存在*/
        for (i = 0; i < card->num_links; i++) {
                struct snd_soc_dai_link *link = &card->dai_link[i];

                ret = snd_soc_init_multicodec(card, link);
                if (ret) {
                        dev_err(card->dev, "ASoC: failed to init multicodec\n");
                        return ret;
                }

                for (j = 0; j < link->num_codecs; j++) {
                        /*
                         * Codec must be specified by 1 of name or OF node,
                         * not both or neither.
                         */
                        if (!!link->codecs[j].name ==
                            !!link->codecs[j].of_node) {
                                dev_err(card->dev, "ASoC: Neither/both codec name/of_node are set for %s\n",
                                        link->name);
                                return -EINVAL;
                        }
                        /* Codec DAI name must be specified */
                        if (!link->codecs[j].dai_name) {
                                dev_err(card->dev, "ASoC: codec_dai_name not set for %s\n",
                                        link->name);
                                return -EINVAL;
                        }
                }

                /*
                 * Platform may be specified by either name or OF node, but
                 * can be left unspecified, and a dummy platform will be used.
                 */
                if (link->platform_name && link->platform_of_node) {
                        dev_err(card->dev,
                                "ASoC: Both platform name/of_node are set for %s\n",
                                link->name);
                        return -EINVAL;
                }

                /*
                 * CPU device may be specified by either name or OF node, but
                 * can be left unspecified, and will be matched based on DAI
                 * name alone..
                 */
                if (link->cpu_name && link->cpu_of_node) {
                        dev_err(card->dev,
                                "ASoC: Neither/both cpu name/of_node are set for %s\n",
                                link->name);
                        return -EINVAL;
                }
                /*
                 * At least one of CPU DAI name or CPU device name/node must be
                 * specified
                 */
                if (!link->cpu_dai_name &&
                    !(link->cpu_name || link->cpu_of_node)) {
                        dev_err(card->dev,
                                "ASoC: Neither cpu_dai_name nor cpu_name/of_node are set for %s\n",
                                link->name);
                        return -EINVAL;
                }
        }

        dev_set_drvdata(card->dev, card);
        
		/*內聯函數
		INIT_LIST_HEAD(&card->codec_dev_list);
        INIT_LIST_HEAD(&card->widgets);
        INIT_LIST_HEAD(&card->paths);
        INIT_LIST_HEAD(&card->dapm_list);
		初始化各個部件的鏈表
		*/
        snd_soc_initialize_card_lists(card);

        card->rtd = devm_kzalloc(card->dev,
                                 sizeof(struct snd_soc_pcm_runtime) *
                                 (card->num_links + card->num_aux_devs),
                                 GFP_KERNEL);
        if (card->rtd == NULL)
                return -ENOMEM;
        card->num_rtd = 0;
        card->rtd_aux = &card->rtd[card->num_links];

/*把平臺數據snd_soc_card結構體中的dai_link拷貝到snd_soc_pcm_runtime結構體的對應成員中*/
        for (i = 0; i < card->num_links; i++) {
                card->rtd[i].card = card;
                card->rtd[i].dai_link = &card->dai_link[i];
                card->rtd[i].codec_dais = devm_kzalloc(card->dev,
                                        sizeof(struct snd_soc_dai *) *
                                        (card->rtd[i].dai_link->num_codecs),
                                        GFP_KERNEL);
                if (card->rtd[i].codec_dais == NULL)
                        return -ENOMEM;
        }

        for (i = 0; i < card->num_aux_devs; i++)
                card->rtd_aux[i].card = card;

        INIT_LIST_HEAD(&card->dapm_dirty);
        card->instantiated = 0;
        mutex_init(&card->mutex);
        mutex_init(&card->dapm_mutex);

		/*初始化聲卡*/
        ret = snd_soc_instantiate_card(card);
        if (ret != 0)
                return ret;
        /* deactivate pins to sleep state */
   		...
   		...

        return ret;
}

四、snd_soc_instantiate_card

static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
        struct snd_soc_codec *codec;
		...
/*
ASoC定義了三個全局的鏈表頭變量:codec_list、dai_list、platform_list,系統中所有的Codec、DAI、
Platform都在註冊時連接到這三個全局鏈表上。soc_bind_dai_link函數逐個掃描這三個鏈表,根據
card->dai_link[]中的名稱(name)進行匹配,匹配成功後把相應的codec、dai和platform實例賦值到card->rtd[]
中(snd_soc_pcm_runtime)。經過這個過程後,snd_soc_pcm_runtime結構體(card->rtd)中保存了本Machine中
使用的Codec,DAI和Platform驅動的信息。
*/
        /* bind DAIs */
        for (i = 0; i < card->num_links; i++) {
                ret = soc_bind_dai_link(card, i);
        }

        /* bind aux_devs too */
        for (i = 0; i < card->num_aux_devs; i++) {
                ret = soc_bind_aux_dev(card, i);
        }
       /* initialize the register cache for each available codec */
        list_for_each_entry(codec, &codec_list, list) {
                snd_soc_init_codec_cache(codec);
        }
        
        /*創建聲卡實例*/
		snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);
		
        snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, card->num_dapm_widgets);

        /* probe all components used by DAI links on this card */
        for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
                        order++) {
                for (i = 0; i < card->num_links; i++) {
                        ret = soc_probe_link_components(card, i, order);
                        if (ret < 0) {
                                dev_err(card->dev,
                                        "ASoC: failed to instantiate card %d\n",
                                        ret);
                                goto probe_dai_err;
                        }
                }
        }

        /* probe all DAI links on this card */
        for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
                        order++) {
                for (i = 0; i < card->num_links; i++) {
                        ret = soc_probe_link_dais(card, i, order);//關鍵函數
                        if (ret < 0) {
                                dev_err(card->dev,
                                        "ASoC: failed to instantiate card %d\n",
                                        ret);
                                goto probe_dai_err;
                        }
                }
        }
       for (i = 0; i < card->num_aux_devs; i++) {
                ret = soc_probe_aux_dev(card, i);
                if (ret < 0) {
                        dev_err(card->dev,
                                "ASoC: failed to add auxiliary devices %d\n",
                                ret);
                        goto probe_aux_dev_err;
                }
        }

        snd_soc_dapm_link_dai_widgets(card);
        snd_soc_dapm_connect_dai_link_widgets(card);

        if (card->controls)
                snd_soc_add_card_controls(card, card->controls, card->num_controls);

        if (card->dapm_routes)
                snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
                                        card->num_dapm_routes);

        if (card->of_dapm_routes)
                snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
                                        card->num_of_dapm_routes);
        snd_soc_dapm_new_widgets(card);

        snd_card_register(card->snd_card);
		...
		...
}

五、soc_probe_link_dais

static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
/*執行cpu_dai下的probe函數*/
        ret = soc_probe_dai(cpu_dai, order);
        if (ret)
                return ret;

        /* probe the CODEC DAI */
        /*執行codec_dai下的prove函數*/
        for (i = 0; i < rtd->num_codecs; i++) {
                ret = soc_probe_dai(rtd->codec_dais[i], order);
                if (ret)
                        return ret;
        }
        /* 調用標準的alsa的接口函數snd_pcm_new()函數用於創建標準alsa驅動的pcm邏輯設備 */
        soc_new_pcm(rtd, num);
}

六、soc_new_pcm

static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
	ret = snd_pcm_new(rtd->card->snd_card, new_name,
			num, playback, capture, &pcm);  // 創建標準alsa的pcm邏輯設備
	rtd->pcm = pcm;  // 讓snd_soc_pcm_runtime中的pcm指向剛剛創建的pcm
	pcm->private_data = rtd;

/* 設置真正操作底層聲卡的操作函數!!一共有3條路徑 */
	(1):用platform中的驅動層的ops來填充
if (platform->driver->ops) {
		soc_pcm_ops.mmap = platform->driver->ops->mmap;
		soc_pcm_ops.pointer = platform->driver->ops->pointer;
		soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
		soc_pcm_ops.copy = platform->driver->ops->copy;
		soc_pcm_ops.silence = platform->driver->ops->silence;
		soc_pcm_ops.ack = platform->driver->ops->ack;
		soc_pcm_ops.page = platform->driver->ops->page;
	}
(2):用標準的alsa的api接口來創建ops
	if (playback)
            snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

    if (capture)
            snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);

(3):調用pcm_new函數,來創建一個
	if (platform->driver->pcm_new) {
		ret = platform->driver->pcm_new(rtd);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章