/*****************************************************************************************************************/
声明: 本博客内容均由https://blog.csdn.net/weixin_47702410原创,转载or引用请注明出处,谢谢!
创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢
/*****************************************************************************************************************/
本文的codec指的是ASOC架构下的一部分,它是ASOC三大组成部分的codec驱动程序,主要是各种音频编解码IC的驱动程序。
ASOC之所以提出codec的目的主要是为了解决编解码器驱动程序与底层 SoC CPU 紧密耦合的问题。在提出这个框架后,编解码驱动程序(codec驱动)便可与具体的平台分离开,成为独立的一部分。这十分有利于开发产品的厂商去整合音频IC与主控IC,他们只需要在machine端将两者连接起来便可将音频IC整合到主控IC的平台上使用了!
Codec驱动程序可以包含下面七种内容:
Codec DAI和PCM配置:必须能够配置codec的数字音频接口(DAI)和PCM(脉冲编码调制)音频数据的参数。每个编解码器驱动程序必须有一个 struct snd_soc_dai_driver 来定义其 DAI 和 PCM 功能和操作。
Codec控制IO:使用RegMap API进行寄存器访问。RegMap API提供了一种简化的方式来读写codec的寄存器。
混音器和音频控制:提供音频路径的混合和音量控制等功能。
Codec音频操作:实现codec的基本音频操作,如初始化、启动、停止等。
DAPM描述:定义Dynamic Audio Power Management(动态音频功率管理)的小部件、路径和控制点,以优化功率消耗。
DAPM事件处理器:处理DAPM系统中的事件,如音频流的启动和停止,以及功率状态的变化。
DAC数字静音控制:如果需要,可以提供数字到模拟转换器(DAC)的静音控制功能。
本文分析Codec DAI和PCM配置、Codec音频操作的一些API函数是如何构成的。关于这两个API函数,一般使用如下的函数进行注册:
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)
有些codec驱动程序可能用的是devm_snd_soc_register_component函数去调用,但是其实devm_snd_soc_register_component函数也是调用snd_soc_register_component函数去完成的相关操作。devm_snd_soc_register_component函数源码如下所示:
//file path:/sound/soc/soc-devres.c
int devm_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)
{
const struct snd_soc_component_driver **ptr;
int ret;
ptr = devres_alloc(devm_component_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
if (ret == 0) {
*ptr = cmpnt_drv;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return ret;
}
首先分析一下注册声卡驱动所用的API,了解注册声卡需要提供哪些函数或者结构体:
注册声卡的函数原型如下:
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 device *dev: 这个参数是一个指向device结构体的指针,代表了要注册的音频组件的设备。
const struct snd_soc_component_driver *component_driver: 这个参数是一个指向snd_soc_component_driver结构体的指针,它定义了组件驱动的操作和行为。这个结构体通常包含了一系列的回调函数,比如初始化(init)、读写寄存器(read/write)、挂起(suspend)和恢复(resume)等,以及可能包含的组件特有的控制元素和调试信息。
struct snd_soc_dai_driver *dai_drv: 这个参数是一个指向snd_soc_dai_driver结构体的指针,它代表了数字音频接口(DAI)的驱动程序。DAI是SoC音频组件的一部分,负责处理数字音频流。dai_drv结构体包含了DAI的配置信息和操作函数,如启动(startup)、停止(shutdown)、设置格式(set_fmt)等。
int num_dai: 这个参数表示数字音频接口的数量。
结构体struct snd_soc_component_driver的原型可以见源码:/include/sound/soc-component.h
结构体struct snd_soc_dai_driver的原型可以见源码:/include/sound/soc-dai.h
结构体的内容比较多,但是一般情况下,我们只需要完成probe、remove函数,外加controls和DAPM相关定义便可。如下是一个例子:
static const struct snd_soc_component_driver my_soc_component_driver = {
.name = my_codec_name,
.probe = my_codec_probe, //probe和remove
.remove = my_codec_remove,
.controls = my_snd_controls, //controls相关
.num_controls = ARRAY_SIZE(my_snd_controls),
.dapm_widgets = my_dapm_widgets, //DAPM相关
.num_dapm_widgets = ARRAY_SIZE(my_dapm_widgets),
.dapm_routes = my_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(my_dapm_routes),
};
注意
这个结构体是对应到DAI相关操作的结构体,主要是涉及到数据流这块的,一般定义如下:
static const struct snd_soc_dai_ops my_dai_ops = {
.startup = my_startup,
.hw_params = my_hw_params,
.prepare = my_prepare,
.trigger = my_trigger,
.shutdown = my_shutdown,
};
static struct snd_soc_dai_driver my_dai_driver[] = {
{
.id = my_ID,
.name = "my-snd-codec",
.playback = {
.stream_name = "MY Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_192000,
.formats = my_FORMATS,
},
.capture = {
.stream_name = "MY Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_192000,
.formats = my_FORMATS,
},
.ops = &my_codec_dai_ops,
},
//...
};
上面是一个示例,值得注意的是snd_soc_dai_ops中可定义的不止上面这些ops,也可能包含更多的ops或者更少的ops,要看codec厂商如何设计使用IC。
在ASoC (ALSA System on Chip) 框架中,snd_soc_component_driver 和 snd_soc_dai_driver 是两个核心的结构体,它们分别代表了codec组件的不同方面。
snd_soc_component_driver: 这个结构体代表了codec组件的控制部分。它包含了一系列的回调函数和操作,用于管理codec的音频控制、DAPM (Dynamic Audio Power Management) 配置和其他codec特定的功能。例如,它可以包含音量控制、静音开关、EQ设置等的回调函数。此外,它还可以包含用于初始化和关闭codec的回调函数。
snd_soc_component_driver 结构体的定义可能包含以下字段(不是完整列表):
.probe 和 .remove:当codec设备被绑定或解绑时调用的函数。
.controls、.dapm_widgets 和 .dapm_routes:定义codec的控制元素、DAPM小部件和音频路径。
.idle_bias_off:指示codec在空闲时是否关闭偏置电压。
.suspend 和 .resume:在系统挂起和恢复时调用的函数。
snd_soc_dai_driver: 这个结构体代表了codec的DAI (Digital Audio Interface) 部分,它定义了codec与其他音频组件(如处理器或其他数字音频设备)之间的接口。这包括支持的音频格式、时钟配置、数据传输模式等。
snd_soc_dai_driver 结构体的定义可能包含以下字段(不是完整列表):
.playback 和 .capture:定义了播放和录音的能力,如支持的格式、速率、通道数等。
.ops:包含了操作DAI的回调函数,如启动、停止、设置格式等。
.symmetric_rates:指示是否使用对称的采样率进行播放和录音。
这两个结构体之间的主要区别在于它们的职责范围。snd_soc_component_driver 更关注于codec的控制和管理,而 snd_soc_dai_driver 更关注于定义codec的数字音频接口。
当你注册一个codec时,你需要提供这两个结构体,因为ASoC框架需要知道如何控制codec(通过snd_soc_component_driver)以及如何通过DAI与codec进行数字音频通信(通过snd_soc_dai_driver)。这样,ASoC框架就可以正确地将codec集成到整个音频系统中,并确保音频数据可以正确地在系统的不同部分之间传输。
如下所示,提供一份完整的codec驱动代码的基本框架:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
static int my_codec_probe(struct snd_soc_component *codec)
{
//int ret;
printk("-----%s----\n",__func__);
return 0;
}
static void my_codec_remove(struct snd_soc_component *codec)
{
printk("-%s,line:%d\n",__func__,__LINE__);
}
static struct snd_soc_component_driver soc_my_codec_drv = {
.probe = my_codec_probe,
.remove = my_codec_remove,
};
static int my_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai) {
printk("-%s,line:%d\n",__func__,__LINE__);
return 0;
}
static int my_codec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
printk("-%s,line:%d\n",__func__,__LINE__);
return 0;
}
static void my_codec_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai) {
printk("-%s,line:%d\n",__func__,__LINE__);
}
static int my_codec_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
printk("-%s: playback start\n",__func__);
} else {
printk("-%s: catpure start\n",__func__);
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
printk("-%s:playback stop\n",__func__);
} else {
printk("-%s:catpure stop\n",__func__);
}
break;
default:
return -EINVAL;
}
return 0;
}
static int my_codec_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai) {
printk("-%s,line:%d\n",__func__,__LINE__);
return 0;
}
static const struct snd_soc_dai_ops my_codec_dai_ops = {
.startup = my_codec_startup,
.hw_params = my_codec_hw_params,
.prepare = my_codec_prepare,
.trigger = my_codec_trigger,
.shutdown = my_codec_shutdown,
};
static struct snd_soc_dai_driver my_codec_dai[] = {
{
.name = "my_codec_dai",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000
| SNDRV_PCM_RATE_KNOT,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE,
},
.ops = &my_codec_dai_ops,
},
};
static int codec_probe(struct platform_device *pdev) {
int ret = 0;
printk("-%s,line:%d\n",__func__,__LINE__);
ret = snd_soc_register_component(&pdev->dev, &soc_my_codec_drv,
my_codec_dai, ARRAY_SIZE(my_codec_dai));
if (ret < 0) {
dev_err(&pdev->dev, "register codec failed\n");
return -1;
}
return ret;
}
static int codec_remove(struct platform_device *pdev){
printk("-%s,line:%d\n",__func__,__LINE__);
return 0;
}
static void codec_pdev_release(struct device *dev)
{
printk("-%s,line:%d\n",__func__,__LINE__);
}
static struct platform_device codec_pdev = {
.name = "my_codec",
.dev.release = codec_pdev_release,
};
static struct platform_driver codec_pdrv = {
.probe = codec_probe,
.remove = codec_remove,
.driver = {
.name = "my_codec",
},
};
static int __init codec_init(void)
{
int ret;
ret = platform_device_register(&codec_pdev);
if (ret)
return ret;
ret = platform_driver_register(&codec_pdrv);
if (ret)
platform_device_unregister(&codec_pdev);
return ret;
}
static void __exit codec_exit(void)
{
platform_driver_unregister(&codec_pdrv);
platform_device_unregister(&codec_pdev);
}
module_init(codec_init);
module_exit(codec_exit);
MODULE_LICENSE("GPL");
在codec驱动中我们只需要提供两个信息给machine层:
.codec_dai_name = "my_codec_dai",
.codec_name = "my_codec.0",
细心的读者可能会发现“.codec_name”的右值不是"my_codec"而是“my_codec.0",这个原因是因为在snd_soc_register_component函数中的下面的语句决定的:
//snd_soc_register_component函数 调用 snd_soc_component_initialize函数
int snd_soc_component_initialize(struct snd_soc_component *component,
const struct snd_soc_component_driver *driver,
struct device *dev)
{
INIT_LIST_HEAD(&component->dai_list);
INIT_LIST_HEAD(&component->dobj_list);
INIT_LIST_HEAD(&component->card_list);
INIT_LIST_HEAD(&component->list);
mutex_init(&component->io_mutex);
component->name = fmt_single_name(dev, &component->id);
if (!component->name) {
dev_err(dev, "ASoC: Failed to allocate name\n");
return -ENOMEM;
}
component->dev = dev;
component->driver = driver;
return 0;
}
// codec_name其实就是component->name,是由fmt_single_name函数决定
static char *fmt_single_name(struct device *dev, int *id)
{
const char *devname = dev_name(dev);
char *found, *name;
int id1, id2;
if (devname == NULL)
return NULL;
name = devm_kstrdup(dev, devname, GFP_KERNEL);
if (!name)
return NULL;
/* are we a "%s.%d" name (platform and SPI components) */
found = strstr(name, dev->driver->name);
if (found) {
/* get ID */
if (sscanf(&found[strlen(dev->driver->name)], ".%d", id) == 1) {
/* discard ID from name if ID == -1 */
if (*id == -1)
found[strlen(dev->driver->name)] = '\0';
}
/* I2C component devices are named "bus-addr" */
} else if (sscanf(name, "%x-%x", &id1, &id2) == 2) {
/* create unique ID number from I2C addr and bus */
*id = ((id1 & 0xffff) << 16) + id2;
devm_kfree(dev, name);
/* sanitize component name for DAI link creation */
name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", dev->driver->name, devname);
} else {
*id = 0;
}
return name;
}
/*
fmt_single_name函数分析:
1、先获取设备的名称:const char *devname = dev_name(dev); 我们的设备名称应该是"my_codec"
2、解析设备ID,会有两种情况,一种是非I2C设备,一种是I2C设备。
3、如果是非I2C则codec_name名字是"%s.%d"(%d为ID);如果是I2C设备,则名字是"%s.bus-addr"(bus-addr为I2C的bus和addr)
4、我们在驱动注册的时候没有指定ID的数值是多少,这个ID应该默认为0.也就是我们fmt_single_name函数实际上返回的是“my_codec.0”
*/