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

一、应用测试工具的使用

1.在external/tinyalsa下有以C语言实现的alsa的测试程序,编译后生成tinypcminfo tinyplay tinycap tinymix 四个elf格式的测试工具

(1) tinypcminfo :获取PCM In和PCM

# tinypcminfo -D /dev/snd/controlC0

jia.gif

jian.gif

# 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节点设置获取控制信息,进行控件的设置。比如设置链路,音量调节等。

jia.gif

jian.gif

# 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,

jia.gif

jian.gif

/*

* 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)

jia.gif

jian.gif

#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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/563432.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二元相图软件_FactSage 软件教程 入门学习资料汇总

&#xff08;一&#xff09;英文版的学习资料&#xff08;1&#xff09;FactSage官网&#xff1a;http://www.factsage.com/打开FactSage官网&#xff0c;如下图所示&#xff0c;点击左侧FactSage界面上的模块按钮即可查看其使用方法。点击右侧的链接"Free FactSage Demo …

bufferreader readline一次读一行_python中read(),readline(),readlines()的区别

读取文件的三个方法&#xff1a;read()、readline()、readlines()。三种方法均可接受一个变量size&#xff0c;用于限制每次读取的数据量&#xff0c;也就是说从文件当前位置起读取size个字节&#xff1b;若无参数size&#xff0c;则表示读取至文件结束为止。接下来总结下三种读…

固定 顶部_一楼小院想建阳光房?固定的怕违建,那这样可伸缩的阳光房怎么样...

今天小编又有一个新鲜出炉的案例要跟大家分享啦&#xff01;前几次与大家分享的都是伸缩阳光房顶&#xff0c;不论是手动还是电动款式 &#xff0c;似乎都是针对天井、下沉式庭院等设计的。那针对一些小区里的一楼小院&#xff0c;固定的怕违建&#xff0c;有没有什么对策呢&am…

C语言坐标打飞机,C语言控制台实现打飞机小游戏

本文实例为大家分享了C语言实现打飞机小游戏的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下初学C语言总觉得不能做些什么好玩的&#xff0c;这个小游戏只需 “一点点” (千真万确)C语言知识就能完成&#xff01;总计不到200行的非空白代码(没有强行压缩行数)操作说…

合同相似可逆等价矩阵的关系及性质_行列式的性质问题

行列式的学习一方面要掌握计算行列式的一般方法&#xff1b;对性质要理解。考点与要求&#xff1a;了解&#xff1a;行列式的概念、方阵的乘积、行列式的性质&#xff1b;掌握&#xff1a;行列式的性质&#xff1b;会用&#xff1a;行列式的性质和行列式按行(列)展开定理计算行…

c++ windows 蓝牙库_蓝牙翻页笔(PPT 控制器) | ESP32学习之旅-Arduino版

本系列历史文章目录&#xff1a;ESP32概述与Arduino软件准备新冠肺炎疫情数据实时显示器B 粉计数器本期给大家带来的案例是&#xff1a;蓝牙翻页笔。先来看一下演示视频吧&#xff1a;蓝牙翻页笔 | PPT播放控制器 | 蓝牙键盘 | 用 Arduino 玩转掌控板 ESP32 / ESP8266 | 图形化…

python 程序运行在阿里云主机_阿里云主机Access key利用工具

简介&#xff08;Gamma实验室核心成员&#xff1a;一灯老和尚所写&#xff09;在日常渗透过程中我们经常遇到信息泄露出ALIYUN_ACCESSKEYID与ALIYUN_ACCESSKEYSECRET&#xff08;阿里云API key&#xff09;&#xff0c;特别是laravel框架得debug信息。APP中也会泄露这些信息&am…

一张纸厚度是多少毫米_一张纸的威力有多大?纸折103次捅破宇宙,理论荒诞却无法反驳...

【图文摘自网络&#xff0c;如有侵权请联系删除】人类达到月球需要多久时间&#xff1f;地球和月亮的距离不是一成不变的&#xff0c;它有着近地点和远地点&#xff0c;地球里月球最远的距离在363300千米&#xff0c;最远为405500千米。而当年阿波罗号由火箭发射到登陆月球表面…

oracle 两表两列数据对比_Oracle、PostgreSQL与Mysql数据写入性能对比

最近因为工作需要&#xff0c;需要对Oracle和Mysql写入性能进行对比&#xff0c;以前都是听说Mysql性能比Oracle不是一个级别&#xff0c;现在亲测后&#xff0c;不比不知道&#xff0c;一比吓一跳。。。追加PostgreSql性能测试数据测试场景本地电脑(单机)&#xff0c;通过程序…

锐驰机器人的市场_【年终盘点】2020年,锐驰的王炸新品!

点击蓝字关注我哦辛苦付出、苦熬实干&#xff0c;换来了硕果累累、丰收成片。回首2020年&#xff0c;锐驰秉承着锐意进取、快速创新的理念&#xff0c;在自动化道路上不断探索&#xff0c;推出了多款智能自动化机器&#xff0c;现在着重介绍以下3款&#xff1a;异型插件机器人、…

学生管理系统c#语言代码,基于C#语言的学生管理系统的设计(ASP.NET2.0)

摘 要随着科学技术的不断提高,计算机科学日渐成熟,其强大的功能已为人们深刻认识,它已进入人类社会的各个领域并发挥着越来越重要的作用。学生管理系统是学校管理中不可少的一部分。而基于B/S架构的学生管理系统是方便学校管理&#xff0c;实行电子办公的必要组成部分&#xf…

异常信息_一个针对异常信息通知的springboot starter

前言作为后端开发者&#xff0c;项目上线之后难免会遇到各种问题&#xff0c;一个良好且及时的异常通知机制可以让我们在项目的维护上避免很多不必要的麻烦。本项目的开发愿景是为了给使用者在线上项目的问题排查方面能够带来帮助&#xff0c;简单配置&#xff0c;做到真正的开…

2使用教学_建水三中智能交互式液晶一体机设备投入使用

“张老师&#xff0c;一体机的使用会了吗&#xff1f;”“一体机在教学中好用吗&#xff1f;”“告别多年粉笔加黑板的教学方式&#xff0c;不用再吸入粉笔灰了。”2月25日清晨&#xff0c;建水三中的老师们议论纷纷&#xff0c;大家关注的是如何操作使用教室里安装的一体机。建…

数据结构折半查找算法C语言,数据结构C语言实现----折半查找

运行结果&#xff1a;代码如下&#xff1a;#include//数组初始化函数void Array_get(int array[],int max){printf("请输入一个数组&#xff0c;大小从低到高&#xff0c;各个数字间以空格隔开&#xff1a;");for (size_t i 0; i < max; i){scanf("%d"…

生活中的算法的实际举例_驾校学的技术,在实际生活中,你能运用自如吗?

学驾照的过程不用说&#xff0c;每一个经历过的人都有着刻骨铭心的记忆。很多人认为自己流了很多汗水&#xff0c;付出了很多精力在练车场上摸爬滚打&#xff0c;这下肯定把驾驶技术给夯实了&#xff0c;以后开车上路一点问题也没有。但是事情真的是这样吗&#xff0c;驾校学的…

470p 更换固态硬盘_联想G510换固态硬盘遇到的问题

唉&#xff01;更正一下吧&#xff01;电脑识别光驱位机械硬盘没有成功&#xff0c;出现了新问题&#xff0c;偶尔能识别出来&#xff0c;通常情况下不能识别出来&#xff0c;正在寻找解决办法中.........现在对于刚发表的这篇文章&#xff0c;有点惭愧&#xff01;二更&#x…

linux指针赋值原子,x86_64处理器的指针赋值是原子操作吗?

如题&#xff0c; x86_64处理器的指针赋值是原子操作吗&#xff1f;说实话我很讨厌参与讨论那些似乎不确定东西&#xff0c;倒不是说我对未知不敬畏&#xff0c;而是参与讨论的人大多数都是似懂非懂&#xff0c;对&#xff0c;我说的不确定性指的是参与讨论的人的认知的不确定&…

tecplot批量导出图片_批量导出Excel图片,用这招,半分钟干的活别人一整天完不成...

上个星期&#xff0c;我的一个同学向我求助。她是公司的HR&#xff0c;老板让她把员工信息表中的照片导出到文件夹中&#xff0c;然后打包发送给行政部的文员打印出来。她公司有5000多人&#xff0c;她复制粘贴了整整一个上午&#xff0c;才导出了200多张照片&#xff0c;而且还…

填充table_Excel Power Query | 向下填充的逆过程

上期内容给大家讲解了关于图表精品图表 | Excel绘制“带有阈值分割的条形图和棒棒图”的制作方法。本期给大家讲解一下关于Power Query的知识 。如下图&#xff0c;将下面的数据向下填充逆过程。即将左侧的数据转换至右侧的数据。如果是从右往左的话是直接向下填充即可&#xf…

# 解析bt文件_磁力链接和BT种子使用方法

目前用的最多的是磁力链接和BT种子&#xff0c;不过好多人并不太会使用&#xff0c;因此写个教程给大家说明一下。何为磁力链接&#xff1a;简单地说&#xff0c;磁力链接是一种特殊链接&#xff0c;但是它与传统基于文件的位置或名称的普通链接(如http://xxx)不一样&#xff0…