基于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);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章