在介绍音频总线I2S总线博客的前言里,我就已经说过,后面会把这次移植声卡驱动的前后经过分享出来。
这次,终于忙里偷闲,写出这篇博客来。
众所周知,Linux对于音频,是有自己的一套驱动框架的,那就是——ALSA。
以往,像V4L2视频框架、I2C总线框架、INPUT子系统等,我还耐心研究过,但这套音频框架我却甚至都没有时间去研究。
深圳的工作节奏相比较重庆的工作节奏快太多了,每天都有事情做,忙的不行。
这里我分享一个链接,里面对于ALSA框架介绍的挺清楚的,大家可以去看一下。
提醒:驱动移植,一定要注意平台的差异,旨在学习移植思路,而不是照搬照抄。
DAI:Digital Audio Interfaces,数字音频接口。
LSB的意思是:全称为baiLeast Significant Bit,在二进制数中意为最低有效位。
MSB的意思是:全称为Most Significant Bit,在二进制数中属于最高有效位。
Hi-Fi是英语High-Fidelity的缩写,翻译为“高保真”,其定义是:与原来的声音高度相似的重放声音。
DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思。
ESAI:Enhanced Serial Audio Interface,增强型串行音频接口。
PCM:脉冲编码调制。
ASoC:ALSA System on Chip,专门针对嵌入式的片上ALSA系统。
文件路径:kernel/arch/arm/mach-mx6/board-xxx-xxx.c,kernel/arch/arm/mach-mx6/board-xxx-xxx.h。
在这个文件里面将会配置平台设备信息、i2c设备信息、gpio复用、声卡初始化、esai接口注册等。
文件路径:kernel/sound/soc/codec/tlv320adc310x.c,kernel/sound/soc/codec/tlv320adc310x.h。
由文件路径就可以知道,我这次所用的声卡型号为tlv320adc310x。
声卡驱动的codec驱动一般都会由供应商提供,但是他们提供的代码都是基于他们平台的,不一定适用你的项目平台,所以需要修改。
这里大致介绍一下codec驱动的功能:
在移动设备中,Codec的作用可以归结为4种,分别是:
①、对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号;
②、对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号;
③、对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的;
④、对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等,
我这个项目对这个声卡的需求就只有录音。
文件路径:kernel/sound/soc/imx/imx-tlv320adc310x.c。
我对这个文件的理解,相对于codec驱动是声卡端,imx-tlv320adc310x.c这个文件就是cpu端,在这个驱动会做如下事情:
①、以平台设备的方式注册声卡设备;
②、设置cpu端对于声卡录音的一些硬件配置(如时钟、I2S配置、主从)。
TLV320ADC3100是一款低功耗立体声音频模数转换器(ADC),支持8 kHz到96 kHz的采样率,集成可编程增益放大器(PGA)提供高达40 dB的模拟增益或自动增益控制(AGC)。
可以看到电路图中有四个pin脚与i2c相关,它们分别是sda、scl、i2c_adr1、i2c_adr0,显然这个声卡是通过i2c进行控制的,也就是说,寄存器的写入和读取都是通过i2c来控制的。
对i2c总线熟悉的,就知道这个四个脚分别是什么作用。
这里简单介绍一下i2c_adr1、i2c_adr0两个pin脚。这两个脚用来决定i2c从设备的地址。
图 - I2C Slave Addr table上述电路中将两个脚都接地拉低了,查声卡规格书可知,该i2c从设备地址为0x18。
sda、scl两个脚则分别是数据线和时钟线。
对于I2S一点都不了解的,可以看我上一篇文章——音频总线之I2S总线介绍及相关协议。
I2S这里总共有4个脚,他们分别是bclk、mclk、wclk、dout,也就对应三个时钟线和一个数据线。
I2S还有其他几个脚,他们最终接到了imx的cpu端的esai接口脚,这里我就不贴图出来了,大家根据实际项目实际分析。
总之,我们做驱动移植,首先就需要看原理图,看哪些脚接到了cpu端,这些脚是需要我们控制的。
硬件复位脚有一个,为reset,根据规格书所述:TLV320ADC3100需要在通电后进行硬件复位才能正常工作。在所有电源都达到其指定值后,复位引脚必须低电平驱动至少10 ns。如果不执行此复位序列,TLV320ADC3100可能无法正确响应寄存器读取或写入。
图 - hardware reset所以在给声卡上电时,我会拉低复位脚至少10ns,实际上,在代码中一般都是拉低10ms。
ARM_MIC_IN,这个引脚就不多说了,就是麦或者其他声音输入的引脚。
因为这个项目没有设备树,所以所有的设备信息都需要在板级配置文件里面配置好。
kernel/arch/arm/mach-mx6/board-xxx-xxx.c:
#if defined(CONFIG_SND_SOC_TLV320ADC3100)
#define TLV320ADC3100_RST IMX_GPIO_NR(4, 25)
//平台设备
static struct platform_device tlv320adc_sab_audio_device = {
.name = "imx-tlv320adc310x",
};
static const struct asrc_p2p_params tlv320adc_p2p = {
.p2p_rate = 16000, /* 采样率 */
.p2p_width = ASRC_WIDTH_24_BIT, /* 采样位数 */
};
//平台设备数据
static struct mxc_audio_platform_data tlv320adc_sab_audio_data = {
.codec_name = "adc310x-codec.1-0018",
.priv = (void *)&tlv320adc_p2p,
};
//esai接口驱动注册所需要的数据
static struct imx_esai_platform_data tlv320adc_sab_esai_pdata = {
.flags = IMX_ESAI_NET,
};
//声卡初始化,这里主要就是上电之后硬复位
static int mxc_tlv320adc310x_init(void)
{
//可能会有人疑惑,你说上电之后,那上电的操作呢?这里我解释一下,上电由mcu控制,不归我管
gpio_request(TLV320ADC3100_RST, "TLV320ADC3100_rst_gpio4_25");
gpio_direction_output(TLV320ADC3100_RST, 1);
//复位脚拉低,硬复位10ms
gpio_set_value(TLV320ADC3100_RST, 0);
msleep(10);
//10ms之后将复位脚拉高
gpio_set_value(TLV320ADC3100_RST, 1);
gpio_free(TLV320ADC3100_RST);
return 0;
}
#endif//defined(CONFIG_SND_SOC_TLV320ADC3100)
…………
//在i2c总线1上添加i2c slave设备,这里需要注意,电路图上的i2c是从1开始的,
//而代码中都是从0开始,所以电路图中是连接的i2c2,而这里需要在i2c1添加设备
static struct i2c_board_info mxc_i2c1_board_info[] __initdata = {
#if defined(CONFIG_SND_SOC_TLV320ADC3100)
{
I2C_BOARD_INFO("tlv320adc3100", 0x18),
},
#endif
};
…………
static int __init imx6q_init_audio(void)
{
#ifdef CONFIG_SND_SOC_TLV320ADC3100
//imx专有的平台设备注册函数
mxc_register_device(&tlv320adc_sab_audio_device, &tlv320adc_sab_audio_data);
//imx6q的esai接口驱动注册函数
imx6q_add_imx_esai(0, &tlv320adc_sab_esai_pdata);
//这里调用tlv的声卡初始化函数
mxc_tlv320adc310x_init();
#endif
}
大部分代码都已经有注释了,我就不多解释了,但这里需要特别提醒一下:
平台设备的“.name”一定要和声卡驱动之imx中的平台驱动名字相对应,至于为什么,等你看到它的代码的时候就知道了。
kernel/sound/soc/codec/tlv320adc310x.h:
//这里的就是之前说过的定义gpio的复用功能
//需要注意的是,哪个gpio复用的什么功能具体看原理图
//然后就是,每个gpio基本是只能复用一种功能,需要注意别被其他地方占用了gpio
static iomux_v3_cfg_t mx6q_sabresd_pads[] = {
……
#if defined(CONFIG_SND_SOC_TLV320ADC3100)
//*******************tlv320adc3100********************//
//I2C2
MX6Q_PAD_KEY_COL3__I2C2_SCL, //i2c2_scl
MX6Q_PAD_KEY_ROW3__I2C2_SDA, //i2c2_sad
//RESET
MX6Q_PAD_DISP0_DAT4__GPIO_4_25, //ad_rst
//ESAI
MX6Q_PAD_ENET_MDIO__ESAI1_SCKR, //i2s_slck_rx
MX6Q_PAD_ENET_REF_CLK__ESAI1_FSR, //I2S_LRCLK_RX
MX6Q_PAD_ENET_MDC__ESAI1_TX5_RX0, //I2S_SDI
//mclk
MX6Q_PAD_NANDF_CS2__CCM_CLKO2, //i2s_mck
//GPIO1
MX6Q_PAD_DISP0_DAT3__GPIO_4_24,
//EASI--NAVI
MX6Q_PAD_ENET_CRS_DV__ESAI1_SCKT, //I2S_SCLK_TX
MX6Q_PAD_ENET_RXD1__ESAI1_FST, //I2S_LRCLK_TX
MX6Q_PAD_GPIO_17__ESAI1_TX0, //i2s_SDO1
MX6Q_PAD_EIM_D27__GPIO_3_27, //mic_wake
//*******************tlv320adc3100 end********************//
#endif
……
}
这里先讲imx的驱动,为什么呢?因为按照三个文件的调用流程,这个文件应该是第2个。
按照习惯,看代码从驱动入口开始。
static int __init imx_3stack_asoc_init(void)
{
int ret;
printk("-------->%s\n",__func__);
//平台驱动注册,这里的imx_3stack_tlv320adc310x_driver.name与板级文件提到过的平台设备.name要一致
ret = platform_driver_register(&imx_3stack_tlv320adc310x_driver);
if (ret < 0)
goto exit;
err_device_alloc:
platform_driver_unregister(&imx_3stack_tlv320adc310x_driver);
}
static void __exit imx_3stack_asoc_exit(void)
{
platform_driver_unregister(&imx_3stack_tlv320adc310x_driver);
platform_device_unregister(imx_3stack_snd_device);
}
module_init(imx_3stack_asoc_init);
module_exit(imx_3stack_asoc_exit);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("ALSA SoC tlv320adc310x Machine Layer Driver");
MODULE_LICENSE("GPL");
可以看到,入口函数中几乎没做什么事情,就做了一个平台驱动的注册。我们看到imx_3stack_tlv320adc310x_driver。
static struct platform_driver imx_3stack_tlv320adc310x_driver = {
.probe = imx_3stack_tlv320adc310x_probe,
.remove = __devexit_p(imx_3stack_tlv320adc310x_remove),
.driver = {
.name = "imx-tlv320adc310x",
.owner = THIS_MODULE,
},
};
显然,这里的name就和板级配置文件里的平台设备的name一样,这时,就会match成功,进入它的probe函数了。
static struct platform_device *imx_3stack_snd_device;
static int __devinit imx_3stack_tlv320adc310x_probe(struct platform_device *pdev)
{
struct mxc_audio_platform_data *plat_data = pdev->dev.platform_data;
int i;
printk("-------->%s\n",__func__);
//以平台设备的方式,申请一个音频设备
imx_3stack_snd_device = platform_device_alloc("soc-audio", 4);
if (!imx_3stack_snd_device)
goto err_device_alloc;
//为音频设备设置私有数据snd_soc_card_imx_3stack结构体,这里需要重点关注这个结构体
platform_set_drvdata(imx_3stack_snd_device, &snd_soc_card_imx_3stack);
ret = platform_device_add(imx_3stack_snd_device);
if (0 == ret)
goto exit;
//下面就是一些初始化
if (!plat_data) {
dev_err(&pdev->dev, "plat_data is missing\n");
return -EINVAL;
}
mclk_freq = plat_data->sysclk;
if (plat_data->codec_name) {
for(i = 0;i < ARRAY_SIZE(imx_3stack_dai);i++){
imx_3stack_dai[i].codec_name = plat_data->codec_name;
}
}
esai_asrc = kzalloc(sizeof(struct asrc_p2p_params), GFP_KERNEL);
if (plat_data->priv)
memcpy(esai_asrc, plat_data->priv,
sizeof(struct asrc_p2p_params));
return 0;
exit:
return ret;
}
static int __devexit imx_3stack_tlv320adc310x_remove(struct platform_device *pdev)
{
if (esai_asrc)
kfree(esai_asrc);
return 0;
}
先看到这段代码——platform_device_alloc("soc-audio", 4),有耐心的朋友可以用grep命令搜索一下"soc-audio",就会在kernel/sound/soc/soc-core.c中看到一个平台驱动,这个平台驱动的名字就是它,如下图所示:
图 - soc-audio这里就涉及到ASOC架构相关了,这里我大概讲一下。
ASOC以平台驱动的方式实现一个声卡核心的驱动,所有的声卡设备自然也就要以平台设备的方式注册才行了,而一个项目有时不可能只有一块声卡,那该如何区分它们呢?这时,platform_device_alloc("soc-audio", 4)中的数字4就起到了很好的区分作用。
申请完声卡设备之后,又调用platform_set_drvdata(imx_3stack_snd_device, &snd_soc_card_imx_3stack)函数为其设置私有数据。
再下面一段代码platform_device_add(imx_3stack_snd_device)则是将平台设备加入到Linux内核平台设备链表上去。
probe剩下的代码就不分析了,我们看到结构体snd_soc_card_imx_3stack,这是重点。
static struct snd_soc_card snd_soc_card_imx_3stack = {
.name = "adc310x-audio",
.dai_link = imx_3stack_dai,
.num_links = ARRAY_SIZE(imx_3stack_dai),
};
在ASOC架构中,结构体snd_soc_card就代表一个声卡card。
声卡注册进入soc-core之后,开发板开机,我们在命令行敲命令——“ls /proc/asound”,就会看到adc310x-audio这个名字。
然后我们看到这个dai_link,它涉及到我们是否能link上我们的codec驱动,如果不能link上codec驱动,那我们的声卡设备就只是一个没有任何实际功能的空壳罢了。
static struct snd_soc_dai_link imx_3stack_dai[] = {
{
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai_name = "adc310x",
.codec_name = "adc310x-codec.1-0018",
.cpu_dai_name = "imx-esai.0",
.platform_name = "imx-pcm-audio.3",
.init = imx_3stack_tlv320adc310x_init,
.ops = &imx_3stack_surround_ops,
},
};
name和stream_name似乎并没有太大的作用,我在移植过程中并没有特意关注过他们。
codec_dai_name 则需要和codec驱动中的snd_soc_dai_driver的name相同,如果不同则会导致link失败,snd_soc_dai_driver这个结构体就代表codec的dai(数字音频接口)。
codec_name则需要和codec驱动中的i2c_driver的name相关联,如下:
static struct i2c_driver adc310x_i2c_driver = {
.driver = {
.name = "adc310x-codec",
.owner = THIS_MODULE,
},
.probe = adc310x_i2c_probe,
.remove = adc310x_i2c_remove,
.id_table = adc310x_i2c_id,
};
不难发现它们的关联,这里i2c_driver的name与codec_name相比,只是少了个.1-0018,而这表示这个i2c从设备是挂载在i2c1总线的0018地址上。
如果它们的名字不关联,那么就会出现无法匹配到adc310x_i2c_probe函数的情况。
cpu_dai_name则是代表你用的是cpu内部的什么接口,我所知的,imx6的cpu内部针对音频是有两种接口的,一种ssi,一种就是esai接口。
我们这里用的esai,所以是imx-esai.0,这个0代表使用的esai接口的第0个通道,看imx6的datasheet,esai好像支持5个通道。
platform_name我没有仔细研究过,不太清楚。
继续,看到init。
static int imx_3stack_tlv320adc310x_init(struct snd_soc_pcm_runtime *rtd)
{
return 0;
}
显然,这个函数什么都没有做。
最后一个参数,ops。
static struct snd_soc_ops imx_3stack_surround_ops = {
.startup = imx_3stack_startup,
.shutdown = imx_3stack_shutdown,
.hw_params = imx_3stack_surround_hw_params,
.hw_free = imx_3stack_surround_hw_free,
};
在介绍这些参数之前,先介绍一下这个结构体。
snd_soc_ops结构体,按照我的理解,他就是提供给上层录音使用的,我给大家举一个上层调用ASOC框架提供的接口录音后的调用流程:
startup -> hw_params -> shutdown -> hw_free。
其中hw_params可能会调用到codec中的接口设置codec端参数。
好的,先来看到startup。
struct imx_priv_state {
int hw;
};
static struct imx_priv_state hw_state;
static int imx_3stack_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
if (!cpu_dai->active) {
hw_state.hw = 0;
}
return 0;
}
显然,在这个函数里面几乎什么都没有做,就对hw_state.hw进行了赋值。
继续,shutdown。
static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
if (!cpu_dai->active)
hw_state.hw = 0;
}
这里还是什么都没有做,只是对hw_state.hw赋值。
继续,hw_free。
static int imx_3stack_surround_hw_free(struct snd_pcm_substream *substream)
{
struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
if (iprtd->asrc_enable) {
if (iprtd->asrc_index != -1) {
iprtd->asrc_pcm_p2p_ops_ko->
asrc_p2p_release_pair(
iprtd->asrc_index);
iprtd->asrc_pcm_p2p_ops_ko->
asrc_p2p_finish_conv(iprtd->asrc_index);
}
iprtd->asrc_index = -1;
}
return 0;
}
如我直言,这里我没有研究过。
我们看到最后一个,也是重中之重的hw_params。
static int imx_3stack_surround_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct imx_pcm_runtime_data *iprtd = substream->runtime->private_data;
unsigned int rate = params_rate(params);
unsigned int channels = params_channels(params);
u32 dai_format;
unsigned int lrclk_ratio = 0;
unsigned int lrclk_ratio_pm = 0;
int err = 0;
int ret;
if (iprtd->asrc_enable) {
err = config_asrc(substream, params);
if (err < 0)
return err;
rate = iprtd->p2p->p2p_rate;
}
if (hw_state.hw)
return 0;
hw_state.hw = 1;
switch (rate) {
case 16000:
lrclk_ratio_pm = 110;
lrclk_ratio = 0;
break;
case 44100:
lrclk_ratio_pm = 80;
lrclk_ratio = 0;
break;
default:
pr_info("Rate not support.\n");
return -EINVAL;;
}
//printk("rate==%d \n",rate);
//设置格式为i2s和cpu为主模式
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS;
/* set cpu DAI configuration */
snd_soc_dai_set_fmt(cpu_dai, dai_format);
/* set i.MX active slot mask */
snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
/* set the ESAI system clock as output */
snd_soc_dai_set_sysclk(cpu_dai, ESAI_CLK_EXTAL_DIV,0, SND_SOC_CLOCK_OUT);
/* set the ratio */
snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PSR, 1);
snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_PM, lrclk_ratio_pm);
snd_soc_dai_set_clkdiv(cpu_dai, ESAI_TX_DIV_FP, lrclk_ratio);
snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PSR, 1);
snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_PM, lrclk_ratio_pm);
snd_soc_dai_set_clkdiv(cpu_dai, ESAI_RX_DIV_FP, lrclk_ratio);
return 0;
}
我想,注释已经很清晰了,就不用我再多做解释了。
之前有说过这个函数可能会设置codec端参数,恩,因为我的行业是车机嘛,所以我们和手机这种移动设备对声卡的处理方式是不一样的。
对于codec端的参数配置,我们会在codec驱动probe的时候就会将它的参数一次性设置好,写死,后面就不会再更改它了。
提示:我们通常是cpu为主,codec为从。
声卡驱动移植到这儿,也就剩下最后一个部分codec了。
这一部分主要是设置codec,针对于硬件codec的配置(主要是看规格书,配置寄存器)。
我再啰嗦一句,一般声卡供应商都是会提供codec的参考代码,但是这个参考代码大多时候并不适用,原因有二:一是两者所在平台可能不一样;二是两者所要实现的功能有所差异。
所以,拿到codec代码,我们更多的是用它们的框架,寄存器这些值需要我们自己看规则书再重新配置。
老规矩,看驱动,从入口开始。
static int __init adc310x_drv_init(void)
{
int ret = 0;
ret = i2c_add_driver(&adc310x_i2c_driver);
if (ret != 0) {
printk(KERN_ERR "Failed to register adc310x I2C driver: %d\n",
ret);
}
return ret;
}
static void __exit adc310x_drv_exit(void)
{
i2c_del_driver(&adc310x_i2c_driver);
}
module_init(adc310x_drv_init);
module_exit(adc310x_drv_exit);
MODULE_DESCRIPTION("ASoC TLV320ADC310X codec driver");
MODULE_LICENSE("GPL");
可以看到,codec驱动是以i2c设备驱动的形式注册进入系统的。
i2c_add_driver就是将adc310x_i2c_driver这个i2c子设备的驱动添加进入i2c1总线的子设备驱动链表。
看到adc310x_i2c_driver。
static struct i2c_driver adc310x_i2c_driver = {
.driver = {
.name = "adc310x-codec",
.owner = THIS_MODULE,
},
.probe = adc310x_i2c_probe,
.remove = adc310x_i2c_remove,
.id_table = adc310x_i2c_id,
};
这里我们回顾上一小节,再说一遍,这个name一定要和codec_name相关联。
当name与codec_name关联之后,经过ASOC的core函数比较,他们就会match成功,然后调用probe函数。
static int adc310x_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct adc310x_pdata *pdata = i2c->dev.platform_data;
struct adc310x_priv *adc310x;
struct adc310x_setup_data *adc310x_setup;
int ret, i;
u32 value;
adc310x_dbg("-------->%s\n",__func__);
adc310x = devm_kzalloc(&i2c->dev, sizeof(struct adc310x_priv),
GFP_KERNEL);
if (!adc310x)
return -ENOMEM;
//设置i2c私有数据
i2c_set_clientdata(i2c, adc310x);
//设置控制类型为i2c,我知道的控制类型还有spi
adc310x->control_type = SND_SOC_I2C;
//adc310x初始化
if (pdata) {
adc310x->gpio_reset = pdata->gpio_reset;
adc310x->setup = pdata->setup;
adc310x->micbias1_vg = pdata->micbias1_vg;
adc310x->micbias2_vg = pdata->micbias2_vg;
} else {
adc310x->gpio_reset = -1;
adc310x->micbias1_vg = ADC310X_MICBIAS_2_0V;
adc310x->micbias2_vg = ADC310X_MICBIAS_2_0V;
}
adc310x->i2c = i2c;
adc310x->model = id->driver_data;
//向ASOC核心注册codec
ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_adc310x, &adc310x_dai, 1);
if (ret != 0){
pr_emerg("-------->%s snd_soc_register_codec fail ret=%d\n",__func__,ret);
kfree(adc310x);
}
return ret;
}
先看到这段代码,i2c_set_clientdata(i2c, adc310x)。
这个adc310x是一个struct adc310x_priv结构体的指针变量,他的结构体定义如下:
/* codec private data */
struct adc310x_priv {
struct snd_soc_codec *codec;
struct regulator_bulk_data supplies[ADC310X_NUM_SUPPLIES];
struct adc310x_disable_nb disable_nb[ADC310X_NUM_SUPPLIES];
struct adc310x_setup_data *setup;
struct i2c_client *i2c;
unsigned int sysclk;
unsigned int dai_fmt;
unsigned int tdm_delay;
unsigned int slot_width;
struct list_head list;
int master;
int gpio_reset;
int power;
u16 model;
/* Selects the micbias voltage */
enum adc310x_micbias_voltage micbias1_vg;
enum adc310x_micbias_voltage micbias2_vg;
enum snd_soc_control_type control_type;
};
其实,他就是codec的一些私有数据,定义这个结构体,方便在整个codec的驱动中数据共用。
然后我们看到这里——adc310x->control_type = SND_SOC_I2C。
在ASOC中,针对不同的控制类型,有不同的读写函数,所以,这里需要设置好控制类型。后面会用到它。
接下来,就看到这个probe最重要的部分——snd_soc_register_codec。
我们来看一下该函数几个参数分别代表什么。
/**
* snd_soc_register_codec - Register a codec with the ASoC core
*
* @dev: The parent device for this codec
* @codec_drv: Codec driver
* @dai_drv: The associated DAI driver
* @num_dai: Number of DAIs
*/
int snd_soc_register_codec(struct device *dev,
const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
我们先来看到soc_codec_dev_adc310x结构体。
static struct snd_soc_codec_driver soc_codec_dev_adc310x = {
.set_bias_level = adc310x_set_bias_level,
.probe = adc310x_probe,
.remove = adc310x_remove,
.reg_cache_size = ARRAY_SIZE(dac310X_default_reg),
.reg_word_size = sizeof(u8),
.reg_cache_default = dac310X_default_reg,
};
这里可以知道,soc_codec_dev_adc310x代表codec的driver驱动。
站在我的车机平台的项目需求上来说,这个结构体我需要关注的只有probe函数,为什么呢?
还记得我上面有说过的话:
“因为我的行业是车机嘛,所以我们和手机这种移动设备对声卡的处理方式是不一样的。
对于codec端的参数配置,我们会在codec驱动probe的时候就会将它的参数一次性设置好,写死,后面就不会再更改它了。”
所以,像这个结构体里的set_bias_level,reg_cache_default都可以不需要太过关注,set_bias_level可以不去赋值,但是reg_cache_default这里必须要赋值,至于为什么,不信邪的小伙伴可以去试一下,到时候应该会出现空指针导致内核堆栈崩溃的事情发生。
好的,说那么多,还是进入正题,看到probe。
static int adc310x_probe(struct snd_soc_codec *codec)
{
struct adc310x_priv *adc310x = snd_soc_codec_get_drvdata(codec);
int ret, i;
adc310x_dbg("-------->%s\n",__func__);
adc310x->codec = codec;
//根据控制类型来设置读写方式,这里是i2c
ret = snd_soc_codec_set_cache_io(codec, 8, 8, adc310x->control_type);
if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
}
//codec初始化,里面就是配置各种寄存器的
adc310x_init(codec);
return 0;
err_notif:
return ret;
}
probe函数里面,其实就两个点需要关注,一是snd_soc_codec_set_cache_io,二是adc310x_init。
先来看到snd_soc_codec_set_cache_io函数的定义:
int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
int addr_bits, int data_bits,
enum snd_soc_control_type control)
第一个参数codec就不用解释了;
第二个,是通讯的地址长度,什么意思呢?比如这个声卡是i2c控制的,那么这个地址长度就是寄存器的地址长度,这里设置的是8位;
第三个,是通讯的数据长度,这里设置的也是8位;
第四个是控制类型,这里是i2c控制。
使用了这个函数之后,当你调用snd_soc_read和snd_soc_write函数时,内部就会去根据你设置的参数去调用对应的i2c读写函数。
再看到adc310x_init函数,这是整个codec驱动的重中之重,我这次调声卡驱动看了好久的规格书,说实话,这个型号的声卡的寄存器配置是真的麻烦。
static int adc310x_init(struct snd_soc_codec *codec)
{
unsigned char reg = 0;
adc310x_dbg(" snd_soc_write start-------->%s\n",__func__);
//1
snd_soc_write(codec, ADC310X_PAGE_SELECT, PAGE0_SELECT);//切换寄存器页到页0
snd_soc_write(codec, ADC310X_RESET, SOFT_RESET);//软复位
//2
snd_soc_write(codec, ADC310X_CLK_GEN_MUX, 0x03);//set pll=mclk, codev_clk=pll
snd_soc_write(codec, ADC310X_PLL_PR_VAL, 0x11|0x80);//set p=r=1
snd_soc_write(codec, ADC310X_PLL_J_VAL, 8);/set j=8
//d=1920
snd_soc_write(codec, ADC310X_PLL_D_VAL_MSB, 0X80);
snd_soc_write(codec, ADC310X_PLL_D_VAL_LSB, 0X07);
snd_soc_write(codec, ADC310X_NADC_CLK, 0x80|0x04);//nadc=4
snd_soc_write(codec, ADC310X_MADC_CLK, 0x80|0x08);//madc=8
snd_soc_write(codec, ADC310X_AOSR, 192);//aosr=192
snd_soc_write(codec, ADC310X_ADC_IADC, 192);// IADC=N*AOSR
snd_soc_write(codec, ADC310X_DSP_DECI, 0X02);// CIC GAIN = 1
snd_soc_write(codec, ADC310X_ADC_INTF_CTRL_1, 0x00);//i2s mode 16bit
snd_soc_write(codec, ADC310X_ADC_PROC_BLK, 0x01);
// 3
snd_soc_write(codec, ADC310X_PAGE_SELECT, PAGE1_SELECT);
snd_soc_write(codec, 0x33, 0x00);
snd_soc_write(codec, 0x3B, 0x00);// LEFT PGA not mute, 0db
snd_soc_write(codec, 0x3C, 0x00);// RIGHT PGA not mute,0db
snd_soc_write(codec, 0x34, 0x7F);
snd_soc_write(codec, 0x36, 0x3F);
snd_soc_write(codec, 0x37, 0x7F);
snd_soc_write(codec, 0x39, 0x3F);
// 4
snd_soc_write(codec, ADC310X_PAGE_SELECT, PAGE0_SELECT);
snd_soc_write(codec, ADC310X_ADC_DIGITAL, 0xc2);
snd_soc_write(codec, ADC310X_ADC_VOL_CTRL, 0x00); // UNMUTE
// 5 SET VOL
snd_soc_write(codec, ADC310X_ADC_VOL_L, 9);//+4.5db
snd_soc_write(codec, ADC310X_ADC_VOL_R, 9);//+4.5db
printk("snd_soc_write end-------->%s\n",__func__);
return 0;
}
虽然寄存器配置是重中之重,但说实话,这玩意儿根本就不能照抄,不同的平台不同的需求不同型号的声卡都需要有不同的配置,这里,只能说大致介绍一下我是怎么去看规格书,怎么去配置这些寄存器的。
配置声卡,我个人觉得第一步需要先设置好时钟。
图 - 音频时钟生成处理上图是声卡的音频时钟生成处理框图。
光看这里可能还比较懵逼,我们继续看规格书。
根据这里的example,然后结合我们项目的需求,采样率16k,mclk=24mhz,于是选择的第二个示例,个别参数只需要微调就行。
这里需要注意一个公式和两个条件:
公式:fS = (PLLCLK_IN × K × R) / (NADC × MADC × AOSR × P);
条件1:当d=0000……
条件2:当d≠0000……
显然,我这里的选择是当d≠0000。
查找规格书,配置好这些分频参数。配置代码为init函数的第2部分。
init函数的第1部分是复位,第2部分是时钟,而第3部分则是mic麦的输入通道配置了。
先看规格书:
可以看到mic输入分别左右两个通道,每个通道又有正负之分,他们之间两两组合然后输入进codec芯片。
那么,我们该如何配置呢?总不能随便配吧。当然不是,规格书肯定会告诉你怎么做。
这张表就展示了可用的配置。
我的项目这里选择的是DIFFERENTIAL INPUTS差分输入,具体是选的哪种,这里就不过多介绍了。
第3部分完,还有4、5部分,4、5部分讲的什么呢?
从英文的字面意思,不难知道,这两部分配的是adc相关的寄存器。
什么是adc呢?adc即模拟数字转换器,Analog-to-digital converter。
既然这都是一块codec,那肯定是需要adc的啊!
配adc,主要是使能、取消mute静音、配置增益,大家在配置寄存器的时候需要注意,否则就会出现录不到数据、没有声音、音量很大或者很小。
配置寄存器就大致讲完了。
这里还是说一句,不同的声卡具体分析,要看规格书。
说实话,虽然不是英语学渣,但没过4级看这种纯英文规格书还是脑壳疼的。但没办法,你想吃这碗饭,有些事情还是得去做才行,比如硬着头皮用翻译软件,看完一两百页的英文规格书。
这里讲完了init,我们回到probe,结果发现也讲完了。那么我们回到i2c_probe的snd_soc_register_codec函数。
我们看到snd_soc_register_codec函数的第3个参数adc310x_dai。
static struct snd_soc_dai_driver adc310x_dai[] = {
{
.name = "adc310x",
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = ADC310X_RATES,
.formats = ADC310X_FORMATS,
},
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = ADC310X_RATES,
.formats = ADC310X_FORMATS,
},
.ops = &adc310x_dai_ops,
},
};
可以看到这是一个struct snd_soc_dai_driver类型的结构体,dai:数字音频接口。
因为项目对这块声卡的需求是录音,所以我们其实只需要实现capture部分就可以了。
这里,我们直接看到ops。
static const struct snd_soc_dai_ops adc310x_dai_ops = {
.hw_params = adc310x_hw_params,
.prepare = adc310x_prepare,
.digital_mute = adc310x_mute,
.set_sysclk = adc310x_set_dai_sysclk,
.set_fmt = adc310x_set_dai_fmt,
.set_tdm_slot = adc310x_set_dai_tdm_slot,
};
继续。
static int adc310x_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
return 0;
}
static int adc310x_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
return 0;
}
static int adc310x_mute(struct snd_soc_dai *dai, int mute, int stream)
{
return 0;
}
static int adc310x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
return 0;
}
static int adc310x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
return 0;
}
static int adc310x_set_dai_tdm_slot(struct snd_soc_dai *codec_dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width)
{
return 0;
}
可以看到,上述函数什么都没有做。
还记得之前我说的吗?因为我的项目是车机,所以所有的配置都在probe函数的init函数直接配置好了,后面都不需要改变。
因此,这些设置参数的各个接口其实是不需要去实现任何具体内容的。
声卡移植的过程大致也走完了,这里我总结一下。
首先,作为驱动工程师,移植这些外设驱动是很经常的事情,所以我们需要学会看原理图,了解这个外设需要我们控制哪些pin脚。
其次,我们需要了解我们的平台,因为我们要去配置这个外设所用的gpio引脚复用和一些特殊接口,比如imx的esai。
然后,我们也需要对Linux的各个框架有一定了解,比如这里所用到的音频框架。
最后,需要有看英文规格书的能力和耐心。