一 概述
二 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);