本文未验证,我也没完全理解,而且我对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, };
- wm8960_hw_params 设置PCM格式,比如16位,24位.
- wm8960_mute 输出静音
-
wm8960_set_dai_fmt 设置输出格式 -
wm8960_set_dai_clkdiv 各种分频器 -
wm8960_set_dai_pll 各种倍频器
先确定我们的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,还没验证,日后分析.
高手啊,膜拜,解决了我的一些困惑,厉害厉害