Linux内核4.14版本——alsa框架分析(7)-ASoC(Platform)

1. 概述

2. Platform代码分析

2.1 devm_snd_soc_register_component

2.1.1 snd_soc_register_component

2.2 samsung_asoc_dma_platform_register

3. 总结


1. 概述

      在ASOC在Platform部分,主要是平台相关的DMA操作和音频管理。大概流程先将音频数据从内存通过DMA方式传输到CPU侧的dai接口,然后通过CPU的dai接口(通过I2S总线)将数据从达到Codec中,数据会在Codec侧会解码的操作,最终输出到耳机/音箱中。依然已下图作为参考:

Linux内核4.14版本——alsa框架分析(7)-ASoC(Platform)_第1张图片

      在platfrom侧的主要功能有:  音频数据管理,音频数据传输通过dma; 数据如何通过cpu dai传入到codec dai,已经cpu侧dai的配置。

      而上述的两大类功能在ASOC中使用两个结构体表示:

      snd_soc_dai_driver代表cpu侧的dai驱动,其中包括dai的配置(音频格式,clock,音量等)。

      snd_soc_platform_driver代表平台使用的dma驱动,主要是数据的传输等。

       和Machine一样,使用snd_soc_platform结构对所有platform设备进行统一抽象。

分析例子:

machine: sound\soc\samsung\s3c24xx_uda134x.c

platform: sound\soc\samsung\s3c24xx-i2s.c

codec:   sound\soc\codecs\uda134x.c

2. Platform代码分析

      如何找到Machine对应的Platform呢? 答案也是通过Machine中的snd_soc_dai_link中的platform_name。在内核中搜素platform_name所对应的name。

static struct platform_driver s3c24xx_iis_driver = {
	.probe  = s3c24xx_iis_dev_probe,
	.driver = {
		.name = "s3c24xx-iis",
	},
};

进入probe函数中,继续分析。


static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
{
	struct resource *res;
	int ret;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	s3c24xx_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(s3c24xx_i2s.regs))
		return PTR_ERR(s3c24xx_i2s.regs);

	s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO;
	s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO;

	ret = samsung_asoc_dma_platform_register(&pdev->dev, NULL,
						 NULL, NULL);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register the DMA: %d\n", ret);
		return ret;
	}

	ret = devm_snd_soc_register_component(&pdev->dev,
			&s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
	if (ret)
		dev_err(&pdev->dev, "Failed to register the DAI\n");

	return ret;
}

2.1 devm_snd_soc_register_component

	ret = devm_snd_soc_register_component(&pdev->dev,
			&s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
	if (ret)
		dev_err(&pdev->dev, "Failed to register the DAI\n");

      通过devm_snd_soc_register_component注册一个component组件。传入的参数分别是snd_soc_component_driver和snd_soc_dai_driver。

static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
	.trigger	= s3c24xx_i2s_trigger,
	.hw_params	= s3c24xx_i2s_hw_params,
	.set_fmt	= s3c24xx_i2s_set_fmt,
	.set_clkdiv	= s3c24xx_i2s_set_clkdiv,
	.set_sysclk	= s3c24xx_i2s_set_sysclk,
};

static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
	.probe = s3c24xx_i2s_probe,
	.suspend = s3c24xx_i2s_suspend,
	.resume = s3c24xx_i2s_resume,
	.playback = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.capture = {
		.channels_min = 2,
		.channels_max = 2,
		.rates = S3C24XX_I2S_RATES,
		.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
	.ops = &s3c24xx_i2s_dai_ops,
};

static const struct snd_soc_component_driver s3c24xx_i2s_component = {
	.name		= "s3c24xx-i2s",
};

      根据传入参数,进入到devm_snd_soc_register_component函数分析。其中devm是一种资源管理的方式,不用考虑资源释放,内核会内部做好资源回收。然后进入snd_soc_register_component函数。

2.1.1 snd_soc_register_component

int snd_soc_register_component(struct device *dev,
			       const struct snd_soc_component_driver *component_driver,
			       struct snd_soc_dai_driver *dai_drv,
			       int num_dai)
{
	struct snd_soc_component *component;
	int ret;

	component = kzalloc(sizeof(*component), GFP_KERNEL);
	if (!component) {
		dev_err(dev, "ASoC: Failed to allocate memory\n");
		return -ENOMEM;
	}

	ret = snd_soc_component_initialize(component, component_driver, dev);
	if (ret)
		goto err_free;

	component->ignore_pmdown_time = true;
	component->registered_as_component = true;

	ret = snd_soc_register_dais(component, dai_drv, num_dai, true);
	if (ret < 0) {
		dev_err(dev, "ASoC: Failed to register DAIs: %d\n", ret);
		goto err_cleanup;
	}

	snd_soc_component_add(component);

	return 0;

err_cleanup:
	snd_soc_component_cleanup(component);
err_free:
	kfree(component);
	return ret;
}

      此函数和snd_soc_register_codec的大体流程一致,都是初始化snd_soc_component的实例,然后注册dai,最终将注册的dai放入到component->dai_list中,然后将分配的component放入到component_list链表中。

      上述的步骤只是完成platform的一部分,关于cpu_dai侧的设置,配置。还需要平台相关的dma操作。退回到s3c24xx_iis_dev_probe函数,继续往下分析代码。

2.2 samsung_asoc_dma_platform_register

	ret = samsung_asoc_dma_platform_register(&pdev->dev, NULL,
						 NULL, NULL);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register the DMA: %d\n", ret);
		return ret;
	}

参考文章:

Linux内核4.14版本——alsa框架分析(9)——PCM DMA注册_yangguoyu8023的博客-CSDN博客

Linux内核4.14版本——alsa框架分析(10)——PCM DMA的使用_yangguoyu8023的博客-CSDN博客

    通常还有另一种方式,会将cpu侧dai的驱动和平台相关的dma驱动分离的。也就是machine中的snd_soc_dai_link的platform_name和cpu_dai_name不相同。而上述的samsung的例子则是platform_name和cpu_dai_name是相同的。不过原理都是相同的最后都会调用snd_soc_add_platform函数注册platform到AOSC core的。

3. 总结

       总结:  通过machine中的snd_soc_dai_link中的platform_name和cpu_dai_name分别查找平台的dma设备驱动和cpu侧的dai驱动。最终会将这dai保存到component->dai_list中,platform保存到platform_list当中。然后将component放入到component_list链表中。这些数据会在Machine代码的开始过程中进行匹配操作。

关于cpu侧的驱动总结:

1.   分配一个cpu_dai_name的平台驱动,注册。

2.   分配一个struct snd_soc_dai_driver结构,然后设置相应数据。

3.   调用snd_soc_register_component函数注册cpu侧的dai结构。

4.   分配一个struct snd_soc_platform_driver结构,设置相应的数据。

5.   最终调用snd_soc_add_platform函数添加snd_soc_platform_driver结构。

你可能感兴趣的:(Linux,音频子系统,linux,ASOC)