1.ASoC中Codec Class概述
ASoC中的Codec class的代码组成可以分为codec control、dai_driver、kcontrol和DAPM四个部分。

- dai_driver 提供定义DAI和PCM配置的接口;
- codec control 提供codec芯片控制接口的相关函数;
- kcontrol 提供kcontrols接口,用于用户空间的工具动态控制模块的行为;
- DAPM 定义DAPM widgets,建立DAPM routes用于codec的动态电源管理。
2.Codec Class涉及的主要数据结构
Codec驱动在功能上划分为codec device和DAI component两个部分。通过调用devm_snd_soc_register_component()
函数,codec driver注册一个struct snd_soc_component_driver
和一个或多个struct snd_soc_dai_driver
到ALSA Core中,供后续声卡注册以及声卡配置。
2.1 struct snd_soc_component_driver Codec控制相关的部分
其中struct snd_soc_component_driver
定义codec route、widgets、controls以及与codec相关的回调函数。涉及上述提到的codec control、kcontrol、DAPM部分代码。struct snd_soc_component_driver
是对codec和platform设备的抽象概念,可用于定义codec或platform的功能。
(值得一提的是,在kernel4.18前,snd_soc_component_driver的概念还没有正式使用,在代码中snd_soc_codec_driver表示codec驱动,snd_soc_platform_driver表示platform驱动,在kernel4.18之后的代码中,统一使用snd_soc_component_driver代替,不过这一变动对代码的变化不大,阅读代码是大多数时候只需要将snd_soc_codec_driver和snd_soc_platform_driver的变量的类型替换为snd_soc_component_driver即可理解代码的逻辑,便于后续代码的开发)
以elf2开发板为例,codec为nau8822。codec nau8822中的 snd_soc_component_driver定义如下
static const struct snd_soc_component_driver soc_component_dev_nau8822 = {
.probe = nau8822_probe,
.suspend = nau8822_suspend,
.resume = nau8822_resume,
.set_bias_level = nau8822_set_bias_level,
.controls = nau8822_snd_controls,
.num_controls = ARRAY_SIZE(nau8822_snd_controls),
.dapm_widgets = nau8822_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(nau8822_dapm_widgets),
.dapm_routes = nau8822_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(nau8822_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
其中定义了codec 配置、kcontrol定义、DAPM widgets和route定义三个部分的功能,coedc配置实现对codec的参数设定、kcontrol为用户层提供控制codec的接口,DAPM widget和route定于用于调节codec的能量消耗。这部分功能都是通过codec的I2C接口传输数据从而控制codec的行为。
2.2 struct snd_soc_dai_driver Codec音频相关的部分
其中struct snd_soc_dai_driver
则是定义音频流(audio stream)的信息、PCM流的配置以及DAI(digital audio interface)的操作。
开发板中codec侧相关的定义为
static const struct snd_soc_dai_ops nau8825_dai_ops = {
.hw_params = nau8825_hw_params,
.set_fmt = nau8825_set_dai_fmt,
};
#define NAU8825_RATES SNDRV_PCM_RATE_8000_192000
#define NAU8825_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
| SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver nau8825_dai = {
.name = "nau8825-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = NAU8825_RATES,
.formats = NAU8825_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 1,
.rates = NAU8825_RATES,
.formats = NAU8825_FORMATS,
},
.ops = &nau8825_dai_ops,
};
2.3 注册驱动到系统中
完成snd_soc_component_driver和snd_soc_dai_driver部分的定义后,调用devm_snd_soc_register_component()将component_driver和dai_driver注册到系统中。
Linux以device-driver模型对系统中的设备进行管理,设备驱动会在匹配到设备后,执行相应的初始化操作为设备分配资源。codec在Linux中同样被视为一种设备,而snd_soc_component_driver和snd_soc_dai_driver也是基于codec的功能开发的,所以devm_snd_soc_register_component()调用
一般在设备驱动的probe函数中。
在nau8822.c中就可以看到类似的代码片段
static int nau8822_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct device *dev = &i2c->dev;
struct nau8822 *nau8822 = dev_get_platdata(dev);
int ret;
if (!nau8822) {
nau8822 = devm_kzalloc(dev, sizeof(*nau8822), GFP_KERNEL);
if (nau8822 == NULL)
return -ENOMEM;
}
i2c_set_clientdata(i2c, nau8822);
nau8822->clk = devm_clk_get(&i2c->dev, NULL);
if (IS_ERR(nau8822->clk)) {
dev_err(&i2c->dev, "codec clock missing or invalid\n");
ret = PTR_ERR(nau8822->clk);
}
ret = clk_prepare_enable(nau8822->clk);
if (ret) {
dev_err(&i2c->dev, "unable to prepare codec clk\n");
}
nau8822->regmap = devm_regmap_init_i2c(i2c, &nau8822_regmap_config);
if (IS_ERR(nau8822->regmap)) {
ret = PTR_ERR(nau8822->regmap);
dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret);
return ret;
}
nau8822->dev = dev;
ret = regmap_write(nau8822->regmap, NAU8822_REG_RESET, 0x00);
if (ret != 0) {
dev_err(&i2c->dev, "Failed to issue reset: %d\n", ret);
return ret;
}
ret = devm_snd_soc_register_component(dev, &soc_component_dev_nau8822,
&nau8822_dai, 1);
if (ret != 0) {
dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret);
return ret;
}
return 0;
}
static const struct i2c_device_id nau8822_i2c_id[] = {
{ "nau8822", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, nau8822_i2c_id);
#ifdef CONFIG_OF
static const struct of_device_id nau8822_of_match[] = {
{ .compatible = "nuvoton,nau8822", },
{ }
};
MODULE_DEVICE_TABLE(of, nau8822_of_match);
#endif
static struct i2c_driver nau8822_i2c_driver = {
.driver = {
.name = "nau8822",
.of_match_table = of_match_ptr(nau8822_of_match),
},
.probe = nau8822_i2c_probe,
.id_table = nau8822_i2c_id,
};
module_i2c_driver(nau8822_i2c_driver);
上述代码中nau8822首先作为i2c设备,当系统检测到nau8822设备后,在驱动的probe函数中为codec实现ASoC中需要的功能分配资源,比如分配regmap资源用于codec配置,最后调用devm_snd_soc_register_component
函数,从而完成ASoC中codec的注册。
codec代码分块
头文件部分
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <asm/div64.h>
#include "nau8822.h"
#define NAU_PLL_FREQ_MAX 100000000
#define NAU_PLL_FREQ_MIN 90000000
#define NAU_PLL_REF_MAX 33000000
#define NAU_PLL_REF_MIN 8000000
#define NAU_PLL_OPTOP_MIN 6
static const int nau8822_mclk_scaler[] = { 10, 15, 20, 30, 40, 60, 80, 120 };
kcontrols和DAPM部分,这部分对codec的内部的寄存器操作、kcontrol、route等音频控制项进行定义。
static const struct reg_default nau8822_reg_defaults[] = {
{ NAU8822_REG_POWER_MANAGEMENT_1, 0x0000 },
{ NAU8822_REG_POWER_MANAGEMENT_2, 0x0000 },
{ NAU8822_REG_POWER_MANAGEMENT_3, 0x0000 },
{ NAU8822_REG_AUDIO_INTERFACE, 0x0050 },
{ NAU8822_REG_COMPANDING_CONTROL, 0x0000 },
{ NAU8822_REG_CLOCKING, 0x0140 },
{ NAU8822_REG_ADDITIONAL_CONTROL, 0x0000 },
{ NAU8822_REG_GPIO_CONTROL, 0x0000 },
{ NAU8822_REG_JACK_DETECT_CONTROL_1, 0x0000 },
{ NAU8822_REG_DAC_CONTROL, 0x0000 },
{ NAU8822_REG_LEFT_DAC_DIGITAL_VOLUME, 0x00ff },
{ NAU8822_REG_RIGHT_DAC_DIGITAL_VOLUME, 0x00ff },
{ NAU8822_REG_JACK_DETECT_CONTROL_2, 0x0000 },
{ NAU8822_REG_ADC_CONTROL, 0x0100 },
{ NAU8822_REG_LEFT_ADC_DIGITAL_VOLUME, 0x00ff },
{ NAU8822_REG_RIGHT_ADC_DIGITAL_VOLUME, 0x00ff },
{ NAU8822_REG_EQ1, 0x012c },
{ NAU8822_REG_EQ2, 0x002c },
{ NAU8822_REG_EQ3, 0x002c },
{ NAU8822_REG_EQ4, 0x002c },
{ NAU8822_REG_EQ5, 0x002c },
......
static const struct snd_soc_dapm_route nau8822_dapm_routes[] = {
{"Right DAC", NULL, "PLL", check_mclk_select_pll},
{"Left DAC", NULL, "PLL", check_mclk_select_pll},
{"Right Output Mixer", "LDAC Switch", "Left DAC"},
{"Right Output Mixer", "RDAC Switch", "Right DAC"},
{"Right Output Mixer", "RAUX Switch", "RAUX"},
{"Right Output Mixer", "RINMIX Switch", "Right Boost Mixer"},
{"Left Output Mixer", "LDAC Switch", "Left DAC"},
{"Left Output Mixer", "RDAC Switch", "Right DAC"},
{"Left Output Mixer", "LAUX Switch", "LAUX"},
{"Left Output Mixer", "LINMIX Switch", "Left Boost Mixer"},
{"AUX1 Output Mixer", "RDAC Switch", "Right DAC"},
{"AUX1 Output Mixer", "RMIX Switch", "Right Output Mixer"},
{"AUX1 Output Mixer", "RINMIX Switch", "Right Boost Mixer"},
{"AUX1 Output Mixer", "LDAC Switch", "Left DAC"},
{"AUX1 Output Mixer", "LMIX Switch", "Left Output Mixer"},
{"AUX2 Output Mixer", "LDAC Switch", "Left DAC"},
{"AUX2 Output Mixer", "LMIX Switch", "Left Output Mixer"},
{"AUX2 Output Mixer", "LINMIX Switch", "Left Boost Mixer"},
{"AUX2 Output Mixer", "AUX1MIX Output Switch", "AUX1 Output Mixer"},
{"Right Headphone Out", NULL, "Right Output Mixer"},
{"RHP", NULL, "Right Headphone Out"},
{"Left Headphone Out", NULL, "Left Output Mixer"},
{"LHP", NULL, "Left Headphone Out"},
{"Right Speaker Out", NULL, "Right Output Mixer"},
{"RSPK", NULL, "Right Speaker Out"},
{"Left Speaker Out", NULL, "Left Output Mixer"},
{"LSPK", NULL, "Left Speaker Out"},
{"AUX1 Out", NULL, "AUX1 Output Mixer"},
{"AUX2 Out", NULL, "AUX2 Output Mixer"},
{"AUXOUT1", NULL, "AUX1 Out"},
{"AUXOUT2", NULL, "AUX2 Out"},
{"Right ADC", NULL, "PLL", check_mclk_select_pll},
{"Left ADC", NULL, "PLL", check_mclk_select_pll},
{"Right ADC", NULL, "Right Boost Mixer"},
{"Right Boost Mixer", NULL, "RAUX"},
{"Right Boost Mixer", NULL, "Right Capture PGA"},
{"Right Boost Mixer", NULL, "R2"},
{"Left ADC", NULL, "Left Boost Mixer"},
{"Left Boost Mixer", NULL, "LAUX"},
{"Left Boost Mixer", NULL, "Left Capture PGA"},
{"Left Boost Mixer", NULL, "L2"},
{"Right Capture PGA", NULL, "Right Input Mixer"},
{"Left Capture PGA", NULL, "Left Input Mixer"},
{"Right Input Mixer", "R2 Switch", "R2"},
{"Right Input Mixer", "MicN Switch", "RMICN"},
{"Right Input Mixer", "MicP Switch", "RMICP"},
{"Left Input Mixer", "L2 Switch", "L2"},
{"Left Input Mixer", "MicN Switch", "LMICN"},
{"Left Input Mixer", "MicP Switch", "LMICP"},
{"Digital Loopback", "Switch", "Left ADC"},
{"Digital Loopback", "Switch", "Right ADC"},
{"Left DAC", NULL, "Digital Loopback"},
{"Right DAC", NULL, "Digital Loopback"},
};
定义codec时钟系统相关的操作snd_soc_dai_ops
static const struct snd_soc_dai_ops nau8822_dai_ops = {
.hw_params = nau8822_hw_params,
.mute_stream = nau8822_mute,
.set_fmt = nau8822_set_dai_fmt,
.set_sysclk = nau8822_set_dai_sysclk,
.set_pll = nau8822_set_pll,
.no_capture_mute = 1,
};
snd_soc_dai_driver
,对DAI的驱动以及配置进行配置
static struct snd_soc_dai_driver nau8822_dai = {
.name = "nau8822-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = NAU8822_RATES,
.formats = NAU8822_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = NAU8822_RATES,
.formats = NAU8822_FORMATS,
},
.ops = &nau8822_dai_ops,
.symmetric_rates = 1,
};
接下来是snd_soc_component_driver
的定义
static const struct snd_soc_component_driver soc_component_dev_nau8822 = {
.probe = nau8822_probe,
.suspend = nau8822_suspend,
.resume = nau8822_resume,
.set_bias_level = nau8822_set_bias_level,
.controls = nau8822_snd_controls,
.num_controls = ARRAY_SIZE(nau8822_snd_controls),
.dapm_widgets = nau8822_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(nau8822_dapm_widgets),
.dapm_routes = nau8822_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(nau8822_dapm_routes),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
最后是nau8822 codec芯片的设备驱动部分,nau8822的控制部分是I2C总线,音频数据传输接口是I2S。
在Linux中将其视为I2C设备。按照Linux中的device-driver模型,nau8822芯片能够被Linux系统所用,需要device和driver两个部分,其中device部分由Device Tree中的节点进行定义,driver部分则由nau8822_i2c_driver定义。
static struct i2c_driver nau8822_i2c_driver = {
.driver = {
.name = "nau8822",
.of_match_table = of_match_ptr(nau8822_of_match),
},
.probe = nau8822_i2c_probe,
.id_table = nau8822_i2c_id,
};
其中的nau8822_i2c_probe
函数进行component driver和dai driver的注册,将codec的功能注册到ASoC Core中。