ALSA之platform注册过程

一 概述
二 cpu的snd_soc_dai driver驱动的注册
三 snd_soc_platform_driver的注册

一 概述

ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。在具体实现上,ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和
snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

cpu_dai_driver 部分:
在嵌入式系统里面通常指 SoC 的 I2S、PCM 总线控制器,负责把音频数据从 I2S tx FIFO 搬运到 CODEC(这是回放的情形,录制则方向相反)。cpu_dai 通过 snd_soc_register_dai() 来注册。注:DAI 是 Digital Audio Interface 的简称,分为 cpu_dai 和 codec_dai,这两者通过 I2S/PCM 总线连接;AIF 是 Audio Interface 的简称,嵌入式系统中一般是 I2S 和 PCM 接口。

snd_soc_platform_driver 部分:
负责把 dma buffer 中的音频数据搬运到 I2S tx FIFO。音频 dma 驱动通过 snd_soc_register_platform() 来注册,故也常用 platform 来指 代音频 dma 驱动(这里的 platform 需要与 SoC Platform 区分开)。


二 cpu的snd_soc_dai driver驱动的注册

注册过程中数据关系如下:

1 定义一个 struct snd_soc_component *cmpnt,然后分别用
		static struct platform_driver mtk_routing_driver
		struct snd_soc_dai_driver mtk_routing_dai[]
		struct snd_soc_component_driver dai_routing_component
		初始化自己的对应项,并且将 初始化好了的 struct snd_soc_component 节点链接到LIST_HEAD(component_list) 全局双向链表

2 定义一个 struct snd_soc_dai,然后分别用 
	struct snd_soc_dai_driver mtk_routing_dai[]
	和前面定义初始化好了的 struct snd_soc_component *cmpnt 
	初始化自己的对应项,并且将自己链接到 component->dai_list双向链表中

在这里插入图片描述

工作如下:

1 实现 dai 操作函数,见 snd_soc_dai_ops 定义,用于配置和操作音频数字接口控制器,如

时钟配置 set_sysclk()
格式配置 set_fmt()
硬件参数配置 hw_params()
启动/停止数据传输 trigger()

2 实现 probe 函数(初始化)

3 初始化 snd_soc_dai_driver 实例,包括回放和录制的能力描述、dai 操作函数集、probe/remove 回调、电源管理相关的 suspend/resume 回调

4 
通过 snd_soc_register_dai() 把初始化完成的 snd_soc_dai_driver 注册到 soc-core:首先创建一
个 snd_soc_dai 实例,然后把该 snd_soc_dai 实例插入到 dai_list 链表(声卡注册时会遍历该链表,找
到 dai_link 声明的 cpu_dai 并绑定)。

第4步完成的工作其实分为两部分
	1 定义一个 struct snd_soc_component *cmpnt,然后分别用
	static struct platform_driver mtk_routing_driver
	struct snd_soc_dai_driver mtk_routing_dai[]
	struct snd_soc_component_driver dai_routing_component
	初始化自己的对应项,并且将 初始化好了的 struct snd_soc_component 节点链接到 LIST_HEAD(component_list) 全局双向链表

	2 定义一个 struct snd_soc_dai,然后分别用 
	struct snd_soc_dai_driver mtk_routing_dai[]
	和前面定义初始化好了的 struct snd_soc_component *cmpnt 
	初始化自己的对应项,并且将自己链接到 component->dai_list双向链表中

以 /sound/soc/mediatek/mt_soc_audio_6755/mt_soc_dai_routing.c 为例

static const struct snd_soc_dai_ops mtk_routing_ops = {
	.startup = mt6589_routing_startup,
	.prepare = mt6589_routing_prepare,
	.trigger = mt6589_routing_trigger,
};

static struct snd_soc_dai_driver mtk_routing_dai[] = {
	{
		.playback = {
			.stream_name = MT_SOC_ROUTING_STREAM_NAME,
			.rates = SNDRV_PCM_RATE_8000_48000,
			.formats = SNDRV_PCM_FMTBIT_S16_LE,
			.channels_min = 1,
			.channels_max = 2,
			.rate_min = 8000,
			.rate_max = 48000,
		},
		/*
		   .capture = {
		   .stream_name = MT_SOC_ROUTING_STREAM_NAME,
		   .rates = SNDRV_PCM_RATE_8000_48000,
		   .formats = SNDRV_PCM_FMTBIT_S16_LE,
		   .channels_min = 1,
		   .channels_max = 2,
		   .rate_min = 8000,
		   .rate_max = 48000,
		   },
		 */
		.name = "PLATOFRM_CONTROL",
		.ops = &mtk_routing_ops,
	},
};

static const struct snd_soc_component_driver dai_routing_component = {
	.name = "PLATOFRM_CONTROL",
};

static int mtk_routing_dev_probe(struct platform_device *pdev)
{
	int rc = 0;

    ......
	rc = snd_soc_register_component(&pdev->dev, &dai_routing_component, mtk_routing_dai, ARRAY_SIZE(mtk_routing_dai));
	return rc;
}

#ifdef CONFIG_OF
static const struct of_device_id mt_soc_dai_routing_of_ids[] = {
	{.compatible = "mediatek,mt_soc_dai_routing",},
	{}
};
#endif

static struct platform_driver mtk_routing_driver = {
	.probe = mtk_routing_dev_probe,
	.remove = mtk_routing_dev_remove,
	.driver = {
		.name = MT_SOC_ROUTING_DAI_NAME,//#define MT_SOC_ROUTING_DAI_NAME "Routing-Control"
		.owner = THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table = mt_soc_dai_routing_of_ids,
#endif
	},
};

#ifndef CONFIG_OF
static struct platform_device *soc_routing_dev;
#endif

static int __init mtk_routing_init(void)
{
	pr_warn("%s:\n", __func__);
#ifndef CONFIG_OF
	int ret;

	soc_routing_dev = platform_device_alloc(MT_SOC_ROUTING_DAI_NAME, -1);


	ret = platform_device_add(soc_routing_dev);

	if (ret != 0) {
		platform_device_put(soc_routing_dev);
		return ret;
	}
#endif
	return platform_driver_register(&mtk_routing_driver);
}

sound/soc/soc-core.c

static DEFINE_MUTEX(client_mutex);
static LIST_HEAD(platform_list);
static LIST_HEAD(codec_list);
static LIST_HEAD(component_list);


/*

struct snd_soc_component *cmpnt; 
struct snd_soc_component_driver 
struct device *dev == &pdev->dev

*/
static int snd_soc_component_initialize(struct snd_soc_component *component, const struct snd_soc_component_driver *driver, struct device *dev)
{
	/*定义 struct snd_soc_dapm_context 动态音频电源管理 */
	struct snd_soc_dapm_context *dapm;

/*
snd_soc_component -> name
*/
	component->name = fmt_single_name(dev, &component->id);


	component->dev = dev;
	component->driver = driver;
	component->probe = component->driver->probe;
	component->remove = component->driver->remove;

	if (!component->dapm_ptr)
		component->dapm_ptr = &component->dapm;

	dapm = component->dapm_ptr;
	dapm->dev = dev;
	dapm->component = component;
	dapm->bias_level = SND_SOC_BIAS_OFF;
	dapm->idle_bias_off = true;
	if (driver->seq_notifier)
		dapm->seq_notifier = snd_soc_component_seq_notifier;
	if (driver->stream_event)
		dapm->stream_event = snd_soc_component_stream_event;

	component->controls = driver->controls;
	component->num_controls = driver->num_controls;
	component->dapm_widgets = driver->dapm_widgets;
	component->num_dapm_widgets = driver->num_dapm_widgets;
	component->dapm_routes = driver->dapm_routes;
	component->num_dapm_routes = driver->num_dapm_routes;

	INIT_LIST_HEAD(&component->dai_list);
	mutex_init(&component->io_mutex);

	return 0;
}


/*
struct snd_soc_component *cmpnt;
*/
static void snd_soc_component_add_unlocked(struct snd_soc_component *component)
{
	if (!component->write && !component->read)
		snd_soc_component_init_regmap(component);

	list_add(&component->list, &component_list);
}

/*
struct snd_soc_component *cmpnt;
*/
static void snd_soc_component_add(struct snd_soc_component *component)
{
	mutex_lock(&client_mutex);
	snd_soc_component_add_unlocked(component);
	mutex_unlock(&client_mutex);
}

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

	/* 1 申请 struct snd_soc_component 空间 */
	cmpnt = kzalloc(sizeof(*cmpnt), GFP_KERNEL);

/* 2 
struct snd_soc_component *cmpnt; 
struct snd_soc_component_driver 
struct device *dev == &pdev->dev
*/
	ret = snd_soc_component_initialize(cmpnt, cmpnt_drv, dev);
	if (ret)
		goto err_free;

	cmpnt->ignore_pmdown_time = true;
	cmpnt->registered_as_component = true;

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

	snd_soc_component_add(cmpnt);

	return 0;

err_cleanup:
	snd_soc_component_cleanup(cmpnt);
err_free:
	kfree(cmpnt);
	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_component);


/*
struct snd_soc_component *cmpnt;
struct snd_soc_dai_driver *dai_drv
int num_dai
true
*/

/* 注册  snd_soc_dai_driver

	1 创建 snd_soc_dai 
	2 snd_soc_codec -> snd_soc_component -> snd_soc_dai_driver = snd_soc_dai_driver mtk_routing_dai[]
	3 绑定 
		1 struct snd_soc_dai -> snd_soc_component   = snd_soc_codec -> snd_soc_component 
		2 struct snd_soc_dai -> device              = snd_soc_codec -> snd_soc_component -> device  = platform_device->device
		3 struct snd_soc_dai -> snd_soc_dai_driver  = snd_soc_codec -> snd_soc_component -> snd_soc_dai_driver = snd_soc_dai_driver mtk_routing_dai[]

	4 添加 struct snd_soc_dai -> list_head  到  snd_soc_codec -> snd_soc_component -> list_head dai_list 双向链
 */
static int snd_soc_register_dais(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, size_t count, bool legacy_dai_naming)
{
/*
创建 struct snd_soc_dai *dai
定义 struct device *dev,并用 snd_soc_codec -> snd_soc_component -> device 初始化
*/
	struct device *dev = component->dev;
	struct snd_soc_dai *dai;
	unsigned int i;
	int ret;

	dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count);

	/*
	snd_soc_codec -> snd_soc_component -> snd_soc_dai_driver = snd_soc_dai_driver
	snd_soc_codec -> snd_soc_component -> num_dai  = ARRAY_SIZE(mtk_6323_dai_codecs)
	*/
	component->dai_drv = dai_drv;
	component->num_dai = count;

	/* 为每一个 snd_soc_dai 分配空间 
	设置  struct snd_soc_dai -> name
	struct snd_soc_dai -> snd_soc_component = snd_soc_codec -> snd_soc_component 
	struct snd_soc_dai -> device = snd_soc_codec -> snd_soc_component -> device
	struct snd_soc_dai -> snd_soc_dai_driver = snd_soc_codec -> snd_soc_component -> snd_soc_dai_driver
	struct snd_soc_dai -> snd_soc_dai_driver -> snd_soc_dai_ops = 空
	添加 struct snd_soc_dai -> list_head  插入到  snd_soc_codec -> snd_soc_component -> list_head dai_list 双向链表
	*/
	for (i = 0; i < count; i++) {

		dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
		if (dai == NULL) {
			ret = -ENOMEM;
			goto err;
		}

		if (count == 1 && legacy_dai_naming) {
			dai->name = fmt_single_name(dev, &dai->id);
		} else {
			dai->name = fmt_multiple_name(dev, &dai_drv[i]);
			if (dai_drv[i].id)
				dai->id = dai_drv[i].id;
			else
				dai->id = i;
		}
		if (dai->name == NULL) {
			kfree(dai);
			ret = -ENOMEM;
			goto err;
		}

		dai->component = component;
		dai->dev = dev;
		dai->driver = &dai_drv[i];
		if (!dai->driver->ops)
			dai->driver->ops = &null_dai_ops;//空

		list_add(&dai->list, &component->dai_list);

		dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name);
	}

	return 0;

err:
	snd_soc_unregister_dais(component);

	return ret;
}

三 snd_soc_platform_driver的注册

以 sound/soc/mediatek/mt_soc_audio_6755/mt_soc_pcm_dummy.c 为例
在这里插入图片描述

/* 
整个ASoC都以snd_soc_pcm_runtime为桥梁来操作,可以这么理解:每一个音频物理链路对应一个dai_link,而每个dai_link都有着自身的设备私有数据,
这些私有数据保存在snd_soc_pcm_runtime中。
*/
static struct snd_soc_pcm_runtime *pruntimepcm;//定义该音频物理链路的 snd_soc_pcm_runtime

/*
将 snd_soc_pcm_runtime 绑定
*/
static int mtk_asoc_dummypcm_new(struct snd_soc_pcm_runtime *rtd)
{
	int ret = 0;

	pruntimepcm  = rtd;
	pr_warn("%s\n", __func__);
	return ret;
}

/*
struct snd_soc_platform_driver 为空
*/
static int mtk_afe_dummy_probe(struct snd_soc_platform *platform)
{
	pr_warn("mtk_afe_dummy_probe\n");
	return 0;
}

/*
该ops中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数

open:打开 pcm 逻辑设备时,回调该函数设定 dma 设备的硬件约束;并申请一个私有结构,保存 dma 设备资源如通道号、传输单元、缓冲区信息、IO 信息等,保存在 runtime->private_data
hw_params:设置硬件参数时(cmd=SNDRV_PCM_IOCTL_HW_PARAMS),回调该函数初始化 dma 资源,包括通道号、传输单元、缓冲区信息、IO 设备信息等。
prepare:当数据已准备好(cmd=SNDRV_PCM_IOCTL_PREPARE),回调该函数告知 dma 设备数据已就绪
trigger:数据传送 开始/停止/暂停/恢复 时,回调该函数启动或停止 dma 传输(当上层第一次调用 pcm_write() 时,触发 trigger() 启动 dma 传输;
pointer:dma 每完成一次传输,都会调用该函数获得传输数据的当前位置,这样 pcm native 可计算 dma buffer 指针位置及可用空间。
*/
 
static struct snd_pcm_ops mtk_afe_ops = {
	.open =     mtk_pcm_open,
	.close =    mtk_dummypcm_close,
	.ioctl =    snd_pcm_lib_ioctl,
	.hw_params =    mtk_pcm_hw_params,
	.hw_free =  mtk_dummy_pcm_hw_free,
	.prepare =  mtk_pcm_prepare,
	.trigger =  mtk_dummypcm_trigger,
	.copy =     mtk_pcm_copy,
	.silence =  mtk_pcm_silence,
	.page =     mtk_pcm_page,
};

static struct snd_soc_platform_driver mtk_soc_dummy_platform = {
	.ops        = &mtk_afe_ops,
	.pcm_new    = mtk_asoc_dummypcm_new,
	.probe      = mtk_afe_dummy_probe,
};

static int mtk_dummy_probe(struct platform_device *pdev)
{
    ......

    /* snd_soc_register_platform - Register a platform with the ASoC core */
	return snd_soc_register_platform(&pdev->dev, &mtk_soc_dummy_platform);
}

/*
匹配设备树
*/
#ifdef CONFIG_OF
static const struct of_device_id mt_soc_pcm_dummy_of_ids[] = {
	{ .compatible = "mediatek,mt_soc_pcm_dummy", },
	{}
};
#endif

static struct platform_driver mtk_afedummy_driver = {
	.driver = {
		.name = MT_SOC_DUMMY_PCM,
		.owner = THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table = mt_soc_pcm_dummy_of_ids,
#endif
	},
	.probe = mtk_dummy_probe,
	.remove = mtk_afedummy_remove,
};

sound/soc/soc-core.c

static DEFINE_MUTEX(client_mutex);
static LIST_HEAD(platform_list);
static LIST_HEAD(codec_list);
static LIST_HEAD(component_list);


/**
 * snd_soc_add_platform - Add a platform to the ASoC core
 * @dev: The parent device for the platform
 * @platform: The platform to add
 * @platform_driver: The driver for the platform
 */
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
		const struct snd_soc_platform_driver *platform_drv)
{
	int ret;

......
/* 初始化 struct snd_soc_platform
	snd_soc_platform->dev = dev;//The parent device for the platform 设备树
	snd_soc_platform->driver = struct snd_soc_platform_driver mtk_soc_dummy_platform;
 */
	platform->dev = dev;
	platform->driver = platform_drv;

......

    /* 将 初始化好了的struct snd_soc_platform 添加到 全局链表 platform_list 中*/
	list_add(&platform->list, &platform_list);

	return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_add_platform);


/**
 * snd_soc_register_platform - Register a platform with the ASoC core
 *
 * @platform: platform to register
 */
int snd_soc_register_platform(struct device *dev, const struct snd_soc_platform_driver *platform_drv)
{
	struct snd_soc_platform *platform;
	int ret;

	platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);

    /**
     * snd_soc_add_platform - Add a platform to the ASoC core
     * @dev: The parent device for the platform
     * @platform: The platform to add
     * @platform_driver: The driver for the platform
     */
	ret = snd_soc_add_platform(dev, platform, platform_drv);

	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_platform);
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux老A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值