SSI音频驱动
音频驱动的文章网上有很多,分析的也很具体,这里只记录本人在调试音频驱动的过程和理解。调试一个驱动,最主要的还是从本质上去理解它的工作原理,包括时钟,数据,中断,寄存器配置等。或许并不需要全部知道,但是追根到底会对以后的驱动有很大帮助。
音频驱动
linux音频驱动的结构我个人感觉做得很漂亮,有面向对象编程的人不难看出这个结构与类图很想,将变化的东西封装了起来,层次清晰,方便理解和调试:
cpu 驱动:即总线驱动,如I2S,SSI,AC97,ESAI,SPDIF等。封装了对总线的配置,如主从模式的设置。
codec驱动:编码器驱动,对应的是解码芯片,封装了对解码芯片的操作,包括设置寄存器等。
machine驱动:将cpu驱动和codec驱动糅合起来,有人把machine驱动比作插座,我觉得很形象,machine驱动就是将对应的cpu和codec插到板子上。
1、board文件
在板级文件中添加音频支持。
static struct platform_device mxc_uda134x_device = {
.name = "imx-3stack-uda134x",
};
static struct mxc_audio_platform_data uda134x_data = {
.ssi_num = 1,
.src_port = 2,
.ext_port = 5,
.init = mxc_uda134x_init,
};
static int mxc_uda134x_init(void)
{
struct clk *ssi_ext1;
struct clk *ssi_sys_clk;
u32 rate;
ssi_ext1 = clk_get(NULL, "ssi_ext1_clk");
if (IS_ERR(ssi_ext1))
return -1;
rate = clk_round_rate(ssi_ext1, 3000000);
if (rate < 2000000 || rate > 24000000) {
printk(KERN_ERR "Error: UDA134x mclk freq %d out of range!\n",
rate);
clk_put(ssi_ext1);
return -1;
}
clk_set_rate(ssi_ext1, rate);
clk_enable(ssi_ext1);
uda134x_data.sysclk = rate;
return 0;
}
这里的ssi_num对应的是SSI的通道号,SSI总线功能功能配置
MX53_PAD_KEY_COL0__AUDMUX_AUD5_TXC,
MX53_PAD_KEY_ROW0__AUDMUX_AUD5_TXD,
MX53_PAD_KEY_COL1__AUDMUX_AUD5_TXFS,
MX53_PAD_KEY_ROW1__AUDMUX_AUD5_RXD,
AUDMUX5是指AUDMUX的外部端口(即与codec相连的端口)号,也就是ext_num,而src_num是指内部端口(即与cpu相连的端口)号。端口的连接规则可以从芯片手册上查到。很奇怪,系统时钟设置为3M就可以正常播放,4M就没有声音了,然后20M又有声音,但是噪音很大,有知道原因的还请告知。
连接示意图如下:
2、时钟:
SSI的时钟示意图:
可以看到SSI_EXT1_CLK可以由pll3,pll2,ssi_lp_apm_clk产生,而与它相关的寄存器是CCM_CCGR3。时钟的配置并不需要我们处理,我们只需要了解就好。SSI的配置如下
_REGISTER_CLOCK("mxc_ssi.0", NULL, ssi1_clk[0]),
_REGISTER_CLOCK("mxc_ssi.1", NULL, ssi2_clk[0]),
_REGISTER_CLOCK("mxc_ssi.2", NULL, ssi3_clk[0]),
_REGISTER_CLOCK(NULL, "ssi_ext1_clk", ssi_ext1_clk),
_REGISTER_CLOCK(NULL, "ssi_ext2_clk", ssi_ext2_clk),
这里的_REGISTER_CLOCK宏我没太看懂它前两个参数的意思。如果有知道的麻烦告诉我。
3、machine驱动
machine驱动大部分都可以参考其他的音频驱动,因为主体基本都一样,而uda1341有一点点的区别,它有一个L3总线,L3总线驱动linux已经有了,我们需要做的只是指定对应的引脚(可以参考s3c24xx_uda1341.c)。
需要特别指出的是2个函数:
1、imx_3stack_init_dam
static void imx_3stack_init_dam(int ssi_port, int dai_port)
{
unsigned int ssi_ptcr = 0;
unsigned int dai_ptcr = 0;
unsigned int ssi_pdcr = 0;
unsigned int dai_pdcr = 0;
/* UDA134X uses SSI1 via AUDMUX port dai_port for audio */
/* reset port ssi_port & dai_port */
__raw_writel(0, DAM_PTCR(ssi_port));
__raw_writel(0, DAM_PTCR(dai_port));
__raw_writel(0, DAM_PDCR(ssi_port));
__raw_writel(0, DAM_PDCR(dai_port));
/* set to synchronous */
ssi_ptcr |= AUDMUX_PTCR_SYN;
dai_ptcr |= AUDMUX_PTCR_SYN;
/* set Rx sources ssi_port <--> dai_port */
ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
/* set Tx frame direction and source dai_port --> ssi_port output */
dai_ptcr |= AUDMUX_PTCR_TFSDIR;
dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port);
/* set Tx Clock direction and source dai_port--> ssi_port output */
dai_ptcr |= AUDMUX_PTCR_TCLKDIR;
dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port);
__raw_writel(ssi_ptcr, DAM_PTCR(ssi_port));
__raw_writel(dai_ptcr, DAM_PTCR(dai_port));
__raw_writel(ssi_pdcr, DAM_PDCR(ssi_port));
__raw_writel(dai_pdcr, DAM_PDCR(dai_port));
}
SSI总线是兼容I2S的,所以也需要设定主从模式。而uda1341只能工作在从模式,所以我们需要将SSI设置成主模式。上面的函数其实是在配置AUDMUX的数据流方向。
The default port-to-port connections are as follows:
• Port 1 to Port 6
• Port 6 provides the clock and frame sync.
• Port 2 to Port 5
• Port 5 provides the clock and frame sync.
• Port 3 to Port 4
• Port 4 provides the clock and frame sync.
• Port 7 to Port 7 (in data loopback mode)
• Clock and frame syncs are inputs.
从文档中我们看到BCLK和frame sync等信号都是由port5提供,也就是说我们需要将port5配置成输出模式,而port2配置成输入模式。就是配置PTCR寄存器。
2、imx_3stack_audio_hw_params
static int imx_3stack_audio_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_link *machine = rtd->dai;
struct snd_soc_dai *cpu_dai = machine->cpu_dai;
struct snd_soc_dai *codec_dai = machine->codec_dai;
unsigned int rate = params_rate(params);
struct imx_ssi *ssi_mode = (struct imx_ssi *)cpu_dai->private_data;
int ret = 0;
u32 dai_format;
int bits;
int channels = params_channels(params);
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS;
ssi_mode->sync_mode = 1;
ssi_mode->network_mode = 1;
/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
if (ret < 0)
return ret;
/* set i.MX active slot mask */
snd_soc_dai_set_tdm_slot(cpu_dai,
channels == 1 ? 0xfffffffe : 0xfffffffc,
channels == 1 ? 0xfffffffe : 0xfffffffc,
2, 32);
/* set the SSI clock div */
// snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_2, 0);
// snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_PSR, 0);
// snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_TX_DIV_PM, 23);
// snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_RX_DIV_2, 0);
// snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_RX_DIV_PSR, 0);
// snd_soc_dai_set_clkdiv(cpu_dai, IMX_SSI_RX_DIV_PM, 23);
/* set the SSI system clock as output */
snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_OUT);
/* set codec DAI configuration */
snd_soc_dai_set_fmt(codec_dai, dai_format);
/* Set codec clock*/
snd_soc_dai_set_sysclk(codec_dai, UDA134X_SYSCLK,rate*384, SND_SOC_CLOCK_OUT);
return 0;
}
这个函数的功能主要是在播放之前配置SSI和CODEC,要理解它们的含义需要结合imx_ssi.c和uda1341.c。首先SSI部分配置模式,SND_SOC_DAIFMT_CBS_CFS在imx_ssi.c中其实是配置成主模式。
然后是codec的系统时钟,需要查看手册,uda1341的系统时钟是256fs、384fs、512fs。fs表示采样率。
这个音频驱动断断续续搞了许久,要完全弄懂需要结合手册一点点的看。不过最后发现其实只是移植的话还是很简单的,只要注意几个地方就够了。
1、板级文件中音频结构体,包括端口的设置,ssi的编号。codec系统时钟的设置。
2、AUDMUX端口功能的配置。注意数据流的方向,以及主从模式的区别。
3、hw_param函数中dai的配置。需要结合cpu_dai和codec_dai源码来对照。
补充:对于音频驱动几个时钟的理解
1、imx_ssi.c:
在probe函数中 priv->ssi_clk = clk_get(&pdev->dev, “ssi_clk”);
这个时钟我的理解是AUDMUX的时钟,用来分配给port5,然后通过分频参数产生BCLK和frame sync这两个时钟信号。它的值读出来固定是12M。
2、uda1341.c:
在machine驱动中会设置codec的sysclk
snd_soc_dai_set_sysclk(codec_dai, 0,rate*384, 0);这个时钟我开始有点疑问。这里设置了codec的sysclk,那与cpu相连的sysclk线是干嘛的?后来找uda1341的芯片手册上发现,它的系统时钟就是256fs、384fs、512fs。也就是说这里确实是在设置它的系统时钟,而这个系统时钟可以理解为内部的解码时钟。
3、board.c中设置的sysclk:
在board.c中初始化了一个时钟ssi_ext1_clk,这个时钟才是I2S的时钟,这个时钟的作用应该理解成给DATAI/DATAO提供的,用来控制数据的输入输出速度。SSI兼容I2S,但是AUDMUX只是给codec提供了3个接口BCLK、Frame sync、DATAO/DATAI。至于I2S的sysclk直接由CPU提供,就是SSI_ext1_clk。所以为什么当我设置20M的时候,声音出现了噪音,应该是DATAI上数据输入太快,导致codec解码速度跟不上数据的速度,然后就出现了音频输出数据不对的问题。