Linux声卡驱动|4——音频驱动实战

Linux声卡驱动|4——音频驱动实战

一、应用测试工具的使用

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=8192Period count:   min=2           max=64PCM 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=16384Period count:   min=2           max=128

View Code

(2) tinymix :通过/dev/snd/controlC0节点设置获取控制信息,进行控件的设置。比如设置链路,音量调节等。

# tinymixMixer name: S3C2440_UDA1341Number of controls: 50ctl     type    num     name                                     value0       INT     2       Capture Volume                           51 511       INT     2       Capture Volume ZC Switch                 0 02       BOOL    2       Capture Switch                           Off On3       INT     2       Playback Volume                          255 2554       INT     2       Headphone Playback Volume                121 1215       BOOL    2       Headphone Playback ZC Switch             Off Off6       INT     2       Speaker Playback Volume                  121 1217       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 0crw-rw----    1 1000     1005      116,   0 Jan  1 12:00 controlC0crw-rw----    1 1000     1005      116,  24 Jan  1 12:00 pcmC0D0ccrw-rw----    1 1000     1005      116,  16 Jan  1 12:00 pcmC0D0pcrw-rw----    1 1000     1005      116,  25 Jan  1 12:00 pcmC0D1ccrw-rw----    1 1000     1005      116,  17 Jan  1 12:00 pcmC0D1pcrw-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 # lsHP_L                  Left Speaker Output   Right Boost MixerHP_R                  Left Speaker PGA      Right DACLINPUT1               MICB                  Right Input MixerLINPUT2               Mic Onboard           Right Output MixerLINPUT3               Mono Output Mixer     Right Speaker OutputLOUT1 PGA             OUT3                  Right Speaker PGALeft ADC              RINPUT1               SPK_LNLeft Boost Mixer      RINPUT2               SPK_LPLeft DAC              RINPUT3               SPK_RNLeft Input Mixer      ROUT1 PGA             SPK_RPLeft Output Mixer     Right ADC             bias_level② Codec的寄存器信息[email protected]:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a # lscache_only  cache_sync  codec_reg③ 为消除pop音的延时时间/sys/kernel/debug/asoc/TINY4412_UDA8960# lsdapm_pop_time④ dapm的bias_level/sys/kernel/debug/asoc/TINY4412_UDA8960/dapm # cat bias_level   Off⑤ 系统中所有注册的Codec,dais和Platform驱动的名字/sys/kernel/debug/asoc # lsS3C2440_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 <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/pm.h>#include <linux/i2c.h>#include <linux/platform_device.h>#include <linux/slab.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/soc.h>#include <sound/initval.h>#include <sound/tlv.h>#include <sound/wm8960.h>#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", 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_newSND_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", 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", 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",         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");    } else {        if (pdata->dres > WM8960_DRES_MAX) {            dev_err(codec->dev, "Invalid DRES: %d", 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", ret);        return ret;    }    ret = wm8960_reset(codec);    if (ret < 0) {        dev_err(codec->dev, "Failed to issue reset");        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,};#endifstatic 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",               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 <linux/clk.h>#include <linux/gpio.h>#include <linux/module.h>#include <sound/soc.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#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", __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!");        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;}/* 参考soundsocsamsungs3c24xx_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",    /*     * 这个名字用于和soundsocsamsungI2s.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相关)驱动,指定这个名字是要求     * 使用soundsocsamsungdma.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

推荐阅读

    探探语言设置|探探怎么设置语言

    探探语言设置|探探怎么设置语言,,1. 探探怎么设置语言打开探探软件,然后就有消息提示的红点,点开就行了!其实这些软件都是挺简单的操作的,都是

    git设置编码|git语言设置

    git设置编码|git语言设置,,git设置编码点击cap4j搜索从git直接链接上拉代码。git语言设置Git是一个开源的分布式版本控制系统,可以有效、高

    01-Vue项目实战-网易云音乐-准备工作

    01-Vue项目实战-网易云音乐-准备工作,网易,项目,前言在接下来的一段时间,我会仿照网易云音乐,利用Vue开发一个移动端的网易云音乐项目。在做

    区域语言设置|区域语言设置工具

    区域语言设置|区域语言设置工具,,区域语言设置工具你好,大致的方法如下,可以参考:1、按下键盘的windows 图标,再开始菜单中单击“设置”;出现的

    c4d语言设置|c4d汉语设置

    c4d语言设置|c4d汉语设置,,1. c4d汉语设置mac版的C4D是这样的,中文字体是有的,但是是以拼音的形式存在,比如黑体就是ht。中文字体以拼音方式

    电脑宣传语|电脑宣传语言

    电脑宣传语|电脑宣传语言,,1. 电脑宣传语言1.我做好了与你过一辈子的打算,也做好了你随时要走的准备,2.每段青春都会苍老,但我希望记忆里的你

    office语言设置|微软office语言设置

    office语言设置|微软office语言设置,,微软office语言设置一、首先点击桌面左下角“WIN键”。二、弹出选项内点击“所有程序”。三、接着点