一、应用测试工具的使用
1.在external/tinyalsa下有以C语言实现的alsa的测试程序,编译后生成tinypcminfo tinyplay tinycap tinymix 四个elf格式的测试工具
(1) tinypcminfo :获取PCM In和PCM
# tinypcminfo -D /dev/snd/controlC0
# tinypcminfo -D /dev/snd/pcmC0D0p
Info for card 0, device 0:
PCM out:
Access: 0x000009
Format[0]: 0x000044
Format[1]: 00000000
Format Name: S16_LE, S24_LE
Subformat: 0x000001
Rate: min=8000Hz max=48000Hz
Channels: min=2 max=2
Sample bits: min=16 max=32
Period size: min=512 max=8192
Period count: min=2 max=64
PCM in:
Access: 0x000009
Format[0]: 0x000044
Format[1]: 00000000
Format Name: S16_LE, S24_LE
Subformat: 0x000001
Rate: min=8000Hz max=48000Hz
Channels: min=1 max=2
Sample bits: min=16 max=32
Period size: min=512 max=16384
Period count: min=2 max=128
View Code
(2) tinymix :通过/dev/snd/controlC0节点设置获取控制信息,进行控件的设置。比如设置链路,音量调节等。
# tinymix
Mixer name: ‘S3C2440_UDA1341‘
Number of controls: 50
ctl type num name value
0 INT 2 Capture Volume 51 51
1 INT 2 Capture Volume ZC Switch 0 0
2 BOOL 2 Capture Switch Off On
3 INT 2 Playback Volume 255 255
4 INT 2 Headphone Playback Volume 121 121
5 BOOL 2 Headphone Playback ZC Switch Off Off
6 INT 2 Speaker Playback Volume 121 121
7 BOOL 2 Speaker Playback ZC Switch Off Off
....
View Code
# tinymix 打印出所有的snd_kcontrol项
可以通过名字操作:
# ./tinymix "Capture Volume" //单获取这项的值
Capture Volume: 51 51 (range 0->63)
# ./tinymix "Capture Volume" 10 //单设置这项的值为10
# ./tinymix "Capture Volume"
Capture Volume: 10 10 (range 0->63)
也可以通过序号操作:
# ./tinymix 0
Capture Volume: 10 10 (range 0->63)
# ./tinymix 0 20
# ./tinymix 0
Capture Volume: 20 20 (range 0->63)
驱动中对应的file_operations是:struct file_operations snd_ctl_f_ops
(3) tinycap : 使用/dev/snd/pcmC0D0c录音
# tinycap a.wav
const struct file_operations snd_pcm_f_ops[1]
(4) tinyplay : 使用/dev/snd/pcmC0D0p播放声音
# tinyplay a.wav
const struct file_operations snd_pcm_f_ops[0]
二、内核导出信息
1.devtmpfs信息(设备节点)
[email protected]:/system # ls /dev/snd/ -l
total 0
crw-rw---- 1 1000 1005 116, 0 Jan 1 12:00 controlC0
crw-rw---- 1 1000 1005 116, 24 Jan 1 12:00 pcmC0D0c
crw-rw---- 1 1000 1005 116, 16 Jan 1 12:00 pcmC0D0p
crw-rw---- 1 1000 1005 116, 25 Jan 1 12:00 pcmC0D1c
crw-rw---- 1 1000 1005 116, 17 Jan 1 12:00 pcmC0D1p
crw-rw---- 1 1000 1005 116, 33 Jan 1 12:00 timer
controlC0: 起控制作用,C0表示Card0
pcmC0D0c: Card 0,Device 0 capture,用来录音。
pcmC0D0p: Card 0,Device 0 playback,用来录音。
pcmC0D1c: Card 0,Device 1 capture,用来录音。
pcmC0D1p: Card 0,Device 1 playback,用来录音。
timer: 很少使用,暂时不用管。
pcmC0D1c/pcmC0D1p是一个辅助的备份的音频设备,先不管。
ALSA框架中一个声卡可以有多个逻辑Device,上面的pcmC0D0X和pcmC0D1X就是两个逻辑设备,一个Device又有播放、录音通道。
2.procfs文件信息
[email protected]:/system # ls /proc/asound/
card0 cards devices hwdep pcm timers tiny4412 version
3.sysfs文件信息
/sys/class/sound/
有声卡的id,number,pcm_class等信息
4.debugfs文件信息
/sys/kernel/debug/asoc/
导出信息包括:
① Codec各个组件的dapm信息TINY4412_UDA8960/wm8960-codec.0-001a/dapm下的
[email protected]:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a/dapm # ls
HP_L Left Speaker Output Right Boost Mixer
HP_R Left Speaker PGA Right DAC
LINPUT1 MICB Right Input Mixer
LINPUT2 Mic Onboard Right Output Mixer
LINPUT3 Mono Output Mixer Right Speaker Output
LOUT1 PGA OUT3 Right Speaker PGA
Left ADC RINPUT1 SPK_LN
Left Boost Mixer RINPUT2 SPK_LP
Left DAC RINPUT3 SPK_RN
Left Input Mixer ROUT1 PGA SPK_RP
Left Output Mixer Right ADC bias_level
② Codec的寄存器信息
[email protected]:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a # ls
cache_only cache_sync codec_reg
③ 为消除pop音的延时时间
/sys/kernel/debug/asoc/TINY4412_UDA8960# ls
dapm_pop_time
④ dapm的bias_level
/sys/kernel/debug/asoc/TINY4412_UDA8960/dapm # cat bias_level
Off
⑤ 系统中所有注册的Codec,dais和Platform驱动的名字
/sys/kernel/debug/asoc # ls
S3C2440_UDA1341 codecs dais platforms
三、驱动实战
驱动分Platform驱动,Codec驱动和Machine驱动。我们对于4412开发板主要任务就是实现Machine驱动的平台设备端,移植调试Codec驱动wm8960.c。
Platform驱动Soc厂商已经实现好了是:sound/soc/samsung/dma.c
Soc端的dai驱动Soc厂商已经实现好了是:sound/soc/samsung/i2s.c
Machine平台设备驱动驱动端sound子系统已经实现好了,是sound/soc/soc-core.c
Codec驱动和Codec端的dai驱动都在Codec驱动中实现。
(1) 调试好的Codec驱动wm8960.c,
/*
* wm8960.c -- WM8960 ALSA SoC Audio driver
*
* Author: Liam Girdwood
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wm8960.h"
/* R25 - Power 1 */
#define WM8960_VMID_MASK 0x180
#define WM8960_VREF 0x40
/* R26 - Power 2 */
#define WM8960_PWR2_LOUT1 0x40
#define WM8960_PWR2_ROUT1 0x20
#define WM8960_PWR2_OUT3 0x02
/* R28 - Anti-pop 1 */
#define WM8960_POBCTRL 0x80
#define WM8960_BUFDCOPEN 0x10
#define WM8960_BUFIOEN 0x08
#define WM8960_SOFT_ST 0x04
#define WM8960_HPSTBY 0x01
/* R29 - Anti-pop 2 */
#define WM8960_DISOP 0x40
#define WM8960_DRES_MASK 0x30
/*
* wm8960 register cache
* We can‘t read the WM8960 register space when we are
* using 2 wire for device control, so we cache them instead.
*/
static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
0x0097, 0x0097, 0x0000, 0x0000,
0x0000, 0x0008, 0x0000, 0x000a,
0x01c0, 0x0000, 0x00ff, 0x00ff,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x007b, 0x0100, 0x0032,
0x0000, 0x00c3, 0x00c3, 0x01c0,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,
0x0100, 0x0100, 0x0050, 0x0050,
0x0050, 0x0050, 0x0000, 0x0000,
0x0000, 0x0000, 0x0040, 0x0000,
0x0000, 0x0050, 0x0050, 0x0000,
0x0002, 0x0037, 0x004d, 0x0080,
0x0008, 0x0031, 0x0026, 0x00e9,
};
struct wm8960_priv {
enum snd_soc_control_type control_type;
void *control_data;
int (*set_bias_level)(struct snd_soc_codec *, enum snd_soc_bias_level level);
struct snd_soc_dapm_widget *lout1;
struct snd_soc_dapm_widget *rout1;
struct snd_soc_dapm_widget *out3;
bool deemph;
int playback_fs;
};
#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0)
/* enumerated controls */ /*极性*/
static const char *wm8960_polarity[] = {
"No Inversion", "Left Inverted",
"Right Inverted", "Stereo Inversion"}; /*立体声反转*/
static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"};
static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"};
static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
static const char *wm8960_alcmode[] = {"ALC", "Limiter"};
/*
#define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts)
{
.reg = xreg,
.shift_l = xshift,
.shift_r = xshift,
.max = xmax,
.texts = xtexts
}
*/
/*
soc_mixer_control来描述mixer控件的寄存器信息
soc_enum 来描述mux控件的寄存器信息
*/
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),
};
/*采样率设置*/
static const int deemph_settings[] = { 0, 32000, 44100, 48000 };
static int wm8960_set_deemph(struct snd_soc_codec *codec)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
int val, i, best;
/* If we‘re using deemphasis select the nearest available sample
* rate.
*/
if (wm8960->deemph) {
best = 1;
for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
if (abs(deemph_settings[i] - wm8960->playback_fs) <
abs(deemph_settings[best] - wm8960->playback_fs))
best = i;
}
val = best << 1;
} else {
val = 0;
}
dev_dbg(codec->dev, "Set deemphasis %d\n", val);
return snd_soc_update_bits(codec, WM8960_DACCTL1,
0x6, val);
}
static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
ucontrol->value.enumerated.item[0] = wm8960->deemph;
return 0;
}
static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
int deemph = ucontrol->value.enumerated.item[0];
if (deemph > 1)
return -EINVAL;
wm8960->deemph = deemph;
return wm8960_set_deemph(codec);
}
/*
补充介绍:
DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);
DECLARE_TLV_DB_LINEAR宏定义的mixer control,它的输出随值的变化而线性变化。 该宏的:
第一个参数是要定义变量的名字,
第二个参数是最小值,以0.01dB为单位。
第三个参数是最大值,以0.01dB为单位。
如果该control处于最小值时会做出mute时,需要把第二个参数设为TLV_DB_GAIN_MUTE。
这两个宏实际上就是定义一个整形数组,所谓tlv,就是Type-Lenght-Value的意思,数组的第0各
元素代表数据的类型,第1个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据。
*/
/*这些定义的数组adc_tlv[]会被放在snd_kcontrol_new.tlv.p中,单位是db.
DECLARE_TLV_DB_SCALE宏定义的mixer control,它所代表的值按一个固定的dB值的步长变化。
该宏的:
第一个参数是要定义变量的名字,
第二个参数是最小值,以0.01dB为单位。
第三个参数是变化的步长,也是以0.01dB为单位。
如果该control处于最小值时会做出mute时,需要把第四个参数设为1。
*/
static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
/*
* 这些snd_kcontrol_new定义的控制项可以通过tinymix工具读取出来,可供app设置.
* 这些是没有使用DAPM的kcontrol,对比SOC_SINGLE和SOC_DAPM_SINGLE可知其内部指定
* 的回调函数不同。
*/
static const struct snd_kcontrol_new wm8960_snd_controls[] = {
SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
0, 63, 0, adc_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, 0),
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[2]),
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("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH,
4, 3, 0),
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),
};
/*
* 宏中带DAPM的表示是使用DAPM的kcontrol.
*
* dapm kcontrol的put回调函数不仅仅会更新控件本身的状态,他还会把这种变化传
* 递到相邻的dapm kcontrol,相邻的dapm kcontrol又会传递这个变化到他自己相邻
* 的dapm kcontrol,知道音频路径的末端,通过这种机制,只要改变其中一个widget
* 的连接状态,与之相关的所有widget都会被扫描并测试一下自身是否还在有效的音频
* 路径中,从而可以动态地改变自身的电源状态,这就是dapm的精髓所在。
*/
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),
};
/*
* snd_kcontrol_new是定义个控件
*/
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),
};
/*
不同的组件初始化的成员不同:
SND_SOC_DAPM_INPUT中没有snd_kcontrol_new
SND_SOC_DAPM_MIXER中有多个snd_kcontrol_new
将上面定义的控件转化为widget
*/
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_MICBIAS("MICB", WM8960_POWER1, 1, 0),
/*
* 若arg2=SND_SOC_NOPM则表示此widget不具备电源属性,但是mux的
* 切换会影响到与之相连的其它具备电源属性的电源状态。
*/
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),
};
/*
route的source和sink成员都是widget的名字,control成员是两个widget之间连接通过的开关
的snd_kcontrol, 若是两个widget之间有开关,那么control成员就是这个开关的名字。若是没
有开关而是直连的,那么control成员就设置为NULL,此时这个连接成为静态连接。
route会被解析成path,使用snd_soc_dapm_path结构表示。
从这个结构体里面可以看出声道的连接路由。
*/
static const struct snd_soc_dapm_route audio_paths[] = {
{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
{ "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
{ "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },
{ "Left Input Mixer", "Boost Switch", "Left Boost Mixer", },
{ "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */
{ "Left Input Mixer", NULL, "LINPUT2" },
{ "Left Input Mixer", NULL, "LINPUT3" },
{ "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
{ "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
{ "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },
{ "Right Input Mixer", "Boost Switch", "Right Boost Mixer", },
{ "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */
{ "Right Input Mixer", NULL, "RINPUT2" },
{ "Right Input Mixer", NULL, "LINPUT3" },
{ "Left ADC", NULL, "Left Input Mixer" },
{ "Right ADC", NULL, "Right Input Mixer" },
{ "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
{ "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} ,
{ "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
{ "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
{ "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } ,
{ "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
{ "LOUT1 PGA", NULL, "Left Output Mixer" },
{ "ROUT1 PGA", NULL, "Right Output Mixer" },
{ "HP_L", NULL, "LOUT1 PGA" },
{ "HP_R", NULL, "ROUT1 PGA" },
{ "Left Speaker PGA", NULL, "Left Output Mixer" },
{ "Right Speaker PGA", NULL, "Right Output Mixer" },
{ "Left Speaker Output", NULL, "Left Speaker PGA" },
{ "Right Speaker Output", NULL, "Right Speaker PGA" },
{ "SPK_LN", NULL, "Left Speaker Output" },
{ "SPK_LP", NULL, "Left Speaker Output" },
{ "SPK_RN", NULL, "Right Speaker Output" },
{ "SPK_RP", NULL, "Right Speaker Output" },
};
static const struct snd_soc_dapm_route audio_paths_out3[] = {
{ "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
{ "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
{ "OUT3", NULL, "Mono Output Mixer", }
};
static const struct snd_soc_dapm_route audio_paths_capless[] = {
{ "HP_L", NULL, "OUT3 VMID" },
{ "HP_R", NULL, "OUT3 VMID" },
{ "OUT3 VMID", NULL, "Left Output Mixer" },
{ "OUT3 VMID", NULL, "Right Output Mixer" },
};
static int wm8960_add_widgets(struct snd_soc_codec *codec)
{
struct wm8960_data *pdata = codec->dev->platform_data;
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
struct snd_soc_dapm_context *dapm = &codec->dapm;
struct snd_soc_dapm_widget *w;
snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, ARRAY_SIZE(wm8960_dapm_widgets));
snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
/* In capless mode OUT3 is used to provide VMID for the
* headphone outputs, otherwise it is used as a mono mixer.
*/
if (pdata && pdata->capless) {
snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless,
ARRAY_SIZE(wm8960_dapm_widgets_capless));
snd_soc_dapm_add_routes(dapm, audio_paths_capless,
ARRAY_SIZE(audio_paths_capless));
} else {
snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3,
ARRAY_SIZE(wm8960_dapm_widgets_out3));
snd_soc_dapm_add_routes(dapm, audio_paths_out3,
ARRAY_SIZE(audio_paths_out3));
}
/* We need to power up the headphone output stage out of
* sequence for capless mode. To save scanning the widget
* list each time to find the desired power state do so now
* and save the result.
*/
list_for_each_entry(w, &codec->card->widgets, list) {
if (w->dapm != &codec->dapm)
continue;
if (strcmp(w->name, "LOUT1 PGA") == 0)
wm8960->lout1 = w;
if (strcmp(w->name, "ROUT1 PGA") == 0)
wm8960->rout1 = w;
if (strcmp(w->name, "OUT3 VMID") == 0)
wm8960->out3 = w;
}
return 0;
}
static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 iface = 0;
/* set master/slave audio interface */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
iface |= 0x0040;
break;
case SND_SOC_DAIFMT_CBS_CFS:
break;
default:
return -EINVAL;
}
/* interface format */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
iface |= 0x0002;
break;
case SND_SOC_DAIFMT_RIGHT_J:
break;
case SND_SOC_DAIFMT_LEFT_J:
iface |= 0x0001;
break;
case SND_SOC_DAIFMT_DSP_A:
iface |= 0x0003;
break;
case SND_SOC_DAIFMT_DSP_B:
iface |= 0x0013;
break;
default:
return -EINVAL;
}
/* clock inversion */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_IF:
iface |= 0x0090;
break;
case SND_SOC_DAIFMT_IB_NF:
iface |= 0x0080;
break;
case SND_SOC_DAIFMT_NB_IF:
iface |= 0x0010;
break;
default:
return -EINVAL;
}
/* set iface */
snd_soc_write(codec, WM8960_IFACE1, iface);
return 0;
}
static struct {
int rate;
unsigned int val;
} alc_rates[] = {
{ 48000, 0 },
{ 44100, 0 },
{ 32000, 1 },
{ 22050, 2 },
{ 24000, 2 },
{ 16000, 3 },
{ 11250, 4 },
{ 12000, 4 },
{ 8000, 5 },
};
/*这个params从何而来???*/
static int wm8960_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_codec *codec = rtd->codec;
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
int i;
/* bit size */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
break;
case SNDRV_PCM_FORMAT_S20_3LE:
iface |= 0x0004;
break;
case SNDRV_PCM_FORMAT_S24_LE:
iface |= 0x0008;
break;
}
/* Update filters for the new rate */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
wm8960->playback_fs = params_rate(params);
wm8960_set_deemph(codec);
} else {
for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
if (alc_rates[i].rate == params_rate(params))
snd_soc_update_bits(codec,
WM8960_ADDCTL3, 0x7,
alc_rates[i].val);
}
/* set iface */
snd_soc_write(codec, WM8960_IFACE1, iface);
return 0;
}
static int wm8960_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7;
if (mute)
snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
else
snd_soc_write(codec, WM8960_DACCTL1, mute_reg);
return 0;
}
static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
u16 reg;
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
/* Set VMID to 2x50k */
reg = snd_soc_read(codec, WM8960_POWER1);
reg &= ~0x180;
reg |= 0x80;
snd_soc_write(codec, WM8960_POWER1, reg);
break;
case SND_SOC_BIAS_STANDBY:
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
/* Enable anti-pop features */
snd_soc_write(codec, WM8960_APOP1,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN | WM8960_BUFIOEN);
/* Enable & ramp VMID at 2x50k */
reg = snd_soc_read(codec, WM8960_POWER1);
reg |= 0x80;
snd_soc_write(codec, WM8960_POWER1, reg);
msleep(100);
/* Enable VREF */
snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF);
/* Disable anti-pop features */
snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
}
/* Set VMID to 2x250k */
reg = snd_soc_read(codec, WM8960_POWER1);
reg &= ~0x180;
reg |= 0x100;
snd_soc_write(codec, WM8960_POWER1, reg);
break;
case SND_SOC_BIAS_OFF:
/* Enable anti-pop features */
snd_soc_write(codec, WM8960_APOP1,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN | WM8960_BUFIOEN);
/* Disable VMID and VREF, let them discharge */
snd_soc_write(codec, WM8960_POWER1, 0);
msleep(600);
break;
}
codec->dapm.bias_level = level;
return 0;
}
static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
int reg;
switch (level) {
case SND_SOC_BIAS_ON:
break;
case SND_SOC_BIAS_PREPARE:
switch (codec->dapm.bias_level) {
case SND_SOC_BIAS_STANDBY:
/* Enable anti pop mode */
snd_soc_update_bits(codec, WM8960_APOP1,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN);
/* Enable LOUT1, ROUT1 and OUT3 if they‘re enabled */
reg = 0;
if (wm8960->lout1 && wm8960->lout1->power)
reg |= WM8960_PWR2_LOUT1;
if (wm8960->rout1 && wm8960->rout1->power)
reg |= WM8960_PWR2_ROUT1;
if (wm8960->out3 && wm8960->out3->power)
reg |= WM8960_PWR2_OUT3;
snd_soc_update_bits(codec, WM8960_POWER2,
WM8960_PWR2_LOUT1 |
WM8960_PWR2_ROUT1 |
WM8960_PWR2_OUT3, reg);
/* Enable VMID at 2*50k */
snd_soc_update_bits(codec, WM8960_POWER1,
WM8960_VMID_MASK, 0x80);
/* Ramp */
msleep(100);
/* Enable VREF */
snd_soc_update_bits(codec, WM8960_POWER1,
WM8960_VREF, WM8960_VREF);
msleep(100);
break;
case SND_SOC_BIAS_ON:
/* Enable anti-pop mode */
snd_soc_update_bits(codec, WM8960_APOP1,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN);
/* Disable VMID and VREF */
snd_soc_update_bits(codec, WM8960_POWER1,
WM8960_VREF | WM8960_VMID_MASK, 0);
break;
default:
break;
}
break;
case SND_SOC_BIAS_STANDBY:
switch (codec->dapm.bias_level) {
case SND_SOC_BIAS_PREPARE:
/* Disable HP discharge */
snd_soc_update_bits(codec, WM8960_APOP2,
WM8960_DISOP | WM8960_DRES_MASK,
0);
/* Disable anti-pop features */
snd_soc_update_bits(codec, WM8960_APOP1,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN,
WM8960_POBCTRL | WM8960_SOFT_ST |
WM8960_BUFDCOPEN);
break;
default:
break;
}
break;
case SND_SOC_BIAS_OFF:
break;
}
codec->dapm.bias_level = level;
return 0;
}
/* PLL divisors */
struct _pll_div {
u32 pre_div:1;
u32 n:4;
u32 k:24;
};
/* The size in bits of the pll divide multiplied by 10
* to allow rounding later */
#define FIXED_PLL_SIZE ((1 << 24) * 10)
static int pll_factors(unsigned int source, unsigned int target,
struct _pll_div *pll_div)
{
unsigned long long Kpart;
unsigned int K, Ndiv, Nmod;
pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);
/* Scale up target to PLL operating frequency */
target *= 4;
Ndiv = target / source;
if (Ndiv < 6) {
source >>= 1;
pll_div->pre_div = 1;
Ndiv = target / source;
} else
pll_div->pre_div = 0;
if ((Ndiv < 6) || (Ndiv > 12)) {
pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv);
return -EINVAL;
}
pll_div->n = Ndiv;
Nmod = target % source;
Kpart = FIXED_PLL_SIZE * (long long)Nmod;
do_div(Kpart, source);
K = Kpart & 0xFFFFFFFF;
/* Check if we need to round */
if ((K % 10) >= 5)
K += 5;
/* Move down to proper range now rounding is done */
K /= 10;
pll_div->k = K;
pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
pll_div->n, pll_div->k, pll_div->pre_div);
return 0;
}
static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
int source, unsigned int freq_in, unsigned int freq_out)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 reg;
static struct _pll_div pll_div;
int ret;
if (freq_in && freq_out) {
ret = pll_factors(freq_in, freq_out, &pll_div);
if (ret != 0)
return ret;
}
/* Disable the PLL: even if we are changing the frequency the
* PLL needs to be disabled while we do so. */
snd_soc_write(codec, WM8960_CLOCK1,
snd_soc_read(codec, WM8960_CLOCK1) & ~1);
snd_soc_write(codec, WM8960_POWER2,
snd_soc_read(codec, WM8960_POWER2) & ~1);
if (!freq_in || !freq_out)
return 0;
reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f;
reg |= pll_div.pre_div << 4;
reg |= pll_div.n;
if (pll_div.k) {
reg |= 0x20;
snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
}
snd_soc_write(codec, WM8960_PLL1, reg);
/* Turn it on */
snd_soc_write(codec, WM8960_POWER2,
snd_soc_read(codec, WM8960_POWER2) | 1);
msleep(250);
snd_soc_write(codec, WM8960_CLOCK1,
snd_soc_read(codec, WM8960_CLOCK1) | 1);
return 0;
}
static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
int div_id, int div)
{
struct snd_soc_codec *codec = codec_dai->codec;
u16 reg;
switch (div_id) {
case WM8960_SYSCLKDIV:
reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9;
snd_soc_write(codec, WM8960_CLOCK1, reg | div);
break;
case WM8960_DACDIV:
reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7;
snd_soc_write(codec, WM8960_CLOCK1, reg | div);
break;
case WM8960_OPCLKDIV:
reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f;
snd_soc_write(codec, WM8960_PLL1, reg | div);
break;
case WM8960_DCLKDIV:
reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f;
snd_soc_write(codec, WM8960_CLOCK2, reg | div);
break;
case WM8960_TOCLKSEL:
reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd;
snd_soc_write(codec, WM8960_ADDCTL1, reg | div);
break;
default:
return -EINVAL;
}
return 0;
}
static int wm8960_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
return wm8960->set_bias_level(codec, level);
}
#define WM8960_RATES SNDRV_PCM_RATE_8000_48000
#define WM8960_FORMATS \
(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE)
static struct snd_soc_dai_ops wm8960_dai_ops = {
.hw_params = wm8960_hw_params,
.digital_mute = wm8960_mute,
.set_fmt = wm8960_set_dai_fmt,
.set_clkdiv = wm8960_set_dai_clkdiv,
.set_pll = wm8960_set_dai_pll,
};
/*这个是dai接口的驱动*/
static struct snd_soc_dai_driver wm8960_dai = {
/*
* 这个name会传递给snd_soc_codec.name, 后者会和
* codec_conf->dev_name匹配
*/
.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,
};
static int wm8960_suspend(struct snd_soc_codec *codec, pm_message_t state)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
static int wm8960_resume(struct snd_soc_codec *codec)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
int i;
u8 data[2];
u16 *cache = codec->reg_cache;
/* Sync reg_cache with the hardware */
for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) {
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
data[1] = cache[i] & 0x00ff;
codec->hw_write(codec->control_data, data, 2);
}
wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0;
}
static int wm8960_probe(struct snd_soc_codec *codec)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
struct wm8960_data *pdata = dev_get_platdata(codec->dev);
int ret;
u16 reg;
wm8960->set_bias_level = wm8960_set_bias_level_out3;
codec->control_data = wm8960->control_data;
if (!pdata) {
dev_warn(codec->dev, "No platform data supplied\n");
} else {
if (pdata->dres > WM8960_DRES_MAX) {
dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres);
pdata->dres = 0;
}
if (pdata->capless)
wm8960->set_bias_level = wm8960_set_bias_level_capless;
}
/*选中codec的控制接口*/
ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type);
if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
}
ret = wm8960_reset(codec);
if (ret < 0) {
dev_err(codec->dev, "Failed to issue reset\n");
return ret;
}
wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
/*对芯片寄存器进行初始化*/
/* Latch the update bits */
reg = snd_soc_read(codec, WM8960_LINVOL);
snd_soc_write(codec, WM8960_LINVOL, reg | 0x100);
reg = snd_soc_read(codec, WM8960_RINVOL);
snd_soc_write(codec, WM8960_RINVOL, reg | 0x100);
reg = snd_soc_read(codec, WM8960_LADC);
snd_soc_write(codec, WM8960_LADC, reg | 0x100);
reg = snd_soc_read(codec, WM8960_RADC);
snd_soc_write(codec, WM8960_RADC, reg | 0x100);
reg = snd_soc_read(codec, WM8960_LDAC);
snd_soc_write(codec, WM8960_LDAC, reg | 0x100);
reg = snd_soc_read(codec, WM8960_RDAC);
snd_soc_write(codec, WM8960_RDAC, reg | 0x100);
reg = snd_soc_read(codec, WM8960_LOUT1);
snd_soc_write(codec, WM8960_LOUT1, reg | 0x100);
reg = snd_soc_read(codec, WM8960_ROUT1);
snd_soc_write(codec, WM8960_ROUT1, reg | 0x100);
reg = snd_soc_read(codec, WM8960_LOUT2);
snd_soc_write(codec, WM8960_LOUT2, reg | 0x100);
reg = snd_soc_read(codec, WM8960_ROUT2);
snd_soc_write(codec, WM8960_ROUT2, reg | 0x100);
/* 如果不想每次上电录音之前都要执行这些命令, 就在此设置寄存器:
* tinymix "Capture Switch" 0
* tinymix "Left Input Mixer Boost Switch" 1
*/
/* Capture Switch off */
snd_soc_update_bits(codec, WM8960_LINVOL, (1<<7), (0<<7));
/* Left Input Mixer Boost Switch */
snd_soc_update_bits(codec, WM8960_LINPATH, (1<<3), (1<<3));
/*初始化controls和widgets*/
snd_soc_add_controls(codec, wm8960_snd_controls,
ARRAY_SIZE(wm8960_snd_controls));
wm8960_add_widgets(codec);
return 0;
}
/* power down chip */
static int wm8960_remove(struct snd_soc_codec *codec)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
/*这个是codec芯片的驱动*/
static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
.probe = wm8960_probe,
.remove = wm8960_remove,
.suspend = wm8960_suspend,
.resume = wm8960_resume,
.set_bias_level = wm8960_set_bias_level,
.reg_cache_size = ARRAY_SIZE(wm8960_reg),
.reg_word_size = sizeof(u16),
.reg_cache_default = wm8960_reg,
};
/*这是主机i2c控制接口的驱动*/
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct wm8960_priv *wm8960;
int ret;
wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
if (wm8960 == NULL)
return -ENOMEM;
i2c_set_clientdata(i2c, wm8960);
/*指定对codec芯片的控制接口是i2c*/
wm8960->control_type = SND_SOC_I2C;
wm8960->control_data = i2c;
/*
* 注册一个codec需要提供snd_soc_codec_driver和snd_soc_dai_driver
* 同时可以有多个dai
* 这个函数内部会实例化一个snd_soc_codec,若全局链表上有数据还会去实例化
* 一个snd_soc_card(这个结构在其它地方也能实例化)
*/
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_wm8960, &wm8960_dai, 1);
if (ret < 0)
kfree(wm8960);
return ret;
}
static __devexit int wm8960_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
kfree(i2c_get_clientdata(client));
return 0;
}
/*设备端在mach-tiny4412.c中定义*/
static const struct i2c_device_id wm8960_i2c_id[] = {
{ "wm8960", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);
/*它是怎么通过名字匹配到设备的呢?*/
static struct i2c_driver wm8960_i2c_driver = {
.driver = {
.name = "wm8960-codec",
.owner = THIS_MODULE,
},
.probe = wm8960_i2c_probe,
.remove = __devexit_p(wm8960_i2c_remove),
.id_table = wm8960_i2c_id,
};
#endif
static int __init wm8960_modinit(void)
{
int ret = 0;
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
ret = i2c_add_driver(&wm8960_i2c_driver);
if (ret != 0) {
printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",
ret);
}
#endif
return ret;
}
module_init(wm8960_modinit);
static void __exit wm8960_exit(void)
{
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
i2c_del_driver(&wm8960_i2c_driver);
#endif
}
module_exit(wm8960_exit);
MODULE_DESCRIPTION("ASoC WM8960 driver");
MODULE_AUTHOR("Liam Girdwood");
MODULE_LICENSE("GPL");
View Code
(2) Machine平台设备驱动设备端(驱动端是: sound/soc/soc-core.c)
#include
#include
#include
#include
#include
#include
#include "i2s.h"
static int set_epll_rate(unsigned long rate)
{
struct clk *fout_epll;
fout_epll = clk_get(NULL, "fout_epll");
if (IS_ERR(fout_epll)) {
printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
return PTR_ERR(fout_epll);
}
if (rate == clk_get_rate(fout_epll))
goto out;
clk_set_rate(fout_epll, rate);
out:
clk_put(fout_epll);
return 0;
}
/*
RFS:IIS Root clk freq select,
BFS:bit clock freq select
*/
static int smdk_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;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int bfs, psr, rfs, ret;
unsigned long rclk;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_U24:
case SNDRV_PCM_FORMAT_S24:
bfs = 48;
break;
case SNDRV_PCM_FORMAT_U16_LE:
case SNDRV_PCM_FORMAT_S16_LE:
bfs = 32;
break;
default:
return -EINVAL;
}
switch (params_rate(params)) {
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
case 88200:
case 96000:
if (bfs == 48)
rfs = 384; /*rfs=bfs*8*/
else
rfs = 256;
break;
case 64000:
rfs = 384;
break;
case 8000:
case 11025:
case 12000:
if (bfs == 48)
rfs = 768;
else
rfs = 512;
break;
default:
return -EINVAL;
}
rclk = params_rate(params) * rfs;
switch (rclk) {
case 4096000:
case 5644800:
case 6144000:
case 8467200:
case 9216000:
psr = 8;
break;
case 8192000:
case 11289600:
case 12288000:
case 16934400:
case 18432000:
psr = 4;
break;
case 22579200:
case 24576000:
case 33868800:
case 36864000:
psr = 2;
break;
case 67737600:
case 73728000:
psr = 1;
break;
default:
printk("Not yet supported!\n");
return -EINVAL;
}
set_epll_rate(rclk * psr);
/*调用platform驱动中的dai驱动的函数*/
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
0, SND_SOC_CLOCK_OUT);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
if (ret < 0)
return ret;
return 0;
}
/* 参考sound\soc\samsung\s3c24xx_uda134x.c
*/
/*
* 1. 分配注册一个名为soc-audio的平台设备
* 2. 这个平台设备有一个私有数据 snd_soc_card
* snd_soc_card里有一项snd_soc_dai_link
* snd_soc_dai_link被用来决定ASOC各部分的驱动
*/
static struct snd_soc_ops tiny4412_wm8960_ops = {
/*这里面指定的params从哪里来????*/
.hw_params = smdk_hw_params,
};
/*
#define SND_SOC_DAPM_MIC(wname, wevent)
{
.id = snd_soc_dapm_mic,
.name = "Mic Onboard",
.kcontrol_news = NULL,
.num_kcontrols = 0,
.reg = SND_SOC_NOPM,
.event = NULL,
.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD
关系的dapm事件:
SND_SOC_DAPM_PRE_PMU: widget要上电前发出的事件
SND_SOC_DAPM_POST_PMD: widget要下电后发出的事件
}
这也是一条输入line的一部分,但是由于是板级的,所以写在这个文件中.
处理单板上的widget,定义的虚拟widget
*/
static const struct snd_soc_dapm_widget tiny4412_wm8960_widgets[] = {
SND_SOC_DAPM_MIC("Mic Onboard", NULL),
};
static const struct snd_soc_dapm_route tiny4412_wm8960_paths[] = {
{ "MICB", NULL, "Mic Onboard" },
{ "LINPUT1", NULL, "MICB" },
};
static int tiny4412_wm8960_machine_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
/* 添加一个虚拟的MIC widget */
snd_soc_dapm_new_controls(dapm, tiny4412_wm8960_widgets,
ARRAY_SIZE(tiny4412_wm8960_widgets));
/* 添加2个route */
snd_soc_dapm_add_routes(dapm, tiny4412_wm8960_paths, ARRAY_SIZE(tiny4412_wm8960_paths));
#define WM8960_IFACE2 0x9
//设置Pin15(ADCLRC/GPIO)为GPIO ,如果不设置而且Pin15上又没有外部时钟则ADC 工作异常
snd_soc_update_bits(codec, WM8960_IFACE2, 0x40, 0x40);
snd_soc_dapm_sync(dapm);
return 0;
}
/*
* snd_soc_dai_link被用来决定ASOC各部分的驱动。
* 通过名字的匹配过程是在soc_bind_dai_link()中完成的。
*/
static struct snd_soc_dai_link tiny4412_wm8960_dai_link = {
/*这两个名字不用与匹配信息*/
.name = "NAME_UDA8960",
.stream_name = "STREAM_NAME_UDA8960",
/*
* "wm8960-codec.0-001a"这个名字来自codec驱动,分为2部分,
* wm8960-codec是在wm8960.c中指定的i2c驱动的名字,0-001a是i2c
* 设备的设备地址,组合起来称为snd_soc_codec->name。
* 这个是用来匹配wm8960这个codec驱动的。
*/
.codec_name = "wm8960-codec.0-001a",
/*
* 这个名字用于匹配wm8960.c中注册的dai,而这个codec的dai的name
* 来自snd_soc_dai_driver->name, 因此指定为codec中注册的
* snd_soc_dai_driver中的name就可以了。
* 作用: 用于匹配codec中的哪个codec_dai
*/
.codec_dai_name = "wm8960-hifi",
/*
* 这个名字用于和sound\soc\samsung\I2s.c中使用snd_soc_register_dai注册的dai_name
* 匹配上才行, 应该用于Soc与Codec之间通信的接口的选择。
*
* codec_dai和cpu_dai都确定下来了,那么一条dai连接就确定下来了。
*/
.cpu_dai_name = "samsung-i2s.0",
.ops = &tiny4412_wm8960_ops,
/*
* 用于选择使用哪个platform(Soc相关)驱动,指定这个名字是要求
* 使用sound\soc\samsung\dma.c中注册的snd_soc_platform
* 一个Soc可能注册了多个snd_soc_platform,这个用于选择使用哪个。
*/
.platform_name = "samsung-audio",
.init = tiny4412_wm8960_machine_init,
};
static struct snd_soc_card myalsa_card = {
/*这里面的几个名字又该如何赋值????*/
/*这个名字去掉"_"显示在//sys/devices/platform/soc-audio/sound/card0/id中*/
.name = "TINY4412_UDA8960",
.owner = THIS_MODULE,
/*
* 这里dai_link其实是个数组,num_links是数组项个数,
* 这里只有1项,就直接当指针使用了。
*/
.dai_link = &tiny4412_wm8960_dai_link,
.num_links = 1,
};
static void asoc_release(struct device * dev)
{
}
/*
* 驱动端:/sound/soc/soc-core.c, 驱动已经在core中抽象出来了。
*
* 注册成平台设备,snd_soc_card最为平台设备的data,在平台设备
* 的驱动端soc-core.c注册。
*/
static struct platform_device asoc_dev = {
/*通过这个名字匹配驱动soc-core.c,公有的驱动*/
.name = "soc-audio",
.id = -1,
.dev = {
.release = asoc_release,
},
};
static int tiny4412_wm8960_init(void)
{
platform_set_drvdata(&asoc_dev, &myalsa_card);
platform_device_register(&asoc_dev);
return 0;
}
static void tiny4412_wm8960_exit(void)
{
platform_device_unregister(&asoc_dev);
}
module_init(tiny4412_wm8960_init);
module_exit(tiny4412_wm8960_exit);
MODULE_LICENSE("GPL");
View Code