一、应用测试工具的使用

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

linux音频驱动修复工具,Linux声卡驱动(4)——音频驱动实战相关推荐

  1. linux分区引导修复工具,linux系统引导过程及引导修复

    系统的引导过程 1.通电 2.bios初始化 eg:内存,硬盘没插好 3.磁盘引导 硬盘里面的数据是以分区形式保存,硬盘上的磁头对硬盘的数据进行扫描 ## 磁道:磁头转一周的轨迹,可以确定数据在哪一环 ...

  2. linux启动分区修复工具,Linux下的分区修复软件Testdisk(转载)

    linux下超帅的分区表修复软件.以前用过n多的windows的分区表修复软件,没想到linux中有这么好用这么方便的修复软件,速度那叫一个快啊..没有见到这个分区表修复软件以前我都白活了..... ...

  3. linux蓝屏修复工具,linux 开机蓝屏怎么处理

    计算机蓝屏原因 你说的这种情况,一般都是由系统软件.内存.硬盘引起的. 1 电脑不心装上了恶意软件,或上网时产生了恶意程序,建议用360 卫士.金山卫士等软件,清理垃圾,查杀恶意软件,就可能解决.实在 ...

  4. linux蓝屏修复工具,linux双系统修复grub

    场景:500G硬盘,250G用于装的RHEL6:一段时间后安装了CentOS6 现象:只能启动CentOS6了,grub里找不到启动RHEL6的项 解决:可以通过修改CentOS6中得grub.cfg ...

  5. linux分区表错误修复工具,在Linux下成功修复分区表出错

    我重启开了Ubuntu,然后插上他的硬盘,终端上输入 sudo fdisk -l 这下竟然可以看到同学那块硬盘的信息,不过还是不能正确读取出 硬盘无法识别,无法通过自检,在Linux下成功修复分区表出 ...

  6. linux 启动 grub 修复工具,当Ubuntu无法启动时,如何修复GRUB2?

    许多其他Linux发行版使用GRUB2引导加载程序.如果GRUB2中断,例如,如果在安装Ubuntu之后安装Windows或覆盖您的MBR,则无法启动到Ubuntu. 您可以从Ubuntu Live ...

  7. linux磁盘文件检查修复工具下载,磁盘修复工具(TestDisk)

    TestDisk是一款专业的磁盘修复工具,可以修复由于软件缺陷或某些病毒导致的分区丢失或分区表丢失导致磁盘无法启动的问题.TestDisk能够检测大量文件系统,包括NTFS,FAT12,FAT16,F ...

  8. 多系统linux系统引导修复工具,EasyBCD引导双系统|EasyBCD双系统引导修复工具 V2.4.0.237 中文免费版 下载_当下软件园_软件下载...

    EasyBCD双系统引导修复工具是一款非常专业的电脑系统辅助软件.这款EasyBCD引导双系统功能强大,能够支持多种操作系统的多动启动,同时支持的有Linux.MacOS.BSD.XP等操作系统,让你 ...

  9. linux光驱引导修复工具,系统引导修复工具(EasyBCD)

    EasyBCD系统引导修复工具使用方法 一.删除菜单条目 1.使用EasyBCD删除条目是一键式操作.只需启动EasyBCD,然后进入"编辑启动菜单"页面: 2.选择您想要删除的条 ...

最新文章

  1. 人工智能,无人能阻挡历史的趋势
  2. 一个令人心醉的谜题——DNA和RNA是如何演化出美妙的螺旋结构?
  3. java io 读取配置文件_java读取配置文件 - tomzhao2008的个人空间 - OSCHINA - 中文开源技术交流社区...
  4. 解析程序员的几个成长阶段
  5. linux 汇编 gdb报错:Invalid register `eip‘(64位系统没有eip只有rip寄存器)
  6. 连接linux的几款工具 简介
  7. x86构架之-Intel8042键盘控制器简介
  8. 【华为云技术分享】【Python算法】分类与预测——支持向量机
  9. 判定两个点是否在一条直线的同一侧_计算几何01-判定两条线段是否相交
  10. 顺序存储循环队列的基本操作
  11. linux磁盘及文件系统管理
  12. 微软放弃WPF了?自定义控件库有前途
  13. 相见恨晚的英语学习方法!百万人读过!
  14. 如何实现单行/多行文本溢出的省略样式?
  15. 需要在计算机安装msxml版本,Win7安装Office2010提示需要MSXML 6.10.1129.0组件怎么办?...
  16. 浅谈LigerUi Tree(树)
  17. [已解决]Notepad++ 无法安装HexEditor
  18. python使用pillow生成纯透明png图片
  19. Xtext语言语法介绍
  20. 解决:Error: geom_point requires the following missing aesthetics: y Run `rlang::last_error()`

热门文章

  1. Activiti的Hello World——请假流程
  2. leetcode 594最长和谐子序列
  3. ARM GPU mali系列产品规划图或天梯图
  4. HTML event
  5. 分割(计数板)展示数字样式
  6. 10分钟从零搭建QQ机器人,实现自动回复、推送等功能
  7. 攻防世界web新手区easyphp题解writeup
  8. android - ROS Wiki 首页翻译(ros第一篇)
  9. wetool个人版_wetool自动接受新好友wetool使用教程-网站
  10. abel 登陆不了_封印者登录不了怎么办 三大登陆难题解决