Android音頻(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=8192
Period count:   min=2           max=64

PCM in:
      Access:   0x000009
   Format[0]:   0x000044
   Format[1]:   00000000
 Format Name:   S16_LE, S24_LE
   Subformat:   0x000001
        Rate:   min=8000Hz      max=48000Hz
    Channels:   min=1           max=2
 Sample bits:   min=16          max=32
 Period size:   min=512         max=16384
Period count:   min=2           max=128
View Code

(2) tinymix :通過/dev/snd/controlC0節點設置獲取控制信息,進行控件的設置。比如設置鏈路,音量調節等。

# tinymix
Mixer name: 'S3C2440_UDA1341'
Number of controls: 50
ctl     type    num     name                                     value
0       INT     2       Capture Volume                           51 51
1       INT     2       Capture Volume ZC Switch                 0 0
2       BOOL    2       Capture Switch                           Off On
3       INT     2       Playback Volume                          255 255
4       INT     2       Headphone Playback Volume                121 121
5       BOOL    2       Headphone Playback ZC Switch             Off Off
6       INT     2       Speaker Playback Volume                  121 121
7       BOOL    2       Speaker Playback ZC Switch               Off Off
....
View Code
# tinymix    打印出所有的snd_kcontrol項
可以通過名字操作:
# ./tinymix "Capture Volume"         //單獲取這項的值        
Capture Volume: 51 51 (range 0->63)
# ./tinymix "Capture Volume" 10     //單設置這項的值為10      
# ./tinymix "Capture Volume"                     
Capture Volume: 10 10 (range 0->63)

也可以通過序號操作:
# ./tinymix 0                                    
Capture Volume: 10 10 (range 0->63)
# ./tinymix 0 20                                 
# ./tinymix 0                                    
Capture Volume: 20 20 (range 0->63)

驅動中對應的file_operations是:struct file_operations snd_ctl_f_ops

(3) tinycap : 使用/dev/snd/pcmC0D0c錄音
# tinycap a.wav
const struct file_operations snd_pcm_f_ops[1]

(4) tinyplay : 使用/dev/snd/pcmC0D0p播放聲音
# tinyplay a.wav

const struct file_operations snd_pcm_f_ops[0]

 

二、內核導出信息

1.devtmpfs信息(設備節點)

shell@tiny4412:/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文件信息

shell@tiny4412:/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下的
shell@tiny4412:/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的寄存器信息
shell@tiny4412:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a # ls
cache_only  cache_sync  codec_reg

③ 為消除pop音的延時時間
/sys/kernel/debug/asoc/TINY4412_UDA8960# ls
dapm_pop_time

④ dapm的bias_level
/sys/kernel/debug/asoc/TINY4412_UDA8960/dapm # cat bias_level   
Off

⑤ 系統中所有注冊的Codec,dais和Platform驅動的名字
/sys/kernel/debug/asoc # ls
S3C2440_UDA1341  codecs           dais             platforms

 

三、驅動實戰

驅動分Platform驅動,Codec驅動和Machine驅動。我們對於4412開發板主要任務就是實現Machine驅動的平台設備端,移植調試Codec驅動wm8960.c。

Platform驅動Soc廠商已經實現好了是:sound/soc/samsung/dma.c
Soc端的dai驅動Soc廠商已經實現好了是:sound/soc/samsung/i2s.c
Machine平台設備驅動驅動端sound子系統已經實現好了,是sound/soc/soc-core.c

Codec驅動和Codec端的dai驅動都在Codec驅動中實現。

(1) 調試好的Codec驅動wm8960.c,

/*
 * wm8960.c  --  WM8960 ALSA SoC Audio driver
 *
 * Author: Liam Girdwood
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <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\n", val);

    return snd_soc_update_bits(codec, WM8960_DACCTL1,
                   0x6, val);
}

static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
                 struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);

    ucontrol->value.enumerated.item[0] = wm8960->deemph;
    return 0;
}

static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
                 struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
    int deemph = ucontrol->value.enumerated.item[0];

    if (deemph > 1)
        return -EINVAL;

    wm8960->deemph = deemph;

    return wm8960_set_deemph(codec);
}


/*
補充介紹:
DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);
DECLARE_TLV_DB_LINEAR宏定義的mixer control,它的輸出隨值的變化而線性變化。 該宏的:
第一個參數是要定義變量的名字,
第二個參數是最小值,以0.01dB為單位。
第三個參數是最大值,以0.01dB為單位。
如果該control處於最小值時會做出mute時,需要把第二個參數設為TLV_DB_GAIN_MUTE。

這兩個宏實際上就是定義一個整形數組,所謂tlv,就是Type-Lenght-Value的意思,數組的第0各
元素代表數據的類型,第1個元素代表數據的長度,第三個元素和之后的元素保存該變量的數據。

*/

/*這些定義的數組adc_tlv[]會被放在snd_kcontrol_new.tlv.p中,單位是db.

DECLARE_TLV_DB_SCALE宏定義的mixer control,它所代表的值按一個固定的dB值的步長變化。
該宏的:
第一個參數是要定義變量的名字,
第二個參數是最小值,以0.01dB為單位。
第三個參數是變化的步長,也是以0.01dB為單位。
如果該control處於最小值時會做出mute時,需要把第四個參數設為1。
*/
static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);


/*
 * 這些snd_kcontrol_new定義的控制項可以通過tinymix工具讀取出來,可供app設置.
 * 這些是沒有使用DAPM的kcontrol,對比SOC_SINGLE和SOC_DAPM_SINGLE可知其內部指定
 * 的回調函數不同。
*/
static const struct snd_kcontrol_new wm8960_snd_controls[] = {
SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
         0, 63, 0, adc_tlv),
SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
    6, 1, 0),
SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
    7, 1, 0),

SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
         0, 255, 0, dac_tlv),

SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
         0, 127, 0, out_tlv),
SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
    7, 1, 0),

SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
         0, 127, 0, out_tlv),
SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
    7, 1, 0),
SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),

SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
SOC_ENUM("ADC Polarity", wm8960_enum[0]),
SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),

SOC_ENUM("DAC Polarity", wm8960_enum[2]),
SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
            wm8960_get_deemph, wm8960_put_deemph),

SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),

SOC_ENUM("ALC Function", wm8960_enum[4]),
SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
SOC_ENUM("ALC Mode", wm8960_enum[5]),
SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),

SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),

SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH,
    4, 3, 0),

SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
           WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
           WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
           WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
           WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
};

/*
 * 宏中帶DAPM的表示是使用DAPM的kcontrol.
 *
 * dapm kcontrol的put回調函數不僅僅會更新控件本身的狀態,他還會把這種變化傳
 * 遞到相鄰的dapm kcontrol,相鄰的dapm kcontrol又會傳遞這個變化到他自己相鄰
 * 的dapm kcontrol,知道音頻路徑的末端,通過這種機制,只要改變其中一個widget
 * 的連接狀態,與之相關的所有widget都會被掃描並測試一下自身是否還在有效的音頻
 * 路徑中,從而可以動態地改變自身的電源狀態,這就是dapm的精髓所在。
 */
static const struct snd_kcontrol_new wm8960_lin_boost[] = {
SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
};

/*
 * snd_kcontrol_new是定義個控件
*/
static const struct snd_kcontrol_new wm8960_lin[] = {
SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
};

static const struct snd_kcontrol_new wm8960_rin_boost[] = {
SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
};

static const struct snd_kcontrol_new wm8960_rin[] = {
SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
};

static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
};

static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
};

static const struct snd_kcontrol_new wm8960_mono_out[] = {
SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
};


/*
不同的組件初始化的成員不同:
SND_SOC_DAPM_INPUT中沒有snd_kcontrol_new
SND_SOC_DAPM_MIXER中有多個snd_kcontrol_new

將上面定義的控件轉化為widget
*/
static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("LINPUT1"),
SND_SOC_DAPM_INPUT("RINPUT1"),
SND_SOC_DAPM_INPUT("LINPUT2"),
SND_SOC_DAPM_INPUT("RINPUT2"),
SND_SOC_DAPM_INPUT("LINPUT3"),
SND_SOC_DAPM_INPUT("RINPUT3"),

SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0),

/*
 * 若arg2=SND_SOC_NOPM則表示此widget不具備電源屬性,但是mux的
 * 切換會影響到與之相連的其它具備電源屬性的電源狀態。
 */
SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
           wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
           wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),

SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
           wm8960_lin, ARRAY_SIZE(wm8960_lin)),
SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
           wm8960_rin, ARRAY_SIZE(wm8960_rin)),

SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),
SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0),

SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),

SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
    &wm8960_loutput_mixer[0], ARRAY_SIZE(wm8960_loutput_mixer)),
SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
    &wm8960_routput_mixer[0], ARRAY_SIZE(wm8960_routput_mixer)),

SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),

SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),

SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),

SND_SOC_DAPM_OUTPUT("SPK_LP"),
SND_SOC_DAPM_OUTPUT("SPK_LN"),
SND_SOC_DAPM_OUTPUT("HP_L"),
SND_SOC_DAPM_OUTPUT("HP_R"),
SND_SOC_DAPM_OUTPUT("SPK_RP"),
SND_SOC_DAPM_OUTPUT("SPK_RN"),
SND_SOC_DAPM_OUTPUT("OUT3"),
};

static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
    &wm8960_mono_out[0], ARRAY_SIZE(wm8960_mono_out)),
};

/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
    SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
};

/*
route的source和sink成員都是widget的名字,control成員是兩個widget之間連接通過的開關
的snd_kcontrol, 若是兩個widget之間有開關,那么control成員就是這個開關的名字。若是沒
有開關而是直連的,那么control成員就設置為NULL,此時這個連接成為靜態連接。

route會被解析成path,使用snd_soc_dapm_path結構表示。

從這個結構體里面可以看出聲道的連接路由。

*/
static const struct snd_soc_dapm_route audio_paths[] = {
    { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
    { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
    { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },

    { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", },
    { "Left Input Mixer", NULL, "LINPUT1", },  /* Really Boost Switch */
    { "Left Input Mixer", NULL, "LINPUT2" },
    { "Left Input Mixer", NULL, "LINPUT3" },

    { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
    { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
    { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },

    { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", },
    { "Right Input Mixer", NULL, "RINPUT1", },  /* Really Boost Switch */
    { "Right Input Mixer", NULL, "RINPUT2" },
    { "Right Input Mixer", NULL, "LINPUT3" },

    { "Left ADC", NULL, "Left Input Mixer" },
    { "Right ADC", NULL, "Right Input Mixer" },

    { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
    { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} ,
    { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },

    { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
    { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } ,
    { "Right Output Mixer", "PCM Playback Switch", "Right DAC" },

    { "LOUT1 PGA", NULL, "Left Output Mixer" },
    { "ROUT1 PGA", NULL, "Right Output Mixer" },

    { "HP_L", NULL, "LOUT1 PGA" },
    { "HP_R", NULL, "ROUT1 PGA" },

    { "Left Speaker PGA", NULL, "Left Output Mixer" },
    { "Right Speaker PGA", NULL, "Right Output Mixer" },

    { "Left Speaker Output", NULL, "Left Speaker PGA" },
    { "Right Speaker Output", NULL, "Right Speaker PGA" },

    { "SPK_LN", NULL, "Left Speaker Output" },
    { "SPK_LP", NULL, "Left Speaker Output" },
    { "SPK_RN", NULL, "Right Speaker Output" },
    { "SPK_RP", NULL, "Right Speaker Output" },
};

static const struct snd_soc_dapm_route audio_paths_out3[] = {
    { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
    { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },

    { "OUT3", NULL, "Mono Output Mixer", }
};

static const struct snd_soc_dapm_route audio_paths_capless[] = {
    { "HP_L", NULL, "OUT3 VMID" },
    { "HP_R", NULL, "OUT3 VMID" },

    { "OUT3 VMID", NULL, "Left Output Mixer" },
    { "OUT3 VMID", NULL, "Right Output Mixer" },
};

static int wm8960_add_widgets(struct snd_soc_codec *codec)
{
    struct wm8960_data *pdata = codec->dev->platform_data;
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
    struct snd_soc_dapm_context *dapm = &codec->dapm;
    struct snd_soc_dapm_widget *w;

    snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, ARRAY_SIZE(wm8960_dapm_widgets));

    snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));

    /* In capless mode OUT3 is used to provide VMID for the
     * headphone outputs, otherwise it is used as a mono mixer.
     */
    if (pdata && pdata->capless) {
        snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless,
                      ARRAY_SIZE(wm8960_dapm_widgets_capless));

        snd_soc_dapm_add_routes(dapm, audio_paths_capless,
                    ARRAY_SIZE(audio_paths_capless));
    } else {
        snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3,
                      ARRAY_SIZE(wm8960_dapm_widgets_out3));

        snd_soc_dapm_add_routes(dapm, audio_paths_out3,
                    ARRAY_SIZE(audio_paths_out3));
    }

    /* We need to power up the headphone output stage out of
     * sequence for capless mode.  To save scanning the widget
     * list each time to find the desired power state do so now
     * and save the result.
     */
    list_for_each_entry(w, &codec->card->widgets, list) {
        if (w->dapm != &codec->dapm)
            continue;
        if (strcmp(w->name, "LOUT1 PGA") == 0)
            wm8960->lout1 = w;
        if (strcmp(w->name, "ROUT1 PGA") == 0)
            wm8960->rout1 = w;
        if (strcmp(w->name, "OUT3 VMID") == 0)
            wm8960->out3 = w;
    }

    return 0;
}

static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
        unsigned int fmt)
{
    struct snd_soc_codec *codec = codec_dai->codec;
    u16 iface = 0;

    /* set master/slave audio interface */
    switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
    case SND_SOC_DAIFMT_CBM_CFM:
        iface |= 0x0040;
        break;
    case SND_SOC_DAIFMT_CBS_CFS:
        break;
    default:
        return -EINVAL;
    }

    /* interface format */
    switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
    case SND_SOC_DAIFMT_I2S:
        iface |= 0x0002;
        break;
    case SND_SOC_DAIFMT_RIGHT_J:
        break;
    case SND_SOC_DAIFMT_LEFT_J:
        iface |= 0x0001;
        break;
    case SND_SOC_DAIFMT_DSP_A:
        iface |= 0x0003;
        break;
    case SND_SOC_DAIFMT_DSP_B:
        iface |= 0x0013;
        break;
    default:
        return -EINVAL;
    }

    /* clock inversion */
    switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
    case SND_SOC_DAIFMT_NB_NF:
        break;
    case SND_SOC_DAIFMT_IB_IF:
        iface |= 0x0090;
        break;
    case SND_SOC_DAIFMT_IB_NF:
        iface |= 0x0080;
        break;
    case SND_SOC_DAIFMT_NB_IF:
        iface |= 0x0010;
        break;
    default:
        return -EINVAL;
    }

    /* set iface */
    snd_soc_write(codec, WM8960_IFACE1, iface);
    return 0;
}

static struct {
    int rate;
    unsigned int val;
} alc_rates[] = {
    { 48000, 0 },
    { 44100, 0 },
    { 32000, 1 },
    { 22050, 2 },
    { 24000, 2 },
    { 16000, 3 },
    { 11250, 4 },
    { 12000, 4 },
    {  8000, 5 },
};


/*這個params從何而來???*/
static int wm8960_hw_params(struct snd_pcm_substream *substream,
                struct snd_pcm_hw_params *params,
                struct snd_soc_dai *dai)
{
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    struct snd_soc_codec *codec = rtd->codec;
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
    u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
    int i;

    /* bit size */
    switch (params_format(params)) {
    case SNDRV_PCM_FORMAT_S16_LE:
        break;
    case SNDRV_PCM_FORMAT_S20_3LE:
        iface |= 0x0004;
        break;
    case SNDRV_PCM_FORMAT_S24_LE:
        iface |= 0x0008;
        break;
    }

    /* Update filters for the new rate */
    if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
        wm8960->playback_fs = params_rate(params);
        wm8960_set_deemph(codec);
    } else {
        for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
            if (alc_rates[i].rate == params_rate(params))
                snd_soc_update_bits(codec,
                            WM8960_ADDCTL3, 0x7,
                            alc_rates[i].val);
    }

    /* set iface */
    snd_soc_write(codec, WM8960_IFACE1, iface);
    return 0;
}

static int wm8960_mute(struct snd_soc_dai *dai, int mute)
{
    struct snd_soc_codec *codec = dai->codec;
    u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7;

    if (mute)
        snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
    else
        snd_soc_write(codec, WM8960_DACCTL1, mute_reg);
    return 0;
}

static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
                      enum snd_soc_bias_level level)
{
    u16 reg;

    switch (level) {
    case SND_SOC_BIAS_ON:
        break;

    case SND_SOC_BIAS_PREPARE:
        /* Set VMID to 2x50k */
        reg = snd_soc_read(codec, WM8960_POWER1);
        reg &= ~0x180;
        reg |= 0x80;
        snd_soc_write(codec, WM8960_POWER1, reg);
        break;

    case SND_SOC_BIAS_STANDBY:
        if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
            /* Enable anti-pop features */
            snd_soc_write(codec, WM8960_APOP1,
                      WM8960_POBCTRL | WM8960_SOFT_ST |
                      WM8960_BUFDCOPEN | WM8960_BUFIOEN);

            /* Enable & ramp VMID at 2x50k */
            reg = snd_soc_read(codec, WM8960_POWER1);
            reg |= 0x80;
            snd_soc_write(codec, WM8960_POWER1, reg);
            msleep(100);

            /* Enable VREF */
            snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF);

            /* Disable anti-pop features */
            snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
        }

        /* Set VMID to 2x250k */
        reg = snd_soc_read(codec, WM8960_POWER1);
        reg &= ~0x180;
        reg |= 0x100;
        snd_soc_write(codec, WM8960_POWER1, reg);
        break;

    case SND_SOC_BIAS_OFF:
        /* Enable anti-pop features */
        snd_soc_write(codec, WM8960_APOP1,
                 WM8960_POBCTRL | WM8960_SOFT_ST |
                 WM8960_BUFDCOPEN | WM8960_BUFIOEN);

        /* Disable VMID and VREF, let them discharge */
        snd_soc_write(codec, WM8960_POWER1, 0);
        msleep(600);
        break;
    }

    codec->dapm.bias_level = level;

    return 0;
}

static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
                     enum snd_soc_bias_level level)
{
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
    int reg;

    switch (level) {
    case SND_SOC_BIAS_ON:
        break;

    case SND_SOC_BIAS_PREPARE:
        switch (codec->dapm.bias_level) {
        case SND_SOC_BIAS_STANDBY:
            /* Enable anti pop mode */
            snd_soc_update_bits(codec, WM8960_APOP1,
                        WM8960_POBCTRL | WM8960_SOFT_ST |
                        WM8960_BUFDCOPEN,
                        WM8960_POBCTRL | WM8960_SOFT_ST |
                        WM8960_BUFDCOPEN);

            /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */
            reg = 0;
            if (wm8960->lout1 && wm8960->lout1->power)
                reg |= WM8960_PWR2_LOUT1;
            if (wm8960->rout1 && wm8960->rout1->power)
                reg |= WM8960_PWR2_ROUT1;
            if (wm8960->out3 && wm8960->out3->power)
                reg |= WM8960_PWR2_OUT3;
            snd_soc_update_bits(codec, WM8960_POWER2,
                        WM8960_PWR2_LOUT1 |
                        WM8960_PWR2_ROUT1 |
                        WM8960_PWR2_OUT3, reg);

            /* Enable VMID at 2*50k */
            snd_soc_update_bits(codec, WM8960_POWER1,
                        WM8960_VMID_MASK, 0x80);

            /* Ramp */
            msleep(100);

            /* Enable VREF */
            snd_soc_update_bits(codec, WM8960_POWER1,
                        WM8960_VREF, WM8960_VREF);

            msleep(100);
            break;

        case SND_SOC_BIAS_ON:
            /* Enable anti-pop mode */
            snd_soc_update_bits(codec, WM8960_APOP1,
                        WM8960_POBCTRL | WM8960_SOFT_ST |
                        WM8960_BUFDCOPEN,
                        WM8960_POBCTRL | WM8960_SOFT_ST |
                        WM8960_BUFDCOPEN);

            /* Disable VMID and VREF */
            snd_soc_update_bits(codec, WM8960_POWER1,
                        WM8960_VREF | WM8960_VMID_MASK, 0);
            break;

        default:
            break;
        }
        break;

    case SND_SOC_BIAS_STANDBY:
        switch (codec->dapm.bias_level) {
        case SND_SOC_BIAS_PREPARE:
            /* Disable HP discharge */
            snd_soc_update_bits(codec, WM8960_APOP2,
                        WM8960_DISOP | WM8960_DRES_MASK,
                        0);

            /* Disable anti-pop features */
            snd_soc_update_bits(codec, WM8960_APOP1,
                        WM8960_POBCTRL | WM8960_SOFT_ST |
                        WM8960_BUFDCOPEN,
                        WM8960_POBCTRL | WM8960_SOFT_ST |
                        WM8960_BUFDCOPEN);
            break;

        default:
            break;
        }
        break;

    case SND_SOC_BIAS_OFF:
        break;
    }

    codec->dapm.bias_level = level;

    return 0;
}

/* PLL divisors */
struct _pll_div {
    u32 pre_div:1;
    u32 n:4;
    u32 k:24;
};

/* The size in bits of the pll divide multiplied by 10
 * to allow rounding later */
#define FIXED_PLL_SIZE ((1 << 24) * 10)

static int pll_factors(unsigned int source, unsigned int target,
               struct _pll_div *pll_div)
{
    unsigned long long Kpart;
    unsigned int K, Ndiv, Nmod;

    pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);

    /* Scale up target to PLL operating frequency */
    target *= 4;

    Ndiv = target / source;
    if (Ndiv < 6) {
        source >>= 1;
        pll_div->pre_div = 1;
        Ndiv = target / source;
    } else
        pll_div->pre_div = 0;

    if ((Ndiv < 6) || (Ndiv > 12)) {
        pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv);
        return -EINVAL;
    }

    pll_div->n = Ndiv;
    Nmod = target % source;
    Kpart = FIXED_PLL_SIZE * (long long)Nmod;

    do_div(Kpart, source);

    K = Kpart & 0xFFFFFFFF;

    /* Check if we need to round */
    if ((K % 10) >= 5)
        K += 5;

    /* Move down to proper range now rounding is done */
    K /= 10;

    pll_div->k = K;

    pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
         pll_div->n, pll_div->k, pll_div->pre_div);

    return 0;
}

static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
        int source, unsigned int freq_in, unsigned int freq_out)
{
    struct snd_soc_codec *codec = codec_dai->codec;
    u16 reg;
    static struct _pll_div pll_div;
    int ret;

    if (freq_in && freq_out) {
        ret = pll_factors(freq_in, freq_out, &pll_div);
        if (ret != 0)
            return ret;
    }

    /* Disable the PLL: even if we are changing the frequency the
     * PLL needs to be disabled while we do so. */
    snd_soc_write(codec, WM8960_CLOCK1,
             snd_soc_read(codec, WM8960_CLOCK1) & ~1);
    snd_soc_write(codec, WM8960_POWER2,
             snd_soc_read(codec, WM8960_POWER2) & ~1);

    if (!freq_in || !freq_out)
        return 0;

    reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f;
    reg |= pll_div.pre_div << 4;
    reg |= pll_div.n;

    if (pll_div.k) {
        reg |= 0x20;

        snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
        snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
        snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
    }
    snd_soc_write(codec, WM8960_PLL1, reg);

    /* Turn it on */
    snd_soc_write(codec, WM8960_POWER2,
             snd_soc_read(codec, WM8960_POWER2) | 1);
    msleep(250);
    snd_soc_write(codec, WM8960_CLOCK1,
             snd_soc_read(codec, WM8960_CLOCK1) | 1);

    return 0;
}

static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
        int div_id, int div)
{
    struct snd_soc_codec *codec = codec_dai->codec;
    u16 reg;

    switch (div_id) {
    case WM8960_SYSCLKDIV:
        reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9;
        snd_soc_write(codec, WM8960_CLOCK1, reg | div);
        break;
    case WM8960_DACDIV:
        reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7;
        snd_soc_write(codec, WM8960_CLOCK1, reg | div);
        break;
    case WM8960_OPCLKDIV:
        reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f;
        snd_soc_write(codec, WM8960_PLL1, reg | div);
        break;
    case WM8960_DCLKDIV:
        reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f;
        snd_soc_write(codec, WM8960_CLOCK2, reg | div);
        break;
    case WM8960_TOCLKSEL:
        reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd;
        snd_soc_write(codec, WM8960_ADDCTL1, reg | div);
        break;
    default:
        return -EINVAL;
    }

    return 0;
}

static int wm8960_set_bias_level(struct snd_soc_codec *codec,
                 enum snd_soc_bias_level level)
{
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);

    return wm8960->set_bias_level(codec, level);
}

#define WM8960_RATES SNDRV_PCM_RATE_8000_48000

#define WM8960_FORMATS \
    (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
    SNDRV_PCM_FMTBIT_S24_LE)

static struct snd_soc_dai_ops wm8960_dai_ops = {
    .hw_params = wm8960_hw_params,
    .digital_mute = wm8960_mute,
    .set_fmt = wm8960_set_dai_fmt,
    .set_clkdiv = wm8960_set_dai_clkdiv,
    .set_pll = wm8960_set_dai_pll,
};

/*這個是dai接口的驅動*/
static struct snd_soc_dai_driver wm8960_dai = {
    /*
     * 這個name會傳遞給snd_soc_codec.name, 后者會和
     * codec_conf->dev_name匹配
     */
    .name = "wm8960-hifi",
    .playback = {
        .stream_name = "Playback",
        .channels_min = 1,
        .channels_max = 2,
        .rates = WM8960_RATES,
        .formats = WM8960_FORMATS,},
    .capture = {
        .stream_name = "Capture",
        .channels_min = 1,
        .channels_max = 2,
        .rates = WM8960_RATES,
        .formats = WM8960_FORMATS,},
    .ops = &wm8960_dai_ops,
    .symmetric_rates = 1,
};

static int wm8960_suspend(struct snd_soc_codec *codec, pm_message_t state)
{
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);

    wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
    return 0;
}

static int wm8960_resume(struct snd_soc_codec *codec)
{
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
    int i;
    u8 data[2];
    u16 *cache = codec->reg_cache;

    /* Sync reg_cache with the hardware */
    for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) {
        data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
        data[1] = cache[i] & 0x00ff;
        codec->hw_write(codec->control_data, data, 2);
    }

    wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);
    return 0;
}

static int wm8960_probe(struct snd_soc_codec *codec)
{
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
    struct wm8960_data *pdata = dev_get_platdata(codec->dev);
    int ret;
    u16 reg;

    wm8960->set_bias_level = wm8960_set_bias_level_out3;
    codec->control_data = wm8960->control_data;

    if (!pdata) {
        dev_warn(codec->dev, "No platform data supplied\n");
    } else {
        if (pdata->dres > WM8960_DRES_MAX) {
            dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres);
            pdata->dres = 0;
        }

        if (pdata->capless)
            wm8960->set_bias_level = wm8960_set_bias_level_capless;
    }

    /*選中codec的控制接口*/
    ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type);
    if (ret < 0) {
        dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
        return ret;
    }

    ret = wm8960_reset(codec);
    if (ret < 0) {
        dev_err(codec->dev, "Failed to issue reset\n");
        return ret;
    }

    wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);

    /*對芯片寄存器進行初始化*/
    /* Latch the update bits */
    reg = snd_soc_read(codec, WM8960_LINVOL);
    snd_soc_write(codec, WM8960_LINVOL, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_RINVOL);
    snd_soc_write(codec, WM8960_RINVOL, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_LADC);
    snd_soc_write(codec, WM8960_LADC, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_RADC);
    snd_soc_write(codec, WM8960_RADC, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_LDAC);
    snd_soc_write(codec, WM8960_LDAC, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_RDAC);
    snd_soc_write(codec, WM8960_RDAC, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_LOUT1);
    snd_soc_write(codec, WM8960_LOUT1, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_ROUT1);
    snd_soc_write(codec, WM8960_ROUT1, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_LOUT2);
    snd_soc_write(codec, WM8960_LOUT2, reg | 0x100);
    reg = snd_soc_read(codec, WM8960_ROUT2);
    snd_soc_write(codec, WM8960_ROUT2, reg | 0x100);

    /* 如果不想每次上電錄音之前都要執行這些命令, 就在此設置寄存器:
     * tinymix "Capture Switch" 0
     * tinymix "Left Input Mixer Boost Switch" 1
     */
    /* Capture Switch off  */
    snd_soc_update_bits(codec, WM8960_LINVOL, (1<<7), (0<<7));

    /* Left Input Mixer Boost Switch */
    snd_soc_update_bits(codec, WM8960_LINPATH, (1<<3), (1<<3));

    /*初始化controls和widgets*/
    snd_soc_add_controls(codec, wm8960_snd_controls,
                     ARRAY_SIZE(wm8960_snd_controls));
    wm8960_add_widgets(codec);

    return 0;
}

/* power down chip */
static int wm8960_remove(struct snd_soc_codec *codec)
{
    struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);

    wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF);
    return 0;
}


/*這個是codec芯片的驅動*/
static struct snd_soc_codec_driver soc_codec_dev_wm8960 = {
    .probe =    wm8960_probe,
    .remove =    wm8960_remove,
    .suspend =    wm8960_suspend,
    .resume =    wm8960_resume,
    .set_bias_level = wm8960_set_bias_level,
    .reg_cache_size = ARRAY_SIZE(wm8960_reg),
    .reg_word_size = sizeof(u16),
    .reg_cache_default = wm8960_reg,
};

/*這是主機i2c控制接口的驅動*/
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
                      const struct i2c_device_id *id)
{
    struct wm8960_priv *wm8960;
    int ret;

    wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
    if (wm8960 == NULL)
        return -ENOMEM;

    i2c_set_clientdata(i2c, wm8960);
    /*指定對codec芯片的控制接口是i2c*/
    wm8960->control_type = SND_SOC_I2C;
    wm8960->control_data = i2c;

    /*
     * 注冊一個codec需要提供snd_soc_codec_driver和snd_soc_dai_driver
     * 同時可以有多個dai
     * 這個函數內部會實例化一個snd_soc_codec,若全局鏈表上有數據還會去實例化
     * 一個snd_soc_card(這個結構在其它地方也能實例化)
    */
    ret = snd_soc_register_codec(&i2c->dev,
            &soc_codec_dev_wm8960, &wm8960_dai, 1);
    if (ret < 0)
        kfree(wm8960);
    return ret;
}

static __devexit int wm8960_i2c_remove(struct i2c_client *client)
{
    snd_soc_unregister_codec(&client->dev);
    kfree(i2c_get_clientdata(client));
    return 0;
}

/*設備端在mach-tiny4412.c中定義*/
static const struct i2c_device_id wm8960_i2c_id[] = {
    { "wm8960", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);

/*它是怎么通過名字匹配到設備的呢?*/
static struct i2c_driver wm8960_i2c_driver = {
    .driver = {
        .name = "wm8960-codec",
        .owner = THIS_MODULE,
    },
    .probe =    wm8960_i2c_probe,
    .remove =   __devexit_p(wm8960_i2c_remove),
    .id_table = wm8960_i2c_id,
};
#endif

static int __init wm8960_modinit(void)
{
    int ret = 0;
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
    ret = i2c_add_driver(&wm8960_i2c_driver);
    if (ret != 0) {
        printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n",
               ret);
    }
#endif
    return ret;
}
module_init(wm8960_modinit);

static void __exit wm8960_exit(void)
{
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
    i2c_del_driver(&wm8960_i2c_driver);
#endif
}
module_exit(wm8960_exit);

MODULE_DESCRIPTION("ASoC WM8960 driver");
MODULE_AUTHOR("Liam Girdwood");
MODULE_LICENSE("GPL");
View Code

 

(2) Machine平台設備驅動設備端(驅動端是: sound/soc/soc-core.c)

#include <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\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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM