WM8960 NXP 官方移植手记的理解

/ 1评 / 2

本文未验证,我也没完全理解,而且我对ALSA驱动,基本没有了解,所以,将就看看就好.另外,里面加上了我一些理解,需要还是看原文吧.
原文地址:https://freescale.jiveon.com/docs/DOC-106295
Introduction
这是一篇移植手记,基于 L3.0.35 Linux BSP(但是我用的是L4.9.11),讨论的是如何移植WM8960这个驱动.
ALSA
更多需要了解的,参考维基百科.
AlsaProject
Advanced Linux Sound Architecture (中文版)
kcontrols 在 ./include/sound/soc.h 和 soc-dapm.h 中定义.
控制通道/路径:(大概可以理解为如何走吧.)
手册19页,输入通道.

输出通道,35页.

根据输入输出通道,和电路图设计,可以修改源码,这些可以被alsamixer使用.
原始是这样的.

static const struct snd_kcontrol_new wm8960_snd_controls[] = {
SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
                 0, 63, 0, inpga_tlv),
SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
        6, 1, 0),
SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
        7, 1, 1),
SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume",
               WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv),
SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume",
               WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv),
SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume",
               WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv),
SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume",
               WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv),
SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume",
                WM8960_RINPATH, 4, 3, 0, micboost_tlv),
SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume",
                WM8960_LINPATH, 4, 3, 0, micboost_tlv),
SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
                 0, 255, 0, dac_tlv),
SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
                 0, 127, 0, out_tlv),
SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
        7, 1, 0),
SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
                 0, 127, 0, out_tlv),
SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
        7, 1, 0),
SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
SOC_ENUM("ADC Polarity", wm8960_enum[0]),
SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
SOC_ENUM("DAC Polarity", wm8960_enum[1]),
SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
                    wm8960_get_deemph, wm8960_put_deemph),
SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
SOC_ENUM("ALC Function", wm8960_enum[4]),
SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
SOC_ENUM("ALC Mode", wm8960_enum[5]),
SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),
SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC,
        0, 255, 0, adc_tlv),
SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
               WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
               WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
               WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
               WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
SOC_ENUM("ADC Data Output Select", wm8960_enum[6]),
SOC_ENUM("DAC Mono Mix", wm8960_enum[7]),
};

其中,字段含义:
SOC_SINGLE,设置一个简单开关

SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),

这个字段名为PCM Playback -6dB Switch
然后WM8960_DACCTL1为0x05,这个在头文件定义的.
其中7指明是第7位.(BIT7)
1表示他只有开关两种状态.
0这个数值1就是开,0就是关,不用翻转.

SOC_SINGLE_TLV,他是有等级的一个开关,意思是一个范围,比如音量.

SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume", WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv),

4代表最低位是第4位(BIT4).
7代表他有7档.
0,是否翻转.这里不翻转.
lineinboost_tlv是一个DECLARE_TLV_DB_SCALE.

static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1);

这个数值为了简单阅读一些准备的(我个人认为).他指示从-1500开始,每一步步进是300,他从1开始计算.(有些DAC的0相当于禁用,静音.).
如下图,每一步3db,-15db开始(-12db - 3db = -15db).

SOC_DOUBLE_R_TLV是立体声版本的SOC_SINGLE_TLV.

SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, 0, 63, 0, inpga_tlv),

依次是:左右音量寄存器,从BIT0开始,分63档,不翻转,由inpga_tlv来描述.
SOC_DOUBLE_R是立体声版本的SOC_SINGLE.

SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, 7, 1, 1),

左右声道静音,这个一次可以描述两个通道.BIT7,只有开关功能,翻转.就是0时候静音,1时候不静音.(貌似跟手册有出入啊.)
SOC_ENUM,枚举型,只有几个档位,没有规律的.

SOC_ENUM("DAC Mono Mix", wm8960_enum[7]),

很明显,枚举数值和配置在wm8960_enum写着.

static const struct soc_enum wm8960_enum[] = {
        SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
        SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
        SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
        SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff),
        SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc),
        SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
        SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel),
        SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix),
};

而上面对应是7,则是.

SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix),

其中的变量又为.

static const char *wm8960_dmonomix[] = {"Stereo", "Mono"};

很容易理解,BIT4是控制符,有2个枚举方案,第一个(也就是0),是立体声,第二个(也就是1),就是单声道.

嗯,当你上面这些都核对,移植好了(什么?我还没管.先继续吧.)
然后可以开始连线?(对,里面这么多通道.)
这玩意叫SOC_DAPM_SINGLE等等... 反正,好乱.但是如果设置不好,就没声音了.(原文作者,把他改到跟寄存器名一模一样,这样在alsamixer上很容易找得到对应的.)
SOC_DAPM_SINGLE和SOC_SINGLE非常类似啊,也是指明BIT,然后开关,是否翻转.

static const struct snd_kcontrol_new wm8960_lin_boost[] = {
SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
};
static const struct snd_kcontrol_new wm8960_lin[] = {
SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
};
static const struct snd_kcontrol_new wm8960_rin_boost[] = {
SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
};
static const struct snd_kcontrol_new wm8960_rin[] = {
SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
};
static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
};
static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
};
static const struct snd_kcontrol_new wm8960_mono_out[] = {
SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
};

创建输入输出连线.

static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("LINPUT1"),
SND_SOC_DAPM_INPUT("RINPUT1"),
SND_SOC_DAPM_INPUT("LINPUT2"),
SND_SOC_DAPM_INPUT("RINPUT2"),
SND_SOC_DAPM_INPUT("LINPUT3"),
SND_SOC_DAPM_INPUT("RINPUT3"),
SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0),
SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
		   wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
		   wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),
SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
		   wm8960_lin, ARRAY_SIZE(wm8960_lin)),
SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
		   wm8960_rin, ARRAY_SIZE(wm8960_rin)),
SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),
SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0),
SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),
SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
	&wm8960_loutput_mixer[0],
	ARRAY_SIZE(wm8960_loutput_mixer)),
SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
	&wm8960_routput_mixer[0],
	ARRAY_SIZE(wm8960_routput_mixer)),
SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("SPK_LP"),
SND_SOC_DAPM_OUTPUT("SPK_LN"),
SND_SOC_DAPM_OUTPUT("HP_L"),
SND_SOC_DAPM_OUTPUT("HP_R"),
SND_SOC_DAPM_OUTPUT("SPK_RP"),
SND_SOC_DAPM_OUTPUT("SPK_RN"),
SND_SOC_DAPM_OUTPUT("OUT3"),
};
static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
	&wm8960_mono_out[0],
	ARRAY_SIZE(wm8960_mono_out)),
};
/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
};

开始布线(这样描述是不是有点奇怪.)
比如原始代码(部分):

static const struct snd_soc_dapm_route audio_paths[] = {
	{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
	{ "Left Input Mixer", "Boost Switch", "Left Boost Mixer" },
	{ "Left ADC", NULL, "Left Input Mixer" },

第一句是什么意思,先分析一下,LINPUT1 Switch描述了LINPUT1寄存器的BIT8位,就是LMN1.

然后打通上路.

第二个用的是Boost Switch.就是20h的BIT3.

这就可以把后面也打通了.

最后一个是WM8960_POWER1的BIT3.正是ADCL的上电.
再举个DAC通路的例子.

static const struct snd_soc_dapm_route audio_paths[] = {
	{ "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
	{ "LOUT1 PGA", NULL, "Left Output Mixer" },
	{ "HP_L", NULL, "LOUT1 PGA" },

先是PCM Playback Switch指向了如下代码.

SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),

就是这个开关.

打通了LD2LO.
接着LOUT1 PGA打通了LOUT1.

最后一个指定了他是HP_L输出源的名字(这条通路就唯一他了.)

把这些每一路配置好,alsamixer就可以用M按键配置开关,用上下箭头配置音量等.
另一个重要部分是这个函数.

static const struct snd_soc_dai_ops wm8960_dai_ops = {
	.hw_params = wm8960_hw_params,
	.hw_free = wm8960_hw_free,
	.digital_mute = wm8960_mute,
	.set_fmt = wm8960_set_dai_fmt,
	.set_clkdiv = wm8960_set_dai_clkdiv,
	.set_pll = wm8960_set_dai_pll,
	.set_sysclk = wm8960_set_dai_sysclk,
};

先确定我们的MCLK.原作者直接设置了分频.但是我们代码是传入频率,然后设置,再怎么样.
具体要确定是否正确,看Table 45. (最方便就是打印寄存器.)

在驱动中,有两个名字很重要, 一个是编解码器的名称,就是wm8960,要与imx-wm8960.c中使用的名称相同.

static struct snd_soc_dai_driver wm8960_dai = {
	.name = "wm8960-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = WM8960_RATES,
		.formats = WM8960_FORMATS,},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = WM8960_RATES,
		.formats = WM8960_FORMATS,},
	.ops = &wm8960_dai_ops,
	.symmetric_rates = 1,
};

另一个是I2C名,几乎所有CODEC都是I2C + I2S通信的.也得与dts的同名.
另外,imx-wm8960.c我从SAI修改成SSI,还没验证,日后分析.

  1. 克林说道:

    高手啊,膜拜,解决了我的一些困惑,厉害厉害

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注