四、Audio-ALSA框架中的machine驱动

machine简介

ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和 Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容: Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器)﹔单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和 Codec驱动等等

Linux内核版本:4.1.15
主芯片:IMX6ULL
codec芯片:WM8960

一、platform device

这里采用dts的方式表示device相关信息:
在这里插入图片描述

这里我们注意compatible属性,这里需要和driver中的compatible属性匹配。
其他属性含义如下:
compatible:非常重要,用于匹配相应的驱动文件,有两个属性值,在整个 linux 内核源码中搜索这两个属性值即可找到对应的驱动文件,这里找到的驱动文件为:sound/soc/fsl/imx-wm8960.c。
model:最终用户看到的此声卡名字,这里设置为“wm8960-audio”。
cpu-dai:CPU DAI(Digital Audio Interface)句柄,这里是 sai2 这个节点。
audio-codec:音频解码芯片句柄,也就是 WM8960 芯片,这里为“codec”这个节点。
asrc-controller:asrc 控制器,asrc 全称为 Asynchronous Sample Rate Converters,翻译过来就是异步采样频率转化器。
hp-det:耳机插入检测引脚设置,第一个参数为检测引脚,3 表示 JD3 为检测引脚。第二个参数设置检测电平,设置为 0 的时候,hp 检测到高电平表示耳机插入;设置为 1 的时候,hp 检测到高电平表示是喇叭,也就是耳机拔出了。
audio-routing:音频器件一系列的连接设置,每个条目都是一对字符串,第一个字符串是连接的 sink,第二个是连接的 source(源)。

二、platform driver

从上面的compatible属性我们就可以找到其machine驱动:
machine驱动路径为:sound\soc\fsl\imx-wm8960.c
在这里插入图片描述

此时驱动中的compatible属性与设备树中的compatible属性相匹配出发其probe函数:


static int imx_wm8960_probe(struct platform_device *pdev)
{
    
    
	struct device_node *cpu_np, *codec_np = NULL;
	struct platform_device *cpu_pdev;
	struct imx_priv *priv = &card_priv;
	struct i2c_client *codec_dev;
	struct imx_wm8960_data *data;
	struct platform_device *asrc_pdev = NULL;
	struct device_node *asrc_np;
	struct of_phandle_args args;
	u32 width;
	int ret;

	priv->pdev = pdev;
    
    /*从设备树中获取cpu-dai接口信息*/
	cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
	if (!cpu_np) {
    
    
		dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
		ret = -EINVAL;
		goto fail;
	}
    /*从设备树中获取codec接口信息*/
	codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
	if (!codec_np) {
    
    
		dev_err(&pdev->dev, "phandle missing or invalid\n");
		ret = -EINVAL;
		goto fail;
	}

······

    /*申请私有数据结构体大小*/
	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
	if (!data) {
    
    
		ret = -ENOMEM;
		goto fail;
	}

······

	data->card.dai_link = imx_wm8960_dai;

	data->card.dev = &pdev->dev;
	data->card.owner = THIS_MODULE;
    
    /*设置声卡的名字*/
	ret = snd_soc_of_parse_card_name(&data->card, "model");
	if (ret)
		goto fail;
	data->card.dapm_widgets = imx_wm8960_dapm_widgets;
	data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets);

       /*设置Audio的路由信息*/
	ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
	if (ret)
		goto fail;

    /*设置声卡后期需要初始化的内容*/
	data->card.late_probe = imx_wm8960_late_probe;
    
    /*设置私有数据*/
	platform_set_drvdata(pdev, &data->card);
	snd_soc_card_set_drvdata(&data->card, data);
    
    /*注册声卡*/
	ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
	if (ret) {
    
    
		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
		goto fail;
	}

········

	return ret;
}

前期获取的所有的信息都是为创建声卡做准备,接下来我们就看一下注册声卡的函数。devm_snd_soc_register_card最终调用snd_soc_register_card函数完成声卡的创建,传入的参数就是我们上面初始化的struct snd_soc_card *card。

snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,

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];

	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;
	}

最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:

该函数遍历每一对dai_link,进行codec、platform、dai的绑定工作,以下只是代码的部分选节,详细的代码请直接参考完整的代码树。

/* bind DAIs */
	for (i = 0; i < card->num_links; i++) {
    
    
		ret = soc_bind_dai_link(card, i);
		if (ret != 0)
			goto base_error;
	}

ASoC定义了三个全局的链表头变量: codec_list、dai_list、platform_list,系统中所有的Codec、DAlI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[中的名称进行匹配,匹配后把相应的codec,dai和 platform实例赋值到Cara->taT(Sna_soe_puan_ruw a 址这个过程后,snd_soc_pcm_runtime: (card->rtd)中保存了本Machine中使用的Codec,DAl和Platform驱动的信息。

soc_probe_link_components依次回调各个probe: probe the CPU-side component、probe the CODEC-side components、probe the platform :

/* 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;
			}
		}
	}

snd_soc_init_codec_cache函数接着初始化Codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例:

/* initialize the register cache for each available codec */
	list_for_each_entry(codec, &codec_list, list) {
    
    
		if (codec->cache_init)
			continue;
		ret = snd_soc_init_codec_cache(codec);
		if (ret < 0)
			goto base_error;
	}

	/* card bind complete so register a sound card */
	ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
			card->owner, 0, &card->snd_card);
	if (ret < 0) {
    
    
		dev_err(card->dev,
			"ASoC: can't create sound card for card %s: %d\n",
			card->name, ret);
		goto base_error;
	}

    /* 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;
			}
		}
	}

在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:

static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
    
    
	struct snd_soc_dai_link *dai_link = &card->dai_link[num];
	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int i, ret;

	dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n",
			card->name, num, order);

	/* set default power off timeout */
	rtd->pmdown_time = pmdown_time;

	ret = soc_probe_dai(cpu_dai, order);
	if (ret)
		return ret;

	/* probe the CODEC DAI */
	for (i = 0; i < rtd->num_codecs; i++) {
    
    
		ret = soc_probe_dai(rtd->codec_dais[i], order);
		if (ret)
			return ret;
	}


	if (cpu_dai->driver->compress_dai) {
    
    
		/*create compress_device"*/
		ret = soc_new_compress(rtd, num);
		if (ret < 0) {
    
    
			dev_err(card->dev, "ASoC: can't create compress %s\n",
					 dai_link->stream_name);
			return ret;
		}
	} else {
    
    

		if (!dai_link->params) {
    
    
			/* create the pcm */
			ret = soc_new_pcm(rtd, num);
			if (ret < 0) {
    
    
				dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
				       dai_link->stream_name, ret);
				return ret;
			}
		} else {
    
    
			INIT_DELAYED_WORK(&rtd->delayed_work,
						codec2codec_close_delayed_work);

			/* link the DAI widgets */
			ret = soc_link_dai_widgets(card, dai_link, rtd);
			if (ret)
				return ret;
		}
	}

	return 0;
}

首先回调cpu_dai与codec_dai的probe函数,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。现在把该函数的部分代码也贴出来:

/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
    
    
	struct snd_soc_platform *platform = rtd->platform;
	struct snd_soc_dai *codec_dai;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct snd_pcm *pcm;
	char new_name[64];
	int ret = 0, playback = 0, capture = 0;
	int i;

    /* create the PCM */
	if (rtd->dai_link->no_pcm) {
    
    
		snprintf(new_name, sizeof(new_name), "(%s)",
			rtd->dai_link->stream_name);

		ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
				playback, capture, &pcm);
	} else {
    
    
		if (rtd->dai_link->dynamic)
			snprintf(new_name, sizeof(new_name), "%s (*)",
				rtd->dai_link->stream_name);
		else
			snprintf(new_name, sizeof(new_name), "%s %s-%d",
				rtd->dai_link->stream_name,
				(rtd->num_codecs > 1) ?
				"multicodec" : rtd->codec_dai->name, num);

		ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
			capture, &pcm);
	}
	if (ret < 0) {
    
    
		dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",
			rtd->dai_link->name);
		return ret;
	}
	dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);

	/* DAPM dai link stream work */
	INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

	pcm->nonatomic = rtd->dai_link->nonatomic;
	rtd->pcm = pcm;
	pcm->private_data = rtd;

	if (rtd->dai_link->no_pcm) {
    
    
		if (playback)
			pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
		if (capture)
			pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
		goto out;
	}
    
    
	/* ASoC PCM operations */
	if (rtd->dai_link->dynamic) {
    
    
		rtd->ops.open		= dpcm_fe_dai_open;
		rtd->ops.hw_params	= dpcm_fe_dai_hw_params;
		rtd->ops.prepare	= dpcm_fe_dai_prepare;
		rtd->ops.trigger	= dpcm_fe_dai_trigger;
		rtd->ops.hw_free	= dpcm_fe_dai_hw_free;
		rtd->ops.close		= dpcm_fe_dai_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	} else {
    
    
		rtd->ops.open		= soc_pcm_open;
		rtd->ops.hw_params	= soc_pcm_hw_params;
		rtd->ops.prepare	= soc_pcm_prepare;
		rtd->ops.trigger	= soc_pcm_trigger;
		rtd->ops.hw_free	= soc_pcm_hw_free;
		rtd->ops.close		= soc_pcm_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	}

	if (platform->driver->ops) {
    
    
		rtd->ops.ack		= platform->driver->ops->ack;
		rtd->ops.copy		= platform->driver->ops->copy;
		rtd->ops.silence	= platform->driver->ops->silence;
		rtd->ops.page		= platform->driver->ops->page;
		rtd->ops.mmap		= platform->driver->ops->mmap;
	}

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

	if (platform->driver->pcm_new) {
    
    
		ret = platform->driver->pcm_new(rtd);
		if (ret < 0) {
    
    
			dev_err(platform->dev,
				"ASoC: pcm constructor failed: %d\n",
				ret);
			return ret;
		}
	}

	pcm->private_free = platform->driver->pcm_free;

	return ret;
}

该函数首先调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,然后初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。

回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化的设置工作后,调用了card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数snd_card_register对声卡进行注册:

if (card->late_probe) {
    
    
		ret = card->late_probe(card);
		if (ret < 0) {
    
    
			dev_err(card->dev, "ASoC: %s late_probe() failed: %d\n",
				card->name, ret);
			goto probe_aux_dev_err;
		}
	}

	snd_soc_dapm_new_widgets(card);

	ret = snd_card_register(card->snd_card);

至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作,整个过程可以用一下的序列图表示:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45309916/article/details/124974304