日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

Android音频(4)——音频驱动实战

發布時間:2023/12/25 综合教程 56 生活家
生活随笔 收集整理的這篇文章主要介紹了 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
", 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
", 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,
};
#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
",
               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

總結

以上是生活随笔為你收集整理的Android音频(4)——音频驱动实战的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

日韩免费av网址 | 欧美日韩99| 久久露脸国产精品 | 婷婷色站| 日日夜夜综合网 | 成人在线视频在线观看 | 久草在线视频网站 | 丁香综合 | 亚洲天堂精品视频 | 激情网站网址 | 国产一区二区在线影院 | 伊人五月天 | 国产 日韩 欧美 中文 在线播放 | 91av美女| 探花视频免费观看 | 免费黄色小网站 | 精品免费国产一区二区三区四区 | 中文字幕免费看 | av黄色免费看 | av夜夜操 | 久久99中文字幕 | 日韩啪啪小视频 | 久草在线免费资源站 | 91视频中文字幕 | 欧美一区二区三区在线 | 98精品国产自产在线观看 | 狠日日| 婷婷色综 | 欧美一级裸体视频 | 91系列在线 | 国产日韩精品在线 | 亚洲一区二区精品在线 | 狠狠色丁香婷婷 | 欧美精品资源 | 五月婷婷综合激情网 | 美女网站视频免费黄 | 成人精品影视 | 久久免费视频在线观看 | 免费精品久久久 | 日日干网| 欧美日韩免费在线观看视频 | 一区二区三区电影 | 国产99视频在线观看 | 国产精品麻豆99久久久久久 | 亚洲最大成人网4388xx | 三上悠亚一区二区在线观看 | 亚洲一片黄 | 一区免费观看 | 91精品国产91p65 | 精品视频久久 | 精品国产成人在线 | 五月天久久久久久 | 久久tv| 国产精品久久久久久久久免费看 | 最新av网址在线 | 日韩一级电影网站 | 亚洲天天在线 | 天天操天天干天天插 | 日韩在线电影观看 | 91成人网页版 | 日本在线视频网址 | 欧美性春潮| 亚洲综合视频在线播放 | 麻豆影视在线免费观看 | 在线 国产 亚洲 欧美 | 精品久久久久久综合 | 黄色a级片在线观看 | 久久视讯 | 成人久久18免费网站 | 欧美日韩xxx| 五月综合激情婷婷 | 正在播放国产一区 | 一区二区三区手机在线观看 | 操高跟美女 | 81国产精品久久久久久久久久 | 丁香六月天 | 美女视频网站久久 | 天天干天天在线 | 亚洲免费婷婷 | 国产资源在线免费观看 | 亚洲电影久久久 | 久久男人免费视频 | 麻豆视频在线播放 | 又色又爽的网站 | 在线观看免费成人av | 91传媒91久久久 | 99精品视频免费观看视频 | 成人h电影 | 日韩欧美在线中文字幕 | 欧美一区二区三区在线 | 国产精品日韩在线观看 | 久久久久久久网 | 免费看片在线观看 | 久久国产免费 | 欧美成人精品在线 | 中文在线免费观看 | 激情在线免费视频 | 狠狠干夜夜爽 | 在线观看视频国产 | 亚洲激情校园春色 | 色婷婷免费视频 | 亚洲四虎影院 | 国产在线播放一区二区三区 | 视频福利在线 | 亚洲精品网址在线观看 | 午夜美女福利 | 99re视频在线观看 | 波多野结衣一区三区 | 午夜黄色大片 | 国产高清久久久久 | 久久久久激情视频 | 欧美一二三区播放 | 欧美亚洲一级片 | 免费网址在线播放 | 国内精品久久久久久久久久清纯 | 久久久午夜精品理论片中文字幕 | 成人av在线播放网站 | 麻豆一区二区三区视频 | 久久黄色片 | 国产视频一区二区三区在线 | 在线观看完整版 | 色婷婷福利 | 国产精品成人aaaaa网站 | 午夜影院在线观看18 | 亚洲欧洲av在线 | 亚洲国产精品日韩 | 中文字幕日韩有码 | 日韩久久精品一区二区 | 午夜免费视频网站 | 成人黄色av免费在线观看 | 在线国产视频一区 | 日韩av一区二区三区 | 精品久久久久久久久久久久久久久久 | 99久久婷婷国产综合亚洲 | 欧亚日韩精品一区二区在线 | 久久国产精品视频观看 | 午夜久久成人 | 亚洲成a人片综合在线 | 亚洲经典精品 | 欧美在线aa | 国产一二三在线视频 | 欧美国产亚洲精品久久久8v | 亚洲精品国产精品国自 | 色99之美女主播在线视频 | 99久久精品国产毛片 | 午夜在线观看一区 | 一区二区三区日韩视频在线观看 | 国产一级大片在线观看 | 久久亚洲欧美日韩精品专区 | 中文字幕色网站 | 91看毛片| 亚洲国产美女精品久久久久∴ | 国产黑丝一区二区三区 | 国产精品一区二区三区四区在线观看 | 欧美另类sm图片 | 99热只有精品在线观看 | 欧美精品二 | 国产理论在线 | 久久草网| av资源免费观看 | 免费男女羞羞的视频网站中文字幕 | 国产激情小视频在线观看 | 欧洲成人免费 | 99免费观看视频 | 免费大片黄在线 | 又黄又爽又湿又无遮挡的在线视频 | 99免费视频| 国际精品久久 | 激情五月五月婷婷 | 国产99久久久国产精品成人免费 | 亚洲禁18久人片 | 天天操天天射天天 | 69国产盗摄一区二区三区五区 | 在线精品亚洲一区二区 | 在线观看国产www | 日韩一区正在播放 | 欧美综合色在线图区 | 中文字幕在线一区观看 | 特级毛片在线 | www.国产毛片 | 蜜臀av网址 | 国产精品video爽爽爽爽 | 国产亚洲成人精品 | 国产精品久久一区二区三区, | 一区二区三区不卡在线 | 亚洲一级片在线观看 | 99综合电影在线视频 | 一级α片免费看 | 日韩av一区二区在线影视 | 中文字幕资源站 | 国产视频一区在线免费观看 | 亚洲成a人片综合在线 | 国产日产精品一区二区三区四区 | 久久久精品国产免费观看同学 | 色婷婷啪啪免费在线电影观看 | 热久久影视 | 日韩欧美一区二区三区免费观看 | 色婷五月| 色婷婷综合五月 | 久久久一本精品99久久精品 | 综合久久久 | 黄色成人av网址 | 亚洲视频网站在线观看 | 91av网站在线观看 | 91视频高清完整版 | 成年人网站免费观看 | 中文字幕中文字幕在线中文字幕三区 | 中文字幕无吗 | a黄色大片 | 亚洲国产久 | 国产精品久久久av久久久 | 国产h在线观看 | 欧美日韩国产三级 | 国产精品久久久久久久久久东京 | 成人av免费 | 超碰97久久| 五月婷婷中文字幕 | 久久精品视频日本 | 久久五月精品 | 亚洲国产三级在线 | 日韩 精品 一区 国产 麻豆 | 欧美精品久久久久久久久免 | 成人在线免费观看视视频 | 波多野结衣在线视频一区 | 亚洲黄色在线免费观看 | 人人爽人人澡人人添人人人人 | 99久久夜色精品国产亚洲96 | 精品视频在线免费观看 | 99热最新精品| 丁香色婷 | 成人黄色小说网 | 丁香六月在线观看 | 中文字幕在线观看1 | 日本高清中文字幕有码在线 | 久久国产精品一区二区 | 国产精品一区二区在线免费观看 | 久久精品中文视频 | 黄色资源网站 | 亚洲综合视频在线播放 | 日韩视频在线播放 | 国产黄a三级三级 | 精品国产免费看 | 精品美女久久 | 久久久五月婷婷 | 国产a网站 | 国产精品3| 国产色道 | 精品美女视频 | 久久99在线| 国产一区二区三区高清播放 | 午夜体验区 | 99视频99| 69精品在线观看 | 精品久久久一区二区 | 激情婷婷色 | 国产精品福利在线观看 | 国产精品美女久久久久久 | 日b视频国产 | 亚洲另类在线视频 | 五月婷网| 国产做a爱一级久久 | 日本韩国精品一区二区在线观看 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 中文字幕一区二区在线播放 | 97超碰成人 | 中文在线中文资源 | 久久精品一区二区三区国产主播 | 欧美日韩在线视频一区二区 | 国产在线自 | 国产成人精品不卡 | 青青河边草免费 | 99国产免费网址 | 2021av在线| 亚洲精品久久激情国产片 | 超碰在线观看97 | 国产高清视频色在线www | 久久99久久99精品免观看粉嫩 | 日本夜夜草视频网站 | 精品一区 在线 | 蜜桃av人人夜夜澡人人爽 | 免费观看丰满少妇做爰 | 久久在线精品视频 | 日韩精品在线播放 | 欧美特一级 | 最近日本韩国中文字幕 | 99精品偷拍视频一区二区三区 | 国产破处精品 | 久久久久久久久久亚洲精品 | 天天色中文 | 久久久久亚洲精品男人的天堂 | 亚洲在线成人精品 | 91在线看网站 | 国产精品第52页 | 久久久久久草 | 色婷婷综合久久久 | 麻豆国产精品va在线观看不卡 | 免费视频色 | 国产精品久久久久久久免费大片 | 成年人免费在线观看网站 | 日韩高清成人在线 | 日韩在线在线 | 91爱爱视频 | 麻花传媒mv免费观看 | 97精品国产91久久久久久 | 成人精品一区二区三区中文字幕 | 日本女人的性生活视频 | 综合色天天 | 国产精品日韩 | 天天色天天操综合网 | 免费欧美高清视频 | 欧美久草在线 | 久久综合九色综合网站 | 99久久这里有精品 | 久久免费的视频 | 婷婷色综合 | 激情五月五月婷婷 | 欧美激情精品久久久久久 | 成人91av| 人人爽人人爽人人片 | 国产成人av电影在线 | 亚洲男男gⅴgay双龙 | 精品字幕| 国产精品福利视频 | 久草在线91 | 日本系列中文字幕 | 天天操比| 欧美一级欧美一级 | 国内三级在线观看 | 青青草国产免费 | 在线亚洲播放 | 97色婷婷成人综合在线观看 | 中文字幕一区在线观看视频 | 天天天天天天操 | 五月激情视频 | 久久视频在线观看中文字幕 | 国产视频欧美视频 | 国产精品亚洲片夜色在线 | 韩国av免费观看 | 在线观看网站av | 国产一区二区三区在线 | 超碰在97| 婷婷六月天天 | av超碰免费在线 | 国产呻吟在线 | 久久精品亚洲 | 久久dvd | 97夜夜澡人人双人人人喊 | 丁香电影小说免费视频观看 | 久久免费视屏 | 美女久久久久久 | 久草在线中文视频 | 91欧美精品| 国产精品网红直播 | 伊人久久五月天 | 人人爽人人香蕉 | 五月香视频在线观看 | 日日日视频 | 国产精品久久久99 | 在线视频一区观看 | 成人电影毛片 | 五月天视频网 | 日韩免费网站 | 久草视频在线免费 | 日韩一级电影在线观看 | 日韩影片在线观看 | 九九热免费视频在线观看 | 免费在线91 | 黄色小说免费观看 | 日日久视频 | 999久久国精品免费观看网站 | 超碰在线观看av.com | 黄色tv视频 | 亚洲性视频| 国产黑丝一区二区 | 99精品在线视频观看 | 日韩专区在线观看 | 国产伦理久久 | 六月天色婷婷 | 亚洲精品国产精品久久99 | 中文字幕999 | 国产剧情av在线播放 | 亚洲国产精品va在线 | 中文字幕一二三区 | 免费看精品久久片 | 国产探花视频在线播放 | 最近久乱中文字幕 | 亚洲夜夜综合 | 欧美在线视频精品 | 狠狠干夜夜操天天爽 | 欧美日韩中文字幕综合视频 | 久久国产精品久久国产精品 | 亚洲国产一区二区精品专区 | 在线午夜电影神马影院 | 在线成人免费电影 | 日韩毛片在线一区二区毛片 | 黄色美女免费网站 | 国产这里只有精品 | 91超碰免费在线 | 波多野结衣久久精品 | 中文字幕首页 | 国产一级片免费观看 | 黄色日本免费 | 国产精品一区电影 | 国产无限资源在线观看 | 免费在线激情视频 | 国产视频一二区 | 天天干天天干天天操 | 在线性视频日韩欧美 | 亚洲天堂精品视频 | 不卡的av电影在线观看 | 日韩中文字幕亚洲一区二区va在线 | 99视频在线精品 | 操操日日 | 欧美色图亚洲图片 | 欧美日韩一级久久久久久免费看 | 免费成人av在线看 | 婷婷午夜| 香蕉在线视频观看 | 国产精品久久嫩一区二区免费 | 高清不卡免费视频 | 国产免费观看久久黄 | 国产精品一区二区三区视频免费 | 中文字幕xxxx | 国产美女免费视频 | www.com黄色| 黄色小说网站在线 | 91精品影视| 欧美一区二区三区在线看 | 丁香婷婷在线 | 色综合天天狠狠 | 久草视频视频在线播放 | 久久久久99精品国产片 | 国产免费小视频 | 99精彩视频在线观看免费 | 国产丝袜网站 | 五月天激情综合 | 色诱亚洲精品久久久久久 | 国产精品美女999 | 欧美激情视频一区二区三区免费 | 日日噜噜噜噜夜夜爽亚洲精品 | 黄色av三级在线 | 狠狠色噜噜狠狠狠 | 韩国av免费在线观看 | 久久手机在线视频 | 天天射网| 亚洲激情免费 | bbw av| 天天干天天天天 | 成人毛片在线观看视频 | 国产精品99免视看9 国产精品毛片一区视频 | 色婷在线| 欧美精品国产综合久久 | 一级黄色片网站 | 奇米四色影狠狠爱7777 | 9ⅰ精品久久久久久久久中文字幕 | 成人福利在线 | 天天做日日做天天爽视频免费 | 黄色日视频| 国产精品一区二区在线播放 | 99中文字幕 | 久久精品欧美日韩精品 | 日韩专区 在线 | 日本爱爱免费 | 中文字幕色在线视频 | 91精品啪啪 | 在线激情av电影 | 99久久爱| 国产精品久久久久影院 | 欧美性直播| 免费亚洲黄色 | 色网站在线免费观看 | 亚洲一区二区三区四区精品 | 日韩精品在线观看av | 久久久久久97三级 | 少妇性aaaaaaaaa视频 | 色香com.| 中文字幕精品在线 | 中文字幕资源网 | 日韩在线一区二区免费 | 亚洲欧美日韩国产一区二区三区 | 色爱成人网 | 免费看一级特黄a大片 | 精品视频专区 | 日韩黄色在线电影 | 日本在线观看一区二区 | 国产xxxx做受性欧美88 | 久久成人国产精品一区二区 | 国产精品综合久久久久久 | 91在线网址 | 国产黄在线免费观看 | 久久午夜精品视频 | 天天综合网在线观看 | 久久天天躁夜夜躁狠狠躁2022 | 久久综合婷婷国产二区高清 | 4hu视频 | 最新日韩中文字幕 | 天天草天天干天天 | 色成人亚洲网 | 久久久久久久久福利 | 97超碰中文字幕 | 超碰97久久 | 中文字幕在线播出 | 天天干天天操天天搞 | 国产xvideos免费视频播放 | 69国产盗摄一区二区三区五区 | 91在线免费视频 | 欧美日韩一区二区免费在线观看 | 亚洲永久精品一区 | 欧美精品一区二区在线播放 | 黄色精品一区 | 国产视频一区二区三区在线 | 色吊丝在线永久观看最新版本 | 在线 高清 中文字幕 | 精品av在线播放 | 五月婷久| 91精品国产自产在线观看永久 | 久久a国产 | 久 久久影院 | 久久久久亚洲精品中文字幕 | 天天综合五月天 | 九九久久在线看 | 日韩高清一二区 | 国产中文在线字幕 | 国产91在线免费视频 | 91精品啪在线观看国产81旧版 | 国产精品一区二区吃奶在线观看 | 国产美女网站在线观看 | 欧美狠狠操 | 欧美视频99 | 久久99爱视频| 精品日韩中文字幕 | 亚洲综合在线一区二区三区 | 免费在线电影网址大全 | 99在线热播精品免费 | 国产精品免费av | 麻豆传媒视频在线 | 亚洲黄色免费电影 | 午夜精品视频一区二区三区在线看 | 亚洲天天看 | 99热这里只有精品在线观看 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | av日韩不卡 | 欧美在线观看小视频 | 成年人在线看片 | 国产精品一区二区久久精品爱微奶 | 二区三区在线 | 国产a免费 | 久久婷婷一区二区三区 | 免费一级日韩欧美性大片 | 蜜桃av人人夜夜澡人人爽 | 天天爱天天操天天射 | 99久久99久国产黄毛片 | 欧美日韩国产在线精品 | 国产精品热 | 亚洲天堂精品视频 | 九九免费视频 | 四虎在线免费观看视频 | 亚洲国产精品成人女人久久 | aaa黄色毛片 | 欧美性久久久久久 | 国产成人一区二区三区久久精品 | 久久亚洲私人国产精品va | a一片一级 | 开心激情五月网 | 在线影视 一区 二区 三区 | 日韩久久精品一区二区三区 | 亚洲激情五月 | 亚洲精品综合在线观看 | 亚洲天天摸日日摸天天欢 | 日本中文字幕在线播放 | 国产99视频在线观看 | 国产精品成人一区二区 | 久久a v电影 | 国产一级不卡毛片 | 丁香久久激情 | 久久久久久美女 | 日韩在线小视频 | 国产精品国产三级国产aⅴ9色 | 日本中文字幕在线一区 | 人人澡人人爽欧一区 | 99久久精品一区二区成人 | 观看免费av | 国产精品9999 | 久久综合欧美 | 激情综合狠狠 | 久久夜色精品国产欧美一区麻豆 | 国产精品久久久久久久久久99 | 蜜臀av夜夜澡人人爽人人桃色 | 高清视频一区二区三区 | 久草香蕉在线视频 | 亚洲欧洲成人 | 国产色道 | 中文字幕乱码一区二区 | 97视频在线观看成人 | 99 色| 五月天综合激情网 | 高清有码中文字幕 | 免费看一级 | 2019免费中文字幕 | 日韩精品中文字幕av | 国产大陆亚洲精品国产 | 国产精品久久久久免费a∨ 欧美一级性生活片 | 亚洲国产精品女人久久久 | 久久久久国产a免费观看rela | 亚洲高清激情 | 91免费黄视频 | 欧美经典久久 | 久久伊人综合 | 亚洲专区 国产精品 | 成人av影视观看 | 国产成人香蕉 | 欧美一级看片 | 成人动漫一区二区三区 | 国产精品乱码一区二区视频 | 九九视频精品免费 | 国产视频亚洲精品 | 日本公乱妇视频 | 国产99久久精品一区二区300 | 最新国产在线观看 | 色婷婷综合久久久 | 亚洲乱码精品久久久 | 手机在线观看国产精品 | 亚洲一级国产 | 久草在线综合 | 免费高清在线观看成人 | 日韩av中文 | 在线观看国产成人av片 | 精品视频成人 | 久久久久久久久毛片精品 | 日韩一区二区三区免费电影 | 91九色网址| 国产高清在线a视频大全 | 久久久久久高潮国产精品视 | 激情小说 五月 | 国产原创91 | 成人九九视频 | 欧美亚洲国产精品久久高清浪潮 | 超碰97在线资源站 | 免费福利片2019潦草影视午夜 | 国产成人三级 | 9999激情| 欧美日韩精品国产 | 麻豆成人小视频 | 久久综合狠狠综合久久综合88 | 国产日韩在线看 | 久久99久久99精品免费看小说 | 日韩av不卡在线 | 欧美精品xxx| 在线视频电影 | 久久综合久久综合这里只有精品 | 久久一区二区三区四区 | 亚洲91精品在线观看 | 99精品国产一区二区三区麻豆 | 亚洲一区精品人人爽人人躁 | 欧美狠狠操 | 五月婷婷操| 精品免费久久久久 | 91香蕉视频在线 | 亚洲高清国产视频 | 免费日韩 精品中文字幕视频在线 | av官网 | 日韩二区在线播放 | 99免费在线视频观看 | 麻豆影视在线观看 | 国产婷婷vvvv激情久 | 操操操人人人 | 日本h视频在线观看 | 亚州av网站| 九九99| 日韩在线电影观看 | 久久久亚洲电影 | 91av在线播放视频 | 97日日碰人人模人人澡分享吧 | 激情综合色综合久久 | 色综合 久久精品 | 国产精品自产拍在线观看蜜 | 国产在线专区 | 亚洲综合视频在线播放 | 亚洲国产精品电影 | 亚洲国产久 | 福利网址在线观看 | 日韩精品视频免费专区在线播放 | a在线观看视频 | 欧美成年黄网站色视频 | 欧美韩国在线 | 91免费版在线 | 99精品成人| 天天干国产 | 精品伊人久久久 | 天天鲁天天干天天射 | 欧美精品在线观看一区 | 日韩在线视频一区 | 99久久婷婷国产一区二区三区 | 日韩素人在线观看 | 99精品国产在热久久 | 婷婷色网 | 91探花国产综合在线精品 | 久久久久久国产一区二区三区 | 在线观看91视频 | 国产午夜在线 | 91秒拍国产福利一区 | 国产在线观看午夜 | 江苏妇搡bbbb搡bbbb | 91精品视频网站 | 欧美激情另类文学 | www.黄色在线 | 欧美精品一区二区蜜臀亚洲 | 日本一区二区不卡高清 | 久久久久久久久精 | 欧美va天堂va视频va在线 | 精品一区二区三区四区在线 | www.av中文字幕.com| 午夜精品一区二区国产 | 日本中文字幕网站 | 免费成人在线观看视频 | 国产精品日韩欧美一区二区 | 久久精品久久精品 | 亚洲国产成人在线 | 国产一区二区电影在线观看 | 免费高清男女打扑克视频 | 中文不卡视频 | 久久久在线视频 | 美女视频网站久久 | a级黄色片视频 | 国产 视频 高清 免费 | 欧美一区二区在线免费看 | 精品嫩模福利一区二区蜜臀 | 欧美大荫蒂xxx | 久久99久久99久久 | 日韩av不卡播放 | av在线免费在线 | 国产精品手机在线 | 久久国产精品系列 | 欧美日韩高清国产 | 婷婷婷国产在线视频 | 成人在线播放av | 特级毛片在线观看 | 欧美日韩中文在线观看 | 欧美91成人网 | 天天操天天射天天操 | 国产精品日韩在线观看 | 91中文在线视频 | 成人在线一区二区 | 国产精品成人一区二区三区 | 欧美成人xxxx | 欧美日韩二三区 | 国产高清视频免费在线观看 | 深夜免费小视频 | 日韩乱理 | 一区二区三区国产精品 | 黄色性av | 成人在线免费看 | 成人午夜影院在线观看 | 日韩一区二区三区免费视频 | 91视频黄色 | 国产福利精品视频 | 免费看黄色毛片 | 在线v| 精品一区二区三区久久久 | 五月婷香| 九九在线视频免费观看 | 夜夜夜夜猛噜噜噜噜噜初音未来 | 国产欧美综合在线观看 | 久久国产综合视频 | 日韩精品专区在线影院重磅 | 国产精品黄网站在线观看 | 很黄很色很污的网站 | 欧美a性| 久久99国产精品二区护士 | 手机在线看永久av片免费 | av电影在线免费 | 探花系列在线 | 中文字幕一区2区3区 | 六月久久婷婷 | 国产女做a爱免费视频 | 久久亚洲综合色 | 久久精品在线免费观看 | 在线观看国产www | 欧美日韩高清一区二区 | 国产一级视频免费看 | 久久综合免费视频 | 韩国精品视频在线观看 | 国产不卡av在线 | 国产热re99久久6国产精品 | 亚洲精品永久免费视频 | 免费一级片视频 | 日韩在线视频线视频免费网站 | 国产成人精品一区一区一区 | 激情六月婷婷久久 | 欧美日韩综合在线观看 | 在线导航av | 91在线视频观看 | 天天鲁一鲁摸一摸爽一爽 | 欧美一级免费高清 | 999久久国精品免费观看网站 | 一区二区精品 | 日韩精品视频一二三 | 精品1区二区 | 成人黄色片在线播放 | 久久天天躁夜夜躁狠狠躁2022 | 国产精品一区二区三区免费视频 | 日韩电影中文字幕在线观看 | 三上悠亚一区二区在线观看 | 日本精品视频免费观看 | 久久精品一区二区三区中文字幕 | 大片网站久久 | 国产69精品久久久久久久久久 | 国产99久久久久 | www免费视频com━ | 在线观看91av | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 91视频在线免费下载 | 日韩亚洲欧美中文字幕 | 国产精品女人久久久 | 91精品一区二区三区蜜臀 | 日韩在线观看中文字幕 | 一二区av | 婷婷九月激情 | 91资源在线免费观看 | 亚洲 欧美变态 另类 综合 | 99视频精品免费观看, | 久久久国产网站 | 夜夜躁天天躁很躁波 | 久久草| 午夜国产在线 | 免费看精品久久片 | 国产流白浆高潮在线观看 | 9999在线视频 | 国产在线视频资源 | 亚洲自拍偷拍色图 | av成人黄色| 国产自产高清不卡 | 久草视频在| 天堂在线一区 | 日韩有码在线观看视频 | 在线视频婷婷 | 亚洲精品久久久久久久不卡四虎 | 欧美日韩三区二区 | 久久久 精品 | 国精产品999国精产品岳 | 色网站在线看 | 国产夫妻自拍av | 在线免费中文字幕 | 欧美日韩视频在线播放 | 国产精品一区二区av麻豆 | 国产精品久久久区三区天天噜 | 国产黄色片在线 | 欧美精品亚州精品 | 97精品国产97久久久久久久久久久久 | sm免费xx网站 | 午夜狠狠干 | 久久国产精品99久久久久久进口 | 免费亚洲黄色 | 国产精品久久久久久久av电影 | 欧洲av不卡 | 在线免费黄 | 天天操狠狠操夜夜操 | 人人添人人 | 狠狠精品 | 久久久国产一区二区三区四区小说 | 中文字幕免费高清av | 超碰在线公开 | 成人精品电影 | 久久久久女教师免费一区 | 欧美日韩精品久久久 | 亚洲三级性片 | 国产91在线观 | 国产综合在线观看视频 | 一区二区三区四区五区在线 | 亚洲国产日韩在线 | 国内综合精品午夜久久资源 | 99视频在线免费看 | 激情欧美日韩一区二区 | 99视频在线观看免费 | 日韩av高清 | 婷婷色中文 | 国产精品嫩草69影院 | 国产成人在线综合 | 91九色精品国产 | 麻豆影视在线免费观看 | 最近中文字幕免费大全 | 天天天操操操 | 一区二区三区中文字幕在线 | 日韩福利在线观看 | 欧美日韩国产免费视频 | 精品国产精品久久 | 2019中文在线观看 | 天天操天天摸天天干 | 欧美色一色 | 最近2019好看的中文字幕免费 | 国产精品久久麻豆 | 视频在线精品 | 国产高清视频免费观看 | 亚洲成人资源网 | 国产成人黄色片 | 国产精品欧美久久久久久 | 西西4444www大胆视频 | 美女网站黄免费 | 99久久婷婷| 欧美男男激情videos | 国产视频资源 | 九九电影在线 | av在线播放不卡 | 亚洲成av人片 | 手机成人av | 亚洲精选在线 | 欧美黄网站 | 在线观看精品黄av片免费 | 97影视| 欧美日韩精品在线一区二区 | 国产精品h在线观看 | 国产一级特黄毛片在线毛片 | 久久精品亚洲国产 | 91九色视频国产 | 亚洲综合情| 成人欧美在线 | 婷婷色五 | 日日操操 | 日韩91精品| 久久综合久久综合这里只有精品 | 中文字幕在线观看完整 | 欧美在线aa | 国产精品久久久亚洲 | 亚洲精品动漫久久久久 | 国产一区精品在线观看 | 久久久久久高清 | 久久精品综合视频 | 久久久久久久国产精品影院 | 久久超碰99| 国产老妇av | 三级黄色网络 | av综合站 | 国产黄色在线看 | 欧美俄罗斯性视频 | 国产精品扒开做爽爽的视频 | 丁香六月婷婷开心婷婷网 | av色网站 | 亚洲精品福利在线观看 | 激情综合一区 | 99热国内精品 | 国产成人61精品免费看片 | 337p日本欧洲亚洲大胆裸体艺术 | 草久在线播放 | 91av视屏 | 久久久久国产视频 | 91精品色| 国产综合婷婷 | 一区二区三区四区不卡 | 亚洲天天在线日亚洲洲精 | 中文字幕在线看视频国产中文版 | 国产成人精品免高潮在线观看 | 天天艹 | 四虎影视精品成人 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 毛片无卡免费无播放器 | 中文字幕色婷婷在线视频 | 日本精品视频一区 | 日日噜噜噜噜夜夜爽亚洲精品 | 国产高清久久久久 | 69av国产| www.香蕉| 综合天天久久 | 亚洲精品免费视频 | 日韩av免费在线电影 | 亚洲第一区精品 | 日韩免费在线观看视频 | 久久久久久美女 | 特黄免费av | 久久久资源| 久久艹人人 | 亚洲一区黄色 | 国产精成人品免费观看 | 欧美日韩中文在线观看 | 久久99精品久久久久久三级 | 亚洲精品久久视频 | 久久精品久久99精品久久 | 成人av久久 | 午夜10000 | 亚洲国产精品女人久久久 | 啪啪av在线| 成人av手机在线 | 最近日本韩国中文字幕 | 欧美精品在线观看一区 | 国产美女精品视频 | 少妇自拍av | 天天射天天射天天 | 国产精品免费人成网站 | 亚洲一区美女视频在线观看免费 | 97精品国产一二三产区 | 欧美精品一区在线发布 | 一区二区三区在线免费播放 | 四虎影视成人永久免费观看视频 | 国产97色| 久久96国产精品久久99软件 | av成人免费网站 | 亚洲国产成人精品久久 | 成人免费xyz网站 | 一级性视频 |