安卓声卡驱动:2.Machine驱动与声卡

一 声卡简介

一个音频系统由硬件和驱动这些硬件的软件组成。

在一台设备上,如果硬件完整,且驱动硬件的软件全部初始化成功后,ALSA系统就会注册一个声卡,通过这个声卡,应用程序可以控制硬件设备的链路联通,并向声卡写入数据或读取数据来完成播放与录音的音频功能。

在这里说的声卡不是配电脑时说的硬件声卡,而是Linux内核中的一个软件概念的声卡。

二 声卡目录下设备节点的作用

在注册声卡时,ALSA core会在dev/snd/下创建声卡的设备节点,在下面这张图中,可以很明显的看到有三种设备节点,分别是control设备pcm设备,以及一个timer节点
在这里插入图片描述
PCM设备就是应用层直接操作的节点,应用层的tinyalsa库就是直接对这个节点进行读写来实现播放和录音的。
想知道tinyalsa怎么将数据写入PCM节点,可以看external\tinyalsa\pcm.c这个文件,在安卓的Audio HAL中,播放音乐时,就是通过调用pcm_write来将数据写下去。

int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
    struct snd_xferi x;

    if (pcm->flags & PCM_IN)
        return -EINVAL;

    x.buf = (void*)data;
    x.frames = count / (pcm->config.channels *
                        pcm_format_to_bits(pcm->config.format) / 8);

    for (;;) {
        if (!pcm->running) {
            int prepare_error = pcm_prepare(pcm);
            if (prepare_error)
                return prepare_error;
            if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
                return oops(pcm, errno, "cannot write initial data");
            pcm->running = 1;
            return 0;
        }
        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {	//在这里写入数据
            pcm->prepared = 0;
            pcm->running = 0;
            if (errno == EPIPE) {
                /* we failed to make our window -- try to restart if we are
                 * allowed to do so.  Otherwise, simply allow the EPIPE error to
                 * propagate up to the app level */
                pcm->underruns++;
                if (pcm->flags & PCM_NORESTART)
                    return -EPIPE;
                continue;
            }
            return oops(pcm, errno, "cannot write stream data");
        }
        return 0;
    }
}

control设备用于控制声卡的内部通路开关。为了节省功耗,在移动设备上音频通路不是一直接通的,只有在需要用到时,通路才会接通,

例如下面这个例子
当需要把音频输出到Speaker时,就需要通过操作control节点,来吧I2S0的RX1接到MUX1,而当需要输出到耳机时,则可以把RX1接到MUX2。
在这里插入图片描述
至于timer设备,目前我暂时没有用过。

Machine驱动

Machine驱动里面描述了当前设备上的各个组件的连接关系,从platform到codec再到Speaker,Machine驱动也是一个单独的驱动,能单独的注册到ALSA中。

ALSA core会在驱动程序注册最后一步尝试去初始化声卡,这里的驱动程序是指所有的音频驱动程序,包括platform,codec,Machine。

注册声卡时,会根据Machine驱动中定义的组件连接关系,去查找所需要的组件,如果此时音频组件没有全部注册完成,则声卡会初始化失败,并返回错误码 -517,这个错误码在Linux内核的含义是需要重试,当最后的组件注册完成时,ALSA会再次尝试初始化声卡。

Machine驱动描述了platform和codec之间的连接关系,通过连接关系,来找到对应的接口。
在这里插入图片描述
一条简单的音频链路如图所示,CPU DAI与Codec DAI相连接,进行数据传输。

在Machine驱动中,通过 struct snd_soc_dai_link 结构体来定义连接关系,DAI全称是Digital Audio Interface,以三星的s5pv4418开发板举例,定义一个简单dai_link如下所示

static struct snd_soc_dai_link es8316_dai_link = {
	.name		= "ASOC-ES8316",
	.stream_name	= "es8316 HiFi",
	.cpu_dai_name	= "c0055000.i2s",		/* nx_snd_i2s_driver name */
	.platform_name  = "nexell-pcm",		/* nx_snd_pcm_driver name */
	.codec_dai_name = "ES8316 HiFi",	/* es8316_dai's name */
	.codec_name	= "ES8316.0-0011",	/* es8316_i2c_driver name
						   + '.' + bus + '-'
						   + address(7bit) */
	.ops		= &es8316_ops,
	.symmetric_rates = 1,
	.init		= es8316_dai_init,
};

在这个定义中,可以很清楚的知道这条链路的源头DAI是c0055000.i2s,连接到codec的ES8316 HiFi这个接口上。

在dai_link描述了完整的链路信息后,就可以用这个dai_link来注册声卡,为此,需要创建一个声卡实例。

static struct snd_soc_card es8316_card = {
	.name			= "I2S-ES8316",	/* proc/asound/cards */
	.owner			= THIS_MODULE,
	.dai_link		= &es8316_dai_link,
	.num_links		= 1,
	.suspend_pre	= &es8316_suspend_pre,
	.resume_pre		= &es8316_resume_pre,
	.resume_post		= &es8316_resume_post,
	.dapm_widgets		= es8316_dapm_widgets,
	.num_dapm_widgets	= ARRAY_SIZE(es8316_dapm_widgets),
	.dapm_routes		= es8316_audio_map,
	.num_dapm_routes	= ARRAY_SIZE(es8316_audio_map),
};

暂时只需要知道dai_link这个成员的含义。
有了声卡实例之后,可以用snd_soc_register_card来注册声卡,这个函数只需要传入一个声卡实例,这里省略了大量对平台的gpio,memory,clock进行初始化的代码,只对ALSA注册声卡进行分析

int snd_soc_register_card(struct snd_soc_card *card)
{
	int i, j, ret;

	if (!card->name || !card->dev)
		return -EINVAL;

	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++) {	//有很多种定义dai_link的形式,这个循环内是对dai_link的数据进行检查以及标准化
			/*
			 * 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);

	snd_soc_initialize_card_lists(card);	//对声卡结构体内的几个链表头进行初始化

	card->rtd = devm_kzalloc(card->dev,				//根据 dai_link 创建rtd
				 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];

	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);
	INIT_LIST_HEAD(&card->dobj_list);
	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 */
	for (i = 0; i < card->num_rtd; i++) {
		struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
		struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
		int j;

		for (j = 0; j < rtd->num_codecs; j++) {
			struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
			if (!codec_dai->active)
				pinctrl_pm_select_sleep_state(codec_dai->dev);
		}

		if (!cpu_dai->active)
			pinctrl_pm_select_sleep_state(cpu_dai->dev);
	}

	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);

比较核心的是在snd_soc_instantiate_card,在这里才是真正的对声卡进行初始化,会依次调用dai_link上所有组建的初始化函数,来对系统中的所有组件进行初始化,这个函数非常复杂,需要在看动platform和codec驱动,以及dapm之后回来看才能看懂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值