Realtek ALC5625 Driver

/*
 *
 * rt5625.c  --  ALSA Soc ALC5625 codec Kernel 2.6.27+ support
 *
 * Original code is based on Realtek
 *
 * Copyright Androidin.org
 * Author:tommy.hong<hongjiujing@gmail.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 * V0.1 Add 2.6.27 Alsa driver framework support
 * V0.2 Fix Amixter snd_ioctl kernel oops
 *
 * Have Test successfully in ARM11 Android Platform environment
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/jiffies.h>
#include <asm/delay.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <asm/div64.h>

#include "rt5625.h"

#define RT5625_VERSION "0.04"


struct alc5625{
        struct i2c_client *client;
        unsigned char send_data[2];
        unsigned char recv_data[2];
        unsigned int mclk_freq;
};

struct rt5625_priv {
    unsigned int stereo_sysclk;
    unsigned int voice_sysclk;
};

struct rt5625_init_reg {
    char name[30];
    u16 reg_value;
    u8 reg_index;
};

struct aec_config *rt5625_aec_cfg;

static struct rt5625_init_reg rt5625_init_list[] = {
    {"HP Output Volume", 0x9090, RT5625_HP_OUT_VOL},
    {"SPK Output Volume", 0x8080, RT5625_SPK_OUT_VOL},
    {"DAC Mic Route", 0xee03, RT5625_DAC_AND_MIC_CTRL},
    {"Output Mixer Control", 0x0748, RT5625_OUTPUT_MIXER_CTRL},
    {"Mic Control", 0x0500, RT5625_MIC_CTRL},
    {"Voice DAC Volume", 0x6008, RT5625_VOICE_DAC_OUT_VOL},
    {"ADC Rec Mixer", 0x3f3f, RT5625_ADC_REC_MIXER},
    {"General Control", 0x0c0a, RT5625_GEN_CTRL_REG1}
};

#define RT5625_INIT_REG_NUM ARRAY_SIZE(rt5625_init_list)

/*
 *    bit[0]  for linein playback switch
 *    bit[1] phone
 *    bit[2] mic1
 *    bit[3] mic2
 *    bit[4] vopcm
 *   
 */
 #define HPL_MIXER 0x80
#define HPR_MIXER 0x82
static unsigned int reg80 = 0, reg82 = 0;

/*
 *    bit[0][1] use for aec control
 *    bit[2][3] for ADCR func
 *    bit[4] for SPKL pga
 *    bit[5] for SPKR pga
 *    bit[6] for hpl pga
 *    bit[7] for hpr pga
 *    bit[8] for aec mode
 */
 #define VIRTUAL_REG_FOR_MISC_FUNC 0x84
static unsigned int reg84 = 0;


static const u16 rt5625_reg[] = {
    0x59b4, 0x8080, 0x8080, 0x8080,        /*reg00-reg06*/
    0xc800, 0xe808, 0x1010, 0x0808,        /*reg08-reg0e*/
    0xe0ef, 0xcbcb, 0x7f7f, 0x0000,        /*reg10-reg16*/
    0xe010, 0x0000, 0x8008, 0x2007,        /*reg18-reg1e*/
    0x0000, 0x0000, 0x00c0, 0xef00,        /*reg20-reg26*/
    0x0000, 0x0000, 0x0000, 0x0000,        /*reg28-reg2e*/
    0x0000, 0x0000, 0x0000, 0x0000,        /*reg30-reg36*/
    0x0000, 0x0000, 0x0000, 0x0000,         /*reg38-reg3e*/
    0x0c0a, 0x0000, 0x0000, 0x0000,        /*reg40-reg46*/
    0x0029, 0x0000, 0xbe3e, 0x3e3e,        /*reg48-reg4e*/
    0x0000, 0x0000, 0x803a, 0x0000,        /*reg50-reg56*/
    0x0000, 0x0009, 0x0000, 0x3000,        /*reg58-reg5e*/
    0x3075, 0x1010, 0x3110, 0x0000,        /*reg60-reg66*/
    0x0553, 0x0000, 0x0000, 0x0000,        /*reg68-reg6e*/
    0x0000, 0x0000, 0x0000, 0x0000,        /*reg70-reg76*/
    0x0000, 0x0000, 0x0000, 0x0000,               /*reg76-reg7e*/
};


Voice_DSP_Reg VODSP_AEC_Init_Value[]=
{
    {0x232C, 0x0025},
    {0x230B, 0x0001},
    {0x2308, 0x007F},
    {0x23F8, 0x4003},
    {0x2301, 0x0002},
    {0x2328, 0x0001},
    {0x2304, 0x00FA},
    {0x2305, 0x0100},
    {0x2306, 0x4000},
    {0x230D, 0x0400},
    {0x230E, 0x0100},
    {0x2312, 0x00B1},
    {0x2314, 0xC000},
    {0x2316, 0x0041},
    {0x2317, 0x2000},
    {0x2318, 0x0C00},
    {0x231D, 0x00A0},
    {0x231F, 0x5800},
    {0x2330, 0x0008},
    {0x2335, 0x0005},
    {0x2336, 0x0001},
    {0x2337, 0x5800},
    {0x233A, 0x0300},
    {0x233B, 0x0030},
    {0x2341, 0x0008},
    {0x2343, 0x0800},       
    {0x23A7, 0x0200},
    {0x22CE, 0x0400},
    {0x22D3, 0x0C00},
    {0x22D4, 0x1800},
    {0x230C, 0x0000},    //to enable VODSP AEC function
};

#define SET_VODSP_REG_INIT_NUM  ARRAY_SIZE(VODSP_AEC_Init_Value)


static struct snd_soc_device *rt5625_socdev;

static inline unsigned int rt5625_read_reg_cache(struct snd_soc_codec *codec,
    unsigned int reg)
{
    u16 *cache = codec->reg_cache;

    if (reg > 0x7e)
        return 0;
    return cache[reg / 2];
}


static unsigned int rt5625_read_hw_reg(struct snd_soc_codec *codec, unsigned int reg)
{
    u8 data[2] = {0};
    unsigned int value = 0x0;
   
    data[0] = reg;
    if (codec->hw_write(codec->control_data, data, 1) == 1)
    {
        codec->hw_read(codec->control_data, data, 2);
        value = (data[0] << 8) | data[1];
//        printk(KERN_DEBUG "%s read reg%x = %x/n", __func__, reg, value);
        return value;
    }
    else
    {
        printk(KERN_DEBUG "%s failed/n", __func__);
        return -EIO;
    }
}


static unsigned int rt5625_read(struct snd_soc_codec *codec, unsigned int reg)
{
    if ((reg == 0x80)
        || (reg == 0x82)
        || (reg == 0x84))
        return (reg == 0x80) ? reg80 : ((reg == 0x82) ? reg82 : reg84);
   
        return rt5625_read_hw_reg(codec, reg);
}


static inline void rt5625_write_reg_cache(struct snd_soc_codec *codec,
    unsigned int reg, unsigned int value)
{
    u16 *cache = codec->reg_cache;
    if (reg > 0x7E)
        return;
    cache[reg / 2] = value;
}

static int rt5625_write(struct snd_soc_codec *codec, unsigned int reg,
    unsigned int value)
{
    u8 data[3];
    unsigned int *regvalue = NULL;

    data[0] = reg;
    data[1] = (value & 0xff00) >> 8;
    data[2] = value & 0x00ff;
    
    if ((reg == 0x80)
        || (reg == 0x82)
        || (reg == 0x84))
    {       
        regvalue = ((reg == 0x80) ? &reg80 : ((reg == 0x82) ? &reg82 : &reg84));
        *regvalue = value;
//        printk(KERN_INFO "rt5625_write ok, reg = %x, value = %x/n", reg, value);
        return 0;
    }
    rt5625_write_reg_cache(codec, reg, value);
    if (codec->hw_write(codec->control_data, data, 3) == 3)
    {
//        printk(KERN_INFO "rt5625_write ok, reg = %x, value = %x/n", reg, value);
        return 0;
    }
    else
    {
//        printk(KERN_ERR "rt5625_write fail/n");
        return -EIO;
    }
}

#define rt5625_write_mask(c, reg, value, mask) snd_soc_update_bits(c, reg, mask, value)

#define rt5625_reset(c) rt5625_write(c, RT5625_RESET, 0)

static unsigned int rt5625_read_index(struct snd_soc_codec *codec, unsigned int reg_index)
{
    rt5625_write(codec, 0x6a, reg_index);
    return rt5625_read(codec, 0x6c);
}

/*read/write dsp reg*/
static int rt5625_wait_vodsp_i2c_done(struct snd_soc_codec *codec)
{
    unsigned int checkcount = 0, vodsp_data;

    vodsp_data = rt5625_read(codec, RT5625_VODSP_REG_CMD);
    while(vodsp_data & VODSP_BUSY)
    {
        if(checkcount > 10)
            return -EBUSY;
        vodsp_data = rt5625_read(codec, RT5625_VODSP_REG_CMD);
        checkcount ++;       
    }
    return 0;
}
static int rt5625_write_vodsp_reg(struct snd_soc_codec *codec, unsigned int vodspreg, unsigned int value)
{
    int ret = 0;

    if(ret = rt5625_wait_vodsp_i2c_done(codec))
        return ret;

    rt5625_write(codec, RT5625_VODSP_REG_ADDR, vodspreg);
    rt5625_write(codec, RT5625_VODSP_REG_DATA, value);
    rt5625_write(codec, RT5625_VODSP_REG_CMD, VODSP_WRITE_ENABLE | VODSP_CMD_MW);
    mdelay(10);
    return ret;
   
}

static unsigned int rt5625_read_vodsp_reg(struct snd_soc_codec *codec, unsigned int vodspreg)
{
    int ret = 0;
    unsigned int nDataH, nDataL;
    unsigned int value;

    if(ret = rt5625_wait_vodsp_i2c_done(codec))
        return ret;
   
    rt5625_write(codec, RT5625_VODSP_REG_ADDR, vodspreg);
    rt5625_write(codec, RT5625_VODSP_REG_CMD, VODSP_READ_ENABLE | VODSP_CMD_MR);

    if (ret = rt5625_wait_vodsp_i2c_done(codec))
        return ret;
    rt5625_write(codec, RT5625_VODSP_REG_ADDR, 0x26);
    rt5625_write(codec, RT5625_VODSP_REG_CMD, VODSP_READ_ENABLE | VODSP_CMD_RR);

    if(ret = rt5625_wait_vodsp_i2c_done(codec))
        return ret;
    nDataH = rt5625_read(codec, RT5625_VODSP_REG_DATA);
    rt5625_write(codec, RT5625_VODSP_REG_ADDR, 0x25);
    rt5625_write(codec, RT5625_VODSP_REG_CMD, VODSP_READ_ENABLE | VODSP_CMD_RR);

    if(ret = rt5625_wait_vodsp_i2c_done(codec))
        return ret;
    nDataL = rt5625_read(codec, RT5625_VODSP_REG_DATA);
    value = ((nDataH & 0xff) << 8) |(nDataL & 0xff);
//    printk(KERN_INFO "%s vodspreg=0x%x, value=0x%x/n", __func__, vodspreg, value);
    return value;
}

static int rt5625_reg_init(struct snd_soc_codec *codec)
{
    int i;

    for (i = 0; i < RT5625_INIT_REG_NUM; i++)
        rt5625_write(codec, rt5625_init_list[i].reg_index, rt5625_init_list[i].reg_value);

    return 0;
}



static const char *rt5625_spk_out_sel[] = {"Class AB", "Class D"};                     /*1*/
static const char *rt5625_spk_l_source_sel[] = {"LPRN", "LPRP", "LPLN", "MM"};        /*2*/   
static const char *rt5625_spkmux_source_sel[] = {"VMID", "HP Mixer",
                            "SPK Mixer", "Mono Mixer"};                                   /*3*/
static const char *rt5625_hplmux_source_sel[] = {"VMID","HPL Mixer"};                /*4*/
static const char *rt5625_hprmux_source_sel[] = {"VMID","HPR Mixer"};                /*5*/
static const char *rt5625_auxmux_source_sel[] = {"VMID", "HP Mixer",
                            "SPK Mixer", "Mono Mixer"};                            /*6*/
static const char *rt5625_spkamp_ratio_sel[] = {"2.25 Vdd", "2.00 Vdd",
                    "1.75 Vdd", "1.50 Vdd", "1.25 Vdd", "1.00 Vdd"};                /*7*/
static const char *rt5625_mic1_boost_sel[] = {"Bypass", "+20db", "+30db", "+40db"};    /*8*/
static const char *rt5625_mic2_boost_sel[] = {"Bypass", "+20db", "+30db", "+40db"};    /*9*/
static const char *rt5625_dmic_boost_sel[] = {"Bypass", "+6db", "+12db", "+18db",
                    "+24db", "+30db", "+36db", "+42db"};                        /*10*/



static const struct soc_enum rt5625_enum[] = {

SOC_ENUM_SINGLE(RT5625_OUTPUT_MIXER_CTRL, 13, 2, rt5625_spk_out_sel),        /*1*/
SOC_ENUM_SINGLE(RT5625_OUTPUT_MIXER_CTRL, 14, 4, rt5625_spk_l_source_sel),    /*2*/
SOC_ENUM_SINGLE(RT5625_OUTPUT_MIXER_CTRL, 10, 4, rt5625_spkmux_source_sel),/*3*/
SOC_ENUM_SINGLE(RT5625_OUTPUT_MIXER_CTRL, 9, 2, rt5625_hplmux_source_sel),    /*4*/
SOC_ENUM_SINGLE(RT5625_OUTPUT_MIXER_CTRL, 8, 2, rt5625_hprmux_source_sel),/*5*/
SOC_ENUM_SINGLE(RT5625_OUTPUT_MIXER_CTRL, 6, 4, rt5625_auxmux_source_sel),/*6*/
SOC_ENUM_SINGLE(RT5625_GEN_CTRL_REG1, 1, 6, rt5625_spkamp_ratio_sel),        /*7*/
SOC_ENUM_SINGLE(RT5625_MIC_CTRL, 10, 4,  rt5625_mic1_boost_sel),            /*8*/
SOC_ENUM_SINGLE(RT5625_MIC_CTRL, 8, 4, rt5625_mic2_boost_sel),                /*9*/
SOC_ENUM_SINGLE(RT5625_DMIC_CTRL, 0, 8, rt5625_dmic_boost_sel),                /*10*/
};


//static int rt5625_dump_dsp_reg(struct snd_soc_codec *codec);
static int init_vodsp_aec(struct snd_soc_codec *codec)
{
    int i;
    int ret = 0;

   
    /*enable LDO power and set output voltage to 1.2V*/
    rt5625_write_mask(codec, RT5625_LDO_CTRL,LDO_ENABLE|LDO_OUT_VOL_CTRL_1_20V,LDO_ENABLE|LDO_OUT_VOL_CTRL_MASK);
    mdelay(20);
    /*enable power of VODSP I2C interface*/
    rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD3,PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP,PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP);
    mdelay(1);
    rt5625_write_mask(codec, RT5625_VODSP_CTL,0,VODSP_NO_RST_MODE_ENA);    /*Reset VODSP*/
    mdelay(1);
    rt5625_write_mask(codec, RT5625_VODSP_CTL,VODSP_NO_RST_MODE_ENA,VODSP_NO_RST_MODE_ENA);    /*set VODSP to non-reset status*/       
    mdelay(20);
    /*initize AEC paramter*/
//    for(i = 0; i < SET_VODSP_REG_INIT_NUM; i++)
    for(i = 0; i < ARRAY_SIZE(VODSP_AEC_Init_Value); i++)
    {
        ret = rt5625_write_vodsp_reg(codec, VODSP_AEC_Init_Value[i].VoiceDSPIndex,VODSP_AEC_Init_Value[i].VoiceDSPValue);
        if(ret)
            return -EIO;
    }       

    schedule_timeout_uninterruptible(msecs_to_jiffies(100));
    //set VODSP to pown down mode   
    rt5625_write_mask(codec, RT5625_VODSP_CTL,0,VODSP_NO_PD_MODE_ENA);   
//    rt5625_dump_dsp_reg(codec);
    return 0;
}

static int rt5625_set_aec_sysclk(struct snd_soc_codec *codec, unsigned int mode);
static int aec_additional_config_for_analog_path(struct snd_soc_codec *codec, unsigned int mode)
{
    if (mode != VODSP_AEC_DISABLE) {
        rt5625_set_aec_sysclk(codec, mode);
        snd_soc_dapm_stream_event(codec, "Playback", SND_SOC_DAPM_STREAM_START);
        snd_soc_dapm_stream_event(codec, "Capture", SND_SOC_DAPM_STREAM_START);
       
    } else {
        snd_soc_dapm_stream_event(codec, "Playback", SND_SOC_DAPM_STREAM_STOP);
        snd_soc_dapm_stream_event(codec, "Capture", SND_SOC_DAPM_STREAM_STOP);
        rt5625_set_aec_sysclk(codec, mode);
    }
}
static int set_vodsp_aec_path(struct snd_soc_codec *codec, unsigned int mode)
{

        if (rt5625_aec_cfg->is_analog_path)
        {
            aec_additional_config_for_analog_path(codec, mode);
        }
       
        switch(mode)
        {
            /*Far End signal is from Voice interface and Near End signal is from MIC1/MIC2)*/
            case PCM_IN_PCM_OUT:

                 /*    1.Far End setting(Far end device PCM out-->VODAC PCM IN--->VODSP_RXDP )*/
                 
                /*****************************************************************************
                  *    a.Enable RxDP power and select RxDP source from "Voice to Stereo Digital path"       
                  *    b.Voice PCM out from VoDSP TxDP(VODSP TXDP--->VODAC PCM out-->Far End devie PCM out)
                  ******************************************************************************/
                rt5625_write_mask(codec, RT5625_VODSP_PDM_CTL,VODSP_RXDP_PWR|VODSP_RXDP_S_SEL_VOICE|VOICE_PCM_S_SEL_AEC_TXDP
                                                            ,VODSP_RXDP_PWR|VODSP_RXDP_S_SEL_MASK|VOICE_PCM_S_SEL_MASK);
                 /*    2.Near end setting*/
                /***********************************************************************************     
                  *    a.ADCR function select PDM Slave interface(Mic-->ADCR-->PDM interface)
                  *    b.Voice DAC Source Select VODSP_TxDC
                  ************************************************************************************/
                rt5625_write_mask(codec, RT5625_DAC_ADC_VODAC_FUN_SEL,ADCR_FUNC_SEL_PDM|VODAC_SOUR_SEL_VODSP_TXDC
                                                                ,ADCR_FUNC_SEL_MASK|VODAC_SOUR_SEL_MASK);

                /*3.setting VODSP LRCK to 8k*/
                rt5625_write_mask(codec, RT5625_VODSP_CTL,VODSP_LRCK_SEL_8K,VODSP_LRCK_SEL_MASK);                       

                break;
           
            /*Far End signal is from Analog input and Near End signal is from MIC1/MIC2)*/
            case ANALOG_IN_ANALOG_OUT:   
                /*    1.Far End setting(Far end device-->Analog in-->ADC_L-->VODSP_RXDP)   */
                /************************************************************************   
                  *    a.Enable RxDP power and select RxDP source from ADC_L
                  ************************************************************************/
                rt5625_write_mask(codec, RT5625_VODSP_PDM_CTL,VODSP_RXDP_PWR|VODSP_RXDP_S_SEL_ADCL,
                                                             VODSP_RXDP_PWR|VODSP_RXDP_S_SEL_MASK);

                /*2.Near end setting*/
                    /*************************************************************************
                      *a.VoDSP TxDP--->VODAC--->analog out-->to Far end analog input
                      *b.ADCR function select PDM Slave interface(Mic-->ADCR-->PDM interface)
                      *************************************************************************/
                rt5625_write_mask(codec, RT5625_DAC_ADC_VODAC_FUN_SEL,ADCR_FUNC_SEL_PDM|VODAC_SOUR_SEL_VODSP_TXDP
                                                                    ,ADCR_FUNC_SEL_MASK|VODAC_SOUR_SEL_MASK);
                /*3.setting VODSP LRCK to 16k*/
                rt5625_write_mask(codec, RT5625_VODSP_CTL,VODSP_LRCK_SEL_16K,VODSP_LRCK_SEL_MASK);   
           
                break;

            /*Far End signal is from Playback and Near End signal is from MIC1/MIC2)*/
            case DAC_IN_ADC_OUT:   
                /***********************************************************************
                  *    1.Far End setting(Playback-->SRC1-->VODSP_RXDP)
                  *        a.enable SRC1 and VoDSP_RXDP source select SRC1
                  *    2.Near End setting(VoDSP_TXDP-->SRC2-->Stereo Record)
                  *        a.enable SRC2 and select Record source from SRC2
                  **********************************************************************/
                rt5625_write_mask(codec, RT5625_VODSP_PDM_CTL,VODSP_SRC1_PWR|VODSP_SRC2_PWR|VODSP_RXDP_PWR|VODSP_RXDP_S_SEL_SRC1|REC_S_SEL_SRC2,
                                                             VODSP_SRC1_PWR|VODSP_SRC2_PWR|VODSP_RXDP_PWR|VODSP_RXDP_S_SEL_MASK|REC_S_SEL_MASK);                   

                break;

            case VODSP_AEC_DISABLE:
            default:
           
                /*set stereo DAC&Voice DAC&Stereo ADC function select to default*/
                rt5625_write(codec, RT5625_DAC_ADC_VODAC_FUN_SEL,0);

       
                /*set VODSP&PDM Control to default*/
                rt5625_write(codec, RT5625_VODSP_PDM_CTL,0);   
           
                break;
        }       

    return 0;
}

static int enable_vodsp_aec(struct snd_soc_codec *codec, unsigned int VodspAEC_En, unsigned int AEC_mode)
{
    int  ret = 0;

   
    if (VodspAEC_En != 0)
    {   
        //select input/output of VODSP AEC
        set_vodsp_aec_path(codec, AEC_mode);       
        //enable power of VODSP I2C interface & VODSP interface
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD3,PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP,PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP);
        //enable power of VODSP I2S interface
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD1,PWR_I2S_INTERFACE,PWR_I2S_INTERFACE);   
        //set VODSP to active           
        rt5625_write_mask(codec, RT5625_VODSP_CTL,VODSP_NO_PD_MODE_ENA,VODSP_NO_PD_MODE_ENA);   
        mdelay(50);   
    }
    else
    {
        //set VODSP AEC to power down mode           
        rt5625_write_mask(codec, RT5625_VODSP_CTL,0,VODSP_NO_PD_MODE_ENA);
        //disable power of VODSP I2C interface & VODSP interface
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD3,0,PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP);
        //disable VODSP AEC path
        set_vodsp_aec_path(codec, VODSP_AEC_DISABLE);               
    }

    return ret;
}



static int rt5625_get_aec_mode(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
    int mode;

    mode = codec->read(codec, 0x84);
    ucontrol->value.integer.value[0] = (mode & 0x0100) >> 8;
    return 0;
}

static int rt5625_set_aec_mode(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
    int mode;

    mode = codec->read(codec, 0x84);
    if ((mode & 0x0100) == (ucontrol->value.integer.value[0] << 8))
        return 0;

    mode &= ~(0x0100);
    mode |= ucontrol->value.integer.value[0] << 8;
    codec->write(codec, 0x84, mode);
    enable_vodsp_aec(codec, ucontrol->value.integer.value[0], rt5625_aec_cfg->aec_mode);
   
}
static const struct snd_kcontrol_new rt5625_snd_controls[] = {

SOC_ENUM("SPK Amp Type", rt5625_enum[0]),
SOC_ENUM("Left SPK Source", rt5625_enum[1]),
SOC_ENUM("SPK Amp Ratio", rt5625_enum[6]),
SOC_ENUM("Mic1 Boost", rt5625_enum[7]),
SOC_ENUM("Mic2 Boost", rt5625_enum[8]),
SOC_ENUM("Dmic Boost", rt5625_enum[9]),
/*Workaround,beyond array cause snd_ioctl alsa control kernel oops,must be reconsidered*/
/*SOC_ENUM("ADCR Func", rt5625_enum[10]),*/
SOC_DOUBLE("PCM Playback Volume", RT5625_STEREO_DAC_VOL, 8, 0, 63, 1),
SOC_DOUBLE("LineIn Playback Volume", RT5625_LINE_IN_VOL, 8, 0, 31, 1),
SOC_SINGLE("Phone Playback Volume", RT5625_PHONEIN_VOL, 8, 31, 1),
SOC_SINGLE("Mic1 Playback Volume", RT5625_MIC_VOL, 8, 31, 1),
SOC_SINGLE("Mic2 Playback Volume", RT5625_MIC_VOL, 0, 31, 1),
SOC_DOUBLE("PCM Capture Volume", RT5625_ADC_REC_GAIN, 8, 0, 31, 1),
SOC_DOUBLE("SPKOUT Playback Volume", RT5625_SPK_OUT_VOL, 8, 0, 31, 1),
SOC_DOUBLE("SPKOUT Playback Switch", RT5625_SPK_OUT_VOL, 15, 7, 1, 1),
SOC_DOUBLE("HPOUT Playback Volume", RT5625_HP_OUT_VOL, 8, 0, 31, 1),
SOC_DOUBLE("HPOUT Playback Switch", RT5625_HP_OUT_VOL, 15, 7, 1, 1),
SOC_DOUBLE("AUXOUT Playback Volume", RT5625_AUX_OUT_VOL, 8, 0, 31, 1),
SOC_DOUBLE("AUXOUT Playback Switch", RT5625_AUX_OUT_VOL, 15, 7, 1, 1),
SOC_DOUBLE("ADC Record Gain", RT5625_ADC_REC_GAIN, 8, 0, 31, 0),
SOC_SINGLE_EXT("Echo Cancelation", VIRTUAL_REG_FOR_MISC_FUNC, 8, 1, 0, rt5625_get_aec_mode, rt5625_set_aec_mode),
};

static int rt5625_add_controls(struct snd_soc_codec *codec)
{
    int err, i;

    for (i = 0; i < ARRAY_SIZE(rt5625_snd_controls); i++){
        err = snd_ctl_add(codec->card,
                snd_soc_cnew(&rt5625_snd_controls[i],
                        codec, NULL));
        if (err < 0)
            return err;
    }
    return 0;
}

static void hp_depop_mode2(struct snd_soc_codec *codec)
{
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD1, PWR_MAIN_BIAS, PWR_MAIN_BIAS);
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD2, PWR_MIXER_VREF, PWR_MIXER_VREF);
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD1, PWR_SOFTGEN_EN, PWR_SOFTGEN_EN);
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD3, PWR_HP_R_OUT_VOL|PWR_HP_L_OUT_VOL,
                PWR_HP_R_OUT_VOL|PWR_HP_L_OUT_VOL);
        rt5625_write_mask(codec, RT5625_MISC_CTRL, HP_DEPOP_MODE2_EN, HP_DEPOP_MODE2_EN);
        schedule_timeout_uninterruptible(msecs_to_jiffies(300));

        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD1, PWR_HP_OUT_AMP|PWR_HP_OUT_ENH_AMP,
                PWR_HP_OUT_AMP|PWR_HP_OUT_ENH_AMP);
        rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD1, 0, HP_DEPOP_MODE2_EN);

}


#if USE_DAPM_CTRL
/*
 * _DAPM_ Controls
 */
 /*Left ADC Rec mixer*/
static const struct snd_kcontrol_new rt5625_left_adc_rec_mixer_controls[] = {
SOC_DAPM_SINGLE("Mic1 Capture Switch", RT5625_ADC_REC_MIXER, 14, 1, 1),
SOC_DAPM_SINGLE("Mic2 Capture Switch", RT5625_ADC_REC_MIXER, 13, 1, 1),
SOC_DAPM_SINGLE("LineIn Capture Switch", RT5625_ADC_REC_MIXER, 12, 1, 1),
SOC_DAPM_SINGLE("Phone Capture Switch", RT5625_ADC_REC_MIXER, 11, 1, 1),
SOC_DAPM_SINGLE("HP Mixer Capture Switch", RT5625_ADC_REC_MIXER, 10, 1, 1),
SOC_DAPM_SINGLE("MoNo Mixer Capture Switch", RT5625_ADC_REC_MIXER, 8, 1, 1),
SOC_DAPM_SINGLE("SPK Mixer Capture Switch", RT5625_ADC_REC_MIXER, 9, 1, 1),

};

/*Left ADC Rec mixer*/
static const struct snd_kcontrol_new rt5625_right_adc_rec_mixer_controls[] = {
SOC_DAPM_SINGLE("Mic1 Capture Switch", RT5625_ADC_REC_MIXER, 6, 1, 1),
SOC_DAPM_SINGLE("Mic2 Capture Switch", RT5625_ADC_REC_MIXER, 5, 1, 1),
SOC_DAPM_SINGLE("LineIn Capture Switch", RT5625_ADC_REC_MIXER, 4, 1, 1),
SOC_DAPM_SINGLE("Phone Capture Switch", RT5625_ADC_REC_MIXER, 3, 1, 1),
SOC_DAPM_SINGLE("HP Mixer Capture Switch", RT5625_ADC_REC_MIXER, 2, 1, 1),
SOC_DAPM_SINGLE("MoNo Mixer Capture Switch", RT5625_ADC_REC_MIXER, 0, 1, 1),
SOC_DAPM_SINGLE("SPK Mixer Capture Switch", RT5625_ADC_REC_MIXER, 1, 1, 1),
};

static const struct snd_kcontrol_new rt5625_left_hp_mixer_controls[] = {
SOC_DAPM_SINGLE("ADC Playback Switch", RT5625_ADC_REC_GAIN, 15, 1, 1),
SOC_DAPM_SINGLE("LineIn Playback Switch", HPL_MIXER, 0, 1, 0),
SOC_DAPM_SINGLE("Phone Playback Switch", HPL_MIXER, 1, 1, 0),
SOC_DAPM_SINGLE("Mic1 Playback Switch", HPL_MIXER, 2, 1, 0),
SOC_DAPM_SINGLE("Mic2 Playback Switch", HPL_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("HIFI DAC Playback Switch", RT5625_DAC_AND_MIC_CTRL, 3, 1, 1),
SOC_DAPM_SINGLE("Voice DAC Playback Switch", HPL_MIXER, 4, 1, 0),
};

static const struct snd_kcontrol_new rt5625_right_hp_mixer_controls[] = {
SOC_DAPM_SINGLE("ADC Playback Switch", RT5625_ADC_REC_GAIN, 7, 1, 1),
SOC_DAPM_SINGLE("LineIn Playback Switch", HPR_MIXER, 0, 1, 0),
SOC_DAPM_SINGLE("Phone Playback Switch", HPR_MIXER, 1, 1, 0),
SOC_DAPM_SINGLE("Mic1 Playback Switch", HPR_MIXER, 2, 1, 0),
SOC_DAPM_SINGLE("Mic2 Playback Switch", HPR_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("HIFI DAC Playback Switch", RT5625_DAC_AND_MIC_CTRL, 2, 1, 1),
SOC_DAPM_SINGLE("Voice DAC Playback Switch", HPR_MIXER, 4, 1, 0),
};

static const struct snd_kcontrol_new rt5625_mono_mixer_controls[] = {
SOC_DAPM_SINGLE("ADCL Playback Switch", RT5625_ADC_REC_GAIN, 14, 1, 1),
SOC_DAPM_SINGLE("ADCR Playback Switch", RT5625_ADC_REC_GAIN, 6, 1, 1),
SOC_DAPM_SINGLE("Line Mixer Playback Switch", RT5625_LINE_IN_VOL, 13, 1, 1),
SOC_DAPM_SINGLE("Mic1 Playback Switch", RT5625_DAC_AND_MIC_CTRL, 13, 1, 1),
SOC_DAPM_SINGLE("Mic2 Playback Switch", RT5625_DAC_AND_MIC_CTRL, 9, 1, 1),
SOC_DAPM_SINGLE("DAC Mixer Playback Switch", RT5625_DAC_AND_MIC_CTRL, 0, 1, 1),
SOC_DAPM_SINGLE("Voice DAC Playback Switch", RT5625_VOICE_DAC_OUT_VOL, 13, 1, 1),
};

static const struct snd_kcontrol_new rt5625_spk_mixer_controls[] = {
SOC_DAPM_SINGLE("Line Mixer Playback Switch", RT5625_LINE_IN_VOL, 14, 1, 1),   
SOC_DAPM_SINGLE("Phone Playback Switch", RT5625_PHONEIN_VOL, 14, 1, 1),
SOC_DAPM_SINGLE("Mic1 Playback Switch", RT5625_DAC_AND_MIC_CTRL, 14, 1, 1),
SOC_DAPM_SINGLE("Mic2 Playback Switch", RT5625_DAC_AND_MIC_CTRL, 10, 1, 1),
SOC_DAPM_SINGLE("DAC Mixer Playback Switch", RT5625_DAC_AND_MIC_CTRL, 1, 1, 1),
SOC_DAPM_SINGLE("Voice DAC Playback Switch", RT5625_VOICE_DAC_OUT_VOL, 14, 1, 1),
};

static int mixer_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event)
{
    struct snd_soc_codec *codec = w->codec;
    unsigned int l, r;

//    printk(KERN_INFO "enter %s/n", __func__);

    l= rt5625_read(codec, HPL_MIXER);
    r = rt5625_read(codec, HPR_MIXER);
   
    if ((l & 0x1) || (r & 0x1))
        rt5625_write_mask(codec, 0x0a, 0x0000, 0x8000);
    else
        rt5625_write_mask(codec, 0x0a, 0x8000, 0x8000);

    if ((l & 0x2) || (r & 0x2))
        rt5625_write_mask(codec, 0x08, 0x0000, 0x8000);
    else
        rt5625_write_mask(codec, 0x08, 0x8000, 0x8000);

    if ((l & 0x4) || (r & 0x4))
        rt5625_write_mask(codec, 0x10, 0x0000, 0x8000);
    else
        rt5625_write_mask(codec, 0x10, 0x8000, 0x8000);

    if ((l & 0x8) || (r & 0x8))
        rt5625_write_mask(codec, 0x10, 0x0000, 0x0800);
    else
        rt5625_write_mask(codec, 0x10, 0x0800, 0x0800);

    if ((l & 0x10) || (r & 0x10))
        rt5625_write_mask(codec, 0x18, 0x0000, 0x8000);
    else
        rt5625_write_mask(codec, 0x18, 0x8000, 0x8000);

    return 0;
}


/*
 *    bit[0][1] use for aec control
 *    bit[2][3] for ADCR func
 *    bit[4] for SPKL pga
 *    bit[5] for SPKR pga
 *    bit[6] for hpl pga
 *    bit[7] for hpr pga
 */
static int spk_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event)
 {
    struct snd_soc_codec *codec = w->codec;
    int reg;
   
    printk(KERN_DEBUG "enter %s/n", __func__);
    reg = rt5625_read(codec, VIRTUAL_REG_FOR_MISC_FUNC) & (0x3 << 4);
    if ((reg >> 4) != 0x3 && reg != 0)
        return 0;

    switch (event)
    {
        case SND_SOC_DAPM_POST_PMU:
//            printk(KERN_INFO "after virtual spk power up!/n");
            rt5625_write_mask(codec, 0x3e, 0x3000, 0x3000);
            rt5625_write_mask(codec, 0x02, 0x0000, 0x8080);
            rt5625_write_mask(codec, 0x3a, 0x0400, 0x0400);                  //power on spk amp
            break;
        case SND_SOC_DAPM_POST_PMD:
//            printk(KERN_INFO "aftet virtual spk power down!/n");
            rt5625_write_mask(codec, 0x3a, 0x0000, 0x0400);//power off spk amp
            rt5625_write_mask(codec, 0x02, 0x8080, 0x8080);
            rt5625_write_mask(codec, 0x3e, 0x0000, 0x3000);                
            break;
        default:
            return 0;
    }
    return 0;
}




static int hp_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event)
{
    struct snd_soc_codec *codec = w->codec;
    int reg;

    printk(KERN_DEBUG "enter %s/n", __func__);
    reg = rt5625_read(codec, VIRTUAL_REG_FOR_MISC_FUNC) & (0x3 << 6);
    if ((reg >> 6) != 0x3 && reg != 0)
        return 0;
   
    switch (event)
    {
        case SND_SOC_DAPM_POST_PMD:
//            printk(KERN_INFO "aftet virtual hp power down!/n");
            rt5625_write_mask(codec, 0x04, 0x8080, 0x8080);
            rt5625_write_mask(codec, 0x3e, 0x0000, 0x0600);
            rt5625_write_mask(codec, 0x3a, 0x0000, 0x0030);
            break;
        case SND_SOC_DAPM_POST_PMU:   
//            printk(KERN_INFO "after virtual hp power up!/n");
            hp_depop_mode2(codec);
            rt5625_write_mask(codec ,0x04, 0x0000, 0x8080);
            break;
        default:
            return 0;
    }   
    return 0;
}



static int aux_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event)
{
    return 0;
}

/*SPKOUT Mux*/
static const struct snd_kcontrol_new rt5625_spkout_mux_out_controls =
SOC_DAPM_ENUM("Route", rt5625_enum[2]);

/*HPLOUT MUX*/
static const struct snd_kcontrol_new rt5625_hplout_mux_out_controls =
SOC_DAPM_ENUM("Route", rt5625_enum[3]);

/*HPROUT MUX*/
static const struct snd_kcontrol_new rt5625_hprout_mux_out_controls =
SOC_DAPM_ENUM("Route", rt5625_enum[4]);
/*AUXOUT MUX*/
static const struct snd_kcontrol_new rt5625_auxout_mux_out_controls =
SOC_DAPM_ENUM("Route", rt5625_enum[5]);

static const struct snd_soc_dapm_widget rt5625_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("Left LineIn"),
SND_SOC_DAPM_INPUT("Right LineIn"),
SND_SOC_DAPM_INPUT("Phone"),
SND_SOC_DAPM_INPUT("Mic1"),
SND_SOC_DAPM_INPUT("Mic2"),

SND_SOC_DAPM_PGA("Mic1 Boost", RT5625_PWR_MANAG_ADD3, 1, 0, NULL, 0),
SND_SOC_DAPM_PGA("Mic2 Boost", RT5625_PWR_MANAG_ADD3, 0, 0, NULL, 0),

SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback DAC", RT5625_PWR_MANAG_ADD2, 9, 0),
SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback DAC", RT5625_PWR_MANAG_ADD2, 8, 0),
SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback DAC", RT5625_PWR_MANAG_ADD2, 10, 0),

SND_SOC_DAPM_PGA("Left LineIn PGA", RT5625_PWR_MANAG_ADD3, 7, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right LineIn PGA", RT5625_PWR_MANAG_ADD3, 6, 0, NULL, 0),
SND_SOC_DAPM_PGA("Phone PGA", RT5625_PWR_MANAG_ADD3, 5, 0, NULL, 0),
SND_SOC_DAPM_PGA("Mic1 PGA", RT5625_PWR_MANAG_ADD3, 3, 0, NULL, 0),
SND_SOC_DAPM_PGA("Mic2 PGA", RT5625_PWR_MANAG_ADD3, 2, 0, NULL, 0),
SND_SOC_DAPM_PGA("Left DAC PGA", RT5625_PWR_MANAG_ADD1, 15, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right DAC PGA", RT5625_PWR_MANAG_ADD1, 14, 0, NULL, 0),
SND_SOC_DAPM_PGA("VoDAC PGA", RT5625_PWR_MANAG_ADD1, 7, 0, NULL, 0),
SND_SOC_DAPM_MIXER("Left Rec Mixer", RT5625_PWR_MANAG_ADD2, 1, 0,
    &rt5625_left_adc_rec_mixer_controls[0], ARRAY_SIZE(rt5625_left_adc_rec_mixer_controls)),
SND_SOC_DAPM_MIXER("Right Rec Mixer", RT5625_PWR_MANAG_ADD2, 0, 0,
    &rt5625_right_adc_rec_mixer_controls[0], ARRAY_SIZE(rt5625_right_adc_rec_mixer_controls)),
SND_SOC_DAPM_MIXER_E("Left HP Mixer", RT5625_PWR_MANAG_ADD2, 5, 0,
    &rt5625_left_hp_mixer_controls[0], ARRAY_SIZE(rt5625_left_hp_mixer_controls),
    mixer_event, SND_SOC_DAPM_POST_REG),
SND_SOC_DAPM_MIXER_E("Right HP Mixer", RT5625_PWR_MANAG_ADD2, 4, 0,
    &rt5625_right_hp_mixer_controls[0], ARRAY_SIZE(rt5625_right_hp_mixer_controls),
    mixer_event, SND_SOC_DAPM_POST_REG),
SND_SOC_DAPM_MIXER("MoNo Mixer", RT5625_PWR_MANAG_ADD2, 2, 0,
    &rt5625_mono_mixer_controls[0], ARRAY_SIZE(rt5625_mono_mixer_controls)),
SND_SOC_DAPM_MIXER("SPK Mixer", RT5625_PWR_MANAG_ADD2, 3, 0,
    &rt5625_spk_mixer_controls[0], ARRAY_SIZE(rt5625_spk_mixer_controls)),
   
/*hpl mixer --> hp mixer-->spkout mux, hpr mixer-->hp mixer -->spkout mux
   hpl mixer-->hp mixer-->Auxout Mux, hpr muxer-->hp mixer-->auxout mux*/   
SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("DAC Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),

SND_SOC_DAPM_MUX("SPKOUT Mux", SND_SOC_NOPM, 0, 0, &rt5625_spkout_mux_out_controls),
SND_SOC_DAPM_MUX("HPLOUT Mux", SND_SOC_NOPM, 0, 0, &rt5625_hplout_mux_out_controls),
SND_SOC_DAPM_MUX("HPROUT Mux", SND_SOC_NOPM, 0, 0, &rt5625_hprout_mux_out_controls),
SND_SOC_DAPM_MUX("AUXOUT Mux", SND_SOC_NOPM, 0, 0, &rt5625_auxout_mux_out_controls),

SND_SOC_DAPM_PGA_E("SPKL Out PGA", VIRTUAL_REG_FOR_MISC_FUNC, 4, 0, NULL, 0,
                spk_pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_PGA_E("SPKR Out PGA", VIRTUAL_REG_FOR_MISC_FUNC, 5, 0, NULL, 0,
                spk_pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_PGA_E("HPL Out PGA",VIRTUAL_REG_FOR_MISC_FUNC, 6, 0, NULL, 0,
                hp_pga_event, SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU),
SND_SOC_DAPM_PGA_E("HPR Out PGA",VIRTUAL_REG_FOR_MISC_FUNC, 7, 0, NULL, 0,
                hp_pga_event, SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU),
SND_SOC_DAPM_PGA_E("AUX Out PGA",RT5625_PWR_MANAG_ADD3, 14, 0, NULL, 0,
                aux_pga_event, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU),
               

SND_SOC_DAPM_ADC("Left ADC", "Left ADC HiFi Capture", RT5625_PWR_MANAG_ADD2, 7, 0),
SND_SOC_DAPM_ADC("Right ADC", "Right ADC HiFi Capture", RT5625_PWR_MANAG_ADD2, 6, 0),
SND_SOC_DAPM_OUTPUT("SPKL"),
SND_SOC_DAPM_OUTPUT("SPKR"),
SND_SOC_DAPM_OUTPUT("HPL"),
SND_SOC_DAPM_OUTPUT("HPR"),
SND_SOC_DAPM_OUTPUT("AUX"),
SND_SOC_DAPM_MICBIAS("Mic1 Bias", RT5625_PWR_MANAG_ADD1, 3, 0),
SND_SOC_DAPM_MICBIAS("Mic2 Bias", RT5625_PWR_MANAG_ADD1, 2, 0),
};

static const struct snd_soc_dapm_route audio_map[] = {
        /*Input PGA*/

        {"Left LineIn PGA", NULL, "Left LineIn"},
        {"Right LineIn PGA", NULL, "Right LineIn"},
        {"Phone PGA", NULL, "Phone"},
        {"Mic1 Boost", NULL, "Mic1"},
        {"Mic2 Boost", NULL, "Mic2"},
        {"Mic1 PGA", NULL, "Mic1 Boost"},
        {"Mic2 PGA", NULL, "Mic2 Boost"},
        {"Left DAC PGA", NULL, "Left DAC"},
        {"Right DAC PGA", NULL, "Right DAC"},
        {"VoDAC PGA", NULL, "Voice DAC"},
       
        /*Left ADC mixer*/
        {"Left Rec Mixer", "LineIn Capture Switch", "Left LineIn"},
        {"Left Rec Mixer", "Phone Capture Switch", "Phone"},
        {"Left Rec Mixer", "Mic1 Capture Switch", "Mic1"},
        {"Left Rec Mixer", "Mic2 Capture Switch", "Mic2"},
        {"Left Rec Mixer", "HP Mixer Capture Switch", "Left HP Mixer"},
        {"Left Rec Mixer", "SPK Mixer Capture Switch", "SPK Mixer"},
        {"Left Rec Mixer", "MoNo Mixer Capture Switch", "MoNo Mixer"},

        /*Right ADC Mixer*/
        {"Right Rec Mixer", "LineIn Capture Switch", "Right LineIn"},
        {"Right Rec Mixer", "Phone Capture Switch", "Phone"},
        {"Right Rec Mixer", "Mic1 Capture Switch", "Mic1"},
        {"Right Rec Mixer", "Mic2 Capture Switch", "Mic2"},
        {"Right Rec Mixer", "HP Mixer Capture Switch", "Right HP Mixer"},
        {"Right Rec Mixer", "SPK Mixer Capture Switch", "SPK Mixer"},
        {"Right Rec Mixer", "MoNo Mixer Capture Switch", "MoNo Mixer"},
       
        /*HPL mixer*/
        {"Left HP Mixer", "ADC Playback Switch", "Left Rec Mixer"},
        {"Left HP Mixer", "LineIn Playback Switch", "Left LineIn PGA"},
        {"Left HP Mixer", "Phone Playback Switch", "Phone PGA"},
        {"Left HP Mixer", "Mic1 Playback Switch", "Mic1 PGA"},
        {"Left HP Mixer", "Mic2 Playback Switch", "Mic2 PGA"},
        {"Left HP Mixer", "HIFI DAC Playback Switch", "Left DAC PGA"},
        {"Left HP Mixer", "Voice DAC Playback Switch", "VoDAC PGA"},
       
        /*HPR mixer*/
        {"Right HP Mixer", "ADC Playback Switch", "Right Rec Mixer"},
        {"Right HP Mixer", "LineIn Playback Switch", "Right LineIn PGA"},   
        {"Right HP Mixer", "HIFI DAC Playback Switch", "Right DAC PGA"},
        {"Right HP Mixer", "Phone Playback Switch", "Phone PGA"},
        {"Right HP Mixer", "Mic1 Playback Switch", "Mic1 PGA"},
        {"Right HP Mixer", "Mic2 Playback Switch", "Mic2 PGA"},
        {"Right HP Mixer", "Voice DAC Playback Switch", "VoDAC PGA"},

        /*DAC Mixer*/
        {"DAC Mixer", NULL, "Left DAC PGA"},
        {"DAC Mixer", NULL, "Right DAC PGA"},

        /*line mixer*/
        {"Line Mixer", NULL, "Left LineIn PGA"},
        {"Line Mixer", NULL, "Right LineIn PGA"},

        /*spk mixer*/
        {"SPK Mixer", "Line Mixer Playback Switch", "Line Mixer"},
        {"SPK Mixer", "Phone Playback Switch", "Phone PGA"},
        {"SPK Mixer", "Mic1 Playback Switch", "Mic1 PGA"},
        {"SPK Mixer", "Mic2 Playback Switch", "Mic2 PGA"},
        {"SPK Mixer", "DAC Mixer Playback Switch", "DAC Mixer"},
        {"SPK Mixer", "Voice DAC Playback Switch", "VoDAC PGA"},

        /*mono mixer*/
        {"MoNo Mixer", "Line Mixer Playback Switch", "Line Mixer"},
        {"MoNo Mixer", "ADCL Playback Switch","Left Rec Mixer"},
        {"MoNo Mixer", "ADCR Playback Switch","Right Rec Mixer"},
        {"MoNo Mixer", "Mic1 Playback Switch", "Mic1 PGA"},
        {"MoNo Mixer", "Mic2 Playback Switch", "Mic2 PGA"},
        {"MoNo Mixer", "DAC Mixer Playback Switch", "DAC Mixer"},
        {"MoNo Mixer", "Voice DAC Playback Switch", "VoDAC PGA"},
       
        /*hp mixer*/
        {"HP Mixer", NULL, "Left HP Mixer"},
        {"HP Mixer", NULL, "Right HP Mixer"},

        /*spkout mux*/
        {"SPKOUT Mux", "HP Mixer", "HP Mixer"},
        {"SPKOUT Mux", "SPK Mixer", "SPK Mixer"},
        {"SPKOUT Mux", "Mono Mixer", "MoNo Mixer"},
       
        /*hpl out mux*/
        {"HPLOUT Mux", "HPL Mixer", "Left HP Mixer"},
       
        /*hpr out mux*/
        {"HPROUT Mux", "HPR Mixer", "Right HP Mixer"},

        /*aux out mux*/
        {"AUXOUT Mux", "HP Mixer", "HP Mixer"},
        {"AUXOUT Mux", "SPK Mixer", "SPK Mixer"},
        {"SPKOUT Mux", "Mono Mixer", "MoNo Mixer"},

        /*spkl out pga*/
        {"SPKL Out PGA", NULL, "SPKOUT Mux"},
       
       
        /*spkr out pga*/
        {"SPKR Out PGA", NULL, "SPKOUT Mux"},
       
        /*hpl out pga*/
        {"HPL Out PGA", NULL, "HPLOUT Mux"},

        /*hpr out pga*/
        {"HPR Out PGA", NULL, "HPROUT Mux"},

        /*aux out pga*/
        {"AUX Out PGA", NULL, "AUXOUT Mux"},
       
        /*left adc*/
        {"Left ADC", NULL, "Left Rec Mixer"},
       
        /*right adc*/
        {"Right ADC", NULL, "Right Rec Mixer"},
       
        /*output*/
        {"SPKL", NULL, "SPKL Out PGA"},
        {"SPKR", NULL, "SPKR Out PGA"},
        {"HPL", NULL, "HPL Out PGA"},
        {"HPR", NULL, "HPR Out PGA"},
        {"AUX", NULL, "AUX Out PGA"},
};


static int rt5625_add_widgets(struct snd_soc_codec *codec)
{
    snd_soc_dapm_new_controls(codec, rt5625_dapm_widgets,
                ARRAY_SIZE(rt5625_dapm_widgets));
    snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
    snd_soc_dapm_new_widgets(codec);
    return 0;
}

#endif
#if !USE_DAPM_CTRL

static enum OUTPUT_DEVICE_MASK
{
    SPK_OUTPUT_MASK = 1,
    HP_OUTPUT_MASK,
};



static int rt5625_set_path_from_dac_to_output(struct snd_soc_codec *codec, int enable, int sink)
{
    switch (sink)
    {
        case SPK_OUTPUT_MASK:
            if (enable)
            {   
                rt5625_write_mask(codec, 0x10, 0x000c, 0x000c);          /*unmute dac-->hpmixer*/
                rt5625_write_mask(codec, 0x1c, 0x0400, 0x0400);       /*choose mux to hpmixer-->spk  */       
                rt5625_write_mask(codec, 0x3c, 0x0300, 0x0300);      /*power up dac lr */
                rt5625_write_mask(codec, 0x3c, 0x0030, 0x0030);      /*power up hp mixer lr*/
                rt5625_write_mask(codec, 0x3c, 0x3000, 0x3000);      /*power up spk lr*/
               
            }
            else
                rt5625_write_mask(codec, 0x02, 0x8080, 0x8080);      /*mute spk*/           
            break;
        case HP_OUTPUT_MASK:
            if (enable)
            {
                rt5625_write_mask(codec, 0x10, 0x000c, 0x000c);          /*unmute dac-->hpmixer*/
                rt5625_write_mask(codec, 0x1c, 0x0300, 0x0300);          /*hpmixer to hp out*/
                rt5625_write_mask(codec, 0x3c, 0x0300, 0x0300);      /*power up dac lr */
                rt5625_write_mask(codec, 0x3c, 0x0030, 0x0030);      /*power up hp mixer lr*/
                hp_depop_mode2(codec);
            }
            else
            {
                rt5625_write_mask(codec, 0x3a, 0x0000, 0x0300);                     /*power off hp amp*/
                rt5625_write_mask(codec, 0x04, 0x8080, 0x8080);
            }
            break;
        default:
        return 0;
    }
    return 0;
}


#endif

#if !USE_DAPM_CTRL
rt5625_pcm_hw_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai)
{
    struct snd_soc_codec *codec = codec_dai->codec;
    int stream = substream->stream;


    switch (stream)
    {
        case SNDRV_PCM_STREAM_PLAYBACK:   
            rt5625_write_mask(codec, 0x3a, 0xc000, 0xc000);
            rt5625_write_mask(codec, 0x3c, 0x0330, 0x0330);        /*power daclr*/
            hp_depop_mode2(codec);
            rt5625_write_mask(codec, 0x3c, 0x3000, 0x3000);       /*power spklr volume*/
            rt5625_write_mask(codec, 0x04, 0x0000, 0x8080);        /*unmute hp*/
            rt5625_write_mask(codec, 0x02, 0x0000, 0x8080);        /*unmute spk*/
            rt5625_write_mask(codec, 0x3a, 0x0400, 0x0400);        /*power on classabd amp*/   
            break;
        case SNDRV_PCM_STREAM_CAPTURE:           
            rt5625_write_mask(codec, 0x3e, 0x0002, 0x0002);        /*power mic1 boost*/
            rt5625_write_mask(codec, 0x3c, 0x0003, 0x0003);        /*power adc rec mixer lr*/
            rt5625_write_mask(codec, 0x3c, 0x00c0, 0x00c0);         /*power adc lr*/
            break;
        default:
            return 0;
    }
    return 0;   
}
#endif
struct _pll_div{
    u32 pll_in;
    u32 pll_out;
    u16 regvalue;
};


/**************************************************************
  *    watch out!
  *    our codec support you to select different source as pll input, but if you
  *    use both of the I2S audio interface and pcm interface instantially.
  *    The two DAI must have the same pll setting params, so you have to offer
  *    the same pll input, and set our codec's sysclk the same one, we suggest
  *    24576000.
  **************************************************************/
static const struct _pll_div codec_master_pll1_div[] = {
       
    {  2048000,  24576000,    0x2ea0},
    {  3686400,  24576000,    0xee27},   
    { 12000000,  24576000,    0x2915},  
    { 13000000,  24576000,    0x772e},
    { 13100000,     24576000,    0x0d20},   
    {18432000, 24576000, 0x0290},
    {12288000, 24576000, 0x0490},
    {7872000, 24576000, 0x6519},
    {6144000, 24576000, 0x0a90},
    {4096000, 24576000, 0x1090},   
};

static const struct _pll_div codec_bclk_pll1_div[] = {

    {  1536000,     24576000,    0x3ea0},   
    {  3072000,     24576000,    0x1ea0},
};

static const struct _pll_div codec_vbclk_pll1_div[] = {

    {  1536000,     24576000,    0x3ea0},   
    {  3072000,     24576000,    0x1ea0},
};


struct _coeff_div_stereo {
    unsigned int mclk;
    unsigned int rate;
    unsigned int reg60;
    unsigned int reg62;
};

struct _coeff_div_voice {
    unsigned int mclk;
    unsigned int rate;
    unsigned int reg64;
};

static const struct _coeff_div_stereo coeff_div_stereo[] = {
        /*bclk is config to 32fs, if codec is choose to be slave mode , input bclk should be 32*fs */
        {24576000, 48000, 0x3174, 0x1010},     
        {12288000, 48000, 0x1174, 0x0000},
        {18432000, 48000, 0x2174, 0x1111},
        {36864000, 48000, 0x2274, 0x2020},
        {49152000, 48000, 0xf074, 0x3030},
        {0, 0, 0, 0},
};

static const struct _coeff_div_voice coeff_div_voice[] = {
        /*bclk is config to 32fs, if codec is choose to be slave mode , input bclk should be 32*fs */
        {24576000, 16000, 0x2622},
        {24576000, 8000, 0x2824},
        {0, 0, 0},
};

static int get_coeff(unsigned int mclk, unsigned int rate, int mode)
{
    int i;

//    printk("get_coeff mclk = %d, rate = %d/n", mclk, rate);
    if (!mode){
        for (i = 0; i < ARRAY_SIZE(coeff_div_stereo); i++) {
            if ((coeff_div_stereo[i].rate == rate) && (coeff_div_stereo[i].mclk == mclk))
                return i;
        }
    }
    else {
        for (i = 0; i< ARRAY_SIZE(coeff_div_voice); i++) {
            if ((coeff_div_voice[i].rate == rate) && (coeff_div_voice[i].mclk == mclk))
                return i;
        }
    }

    return -EINVAL;
    printk(KERN_ERR "can't find a matched mclk and rate in %s/n",
                (mode ? "coeff_div_voice[]" : "coeff_div_audio[]"));
}


static int rt5625_codec_set_dai_pll(struct snd_soc_dai *codec_dai,
        int pll_id, unsigned int freq_in, unsigned int freq_out)
{
    int i;
    int ret = -EINVAL;
    struct snd_soc_codec *codec = codec_dai->codec;

//    printk(KERN_DEBUG "enter %s/n", __func__);

    if (pll_id < RT5625_PLL1_FROM_MCLK || pll_id > RT5625_PLL1_FROM_VBCLK)
        return -EINVAL;

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

    if (RT5625_PLL1_FROM_MCLK == pll_id)
    {
        for (i = 0; i < ARRAY_SIZE(codec_master_pll1_div); i ++)
        {
            if ((freq_in == codec_master_pll1_div[i].pll_in) && (freq_out == codec_master_pll1_div[i].pll_out))
            {
                rt5625_write(codec, RT5625_GEN_CTRL_REG2, 0x0000);                                 /*PLL source from MCLK*/
                rt5625_write(codec, RT5625_PLL_CTRL, codec_master_pll1_div[i].regvalue);   /*set pll code*/
                rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD2, 0x8000, 0x8000);            /*enable pll1 power*/
                ret = 0;
            }
        }
    }
    else if (RT5625_PLL1_FROM_BCLK == pll_id)
    {
        for (i = 0; i < ARRAY_SIZE(codec_bclk_pll1_div); i ++)
        {
            if ((freq_in == codec_bclk_pll1_div[i].pll_in) && (freq_out == codec_bclk_pll1_div[i].pll_out))
            {
                rt5625_write(codec, RT5625_GEN_CTRL_REG2, 0x2000);                        /*PLL source from BCLK*/
                rt5625_write(codec, RT5625_PLL_CTRL, codec_bclk_pll1_div[i].regvalue);   /*set pll1 code*/
                rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD2, 0x8000, 0x8000);            /*enable pll1 power*/
                ret = 0;
            }
        }
    }
    else if (RT5625_PLL1_FROM_VBCLK == pll_id)
    {
        for (i = 0; i < ARRAY_SIZE(codec_vbclk_pll1_div); i ++)
        {
            if ((freq_in == codec_vbclk_pll1_div[i].pll_in) && (freq_out == codec_vbclk_pll1_div[i].pll_out))
            {
                rt5625_write(codec, RT5625_GEN_CTRL_REG2, 0x2000);                        /*PLL source from BCLK*/
                rt5625_write(codec, RT5625_PLL_CTRL, codec_vbclk_pll1_div[i].regvalue);   /*set pll1 code*/
                rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD2, 0x8000, 0x8000);            /*enable pll1 power*/
                ret = 0;
            }
        }
    }
   
    rt5625_write_mask(codec, RT5625_GEN_CTRL_REG1, 0x8000, 0x8000);
    return ret;
}

static int rt5625_set_aec_sysclk(struct snd_soc_codec *codec, unsigned int mode)
{
    unsigned int pll_in;
    int i;

    pll_in = rt5625_aec_cfg->pll_input;

    if (pll_in < 2000000 || pll_in > 4000000) {
        printk(KERN_ERR "your pll input isn't matched, not a proper one/n");
        return 0;
    }

    if (mode != VODSP_AEC_DISABLE) {
        for (i = 0; i < ARRAY_SIZE(codec_bclk_pll1_div); i ++)
        {
            if ((pll_in == codec_bclk_pll1_div[i].pll_in) && (24576000 == codec_bclk_pll1_div[i].pll_out))
            {
                rt5625_write(codec, RT5625_GEN_CTRL_REG2, 0x2000);                        /*PLL source from BCLK*/
                rt5625_write(codec, RT5625_PLL_CTRL, codec_bclk_pll1_div[i].regvalue);   /*set pll1 code*/
                rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD2, 0x8000, 0x8000);            /*enable pll1 power*/
   
            }
        }
        rt5625_write_mask(codec, 0x3a, 0x8000, 0x8000);     //enable pll
        rt5625_write_mask(codec, 0x3a, 0x0001, 0x0001);    //enable dac ref
        rt5625_write_mask(codec, 0x42, 0x0000, 0x3000);   //pll1 source from mclk
        rt5625_write_mask(codec, 0x40, 0x8000, 0x8000);    //main sysclk from pll1
       
    } else {
        rt5625_write_mask(codec, 0x3a, 0x0000, 0x8000);     //disable pll
        rt5625_write_mask(codec, 0x3a, 0x0000, 0x0001);    //disable dac ref
    }
    return 0;
   
}
static int rt5625_hifi_codec_set_dai_sysclk(struct snd_soc_dai *codec_dai,
        int clk_id, unsigned int freq, int dir)
{
    struct snd_soc_codec *codec = codec_dai->codec;
    struct rt5625_priv * rt5625 = codec->private_data;
    printk(KERN_DEBUG "enter %s/n", __func__);
   
    if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) {
        rt5625->stereo_sysclk = freq;
        return 0;
    }
   
    printk(KERN_ERR "unsupported sysclk freq %u for audio i2s/n", freq);
    return -EINVAL;
}

static int rt5625_voice_codec_set_dai_sysclk(struct snd_soc_dai *codec_dai,
        int clk_id, unsigned int freq, int dir)
{
    struct snd_soc_codec *codec = codec_dai->codec;
    struct rt5625_priv * rt5625 = codec->private_data;
   

    if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) {
        rt5625->voice_sysclk = freq;
        return 0;
    }           

    printk(KERN_ERR "unsupported sysclk freq %u for voice pcm/n", freq);
    return -EINVAL;
}


static int rt5625_hifi_pcm_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_device *socdev = rtd->socdev;
    struct snd_soc_codec *codec = socdev ->codec;
    struct rt5625_priv *rt5625 = codec->private_data;
    struct snd_soc_dapm_widget *w;
    unsigned int iface = rt5625_read(codec, RT5625_MAIN_SDP_CTRL) & 0xfff3;
    int rate = params_rate(params);
    int coeff = get_coeff(rt5625->stereo_sysclk, rate, 0);
   
    printk(KERN_DEBUG "enter %s/n", __func__);

    list_for_each_entry(w, &codec->dapm_widgets, list)
    {
        if (!w->sname)
            continue;
        if (!strcmp(w->name, "Right ADC"))
            strcpy(w->sname, "Right ADC HiFi Capture");
    }
   
    switch (params_format(params))
    {
        case SNDRV_PCM_FORMAT_S16_LE:
            break;
        case SNDRV_PCM_FORMAT_S20_3LE:
            iface |= 0x0004;
        case SNDRV_PCM_FORMAT_S24_LE:
            iface |= 0x0008;
        case SNDRV_PCM_FORMAT_S8:
            iface |= 0x000c;
    }
   
    rt5625_write(codec, RT5625_MAIN_SDP_CTRL, iface);
    rt5625_write_mask(codec, 0x3a, 0x0801, 0x0801);   /*power i2s and dac ref*/
   
   
    rt5625_write(codec, RT5625_STEREO_DAC_CLK_CTRL1, 0x1075);/*Jiujin.hong IIS BCLK DIV*/
    rt5625_write(codec, RT5625_STEREO_DAC_CLK_CTRL2, 0x2020);
#if 0
    if (coeff >= 0) {
        rt5625_write(codec, RT5625_STEREO_DAC_CLK_CTRL1, coeff_div_stereo[coeff].reg60);
        rt5625_write(codec, RT5625_STEREO_DAC_CLK_CTRL2, coeff_div_stereo[coeff].reg62);
    }
    else
        return coeff;
#endif
    return 0;
}

static int rt5625_voice_pcm_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_device *socdev = rtd->socdev;
    struct snd_soc_codec *codec = socdev ->codec;
    struct rt5625_priv *rt5625 = codec->private_data;
    struct snd_soc_dapm_widget *w;
    unsigned int iface = rt5625_read(codec, RT5625_EXTEND_SDP_CTRL) & 0xfff3;
    int rate = params_rate(params);
    int coeff = get_coeff(rt5625->voice_sysclk, rate, 1);

    printk(KERN_DEBUG "enter %s/n", __func__);

    list_for_each_entry(w, &codec->dapm_widgets, list)
    {
        if (!w->sname)
            continue;
        if (!strcmp(w->name, "Right ADC"))
            strcpy(w->sname, "Right ADC Voice Capture");
    }
   
    switch (params_format(params))
    {
        case SNDRV_PCM_FORMAT_S16_LE:
            break;
        case SNDRV_PCM_FORMAT_S20_3LE:
            iface |= 0x0004;
        case SNDRV_PCM_FORMAT_S24_LE:
            iface |= 0x0008;
        case SNDRV_PCM_FORMAT_S8:
            iface |= 0x000c;
    }
    rt5625_write_mask(codec, 0x3a, 0x0801, 0x0801);   /*power i2s and dac ref*/
    rt5625_write(codec, RT5625_EXTEND_SDP_CTRL, iface);
    if (coeff >= 0)
        rt5625_write(codec, RT5625_VOICE_DAC_PCMCLK_CTRL1, coeff_div_voice[coeff].reg64);
    else
        return coeff;
    return 0;
}


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

    printk(KERN_DEBUG "enter %s/n", __func__);

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

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

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

    rt5625_write(codec, RT5625_MAIN_SDP_CTRL, iface);
    return 0;
}

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

    printk(KERN_DEBUG "enter %s/n", __func__);
    /*set slave/master mode*/
    switch (fmt & SND_SOC_DAIFMT_MASTER_MASK)
    {
        case SND_SOC_DAIFMT_CBM_CFM:
            iface = 0x0000;
            break;
        case SND_SOC_DAIFMT_CBS_CFS:
            iface = 0x4000;
            break;
        default:
            return -EINVAL;           
    }

    switch(fmt & SND_SOC_DAIFMT_FORMAT_MASK)
    {
        case SND_SOC_DAIFMT_I2S:
            iface |= 0x0000;
            break;
        case SND_SOC_DAIFMT_LEFT_J:
            iface |= 0x0001;
            break;
        case SND_SOC_DAIFMT_DSP_A:
            iface |= 0x0002;
            break;
        case SND_SOC_DAIFMT_DSP_B:
            iface |= 0x0003;
            break;
        default:
            return -EINVAL;       
    }

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

    iface |= 0x8000;      /*enable vopcm*/
    rt5625_write(codec, RT5625_EXTEND_SDP_CTRL, iface);   
    return 0;
}


static int rt5625_hifi_codec_mute(struct snd_soc_dai *dai, int mute)
{
    struct snd_soc_codec *codec = dai->codec;
   
    if (mute)
        rt5625_write_mask(codec, RT5625_STEREO_DAC_VOL, 0x8080, 0x8080);
    else
        rt5625_write_mask(codec, RT5625_STEREO_DAC_VOL, 0x0000, 0x8080);
    return 0;
}

static int rt5625_voice_codec_mute(struct snd_soc_dai *dai, int mute)
{
    struct snd_soc_codec *codec = dai->codec;

    if (mute)
        rt5625_write_mask(codec, RT5625_VOICE_DAC_OUT_VOL, 0x1000, 0x1000);
    else
        rt5625_write_mask(codec, RT5625_VOICE_DAC_OUT_VOL, 0x0000, 0x1000);
    return 0;
}


#if USE_DAPM_CTRL
static int rt5625_set_bias_level(struct snd_soc_codec *codec,
            enum snd_soc_bias_level level)
{
    switch(level) {
    case SND_SOC_BIAS_ON:
    case SND_SOC_BIAS_PREPARE:
        break;
    case SND_SOC_BIAS_STANDBY:
    case SND_SOC_BIAS_OFF:
        rt5625_write_mask(codec, 0x3a, 0x0000, 0x0800);    /*disable main i2s*/
        rt5625_write_mask(codec, 0x3a, 0x0000, 0x0001);    /*disable dac ref*/
        rt5625_write_mask(codec, 0x3c, 0x0000, 0xc000);  /*off pll*/
        break;
    }
    codec->bias_level = level;
    return 0;
}
#else
static int rt5625_set_bias_level(struct snd_soc_codec *codec,
            enum snd_soc_bias_level level)
{
    switch(level) {
    case SND_SOC_BIAS_ON:
    case SND_SOC_BIAS_PREPARE:
        break;
    case SND_SOC_BIAS_STANDBY:
    case SND_SOC_BIAS_OFF:
       
        rt5625_write_mask(codec, 0x3a, 0x0000, 0x0800);    /*enable main i2s*/
        rt5625_write_mask(codec, 0x3a, 0x0000, 0x0001);    /*enable dac ref*/
        rt5625_write(codec, 0x3a, 0x0002);
        rt5625_write(codec, 0x3c, 0x2000);
        rt5625_write(codec, 0x3e, 0x0000);
        break;
    }
    codec->bias_level = level;
    return 0;
}
#endif

static int rt5625_hifi_pcm_shutdown(struct snd_pcm_substream *substream,
        struct snd_soc_dai *codec_dai)
{
    struct snd_soc_codec *codec = codec_dai->codec;

    #if !USE_DAPM_CTRL
    rt5625_write_mask(codec, 0x3a, 0x0000, 0x0300);
    rt5625_write_mask(codec, 0x02, 0x8080, 0x8080);
    rt5625_write_mask(codec, 0x04, 0x8080, 0x8080);
    rt5625_write(codec, 0x3a, 0x0002);
    rt5625_write(codec, 0x3c, 0x2000);
    rt5625_write(codec, 0x3e, 0x0000);
    #endif

    return 0;
}

static int rt5625_voice_pcm_shutdown(struct snd_pcm_substream *substream,
        struct snd_soc_dai *codec_dai)
{
    struct snd_soc_codec *codec = codec_dai->codec;

    rt5625_write_mask(codec, 0x2e, 0x0000, 0x0030);
   
    #if !USE_DAPM_CTRL
    rt5625_write_mask(codec, 0x3a, 0x0000, 0x0300);
    rt5625_write_mask(codec, 0x02, 0x8080, 0x8080);
    rt5625_write_mask(codec, 0x04, 0x8080, 0x8080);
    rt5625_write(codec, 0x3a, 0x0002);
    rt5625_write(codec, 0x3c, 0x2000);
    rt5625_write(codec, 0x3e, 0x0000);
    #endif
   
    return 0;
}

#define RT5625_STEREO_RATES (SNDRV_PCM_RATE_48000 |SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000| SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_8000)


#define RT5626_VOICE_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000)

#define RT5625_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |/
            SNDRV_PCM_FMTBIT_S20_3LE |/
            SNDRV_PCM_FMTBIT_S24_LE |/
            SNDRV_PCM_FMTBIT_S8)


struct snd_soc_dai rt5625_dai[] = {
    /*hifi codec dai*/
    {
        .name = "RT5625 HiFi",
        .id = 1,
        .playback = {
            .stream_name = "HiFi Playback",
            .channels_min = 1,
            .channels_max = 2,
            .rates = RT5625_STEREO_RATES,
            .formats = RT5625_FORMATS,
        },
        .capture = {
            .stream_name = "HiFi Capture",
            .channels_min = 1,
            .channels_max = 2,
            .rates = RT5625_STEREO_RATES,
            .formats = RT5625_FORMATS,
        },
        .ops = {
            .hw_params = rt5625_hifi_pcm_hw_params,
            #if !USE_DAPM_CTRL
            .prepare = rt5625_pcm_hw_prepare,
            #endif
            .shutdown = rt5625_hifi_pcm_shutdown,
        },
    .dai_ops={
            .set_sysclk   = rt5625_hifi_codec_set_dai_sysclk,
            .set_pll      = rt5625_codec_set_dai_pll,
            .digital_mute =rt5625_hifi_codec_mute,
            .set_fmt      =rt5625_hifi_codec_set_dai_fmt,
        },
    },

    /*voice codec dai*/
    {
        .name = "RT5625 Voice",
        .id = 1,
        .playback = {
            .stream_name = "Voice Playback",
            .channels_min = 1,
            .channels_max =1,
            .rates = RT5626_VOICE_RATES,
            .formats = RT5625_FORMATS,
        },
        .capture = {
            .stream_name = "Voice Capture",
            .channels_min = 1,
            .channels_max = 1,
            .rates = RT5626_VOICE_RATES,
            .formats = RT5625_FORMATS,
        },
        .ops = {
            .hw_params = rt5625_voice_pcm_hw_params,
            .shutdown = rt5625_voice_pcm_shutdown,
        },
        .dai_ops={
            .set_fmt = rt5625_voice_codec_set_dai_fmt,
            .set_sysclk = rt5625_voice_codec_set_dai_sysclk,
            .set_pll = rt5625_codec_set_dai_pll,
        },
    },
};

EXPORT_SYMBOL_GPL(rt5625_dai);


static void rt5625_work(struct work_struct *work)
{
    struct snd_soc_codec *codec =
         container_of(work, struct snd_soc_codec, delayed_work.work);
    rt5625_set_bias_level(codec, codec->bias_level);
}

#if defined(CONFIG_SND_HWDEP)
#if REALTEK_HWDEP
#define RT_CE_CODEC_HWDEP_NAME "rt56xx hwdep "


static int rt56xx_hwdep_open(struct snd_hwdep *hw, struct file *file)
{
    printk("enter %s/n", __func__);
    return 0;
}

static int rt56xx_hwdep_release(struct snd_hwdep *hw, struct file *file)
{
    printk("enter %s/n", __func__);
    return 0;
}


static int rt56xx_hwdep_ioctl_common(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg)
{
    struct rt56xx_cmd rt56xx;
    struct rt56xx_cmd __user *_rt56xx = arg;
    struct rt56xx_reg_state *buf;
    struct rt56xx_reg_state *p;
    struct snd_soc_codec *codec = hw->private_data;

    if (copy_from_user(&rt56xx, _rt56xx, sizeof(rt56xx)))
        return -EFAULT;
    buf = kmalloc(sizeof(*buf) * rt56xx.number, GFP_KERNEL);
    if (buf == NULL)
        return -ENOMEM;
    if (copy_from_user(buf, rt56xx.buf, sizeof(*buf) * rt56xx.number)) {
        goto err;
    }
    switch (cmd) {
        case RT_READ_CODEC_REG_IOCTL:
            for (p = buf; p < buf + rt56xx.number; p++)
            {
                p->reg_value = codec->read(codec, p->reg_index);
            }
            if (copy_to_user(rt56xx.buf, buf, sizeof(*buf) * rt56xx.number))
                goto err;
               
            break;
        case RT_WRITE_CODEC_REG_IOCTL:
            for (p = buf; p < buf + rt56xx.number; p++)
                codec->write(codec, p->reg_index, p->reg_value);
            break;
    }

    kfree(buf);
    return 0;

err:
    kfree(buf);
    return -EFAULT;
   
}

static int rt56xx_codec_dump_reg(struct snd_hwdep *hw, struct file *file, unsigned long arg)
{
    struct rt56xx_cmd rt56xx;
    struct rt56xx_cmd __user *_rt56xx = arg;
    struct rt56xx_reg_state *buf;
    struct snd_soc_codec *codec = hw->private_data;
    int number = codec->reg_cache_size;
    int i;

    printk(KERN_DEBUG "enter %s, number = %d/n", __func__, number);   
    if (copy_from_user(&rt56xx, _rt56xx, sizeof(rt56xx)))
        return -EFAULT;
   
    buf = kmalloc(sizeof(*buf) * number, GFP_KERNEL);
    if (buf == NULL)
        return -ENOMEM;

    for (i = 0; i < number; i++)
    {
        buf[i].reg_index = i << 1;
        buf[i].reg_value = codec->read(codec, buf[i].reg_index);
    }
    if (copy_to_user(rt56xx.buf, buf, sizeof(*buf) * i))
        goto err;
    rt56xx.number = number;
    if (copy_to_user(_rt56xx, &rt56xx, sizeof(rt56xx)))
        goto err;
    kfree(buf);
    return 0;
err:
    kfree(buf);
    return -EFAULT;
   
}

static int rt56xx_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg)
{
    if (cmd == RT_READ_ALL_CODEC_REG_IOCTL)
    {
        return rt56xx_codec_dump_reg(hw, file, arg);
    }
    else
    {
        return rt56xx_hwdep_ioctl_common(hw, file, cmd, arg);
    }
}

static int realtek_ce_init_hwdep(struct snd_soc_codec *codec)
{
    struct snd_hwdep *hw;
    struct snd_card *card = codec->card;
    int err;

    if ((err = snd_hwdep_new(card, RT_CE_CODEC_HWDEP_NAME, 0, &hw)) < 0)
        return err;
   
    strcpy(hw->name, RT_CE_CODEC_HWDEP_NAME);
    hw->private_data = codec;
    hw->ops.open = rt56xx_hwdep_open;
    hw->ops.release = rt56xx_hwdep_release;
    hw->ops.ioctl = rt56xx_hwdep_ioctl;
    return 0;
}

#endif
#endif


static int rt5625_init(struct snd_soc_device *socdev)
{
    struct snd_soc_codec *codec = socdev->codec;
    int ret = 0;

    codec->name = "RT5625";
    codec->owner = THIS_MODULE;
    codec->read = rt5625_read;
    codec->write = rt5625_write;
    codec->set_bias_level = rt5625_set_bias_level;
    codec->dai= rt5625_dai;
    codec->num_dai = 2;
    codec->reg_cache_size = ARRAY_SIZE(rt5625_reg);
    codec->reg_cache = kmemdup(rt5625_reg, sizeof(rt5625_reg), GFP_KERNEL);
    if (codec->reg_cache == NULL)
        return -ENOMEM;

    rt5625_reset(codec);

    ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
    if (ret < 0 )
    {
        printk(KERN_ERR "rt5625:  failed to create pcms/n");
        goto pcm_err;
    }
   
    rt5625_write(codec, RT5625_PD_CTRL_STAT, 0);
    rt5625_write(codec, RT5625_PWR_MANAG_ADD1, PWR_MAIN_BIAS);
    rt5625_write(codec, RT5625_PWR_MANAG_ADD2, PWR_MIXER_VREF);
    rt5625_reg_init(codec);
    rt5625_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
    codec->bias_level = SND_SOC_BIAS_STANDBY;
    schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(2000));
   
    rt5625_add_controls(codec);
#if USE_DAPM_CTRL
    rt5625_add_widgets(codec);
#endif

#if defined(CONFIG_SND_HWDEP)
#if REALTEK_HWDEP
     realtek_ce_init_hwdep(codec);
#endif
#endif

    ret = snd_soc_register_card(socdev);
    if (ret < 0)
    {
        printk(KERN_ERR "rt5625: failed to register card/n");
        goto card_err;
    }
    printk(KERN_DEBUG "rt5625: initial ok/n");
    return ret;

    card_err:
        snd_soc_free_pcms(socdev);
        snd_soc_dapm_free(socdev);
   
    pcm_err:
        kfree(codec->reg_cache);
        return ret;
   
   
}


static int rt5625_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
{
    struct snd_soc_device *socdev = rt5625_socdev;
    struct snd_soc_codec *codec = socdev->codec;
    int ret;
    i2c_set_clientdata(i2c, codec);
    codec->control_data = i2c;

    ret = rt5625_init(socdev);
    if (ret < 0)
        pr_err("failed to initialise rt5625/n");

    return ret;
}

static int rt5625_i2c_remove(struct i2c_client *client)
{
    struct snd_soc_codec *codec = i2c_get_clientdata(client);
    kfree(codec->reg_cache);
    return 0;
}
static const struct i2c_device_id rt5625_i2c_id[] = {
        {"rt5625", 0},
        {}
};
MODULE_DEVICE_TABLE(i2c, rt5625_i2c_id);
static struct i2c_driver rt5625_i2c_driver = {
    .driver = {
        .name = "RT5625 I2C Codec",
        .owner = THIS_MODULE,
    },
    .probe =    rt5625_i2c_probe,
    .remove =   rt5625_i2c_remove,
    .id_table = rt5625_i2c_id,
};


static int rt5625_add_i2c_device(struct platform_device *pdev,
                 const struct rt5625_setup_data *setup)
{
    struct i2c_board_info info;
    struct i2c_adapter *adapter;
    struct i2c_client *client;
    int ret;

    ret = i2c_add_driver(&rt5625_i2c_driver);
    if (ret != 0) {
        dev_err(&pdev->dev, "can't add i2c driver/n");
        return ret;
    }
    memset(&info, 0, sizeof(struct i2c_board_info));
    info.addr = setup->i2c_address;
    strlcpy(info.type, "rt5625", I2C_NAME_SIZE);

    adapter = i2c_get_adapter(setup->i2c_bus);
    if (!adapter) {
        dev_err(&pdev->dev, "can't get i2c adapter %d/n",
            setup->i2c_bus);
        goto err_driver;
    }

    client = i2c_new_device(adapter, &info);
    i2c_put_adapter(adapter);
    if (!client) {
        dev_err(&pdev->dev, "can't add i2c device at 0x%x/n",
            (unsigned int)info.addr);
        goto err_driver;
    }

    return 0;

err_driver:
    i2c_del_driver(&rt5625_i2c_driver);
    return -ENODEV;
}

static ssize_t rt5625_dsp_reg_show(struct device *dev,
    struct device_attribute *attr, char *buf)
{
    struct snd_soc_device *socdev = dev_get_drvdata(dev);
    struct snd_soc_codec *codec = socdev->codec;
    int count = 0;
    int dsp_value;
    int i;

    rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD3,PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP,PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP);
    mdelay(1);
    rt5625_write_mask(codec, RT5625_VODSP_CTL, VODSP_NO_PD_MODE_ENA,VODSP_NO_PD_MODE_ENA);
    mdelay(1);
   
    count += sprintf(buf, "%s dsp registers/n", codec ->name);
    for (i = 0; i < ARRAY_SIZE(VODSP_AEC_Init_Value); i ++) {
        count += sprintf(buf + count, "0x%4x:", VODSP_AEC_Init_Value[i].VoiceDSPIndex);
        if (count > PAGE_SIZE - 1)
            break;
        dsp_value = rt5625_read_vodsp_reg(codec, VODSP_AEC_Init_Value[i].VoiceDSPIndex);
        count += snprintf(buf + count, PAGE_SIZE - count, "0x%4x", dsp_value);

        if (count >= PAGE_SIZE - 1)
            break;
       
        count += snprintf(buf + count, PAGE_SIZE - count, "/n");
        if (count >= PAGE_SIZE - 1)
            break;
    }

    if (count >= PAGE_SIZE)
        count = PAGE_SIZE - 1;

    rt5625_write_mask(codec, RT5625_VODSP_CTL, 0,VODSP_NO_PD_MODE_ENA);
    rt5625_write_mask(codec, RT5625_PWR_MANAG_ADD3, 0, PWR_VODSP_INTERFACE|PWR_I2C_FOR_VODSP);
    return count;

}

static DEVICE_ATTR(dsp_reg, 0444, rt5625_dsp_reg_show, NULL);

static ssize_t rt5625_index_reg_show(struct device *dev,
    struct device_attribute *attr, char *buf)
{
    struct snd_soc_device *socdev = dev_get_drvdata(dev);
    struct snd_soc_codec *codec = socdev->codec;
    int count = 0;
    int value;
    int i;
   
    count += sprintf(buf, "%s index register/n", codec->name);

    for (i = 0; i < 60; i++) {
        count += sprintf(buf + count, "index-%2x", i);
        if (count >= PAGE_SIZE - 1)
            break;
        value = rt5625_read_index(codec, i);
        count += snprintf(buf + count, PAGE_SIZE - count, "0x%4x", value);

        if (count >= PAGE_SIZE - 1)
            break;

        count += snprintf(buf + count, PAGE_SIZE - count, "/n");
        if (count >= PAGE_SIZE - 1)
            break;
    }

    if (count >= PAGE_SIZE)
            count = PAGE_SIZE - 1;
       
    return count;
   
}

static DEVICE_ATTR(index_reg, 0444, rt5625_index_reg_show, NULL);

static int rt5625_probe(struct platform_device *pdev)
{
    struct snd_soc_device *socdev = platform_get_drvdata(pdev);
    struct rt5625_setup_data *setup = socdev->codec_data;
    struct snd_soc_codec *codec;
    struct rt5625_priv *rt5625;
    int ret;

    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
    if (codec == NULL)
        return -ENOMEM;

    rt5625 = kzalloc(sizeof(struct rt5625_priv), GFP_KERNEL);
    if (rt5625 == NULL) {
        kfree(codec);
        return -ENOMEM;
    }

    rt5625_aec_cfg = setup->cfg;
    codec->private_data = rt5625;
    socdev->codec = codec;
    mutex_init(&codec->mutex);
    INIT_LIST_HEAD(&codec->dapm_widgets);
    INIT_LIST_HEAD(&codec->dapm_paths);
    rt5625_socdev = socdev;
    INIT_DELAYED_WORK(&codec->delayed_work, rt5625_work);
    ret = device_create_file(&pdev->dev, &dev_attr_dsp_reg);
    if (ret < 0)
        printk(KERN_WARNING "asoc: failed to add dsp_reg sysfs files/n");
    ret = device_create_file(&pdev->dev, &dev_attr_index_reg);
    if (ret < 0)
        printk(KERN_WARNING "asoc: failed to add index_reg sysfs files/n");
   
    ret = -ENODEV;
    if (setup->i2c_address) {
        codec->hw_write = (hw_write_t)i2c_master_send;
        codec->hw_read = (hw_read_t)i2c_master_recv;
        ret = rt5625_add_i2c_device(pdev, setup);
    }

    if (ret != 0) {
        kfree(codec->private_data);
        kfree(codec);
    }
    return ret;
}

static int run_delayed_work(struct delayed_work *dwork)
{
    int ret;

    /* cancel any work waiting to be queued. */
    ret = cancel_delayed_work(dwork);

    /* if there was any work waiting then we run it now and
     * wait for it's completion */
    if (ret) {
        schedule_delayed_work(dwork, 0);
        flush_scheduled_work();
    }
    return ret;
}


static int rt5625_remove(struct platform_device *pdev)
{
    struct snd_soc_device *socdev = platform_get_drvdata(pdev);
    struct snd_soc_codec *codec = socdev->codec;

    if (codec->control_data)
        rt5625_set_bias_level(codec, SND_SOC_BIAS_OFF);
    run_delayed_work(&codec->delayed_work);
    snd_soc_free_pcms(socdev);
    snd_soc_dapm_free(socdev);
    device_remove_file(&pdev->dev, &dev_attr_dsp_reg);
    device_remove_file(&pdev->dev, &dev_attr_index_reg);
    i2c_unregister_device(codec->control_data);
    i2c_del_driver(&rt5625_i2c_driver);
    kfree(codec->private_data);
    kfree(codec);

    return 0;
}


static int rt5625_suspend(struct platform_device *pdev, pm_message_t state)
{
    struct snd_soc_device *socdev = platform_get_drvdata(pdev);
    struct snd_soc_codec *codec = socdev->codec;

    rt5625_set_bias_level(codec, SND_SOC_BIAS_OFF);
    return 0;
}

static int rt5625_resume(struct platform_device *pdev)
{
    struct snd_soc_device *socdev = platform_get_drvdata(pdev);
    struct snd_soc_codec *codec = socdev->codec;
    int i;
    u8 data[3];
    u16 *cache = codec->reg_cache;

    /* Sync reg_cache with the hardware */
    for (i = 0; i < ARRAY_SIZE(rt5625_reg); i++) {
        if (i == RT5625_RESET)
            continue;
        data[0] = i << 1;
        data[1] = (0xff00 & cache[i]) >> 8;
        data[2] = 0x00ff & cache[i];
        codec->hw_write(codec->control_data, data, 3);
    }

    rt5625_set_bias_level(codec, SND_SOC_BIAS_STANDBY);

    /* charge rt5625 caps */
    if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
        rt5625_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
        codec->bias_level = SND_SOC_BIAS_ON;
        schedule_delayed_work(&codec->delayed_work,
                    msecs_to_jiffies(1000));
    }

    return 0;
}


struct snd_soc_codec_device soc_codec_dev_rt5625 = {
    .probe =     rt5625_probe,
    .remove =     rt5625_remove,
    .suspend =     rt5625_suspend,
    .resume =    rt5625_resume,
};

EXPORT_SYMBOL_GPL(soc_codec_dev_rt5625);
MODULE_LICENSE("GPL");

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、下载 4使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值