飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植

一、前言

在介绍音频总线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系统。

三、重要文件

1、板级配置文件

文件路径:kernel/arch/arm/mach-mx6/board-xxx-xxx.c,kernel/arch/arm/mach-mx6/board-xxx-xxx.h。

在这个文件里面将会配置平台设备信息、i2c设备信息、gpio复用、声卡初始化、esai接口注册等。

2、声卡驱动之codec

文件路径: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控制等等,

我这个项目对这个声卡的需求就只有录音。

3、声卡驱动之imx

文件路径:kernel/sound/soc/imx/imx-tlv320adc310x.c。

我对这个文件的理解,相对于codec驱动是声卡端,imx-tlv320adc310x.c这个文件就是cpu端,在这个驱动会做如下事情:

①、以平台设备的方式注册声卡设备;

②、设置cpu端对于声卡录音的一些硬件配置(如时钟、I2S配置、主从)。

四、tlv320adc310x声卡

1、电路图

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第1张图片 图 - 声卡电路

 

2、tlv320adc310x声卡介绍

TLV320ADC3100是一款低功耗立体声音频模数转换器(ADC),支持8 kHz到96 kHz的采样率,集成可编程增益放大器(PGA)提供高达40 dB的模拟增益或自动增益控制(AGC)。

3、I2C控制脚

可以看到电路图中有四个pin脚与i2c相关,它们分别是sda、scl、i2c_adr1、i2c_adr0,显然这个声卡是通过i2c进行控制的,也就是说,寄存器的写入和读取都是通过i2c来控制的。

对i2c总线熟悉的,就知道这个四个脚分别是什么作用。

这里简单介绍一下i2c_adr1、i2c_adr0两个pin脚。这两个脚用来决定i2c从设备的地址。

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第2张图片 图 - I2C Slave Addr table

上述电路中将两个脚都接地拉低了,查声卡规格书可知,该i2c从设备地址为0x18。

sda、scl两个脚则分别是数据线和时钟线。

4、I2S音频总线脚

对于I2S一点都不了解的,可以看我上一篇文章——音频总线之I2S总线介绍及相关协议。

I2S这里总共有4个脚,他们分别是bclk、mclk、wclk、dout,也就对应三个时钟线和一个数据线。

I2S还有其他几个脚,他们最终接到了imx的cpu端的esai接口脚,这里我就不贴图出来了,大家根据实际项目实际分析。

总之,我们做驱动移植,首先就需要看原理图,看哪些脚接到了cpu端,这些脚是需要我们控制的。

5、复位脚

硬件复位脚有一个,为reset,根据规格书所述:TLV320ADC3100需要在通电后进行硬件复位才能正常工作。在所有电源都达到其指定值后,复位引脚必须低电平驱动至少10 ns。如果不执行此复位序列,TLV320ADC3100可能无法正确响应寄存器读取或写入。

图 - hardware reset

所以在给声卡上电时,我会拉低复位脚至少10ns,实际上,在代码中一般都是拉低10ms。

6、MIC声源输入引脚

ARM_MIC_IN,这个引脚就不多说了,就是麦或者其他声音输入的引脚。

五、源码分析

1、板级配置文件

因为这个项目没有设备树,所以所有的设备信息都需要在板级配置文件里面配置好。

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
    ……
}

2、声卡驱动之imx

这里先讲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中看到一个平台驱动,这个平台驱动的名字就是它,如下图所示:

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第3张图片 图 - 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为从。

3、声卡驱动之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;
}

虽然寄存器配置是重中之重,但说实话,这玩意儿根本就不能照抄,不同的平台不同的需求不同型号的声卡都需要有不同的配置,这里,只能说大致介绍一下我是怎么去看规格书,怎么去配置这些寄存器的。

配置声卡,我个人觉得第一步需要先设置好时钟。

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第4张图片 图 - 音频时钟生成处理

上图是声卡的音频时钟生成处理框图。

光看这里可能还比较懵逼,我们继续看规格书。

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第5张图片

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第6张图片

根据这里的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麦的输入通道配置了。

先看规格书:

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第7张图片

可以看到mic输入分别左右两个通道,每个通道又有正负之分,他们之间两两组合然后输入进codec芯片。

那么,我们该如何配置呢?总不能随便配吧。当然不是,规格书肯定会告诉你怎么做。

飞思卡尔IMX6之TLV320ADC310X系列声卡驱动移植_第8张图片

这张表就展示了可用的配置。

我的项目这里选择的是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的各个框架有一定了解,比如这里所用到的音频框架。

最后,需要有看英文规格书的能力和耐心。

你可能感兴趣的:(Linux音频驱动)