这两个礼拜调试WM8994,还算顺利,除了开始LDO不大稳定造成CODEC经常性异常外。不得不感叹,有时候一个电阻阻值的大小都影响了整个系统,做硬件的同学要注意哇。
Wolfson的工程师也很nice,对于我反馈的问题,都能及时得以解答,感谢他们。
下面记录几个大的问题点,至于一些琐碎的,如缺少一些mixer controls (MIC Bias)、回放录音通路设置等就略过了。
1、在线查看/更改CODEC寄存器
mount -t debugfs none /d //挂载debugfs文件系统 cd /d/asoc/WMT_WM8994/wm8994-codec //进入wm8994调试目录 cat codec_reg //查看wm8994寄存器 echo 6 0010 > codec_reg //写0x0010到reg[0006h]
这样调试非常方便,有时候mixer controls还没写好,直接写寄存器也能达到目的。
2、AIF模式(Master/Slave)选择
WM8994支持Master/Slave模式,而每种模式都可使用MCLK或FLL作为AIFnCLK,所以这一块根据自己实际情况配置好。比如我们使用如下模式:
则AIF1的hw_params这样写:
/* Set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S); if (ret< 0) return ret; /* Set cpu DAI configuration for I2S */ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S); if (ret < 0) return ret; /* Set the codec system clock for DAC and ADC */ if (!(params_rate(params) % 11025)) ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, 11289600, SND_SOC_CLOCK_IN); else ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, 12288000, SND_SOC_CLOCK_IN);
3、Voice DAI
在调试Voice DAI中遇到一些问题,颇为耗时的问题。
我们通话模块的设计基本就是WM8994的典型设计:
使用AIF2与MODEM的PCM接口相连。这个在Linux-3.0.8里有个不错的例子可以参考,见sound/soc/samsung/goni_wm8994.c。不过我们配置modem为Master,CODEC为Slave,数据格式是PCM,所以AIF2的hw_params有点变动:
static int wmt_voice_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 *codec_dai = rtd->codec_dai; int ret = 0; if (params_rate(params) != 8000) return -EINVAL; /* Set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* Set the codec system clock for DAC and ADC */ if (!(params_rate(params) % 11025)) ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, 11289600, SND_SOC_CLOCK_IN); else ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, 12288000, SND_SOC_CLOCK_IN); return ret; }
改好了这个后,接下去马上发现一个很大的问题,Android启动后打电话,无法打开AIF2,表现在open /dev/snd/pcmC0D1(or pcmC0D1p) failed (Invalid argument)。这个问题跟了一天,通过audio_hw->tinyalsa->alsa-core一步步加打印最终定位问题所在,出错的地方:
snd_pcm_open_substream --> snd_pcm_attach_substream --> substream->ops->open(substream) --> err = snd_pcm_hw_constraints_complete(substream); --> err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
我们看看snd_pcm_hw_constraints_complete函数:
int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_hardware *hw = &runtime->hw; int err; unsigned int mask = 0; if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) mask |= 1 << SNDRV_PCM_ACCESS_RW_INTERLEAVED; if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) mask |= 1 << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; if (hw->info & SNDRV_PCM_INFO_MMAP) { if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) mask |= 1 << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) mask |= 1 << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED; if (hw->info & SNDRV_PCM_INFO_COMPLEX) mask |= 1 << SNDRV_PCM_ACCESS_MMAP_COMPLEX; } err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask); if (err < 0) return err;
这样基本可以确定hw->info是空的,顺藤摸瓜我们会留意struct snd_pcm_hardware这个结构体,这个东东一般是在DMA(一般是sound/soc/XXX/XXX_pcm.c)里面设置的,如:
static const struct snd_pcm_hardware wmt_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rate_min = 8000, .rate_max = 96000, .period_bytes_min = 32, .period_bytes_max = 4 * 1024, .periods_min = 2, .periods_max = 128, .buffer_bytes_max = 64 * 1024, //.fifo_size = 32, }; static int wmt_pcm_open(struct snd_pcm_substream *substream) { // ... snd_soc_set_runtime_hwparams(substream, &wmt_pcm_hardware); // ... }
这样就很容易理解了,因为在goni_wm8994.c中,它根本没有设置runtime hwparams,而我们照瓜画瓢,自然就出错了。因为AIF2是与MODEM PCM直连的,并不需要为它写一个DMA驱动,因此我们在startup时设置runtime hwparams:
static const struct snd_pcm_hardware wmt_voice_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rate_min = 8000, .rate_max = 16000, .period_bytes_min = 16, .period_bytes_max = 1024, .periods_min = 2, .periods_max = 16, .buffer_bytes_max = 4*1024, //.fifo_size = 32, }; static int wmt_voice_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; int ret; snd_soc_set_runtime_hwparams(substream, &wmt_voice_hardware); /* Ensure that buffer size is a multiple of period size */ ret = snd_pcm_hw_constraint_integer(runtime,SNDRV_PCM_HW_PARAM_PERIODS); return ret; } static struct snd_soc_ops wmt_voice_ops = { .startup = wmt_voice_startup, .hw_params = wmt_voice_hw_params, };
这样上层就能成功打开AIF2,通话通路也正常了。但我还是有点困惑:三星这个方案它如何run起来的呢?我本来很信任它的,经历了这一遭,还是要对任何权威抱有怀疑态度才行。
4、BT DAI
-------------------------------------------------------------------------------------------
2013/3/26
不想多写了,我只想说一句:如果你以后不想被这个项目搞得心力憔悴,各种奇怪问题层出不穷,骚扰得你上天无路入地无门求生不得求死不能的话,那么最好让codec作主,modem作从。
在做硬件设计前,请认真阅读WM8994规格书DIGITAL AUDIO INTERFACE CLOCKING CONFIGURATIONS: