某项目需要通过IMX6Q外接音频解码芯片,但是IMX6Q仅通过I2S连接到CODEC,做业务输出,CODEC的控制由另外的芯片来做。大致架构如下图所示。
即IMX6Q仅仅输出I2S信号,完整的调试过程涉及到ALSA架构的修改,主要是下面几个方面。
1、 确认IMX6Q的IIS引脚
以我实际项目的代码为例
MX6Q_PAD_CSI0_DAT4__AUDMUX_AUD3_TXC, //I2S3_TXC
MX6Q_PAD_CSI0_DAT6__AUDMUX_AUD3_TXFS, //I2S3_TXFS
MX6Q_PAD_CSI0_DAT5__AUDMUX_AUD3_TXD, //I2S3_TXD
MX6Q_PAD_CSI0_DAT7__AUDMUX_AUD3_RXD, //I2S3_RXD
2、 audio初始化中添加下列结构体
好了,你们可以看到我使用的CODEC就是TEF6638,对行业熟悉的同志应该知道我做的是什么产品了。
mxc_register_device(&mx6_sabresd_audio_tef6638_device,
&tef6638_data);
imx6q_add_imx_ssi(1, &mx6_sabresd_ssi_pdata);
mx6_sabresd_tef6638_init();
mxc_register_device(&tef6638_codec,NULL);
这一步就是要做一个imx-tef6638的platform数据,表明使用的是SSI1以及对应的source和dest端口。同时还要做一个虚拟的tef6638_codec数据结构,该结构用来创建一个虚拟的codec,方便按照imx的asoc架构去完成注册。
static struct platform_device mx6_sabresd_audio_tef6638_device = {
.name = "imx-tef6638", //注册一个名为imx-tef6638的platfrom设备
};
static struct imx_ssi_platform_data mx6_sabresd_ssi_pdata = {
.flags = IMX_SSI_DMA | IMX_SSI_SYN, //表明该SSI使用DMA传输,同步
};
static struct mxc_audio_platform_data tef6638_data = {
.ssi_num = 1, //使用SSI 1
.src_port = 2, //内部端口是 2
.ext_port = 3, //外部端口是3
.hp_gpio = -1,
.init = mx6_sabresd_tef6638_init, //设置codec时钟
};
static struct platform_device tef6638_codec = {
.name = "tef6638_codec", //虚拟的codec设备
};
3、 添加imx-tef6638.c文件
仿照其他imx架构的芯片添加imx-tef6638.c文件,在该文件中注册imx的驱动。该驱动重点构造了一个imx_tef6638_dai结构体,在该结构体中描述了整个音频接口。整个注册流程遵照ASOC的标准来做。在这里需要注意两个地方。
1、时钟输出的方向
由于IMX6Q并不控制CODEC,无法保证输出I2S音频的时候CODEC也能确保时钟,所以必须由IMX6Q来输出时钟信号。所以对应的时钟方向设置必须修改。
static int imx_audmux_config(int slave, int master)
{
unsigned int ptcr, pdcr;
slave = slave - 1;
master = master - 1;
ptcr = MXC_AUDMUX_V2_PTCR_SYN;
pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);
mxc_audmux_v2_configure_port(slave, ptcr, pdcr);
ptcr =MXC_AUDMUX_V2_PTCR_SYN |
MXC_AUDMUX_V2_PTCR_TFSDIR |
MXC_AUDMUX_V2_PTCR_TFSEL(slave) |
MXC_AUDMUX_V2_PTCR_TCLKDIR |
MXC_AUDMUX_V2_PTCR_TCSEL(slave); //从slave口输出,也就是第一个参数口,默认为src_port
pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);
mxc_audmux_v2_configure_port(master, ptcr, pdcr);
return 0;
}
2、对应的soc侧的参数解析及配置
static int imx_tef6638_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;
u32 dai_format;
unsigned int sample_rate = 44100;
unsigned int clk=0;
int ret=0;
unsigned int channels = params_channels(params);
//解析左右声道,并设置TDM,如果不做设置将不会产生LRCK
switch (channels) {
case 2:
snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffc, 0xfffffffc, 2, 32);
break;
case 1:
snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffe, 0xfffffffe, 2, 32);
break;
default:
return -EINVAL;
}
//设置I2S格式
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
SND_SOC_DAIFMT_CBS_CFS;
ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
//设置时基及分频系数
ret = snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_PM, 4);
if (ret < 0)
return ret;
//解析采样率
sample_rate=params_rate(params);
clk = sample_rate*64*4;
//设置MCLK时钟
ret=snd_soc_dai_set_sysclk(cpu_dai,IMX_SSP_SYS_CLK,clk,SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops imx_tef6638_hifi_ops = {
.hw_params = imx_tef6638_hw_params,
};
完成上述配置后就可以从I2S引脚输出符合要求的音频信号了。