From 79075a38b5f3e728911d9fdae1b7976f40196e7d Mon Sep 17 00:00:00 2001 From: zhangjun Date: Wed, 21 Aug 2013 10:16:14 +0800 Subject: [PATCH] rt5512 codec: add Richtek5512 support in RK3188_tb_sdk --- arch/arm/mach-rk30/board-rk3168-tb.c | 10 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rt5512.c | 1851 ++++++++++++++++++++++++++ sound/soc/codecs/rt5512.h | 49 + sound/soc/rk29/Kconfig | 9 + sound/soc/rk29/Makefile | 2 + sound/soc/rk29/rk29_rt5512.c | 343 +++++ 8 files changed, 2270 insertions(+) mode change 100644 => 100755 arch/arm/mach-rk30/board-rk3168-tb.c create mode 100755 sound/soc/codecs/rt5512.c create mode 100755 sound/soc/codecs/rt5512.h create mode 100755 sound/soc/rk29/rk29_rt5512.c diff --git a/arch/arm/mach-rk30/board-rk3168-tb.c b/arch/arm/mach-rk30/board-rk3168-tb.c old mode 100644 new mode 100755 index 79dd15fa761d..e6bf4c50e136 --- a/arch/arm/mach-rk30/board-rk3168-tb.c +++ b/arch/arm/mach-rk30/board-rk3168-tb.c @@ -1583,6 +1583,7 @@ static struct i2c_board_info __initdata i2c0_info[] = { .flags = 0, }, #endif + #if defined (CONFIG_SND_SOC_RT5631) { .type = "rt5631", @@ -1591,6 +1592,15 @@ static struct i2c_board_info __initdata i2c0_info[] = { }, #endif +#if defined (CONFIG_SND_SOC_RT5512) + { + .type = "rt5512", + .addr = 0x18, + .flags = 0, + }, +#endif + + #if defined (CONFIG_SND_SOC_RT5640) { .type = "rt5640", diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index fdc4fa94efdf..b978303b0ebc 100755 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -89,6 +89,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_RT5623 if I2C select SND_SOC_RT5639 if I2C select SND_SOC_RT5616 if I2C + select SND_SOC_RT5512 if I2C select SND_SOC_RK610 if I2C select SND_SOC_RK616 if I2C select SND_SOC_WM8903 if I2C @@ -474,3 +475,6 @@ config SND_SOC_RK2928 config SND_SOC_RK3026 tristate depends on ARCH_RK3026 + +config SND_SOC_RT5512 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 0aaf90b2c3d3..849b837570ad 100755 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -103,6 +103,7 @@ snd-soc-rt3224-objs := rt3261.o rt3261_ioctl.o rt_codec_ioctl.o snd-soc-rk2928-objs := rk2928_codec.o snd-soc-rk3026-objs := rk3026_codec.o snd-soc-rt5639-objs := rt5639.o rt5639_ioctl.o rt56xx_ioctl.o +snd-soc-rt5512-objs := rt5512.o # Amp snd-soc-lm4857-objs := lm4857.o @@ -215,6 +216,7 @@ obj-$(CONFIG_SND_SOC_RK610) += snd-soc-rk610.o obj-$(CONFIG_SND_SOC_RK616) += snd-soc-rk616.o obj-$(CONFIG_SND_SOC_RK2928) += snd-soc-rk2928.o obj-$(CONFIG_SND_SOC_RK3026) += snd-soc-rk3026.o +obj-$(CONFIG_SND_SOC_RT5512) += snd-soc-rt5512.o # Amp obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o diff --git a/sound/soc/codecs/rt5512.c b/sound/soc/codecs/rt5512.c new file mode 100755 index 000000000000..171634d9d77e --- /dev/null +++ b/sound/soc/codecs/rt5512.c @@ -0,0 +1,1851 @@ +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_DEBUG_FS +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "rt5512.h" + +#define RT5512_SRATE_MASK 0x7 +#define RT5512_AUDBIT_MASK 0x3 +#define RT5512_MCLKSELBIT_MASK 0xf +#define RT5512_CLKBASEBIT_MASK 0x2 +#define RT5512_AUDFMT_MASK 0xc +#define RT5512_PDBIT_MASK 0x40 +#define RT5512_RSTBIT_MASK 0x80 + +#define RT5512_RATES SNDRV_PCM_RATE_8000_96000 +#define RT5512_FORMATS (SNDRV_PCM_FMTBIT_S16_LE|SNDRV_PCM_FMTBIT_S20_3LE|SNDRV_PCM_FMTBIT_S24_LE) + +#define RT5512_MAX_REG 0xEC +#define RT5512_VIRTUAL_REG 0xEC + +struct rt5512_codec_reg +{ + unsigned char reg_size; + unsigned int reg_cache; // support maximum 4 byte data + unsigned char sync_needed; +}; + +static struct rt5512_codec_reg rt5512_reg_priv[RT5512_MAX_REG+1] = +{ + [0x00] = { 1, 0, 0}, + [0x01] = { 1, 0, 0}, + [0x02] = { 1, 0, 0}, + [0x03] = { 1, 0, 0}, + [0x04] = { 1, 0, 0}, + [0x05] = { 1, 0, 0}, + [0x06] = { 1, 0, 0}, + [0x07] = { 1, 0, 0}, + [0x08] = { 4, 0, 0}, + [0x09] = { 4, 0, 0}, + [0x0A] = { 4, 0, 0}, + [0x0B] = { 4, 0, 0}, + [0x0C] = { 4, 0, 0}, + [0x0D] = { 4, 0, 0}, + [0x0E] = { 4, 0, 0}, + [0x0F] = { 4, 0, 0}, + [0x10] = { 1, 0, 0}, + [0x11] = { 1, 0, 0}, + [0x12] = { 1, 0, 0}, + [0x13] = { 1, 0, 0}, + [0x14] = { 1, 0, 0}, + [0x15] = { 1, 0, 0}, + [0x16] = { 1, 0, 0}, + [0x17] = { 1, 0, 0}, + [0x1A] = { 1, 0, 0}, + [0x1B] = { 1, 0, 0}, + [0x1C] = { 1, 0, 0}, + [0x1D] = { 4, 0, 0}, + [0x1F] = { 1, 0, 0}, + [0x20] = { 20, 0, 0}, + [0x21] = { 20, 0, 0}, + [0x22] = { 20, 0, 0}, + [0x23] = { 20, 0, 0}, + [0x24] = { 20, 0, 0}, + [0x25] = { 20, 0, 0}, + [0x26] = { 20, 0, 0}, + [0x27] = { 20, 0, 0}, + [0x28] = { 20, 0, 0}, + [0x29] = { 20, 0, 0}, + [0x2A] = { 20, 0, 0}, + [0x2B] = { 20, 0, 0}, + [0x2C] = { 20, 0, 0}, + [0x30] = { 1, 0, 0}, + [0x31] = { 1, 0, 0}, + [0x38] = { 8, 0, 0}, + [0x39] = { 8, 0, 0}, + [0x3A] = { 8, 0, 0}, + [0x3B] = { 8, 0, 0}, + [0x3C] = { 8, 0, 0}, + [0x3D] = { 8, 0, 0}, + [0x3E] = { 8, 0, 0}, + [0x3F] = { 8, 0, 0}, + [0x40] = { 16, 0, 0}, + [0x41] = { 16, 0, 0}, + [0x42] = { 16, 0, 0}, + [0x43] = { 16, 0, 0}, + [0x44] = { 16, 0, 0}, + [0x45] = { 16, 0, 0}, + [0x50] = { 1, 0, 0}, + [0x51] = { 1, 0, 0}, + [0x52] = { 1, 0, 0}, + [0x55] = { 4, 0, 0}, + [0x56] = { 1, 0, 0}, + [0x57] = { 1, 0, 0}, + [0x58] = { 1, 0, 0}, + [0x59] = { 1, 0, 0}, + [0x5B] = { 1, 0, 0}, + [0x5C] = { 1, 0, 0}, + [0x5D] = { 1, 0, 0}, + [0x75] = { 1, 0, 0}, + [0x78] = { 1, 0, 0}, + [0x80] = { 1, 0, 0}, + [0x81] = { 1, 0, 0}, + [0x82] = { 1, 0, 0}, + [0x83] = { 1, 0, 0}, + [0x84] = { 1, 0, 0}, + [0x85] = { 1, 0, 0}, + [0x86] = { 1, 0, 0}, + [0x87] = { 1, 0, 0}, + [0x88] = { 1, 0, 0}, + [0x89] = { 1, 0, 0}, + [0x8A] = { 1, 0, 0}, + [0x8B] = { 1, 0, 0}, + [0x8C] = { 1, 0, 0}, + [0x8D] = { 1, 0, 0}, + [0x8E] = { 1, 0, 0}, + [0x8F] = { 1, 0, 0}, + [0x90] = { 1, 0, 0}, + [0x91] = { 1, 0, 0}, + [0x92] = { 1, 0, 0}, + [0x93] = { 1, 0, 0}, + [0x94] = { 1, 0, 0}, + [0x95] = { 1, 0, 0}, + [0x96] = { 1, 0, 0}, + [0x97] = { 1, 0, 0}, + [0x98] = { 1, 0, 0}, + [0x99] = { 1, 0, 0}, + [0x9A] = { 1, 0, 0}, + [0x9B] = { 1, 0, 0}, + [0x9C] = { 1, 0, 0}, + [0x9D] = { 1, 0, 0}, + [0x9E] = { 1, 0, 0}, + [0x9F] = { 1, 0, 0}, + [0xA0] = { 1, 0, 0}, + [0xA1] = { 1, 0, 0}, + [0xA2] = { 1, 0, 0}, + [0xA3] = { 1, 0, 0}, + [0xA4] = { 1, 0, 0}, + [0xA5] = { 1, 0, 0}, + [0xA6] = { 1, 0, 0}, + [0xA7] = { 1, 0, 0}, + [0xA8] = { 1, 0, 0}, + [0xCA] = { 1, 0, 0}, + [0xCC] = { 1, 0, 0}, + [0xCD] = { 1, 0, 0}, + [0xCE] = { 1, 0, 0}, + [0xE0] = { 1, 0, 0}, + [0xE8] = { 1, 0, 0}, + [0xEB] = { 1, 0, 0}, + [0xEC] = { 4, 0, 0}, +}; + +#if FOR_MID + +struct rt5512_init_reg { + u8 reg; + u32 regval; +}; + +static struct rt5512_init_reg init_list[] = { + /*playback*/ + {0x10 , 0x30}, + {0x11 , 0x30}, + {0x82 , 0x30}, + {0x84 , 0x03}, + {0x8c , 0x09}, + {0xec , 0x27c}, + /*record*/ + {0x95 , 0xa0}, + {0x85 , 0x20}, +}; +#define RT5512_INIT_REG_LEN ARRAY_SIZE(init_list) + +static int rt5512_reg_init(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < RT5512_INIT_REG_LEN; i++) + snd_soc_write(codec, init_list[i].reg, init_list[i].regval); + + return 0; +} +#endif + +static int rt5512_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned short srate_reg; + unsigned int new_srate_regval, new_datfmt_regval; + + // set sampling rate + RT_DBG("codec hw_param\n"); + RT_DBG("codec hw_param dai id %d\n", dai->id); + switch (dai->id) + { + case 0: // AIF1 + srate_reg = 0x03; + break; + case 1: // AIF2 + srate_reg = 0x04; + break; + default: + dev_err(codec->dev, "this codec dai id is not supported\n"); + return -EINVAL; + } + + RT_DBG("codec hw_param rate%d\n", params_rate(params)); + switch (params_rate(params)) + { + case 8000: + new_srate_regval = 0x0; + break; + case 11025: + case 12000: + new_srate_regval = 0x1; + break; + case 16000: + new_srate_regval = 0x2; + break; + case 22050: + case 24000: + new_srate_regval = 0x3; + break; + case 32000: + new_srate_regval = 0x4; + break; + case 44100: + case 48000: + new_srate_regval = 0x5; + break; + case 88200: + case 96000: + new_srate_regval = 0x6; + break; + default: + dev_err(codec->dev, "not supported sampling rate\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, srate_reg, RT5512_SRATE_MASK, new_srate_regval); + + // set data format + RT_DBG("codec hw_param format%d\n", params_format(params)); + switch (params_format(params)) + { + case SNDRV_PCM_FORMAT_S16_LE: + new_datfmt_regval = 0x3; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + new_datfmt_regval = 0x1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + new_datfmt_regval = 0x0; + break; + default: + dev_err(codec->dev, "not supported data bit format\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, 0x02, RT5512_AUDBIT_MASK, new_datfmt_regval); + + RT_DBG("stream direction %d\n", substream->stream); + if (substream->stream == 0) //Playback + snd_soc_write(codec, 0x56, 0xA4); + else if (substream->stream == 1) //Record + { + snd_soc_update_bits(codec, 0x9F, 0xC0, 0x40); + snd_soc_update_bits(codec, 0x81, 0x40, 0x00); + snd_soc_write(codec, 0x9E, 0x00); + } + + RT_DBG("special register configuration\n"); + snd_soc_write(codec, 0xE0, 0x2F); + snd_soc_write(codec, 0xEB, 0xB4); + snd_soc_write(codec, 0xE8, 0x53); + + return 0; +} + +static int rt5512_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int new_mclk_sel, new_clk_basesel; + + RT_DBG("codec dai_sysclk\n"); + switch (dai->id) + { + case 0: // AIF1 + new_mclk_sel = 0x1; + break; + case 1: // AIF2 + new_mclk_sel = 0x1; + break; + default: + dev_err(codec->dev, "no this codec dai id\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, 0x01, RT5512_MCLKSELBIT_MASK, new_mclk_sel); + + if (freq == 11289600) //22.579200 MHz + new_clk_basesel = RT5512_CLKBASEBIT_MASK; + else //24.576000 MHz + new_clk_basesel = 0; + + snd_soc_update_bits(codec, 0x1A, RT5512_CLKBASEBIT_MASK, new_clk_basesel); + + snd_soc_dapm_sync(&codec->dapm); + return 0; +} + +static int rt5512_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int mute_mask; + + RT_DBG("codec digital_mute id = %d\n", dai->id); + switch (dai->id) + { + case 0: // AIF1 + mute_mask = 0x03; + break; + case 1: // AIF2 + mute_mask = 0x0C; + break; + default: + dev_err(codec->dev, "no this codec dai id\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, 0x07, mute_mask, (mute?mute_mask:0)); + if (!mute) + msleep(50); + return 0; +} + +static int rt5512_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int new_daifmt_regval; + + RT_DBG("codec dai_fmt\n"); + if (fmt & SND_SOC_DAIFMT_I2S) + new_daifmt_regval = 0x00; + else if (fmt & SND_SOC_DAIFMT_RIGHT_J) + new_daifmt_regval = 0x08; + else if (fmt & SND_SOC_DAIFMT_LEFT_J) + new_daifmt_regval = 0x04; + else + new_daifmt_regval = 0x00; + + snd_soc_update_bits(codec, 0x02, RT5512_AUDFMT_MASK, new_daifmt_regval); + return 0; +} + +static int rt5512_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + RT_DBG("codec clk_div(%d) value %d\n", div_id, div); + switch (div_id) + { + case RT5512_CLK_DIV_ID: + snd_soc_write(codec, 0x55, (div&0x1ffff) << 16); + break; + default: + dev_err(codec->dev, "Invalid clock divider"); + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops rt5512_dai_ops = +{ + .hw_params = rt5512_hw_params, + .digital_mute = rt5512_digital_mute, + .set_sysclk = rt5512_set_dai_sysclk, + .set_fmt = rt5512_set_dai_fmt, + .set_clkdiv = rt5512_set_clkdiv, +}; + +static struct snd_soc_dai_driver rt5512_dai[] = +{ + // AIF1 + { + .name = "RT5512-aif1", + .playback = + { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5512_RATES, + .formats = RT5512_FORMATS, + }, + .capture = + { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5512_RATES, + .formats = RT5512_FORMATS, + }, + .ops = &rt5512_dai_ops, + .symmetric_rates = 1, + }, + #if 0 + // AIF2 + { + .name = "RT5512-aif2", + .playback = + { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT5512_RATES, + .formats = RT5512_FORMATS, + }, + .capture = + { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT5512_RATES, + .formats = RT5512_FORMATS, + }, + .ops = &rt5512_dai_ops, + .symmetric_rates = 1, + }, + #endif /* #if 0 */ +}; + +static const DECLARE_TLV_DB_SCALE(mic_pga_tlv, -1650, 150, 0); +static const DECLARE_TLV_DB_SCALE(mic_lrpga_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(aux_pga_tlv, -500, 100, 0); // range from -5dB to 26dB +static const DECLARE_TLV_DB_SCALE(aux_lrpga_tlv, -300, 300, 0); +static const DECLARE_TLV_DB_SCALE(lrpga2lrspk_tlv, -600, 300, 0); +static const DECLARE_TLV_DB_SCALE(lrpga2lrhp_tlv, -6000, 300, 0); +static const DECLARE_TLV_DB_SCALE(lrspkpga_tlv, -4300, 100, 0); +static const DECLARE_TLV_DB_SCALE(lrhppga_tlv, -3100, 100, 0); +static const DECLARE_TLV_DB_SCALE(recvoutdrv_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(spkoutdrv_tlv, 600, 300, 0); +static const DECLARE_TLV_DB_SCALE(hpoutdrv_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -10375, 25, 0); +static const char *mic_mode_sel[] = {"Single Ended", "Differential"}; +static const char *mic_inv_mode_sel[] = {"Non-inverting mode", "Inverting mode"}; +static const char *analog_bypass_sel[] = {"Aux", "Mic1", "Mic2", "Mic3"}; +static const char *digital_output_percent[] = {"zero", "quarter", "half", "one"}; +static const char *out_path[] = { "Off", "Recv", "LR_SPK", "LR_HP"}; +static const char *in_path[] = {"Off", "Mic1", "Mic2", "Mic3", "Aux"}; +static const char *hpf_freqcut_option[] = {"4Hz", "379Hz", "770Hz", "1594Hz"}; +static const struct soc_enum rt5512_enum[] = +{ + SOC_ENUM_SINGLE(0x92, 7, 2, mic_mode_sel), + SOC_ENUM_SINGLE(0x92, 6, 2, mic_inv_mode_sel), + SOC_ENUM_SINGLE(0x98, 0, 4, analog_bypass_sel), + SOC_ENUM_SINGLE(0x10, 6, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x10, 4, 4, digital_output_percent), //4 + SOC_ENUM_SINGLE(0x10, 2, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x10, 0, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x11, 6, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x11, 4, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x11, 2, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x11, 0, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x12, 6, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x12, 4, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x12, 2, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x12, 0, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x13, 6, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x13, 4, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x13, 2, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x13, 0, 4, digital_output_percent), + SOC_ENUM_SINGLE(0x93, 7, 2, mic_mode_sel), + SOC_ENUM_SINGLE(0x93, 6, 2, mic_inv_mode_sel), + SOC_ENUM_SINGLE(0x94, 7, 2, mic_mode_sel), + SOC_ENUM_SINGLE(0x94, 6, 2, mic_inv_mode_sel), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(out_path), out_path), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(in_path), in_path), + SOC_ENUM_SINGLE(0x17, 0, 4, hpf_freqcut_option), + SOC_ENUM_SINGLE(0x17, 2, 4, hpf_freqcut_option), + SOC_ENUM_SINGLE(0x17, 4, 4, hpf_freqcut_option), +}; + +#if FOR_MID +static const char *rt5512_playback_path_mode[] = {"OFF", "RCV", "SPK", "HP", "HP_NO_MIC", "BT", "SPK_HP", //0-6 + "RING_SPK", "RING_HP", "RING_HP_NO_MIC", "RING_SPK_HP"};//7-10 + +static const SOC_ENUM_SINGLE_DECL(rt5512_path_type, 0, 0, rt5512_playback_path_mode); + +static int rt5512_playback_path_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + + RT_DBG("\n"); + RT_DBG("%s:playback_path=%ld\n",__func__,ucontrol->value.integer.value[0]); + + ucontrol->value.integer.value[0] = chip->curr_outpath; + return 0; +} + + +static int rt5512_playback_path_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; + + RT_DBG("\n"); + if (ucontrol->value.integer.value[0] != chip->curr_outpath) + { + mutex_lock(&codec->mutex); + switch (ucontrol->value.integer.value[0]) + { + case OFF: + RT_DBG(">>>>>>>>>>>>>>>>%s OFF",__FUNCTION__); + snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + snd_soc_dapm_sync(dapm); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + + case RCV: + break; + + case SPK_PATH: + case RING_SPK: + RT_DBG(">>>>>>>>>>>>>>>>%s spk",__FUNCTION__); + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_sync(dapm); + RT_DBG("Ext Spk status=%d\n", snd_soc_dapm_get_pin_status(dapm, "Ext Spk")); + RT_DBG("Headphone Jack status=%d\n", snd_soc_dapm_get_pin_status(dapm, "Headphone Jack")); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + + case HP_PATH: + case HP_NO_MIC: + case RING_HP: + case RING_HP_NO_MIC: + RT_DBG(">>>>>>>>>>>>>>>>%s hp",__FUNCTION__); + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_sync(dapm); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + + case BT: + break; + + case SPK_HP: + case RING_SPK_HP: + RT_DBG(">>>>>>>>>>>>>>>>%s spk+hp",__FUNCTION__); + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_sync(dapm); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + + default: + dev_err(codec->dev, "Not valid path\n"); + mutex_unlock(&codec->mutex); + ret = -EINVAL; + break; + } + mutex_unlock(&codec->mutex); + } + + return ret; +} + +static const char *rt5512_capture_path_mode[] = {"MIC OFF", "Main Mic", "Hands Free Mic", "BT Sco Mic"};// + +static const SOC_ENUM_SINGLE_DECL(rt5512_capture_type, 0, 0, rt5512_capture_path_mode); + +static int rt5512_capture_path_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + + RT_DBG("\n"); + RT_DBG("%s:capture_path=%ld\n",__func__,ucontrol->value.integer.value[0]); + + ucontrol->value.integer.value[0] = chip->curr_outpath; + return 0; +} + + +static int rt5512_capture_path_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; + + RT_DBG("\n"); + if (ucontrol->value.integer.value[0] != chip->curr_outpath) + { + mutex_lock(&codec->mutex); + switch (ucontrol->value.integer.value[0]) + { + case MIC_OFF: + RT_DBG(">>>>>>>>>>>>>>>>%s MIC_OFF",__FUNCTION__); + snd_soc_dapm_disable_pin(dapm, "Main Mic"); + snd_soc_dapm_disable_pin(dapm, "LineIn"); + snd_soc_dapm_sync(dapm); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + + case Main_Mic: + RT_DBG(">>>>>>>>>>>>>>>>%s Main_Mic",__FUNCTION__); + /*record gain*/ + snd_soc_update_bits(codec, 0x93, 0x1f, 0x15); + snd_soc_update_bits(codec, 0x96, 0x0c, 0x02); + snd_soc_dapm_enable_pin(dapm, "Main Mic"); + snd_soc_dapm_disable_pin(dapm, "LineIn"); + snd_soc_dapm_sync(dapm); + RT_DBG("Main Mic status=%d\n", snd_soc_dapm_get_pin_status(dapm, "Main Mic")); + RT_DBG("LineIn status=%d\n", snd_soc_dapm_get_pin_status(dapm, "LineIn")); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + + case Hands_Free_Mic: + RT_DBG(">>>>>>>>>>>>>>>>%s Hands_Free_Mic",__FUNCTION__); + snd_soc_dapm_disable_pin(dapm, "Main Mic"); + snd_soc_dapm_enable_pin(dapm, "LineIn"); + snd_soc_dapm_sync(dapm); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + + case BT_Sco_Mic: + break; + + default: + dev_err(codec->dev, "Not valid path\n"); + mutex_unlock(&codec->mutex); + ret = -EINVAL; + break; + } + mutex_unlock(&codec->mutex); + } + + return ret; +} + +#else +static int rt5512_inpath_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + + RT_DBG("\n"); + ucontrol->value.integer.value[0] = chip->curr_inpath; + return 0; +} + +static int rt5512_inpath_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; + + RT_DBG("\n"); + if (ucontrol->value.integer.value[0] != chip->curr_inpath) + { + switch (ucontrol->value.integer.value[0]) + { + case 0: + snd_soc_dapm_disable_pin(dapm, "Mic1"); + snd_soc_dapm_disable_pin(dapm, "Mic2"); + snd_soc_dapm_disable_pin(dapm, "Mic3"); + snd_soc_dapm_disable_pin(dapm, "Aux"); + chip->curr_inpath = ucontrol->value.integer.value[0]; + break; + case 1: + snd_soc_dapm_enable_pin(dapm, "Mic1"); + snd_soc_dapm_disable_pin(dapm, "Mic2"); + snd_soc_dapm_disable_pin(dapm, "Mic3"); + snd_soc_dapm_disable_pin(dapm, "Aux"); + chip->curr_inpath = ucontrol->value.integer.value[0]; + break; + case 2: + snd_soc_dapm_disable_pin(dapm, "Mic1"); + snd_soc_dapm_enable_pin(dapm, "Mic2"); + snd_soc_dapm_disable_pin(dapm, "Mic3"); + snd_soc_dapm_enable_pin(dapm, "Aux"); + chip->curr_inpath = ucontrol->value.integer.value[0]; + break; + case 3: + snd_soc_dapm_disable_pin(dapm, "Mic1"); + snd_soc_dapm_disable_pin(dapm, "Mic2"); + snd_soc_dapm_enable_pin(dapm, "Mic3"); + snd_soc_dapm_disable_pin(dapm, "Aux"); + chip->curr_inpath = ucontrol->value.integer.value[0]; + break; + case 4: + snd_soc_dapm_disable_pin(dapm, "Mic1"); + snd_soc_dapm_disable_pin(dapm, "Mic2"); + snd_soc_dapm_disable_pin(dapm, "Mic3"); + snd_soc_dapm_enable_pin(dapm, "Aux"); + chip->curr_inpath = ucontrol->value.integer.value[0]; + break; + default: + dev_err(codec->dev, "Not valid path\n"); + ret = -EINVAL; + break; + } + } + + return ret; +} + +static int rt5512_outpath_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + + RT_DBG("\n"); + ucontrol->value.integer.value[0] = chip->curr_outpath; + return 0; +} + +static int rt5512_outpath_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; + + RT_DBG("\n"); + if (ucontrol->value.integer.value[0] != chip->curr_outpath) + { + switch (ucontrol->value.integer.value[0]) + { + case 0: + snd_soc_dapm_disable_pin(dapm, "Receiver"); + snd_soc_dapm_disable_pin(dapm, "LSpeaker"); + snd_soc_dapm_disable_pin(dapm, "RSpeaker"); + snd_soc_dapm_disable_pin(dapm, "LHeadphone"); + snd_soc_dapm_disable_pin(dapm, "RHeadphone"); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + case 1: + snd_soc_dapm_enable_pin(dapm, "Receiver"); + snd_soc_dapm_disable_pin(dapm, "LSpeaker"); + snd_soc_dapm_disable_pin(dapm, "RSpeaker"); + snd_soc_dapm_disable_pin(dapm, "LHeadphone"); + snd_soc_dapm_disable_pin(dapm, "RHeadphone"); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + case 2: + snd_soc_dapm_disable_pin(dapm, "Receiver"); + snd_soc_dapm_enable_pin(dapm, "LSpeaker"); + snd_soc_dapm_enable_pin(dapm, "RSpeaker"); + snd_soc_dapm_disable_pin(dapm, "LHeadphone"); + snd_soc_dapm_disable_pin(dapm, "RHeadphone"); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + case 3: + snd_soc_dapm_disable_pin(dapm, "Receiver"); + snd_soc_dapm_disable_pin(dapm, "LSpeaker"); + snd_soc_dapm_disable_pin(dapm, "RSpeaker"); + snd_soc_dapm_enable_pin(dapm, "LHeadphone"); + snd_soc_dapm_enable_pin(dapm, "RHeadphone"); + chip->curr_outpath = ucontrol->value.integer.value[0]; + break; + default: + dev_err(codec->dev, "Not valid path\n"); + ret = -EINVAL; + break; + } + } + + return ret; +} + +#endif + +static const struct snd_kcontrol_new rt5512_snd_controls[] = +{ + //MicMode parameter + SOC_ENUM("Mic1 ModeSel", rt5512_enum[0]), + SOC_ENUM("Mic1 InvModeSel", rt5512_enum[1]), + SOC_ENUM("Mic2 ModeSel", rt5512_enum[19]), + SOC_ENUM("Mic2 InvModeSel", rt5512_enum[20]), + SOC_ENUM("Mic3 ModeSel", rt5512_enum[21]), + SOC_ENUM("Mic3 InvModeSel", rt5512_enum[22]), + SOC_SINGLE_TLV("Aux PGA Volume", 0x91, 0, 31, 0, aux_pga_tlv), + SOC_SINGLE_TLV("Mic1 PGA Volume", 0x92, 0, 31, 0, mic_pga_tlv), + SOC_SINGLE_TLV("Mic2 PGA Volume", 0x93, 0, 31, 0, mic_pga_tlv), + SOC_SINGLE_TLV("Mic3 PGA Volume", 0x94, 0, 31, 0, mic_pga_tlv), + SOC_SINGLE_TLV("AuxL LPGA Volume", 0x96, 6, 3, 0, aux_lrpga_tlv), + SOC_SINGLE_TLV("AuxR RPGA Volume", 0x97, 6, 3, 0, aux_lrpga_tlv), + SOC_SINGLE_TLV("Mic1 LPGA Volume", 0x96, 4, 3, 0, mic_lrpga_tlv), + SOC_SINGLE_TLV("Mic1 RPGA Volume", 0x97, 4, 3, 0, mic_lrpga_tlv), + SOC_SINGLE_TLV("Mic2 LPGA Volume", 0x96, 2, 3, 0, mic_lrpga_tlv), + SOC_SINGLE_TLV("Mic2 RPGA Volume", 0x97, 2, 3, 0, mic_lrpga_tlv), + SOC_SINGLE_TLV("Mic3 LPGA Volume", 0x96, 0, 3, 0, mic_lrpga_tlv), + SOC_SINGLE_TLV("Mic3 RPGA Volume", 0x97, 0, 3, 0, mic_lrpga_tlv), + SOC_SINGLE_TLV("LPGA2LSPK PGA Volume", 0x8d, 6, 3, 0, lrpga2lrspk_tlv), + SOC_SINGLE_TLV("RPGA2LSPK PGA Volume", 0x8d, 4, 3, 0, lrpga2lrspk_tlv), + SOC_SINGLE_TLV("LPGA2RSPK PGA Volume", 0x8d, 2, 3, 0, lrpga2lrspk_tlv), + SOC_SINGLE_TLV("RPGA2RSPK PGA Volume", 0x8d, 0, 3, 0, lrpga2lrspk_tlv), + SOC_SINGLE_TLV("LPGA2LHP PGA Volume", 0x87, 6, 3, 0, lrpga2lrhp_tlv), + SOC_SINGLE_TLV("RPGA2LHP PGA Volume", 0x87, 4, 3, 0, lrpga2lrhp_tlv), + SOC_SINGLE_TLV("LPGA2RHP PGA Volume", 0x87, 2, 3, 0, lrpga2lrhp_tlv), + SOC_SINGLE_TLV("RPGA2RHP PGA Volume", 0x87, 0, 3, 0, lrpga2lrhp_tlv), + SOC_SINGLE_TLV("LSPKPreMixer PGA Volume", 0x8e, 0, 46, 0, lrspkpga_tlv), + SOC_SINGLE_TLV("RSPKPreMixer PGA Volume", 0x8f, 0, 46, 0, lrspkpga_tlv), + SOC_SINGLE_TLV("LHPPreMixer PGA Volume", 0x88, 0, 31, 0, lrhppga_tlv), + SOC_SINGLE_TLV("RHPPreMixer PGA Volume", 0x89, 0, 31, 0, lrhppga_tlv), + SOC_SINGLE_TLV("RECVDrv Volume", 0x84, 3, 1, 0, recvoutdrv_tlv), + SOC_SINGLE_TLV("LSPKDrv Volume", 0x90, 4, 3, 0, spkoutdrv_tlv), + SOC_SINGLE_TLV("RSPKDrv Volume", 0x90, 0, 3, 0, spkoutdrv_tlv), + SOC_SINGLE_TLV("LHPDrv Volume", 0x8a, 0, 15, 0, hpoutdrv_tlv), + SOC_SINGLE_TLV("RHPDrv Volume", 0x8b, 0, 15, 0, hpoutdrv_tlv), + SOC_DOUBLE_TLV("AUD_ADC1 PGA Volume", 0x0c, 17, 1, 511, 1, digital_tlv), + SOC_DOUBLE_TLV("AUD_ADC2 PGA Volume", 0x0d, 17, 1, 511, 1, digital_tlv), + SOC_DOUBLE_TLV("AUD_DAC1 PGA Volume", 0x0a, 17, 1, 511, 1, digital_tlv), + SOC_DOUBLE_TLV("AUD_DAC2 PGA Volume", 0x0b, 17, 1, 511, 1, digital_tlv), + SOC_DOUBLE_TLV("Sidetone PGA Volume", 0x0e, 17, 1, 511, 1, digital_tlv), + SOC_ENUM("AIF2L2DACL sel", rt5512_enum[3]), + SOC_ENUM("AIF1L2DACL sel", rt5512_enum[4]), + SOC_ENUM("ADCL2DACL sel", rt5512_enum[5]), + SOC_ENUM("ADCR2DACL sel", rt5512_enum[6]), + SOC_ENUM("AIF2R2DACR sel", rt5512_enum[7]), + SOC_ENUM("AIF1R2DACR sel", rt5512_enum[8]), + SOC_ENUM("ADCL2DACR sel", rt5512_enum[9]), + SOC_ENUM("ADCR2DACR sel", rt5512_enum[10]), + SOC_ENUM("AIF2L2TX2L sel", rt5512_enum[11]), + SOC_ENUM("AIF1L2TX2L sel", rt5512_enum[12]), + SOC_ENUM("ADCL2TX2L sel", rt5512_enum[13]), + SOC_ENUM("ADCR2TX2L sel", rt5512_enum[14]), + SOC_ENUM("AIF2R2TX2R sel", rt5512_enum[15]), + SOC_ENUM("AIF1R2TX2R sel", rt5512_enum[16]), + SOC_ENUM("ADCL2TX2R sel", rt5512_enum[17]), + SOC_ENUM("ADCR2TX2R sel", rt5512_enum[18]), + SOC_SINGLE("ADC_HPF1 Switch", 0x16, 1, 1, 0), + SOC_ENUM("ADC_HPF1 FC", rt5512_enum[25]), + SOC_SINGLE("ADC_HPF2 Switch", 0x16, 2, 1, 0), + SOC_ENUM("ADC_HPF2 FC", rt5512_enum[26]), + SOC_SINGLE("ADC_HPF3 Switch", 0x16, 3, 1, 0), + SOC_ENUM("ADC_HPF3 FC", rt5512_enum[27]), + SOC_SINGLE("ADC_DRC1 Switch", 0x16, 4, 1, 0), + SOC_SINGLE("ADC_DRC2 Switch", 0x16, 5, 1, 0), + SOC_SINGLE("ADC_DRC NG Switch", 0x31, 0, 1, 0), + SOC_SINGLE("DAC_3D1 Switch", 0x14, 0, 1, 0), + SOC_SINGLE("DAC_3D2 Switch", 0x14, 1, 1, 0), + SOC_SINGLE("DAC_DRC1 Switch", 0x15, 2, 1, 0), + SOC_SINGLE("DAC_DRC2 Switch", 0x15, 3, 1, 0), + SOC_SINGLE("DAC_DRC3 Switch", 0x15, 4, 1, 0), + SOC_SINGLE("DAC_DRC4 Switch", 0x15, 5, 1, 0), + SOC_SINGLE("DAC_DRC NG Switch", 0x31, 1, 1, 0), + SOC_SINGLE("DAC_BASS1 Switch", 0x14, 2, 1, 0), + SOC_SINGLE("DAC_BASS2 Switch", 0x14, 3, 1, 0), + SOC_SINGLE("DAC_EQ1 Switch", 0x15, 0, 1, 0), + SOC_SINGLE("DAC_EQ2 Switch", 0x15, 1, 1, 0), +#if FOR_MID + SOC_ENUM_EXT("Playback Path", rt5512_path_type, + rt5512_playback_path_get, rt5512_playback_path_put), + SOC_ENUM_EXT("Capture MIC Path", rt5512_capture_type, + rt5512_capture_path_get, rt5512_capture_path_put), +#else + SOC_ENUM_EXT("Out Sel", rt5512_enum[23], rt5512_outpath_get, rt5512_outpath_set), + SOC_ENUM_EXT("In Sel", rt5512_enum[24], rt5512_inpath_get, rt5512_inpath_set), +#endif +}; + +static const struct snd_kcontrol_new linput_mixer_controls[] = +{ + SOC_DAPM_SINGLE("AuxL Switch", 0x95, 7, 1, 0), + SOC_DAPM_SINGLE("Mic1 Switch", 0x95, 6, 1, 0), + SOC_DAPM_SINGLE("Mic2 Switch", 0x95, 5, 1, 0), + SOC_DAPM_SINGLE("Mic3 Switch", 0x95, 4, 1, 0), +}; + +static const struct snd_kcontrol_new rinput_mixer_controls[] = +{ + SOC_DAPM_SINGLE("AuxR Switch", 0x95, 3, 1, 0), + SOC_DAPM_SINGLE("Mic1 Switch", 0x95, 2, 1, 0), + SOC_DAPM_SINGLE("Mic2 Switch", 0x95, 1, 1, 0), + SOC_DAPM_SINGLE("Mic3 Switch", 0x95, 0, 1, 0), +}; + +static const struct snd_kcontrol_new analog_bypass_controls = + SOC_DAPM_ENUM("Analog Bypass", rt5512_enum[2]); + +static const struct snd_kcontrol_new lspkpre_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LPGA Switch", 0x8c, 7, 1, 0), + SOC_DAPM_SINGLE("LSPKDAC Switch", 0x84, 1, 1, 0), + SOC_DAPM_SINGLE("RPGA Switch", 0x8c, 6, 1, 0), +}; + +static const struct snd_kcontrol_new rspkpre_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LPGA Switch", 0x8c, 5, 1, 0), + SOC_DAPM_SINGLE("RSPKDAC Switch", 0x84, 0, 1, 0), + SOC_DAPM_SINGLE("RPGA Switch", 0x8c, 4, 1, 0), +}; + +static const struct snd_kcontrol_new recvout_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LSPKPGA Switch", 0x84, 6, 1, 0), + SOC_DAPM_SINGLE("RSPKPGA Switch", 0x84, 5, 1, 0), + SOC_DAPM_SINGLE("BYPASS Switch", 0x84, 4, 1, 0), +}; + +static const struct snd_kcontrol_new lspkpost_mixer_controls[] = +{ + SOC_DAPM_SINGLE("RSPKPGA Switch", 0x8c, 2, 1, 0), + SOC_DAPM_SINGLE("LSPKPGA Switch", 0x8c, 3, 1, 0), + SOC_DAPM_SINGLE("BYPASS Switch", 0x90, 7, 1, 0), +}; + +static const struct snd_kcontrol_new rspkpost_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LSPKPGA Switch", 0x8c, 1, 1, 0), + SOC_DAPM_SINGLE("RSPKPGA Switch", 0x8c, 0, 1, 0), + SOC_DAPM_SINGLE("BYPASS Switch", 0x90, 3, 1, 0), +}; + +static const struct snd_kcontrol_new lhppre_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LPGA Switch", 0x86, 7, 1, 0), + SOC_DAPM_SINGLE("RPGA Switch", 0x86, 6, 1, 0), +}; + +static const struct snd_kcontrol_new rhppre_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LPGA Switch", 0x86, 5, 1, 0), + SOC_DAPM_SINGLE("RPGA Switch", 0x86, 4, 1, 0), +}; + +static const struct snd_kcontrol_new lhppost_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LHPDAC Switch", RT5512_VIRTUAL_REG, 6, 1, 0), + SOC_DAPM_SINGLE("BYPASS Switch", 0x86, 1, 1, 0), + SOC_DAPM_SINGLE("LRPGAMixer Switch", RT5512_VIRTUAL_REG, 7, 1, 0), +}; + +static const struct snd_kcontrol_new rhppost_mixer_controls[] = +{ + SOC_DAPM_SINGLE("LRPGAMixer Switch", RT5512_VIRTUAL_REG, 8, 1, 0), + SOC_DAPM_SINGLE("BYPASS Switch", 0x86, 0, 1, 0), + SOC_DAPM_SINGLE("RHPDAC Switch", RT5512_VIRTUAL_REG, 9, 1, 0), +}; + +static int rt5512_power_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + RT_DBG("\n"); + switch (event) + { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(w->codec, 0x81, 0x7, 0x7); + mdelay(20); + snd_soc_update_bits(w->codec, 0x81, 0x4, 0x0); + mdelay(1); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(w->codec, 0x81, 0x7, 0x4); + break; + } + return 0; +} + +static int rt5512_lhpdrv_pevent(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + RT_DBG("\n"); + switch (event) + { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(w->codec, 0x83, 0x80, 0x80); + mdelay(1); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(w->codec, 0x8A, 0x0F, 0x00); + mdelay(50); + break; + case SND_SOC_DAPM_POST_PMD: + mdelay(10); + snd_soc_update_bits(w->codec, 0x83, 0x80, 0x0); + mdelay(10); + snd_soc_update_bits(w->codec, 0x8A, 0x0F, 0x0B); + break; + } + return 0; +} + +static int rt5512_rhpdrv_pevent(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + RT_DBG("\n"); + switch (event) + { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(w->codec, 0x83, 0x80, 0x80); + mdelay(1); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(w->codec, 0x8B, 0x0F, 0x00); + mdelay(50); + break; + case SND_SOC_DAPM_POST_PMD: + mdelay(10); + snd_soc_update_bits(w->codec, 0x83, 0x80, 0x0); + mdelay(10); + snd_soc_update_bits(w->codec, 0x8B, 0x0F, 0x0B); + break; + } + return 0; +} + +static int rt5512_spkdac_pevent(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + RT_DBG("\n"); + switch (event) + { + case SND_SOC_DAPM_POST_PMU: + mdelay(125); + break; + } + return 0; +} + +static int rt5512_spkdrv_pevent(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + RT_DBG("\n"); + switch (event) + { + case SND_SOC_DAPM_PRE_PMU: + mdelay(1); + break; + case SND_SOC_DAPM_POST_PMD: + mdelay(1); + break; + } + return 0; +} + +static int rt5512_recvdrv_pevent(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) +{ + RT_DBG("\n"); + switch (event) + { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(w->codec, 0x83, 0x80, 0x80); + mdelay(2); + break; + case SND_SOC_DAPM_POST_PMD: + mdelay(1); + snd_soc_update_bits(w->codec, 0x83, 0x80, 0x0); + mdelay(1); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget rt5512_dapm_widgets[] = +{ + SND_SOC_DAPM_PRE("Pre Power", rt5512_power_event), + SND_SOC_DAPM_POST("Post Power",rt5512_power_event), + // Input Related + // Analog Input Pin + SND_SOC_DAPM_INPUT("Mic1"), + SND_SOC_DAPM_INPUT("Mic2"), + SND_SOC_DAPM_INPUT("Mic3"), + SND_SOC_DAPM_INPUT("Aux"), + // Analog Bypass + SND_SOC_DAPM_MUX("Analog Bypass Mux", RT5512_VIRTUAL_REG, 0, 0, &analog_bypass_controls), + // Input PGA + SND_SOC_DAPM_PGA("Aux PGA", 0x85, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic1 PGA", 0x85, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic2 PGA", 0x85, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic3 PGA", 0x85, 0, 0, NULL, 0), + // Input Mixer + SND_SOC_DAPM_MIXER("LInput Mixer", 0x85, 5, 0, linput_mixer_controls, ARRAY_SIZE(linput_mixer_controls)), + SND_SOC_DAPM_MIXER("RInput Mixer", 0x85, 4, 0, rinput_mixer_controls, ARRAY_SIZE(rinput_mixer_controls)), + // Input ADC + SND_SOC_DAPM_ADC("LADC", NULL, 0x85, 7, 0), + SND_SOC_DAPM_ADC("RADC", NULL, 0x85, 6, 0), + // Digital Input Interface + SND_SOC_DAPM_AIF_OUT("AIF1ADC", NULL, 0, 0x06, 0, 0), + //SND_SOC_DAPM_AIF_OUT("AIF1ADCR", NULL, 0, 0x06, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1ADCDAT", "AIF1 Capture", 0, 0x05, 2, 1), + SND_SOC_DAPM_AIF_OUT("AIF2ADC", NULL, 0, 0x06, 1, 0), + //SND_SOC_DAPM_AIF_OUT("AIF2ADCR", NULL, 0, 0x06, 1, 0), + SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", "AIF2 Capture", 0, 0x05, 3, 1), + + //Digital DSP Routing + //Mixer + SND_SOC_DAPM_MIXER("DACL Mixer", 0x07, 7, 1, NULL, 0), + SND_SOC_DAPM_MIXER("DACR Mixer", 0x07, 6, 1, NULL, 0), + SND_SOC_DAPM_MIXER("TX2L Mixer", 0x07, 5, 1, NULL, 0), + SND_SOC_DAPM_MIXER("TX2R Mixer", 0x07, 4, 1, NULL, 0), + + // Output Related + SND_SOC_DAPM_AIF_IN("AIF1DAC", NULL, 0, 0x06, 2, 0), + //SND_SOC_DAPM_AIF_IN("AIF1DACR", NULL, 0, 0x06, 2, 0), + SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, 0x05, 0, 1), + SND_SOC_DAPM_AIF_IN("AIF2DAC", NULL, 0, 0x06, 3, 0), + //SND_SOC_DAPM_AIF_IN("AIF2DACR", NULL, 0, 0x06, 3, 0), + SND_SOC_DAPM_AIF_IN("AIF2DACDAT", "AIF2 Playback", 0, 0x05, 1, 1), + // Output DAC + SND_SOC_DAPM_DAC_E("LSPKDAC", NULL, 0x82, 1, 0, rt5512_spkdac_pevent, + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_DAC("RSPKDAC", NULL, 0x82, 0, 0), + SND_SOC_DAPM_DAC("LHPDAC", NULL, 0x82, 3, 0), + SND_SOC_DAPM_DAC("RHPDAC", NULL, 0x82, 2, 0), + // Output Mixer + SND_SOC_DAPM_MIXER("LSPK Pre Mixer", 0x82, 5, 0, lspkpre_mixer_controls, ARRAY_SIZE(lspkpre_mixer_controls)), + SND_SOC_DAPM_MIXER("RSPK Pre Mixer", 0x82, 4, 0, rspkpre_mixer_controls, ARRAY_SIZE(rspkpre_mixer_controls)), + SND_SOC_DAPM_MIXER("RECV Mixer", RT5512_VIRTUAL_REG, 1, 0, recvout_mixer_controls, ARRAY_SIZE(recvout_mixer_controls)), + SND_SOC_DAPM_MIXER("LSPK Post Mixer", RT5512_VIRTUAL_REG, 2, 0, lspkpost_mixer_controls, ARRAY_SIZE(lspkpost_mixer_controls)), + SND_SOC_DAPM_MIXER("RSPK Post Mixer", RT5512_VIRTUAL_REG, 3, 0, rspkpost_mixer_controls, ARRAY_SIZE(rspkpost_mixer_controls)), + SND_SOC_DAPM_MIXER("LHP Pre Mixer", 0x82, 7, 0, lhppre_mixer_controls, ARRAY_SIZE(lhppre_mixer_controls)), + SND_SOC_DAPM_MIXER("RHP Pre Mixer", 0x82, 6, 0, rhppre_mixer_controls, ARRAY_SIZE(rhppre_mixer_controls)), + SND_SOC_DAPM_MIXER("LHP Post Mixer", RT5512_VIRTUAL_REG, 4, 0, lhppost_mixer_controls, ARRAY_SIZE(lhppost_mixer_controls)), + SND_SOC_DAPM_MIXER("RHP Post Mixer", RT5512_VIRTUAL_REG, 5, 0, rhppost_mixer_controls, ARRAY_SIZE(rhppost_mixer_controls)), + // Output Drv + SND_SOC_DAPM_OUT_DRV_E("RECV Drv", 0x84, 7, 0, NULL, 0, rt5512_recvdrv_pevent, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("LSPK Drv", 0x83, 3, 0, NULL, 0, rt5512_spkdrv_pevent, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("RSPK Drv", 0x83, 2, 0, NULL, 0, rt5512_spkdrv_pevent, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("LHP Drv", 0x83, 1, 0, NULL, 0, rt5512_lhpdrv_pevent, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_PRE_PMD|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("RHP Drv", 0x83, 0, 0, NULL, 0, rt5512_rhpdrv_pevent, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_PRE_PMD||SND_SOC_DAPM_POST_PMU), + // Output Pin + SND_SOC_DAPM_OUTPUT("Receiver"), + SND_SOC_DAPM_OUTPUT("LSpeaker"), + SND_SOC_DAPM_OUTPUT("RSpeaker"), + SND_SOC_DAPM_OUTPUT("LHeadphone"), + SND_SOC_DAPM_OUTPUT("RHeadphone"), + //MicBias + SND_SOC_DAPM_MICBIAS("MicBias1", 0x81, 5, 0), + SND_SOC_DAPM_MICBIAS("MicBias2", 0x81, 4, 0), +}; + +static const struct snd_soc_dapm_route rt5512_dapm_routes[] = +{ + // Input + {"Mic1 PGA", NULL, "Mic1"}, + {"Mic2 PGA", NULL, "Mic2"}, + {"Mic3 PGA", NULL, "Mic3"}, + {"Aux PGA", NULL, "Aux"}, + {"Analog Bypass Mux", "Mic1", "Mic1"}, + {"Analog Bypass Mux", "Mic2", "Mic2"}, + {"Analog Bypass Mux", "Mic3", "Mic3"}, + {"Analog Bypass Mux", "Aux", "Aux"}, + {"LInput Mixer", "Mic1 Switch", "Mic1 PGA"}, + {"LInput Mixer", "Mic2 Switch", "Mic2 PGA"}, + {"LInput Mixer", "Mic3 Switch", "Mic3 PGA"}, + {"LInput Mixer", "AuxL Switch", "Aux PGA"}, + {"RInput Mixer", "Mic1 Switch", "Mic1 PGA"}, + {"RInput Mixer", "Mic2 Switch", "Mic2 PGA"}, + {"RInput Mixer", "Mic3 Switch", "Mic3 PGA"}, + {"RInput Mixer", "AuxR Switch", "Aux PGA"}, + {"LADC", NULL, "LInput Mixer"}, + {"RADC", NULL, "RInput Mixer"}, + {"AIF1ADC", NULL, "LADC"}, + {"AIF1ADC", NULL, "RADC"}, + {"AIF1ADCDAT", NULL, "AIF1ADC"}, + //{"AIF1ADCDAT", NULL, "AIF1ADCR"}, + {"AIF2ADC", NULL, "TX2L Mixer"}, + {"AIF2ADC", NULL, "TX2R Mixer"}, + {"AIF2ADCDAT", NULL, "AIF2ADC"}, + //{"AIF2ADCDAT", NULL, "AIF2ADCR"}, + // Output + {"AIF1DAC", NULL, "AIF1DACDAT"}, + //{"AIF1DACR", NULL, "AIF1DACDAT"}, + {"AIF2DAC", NULL, "AIF2DACDAT"}, + //{"AIF2DACR", NULL, "AIF2DACDAT"}, + {"DACL Mixer", NULL, "AIF1DAC"}, + {"DACL Mixer", NULL, "AIF2DAC"}, + {"DACL Mixer", NULL, "LADC"}, + {"DACL Mixer", NULL, "RADC"}, + {"DACR Mixer", NULL, "AIF1DAC"}, + {"DACR Mixer", NULL, "AIF2DAC"}, + {"DACR Mixer", NULL, "LADC"}, + {"DACR Mixer", NULL, "RADC"}, + {"TX2L Mixer", NULL, "AIF1DAC"}, + {"TX2L Mixer", NULL, "AIF2DAC"}, + {"TX2L Mixer", NULL, "LADC"}, + {"TX2L Mixer", NULL, "RADC"}, + {"TX2R Mixer", NULL, "AIF1DAC"}, + {"TX2R Mixer", NULL, "AIF2DAC"}, + {"TX2R Mixer", NULL, "LADC"}, + {"TX2R Mixer", NULL, "RADC"}, + {"LSPKDAC", NULL, "DACL Mixer"}, + {"RSPKDAC", NULL, "DACR Mixer"}, + {"LHPDAC", NULL, "DACL Mixer"}, + {"RHPDAC", NULL, "DACR Mixer"}, + {"LSPK Pre Mixer", "LPGA Switch", "LInput Mixer"}, + {"LSPK Pre Mixer", "LSPKDAC Switch", "LSPKDAC"}, + {"LSPK Pre Mixer", "RPGA Switch", "RInput Mixer"}, + {"RSPK Pre Mixer", "LPGA Switch", "LInput Mixer"}, + {"RSPK Pre Mixer", "RSPKDAC Switch", "RSPKDAC"}, + {"RSPK Pre Mixer", "RPGA Switch", "RInput Mixer"}, + {"LHP Pre Mixer", "LPGA Switch", "LInput Mixer"}, + {"LHP Pre Mixer", "RPGA Switch", "RInput Mixer"}, + {"RHP Pre Mixer", "LPGA Switch", "LInput Mixer"}, + {"RHP Pre Mixer", "RPGA Switch", "RInput Mixer"}, + {"RECV Mixer", "LSPKPGA Switch", "LSPK Pre Mixer"}, + {"RECV Mixer", "RSPKPGA Switch", "RSPK Pre Mixer"}, + {"RECV Mixer", "BYPASS Switch", "Analog Bypass Mux"}, + {"LSPK Post Mixer", "RSPKPGA Switch", "RSPK Pre Mixer"}, + {"LSPK Post Mixer", "LSPKPGA Switch", "LSPK Pre Mixer"}, + {"LSPK Post Mixer", "BYPASS Switch", "Analog Bypass Mux"}, + {"RSPK Post Mixer", "LSPKPGA Switch", "LSPK Pre Mixer"}, + {"RSPK Post Mixer", "RSPKPGA Switch", "RSPK Pre Mixer"}, + {"RSPK Post Mixer", "BYPASS Switch", "Analog Bypass Mux"}, + {"LHP Post Mixer", "LHPDAC Switch", "LHPDAC"}, + {"LHP Post Mixer", "BYPASS Switch", "Analog Bypass Mux"}, + {"LHP Post Mixer", "LRPGAMixer Switch", "LHP Pre Mixer"}, + {"RHP Post Mixer", "LRPGAMixer Switch", "RHP Pre Mixer"}, + {"RHP Post Mixer", "BYPASS Switch", "Analog Bypass Mux"}, + {"RHP Post Mixer", "RHPDAC Switch", "RHPDAC"}, + {"RECV Drv", NULL, "RECV Mixer"}, + {"LSPK Drv", NULL, "LSPK Post Mixer"}, + {"RSPK Drv", NULL, "RSPK Post Mixer"}, + {"LHP Drv", NULL, "LHP Post Mixer"}, + {"RHP Drv", NULL, "RHP Post Mixer"}, + {"Receiver", NULL, "RECV Drv"}, + {"LSpeaker", NULL, "LSPK Drv"}, + {"RSpeaker", NULL, "RSPK Drv"}, + {"LHeadphone", NULL, "LHP Drv"}, + {"RHeadphone", NULL, "RHP Drv"}, +}; + +void odroid_audio_tvout(bool tvout) +{ + return; +} + + +static int rt5512_id_check(struct snd_soc_codec *codec) +{ + int ret = -1; + u8 regaddr = 0x00; // always revision id register + u8 regval; + + ret = i2c_master_send(codec->control_data, ®addr, 1); + if (ret != 1) + { + dev_err(codec->dev, "send reg addr fail\n"); + return -EIO; + } + ret = i2c_master_recv(codec->control_data, ®val, 1); + if (ret != 1) + { + dev_err(codec->dev, "read regval fail\n"); + return -EIO; + } + + return regval; +} +static unsigned int rt5512_io_read(struct snd_soc_codec *codec, unsigned int reg) +{ + int ret; + char regaddr = reg&0xff; // 8bit register address + char regval[4] = {0}; + int len = rt5512_reg_priv[reg].reg_size; + int i; + + RT_DBG("reg 0x%02x\n", reg); + if (reg == RT5512_VIRTUAL_REG) + { + return rt5512_reg_priv[reg].reg_cache; + } + + if (len == 8 || len == 16 || len == 20) + { + pr_err("rt5512-codec: not supported reg data size in this read function\n"); + return -EINVAL; + } + else if (len == 0) + { + dev_err(codec->dev, "Invalid reg_addr 0x%02x\n", reg); + return -EINVAL; + } + + if (rt5512_reg_priv[reg].sync_needed) + { + ret = i2c_master_send(codec->control_data, ®addr, 1); + if (ret != 1) + { + dev_err(codec->dev, "send reg addr fail\n"); + return -EIO; + } + ret = i2c_master_recv(codec->control_data, regval, len); + if (ret != len) + { + dev_err(codec->dev, "read regval fail\n"); + return -EIO; + } + + switch (len) + { + case 1: + ret = 0x00 | regval[0]; + break; + case 4: + for (i=0, ret=0x0; i<4 ; i++) + ret |= (regval[i] << (24-8*i)); + break; + } + rt5512_reg_priv[reg].reg_cache = ret; + rt5512_reg_priv[reg].sync_needed = 0; + } + else + ret = rt5512_reg_priv[reg].reg_cache; + + return ret; +} + +static int rt5512_io_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int regval) +{ + int ret; + char data[5] = {0}; + int len = rt5512_reg_priv[reg].reg_size; + int i; + + RT_DBG("reg 0x%02x, val 0x%08x\n", reg, regval); + if (reg == RT5512_VIRTUAL_REG) + { + rt5512_reg_priv[reg].reg_cache = regval; + return 0; + } + + if (len == 8 || len == 16 || len == 20) + { + pr_err("rt5512-codec: not supported reg data size in this write function\n"); + return -EINVAL; + } + else if (len == 0) + { + dev_err(codec->dev, "Invalid reg_addr 0x%02x\n", reg); + return -EINVAL; + } + + data[0] = reg&0xff; + + switch (len) + { + case 1: + data[1] = regval&0xff; + break; + case 4: + for (i=1; i<5; i++) + data[i] = (regval>>(32-8*i))&0xff; + break; + } + + ret = i2c_master_send(codec->control_data, data, 1+len); + if (ret != (1+len)) + { + dev_err(codec->dev, "write register fail\n"); + return -EIO; + } + rt5512_reg_priv[reg].sync_needed = 1; + return 0; +} + +static int rt5512_set_codec_io(struct snd_soc_codec *codec) +{ + struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + + RT_DBG("\n"); + codec->read = rt5512_io_read; + codec->write = rt5512_io_write; + codec->control_data = chip->client; + return 0; +} + +static int rt5512_digital_block_power(struct snd_soc_codec *codec, int pd) +{ + RT_DBG("powerdown = %d\n", pd); + snd_soc_update_bits(codec, 0x80, RT5512_PDBIT_MASK, (pd?RT5512_PDBIT_MASK:0)); + return 0; +} + +static int rt5512_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + RT_DBG("bias_level->%d\n",(int)level); + if (codec->dapm.bias_level != level) + { + switch (level) + { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + rt5512_digital_block_power(codec, 0); + break; + case SND_SOC_BIAS_OFF: + rt5512_digital_block_power(codec, 1); + break; + default: + dev_err(codec->dev, "not supported bias level\n"); + } + //snd_soc_write(codec, reg, value); + codec->dapm.bias_level = level; + } + else + dev_warn(codec->dev, "the same bias level, no need to change\n"); + return 0; +} + +static void rt5512_codec_reset(struct snd_soc_codec *codec) +{ + RT_DBG("\n"); + snd_soc_update_bits(codec, 0x80, RT5512_RSTBIT_MASK, RT5512_RSTBIT_MASK); +} + +static void rt5512_codec_force_reload_cache(struct snd_soc_codec *codec) +{ + int i; + int ret; + RT_DBG("\n"); + for (i=0; iprivate_data = inode->i_private; + return 0; +} + +static int get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + RT_DBG("\n"); + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } + else + break; + } + return cnt; +} + +#define STR_4TIMES "0x%02x 0x%02x 0x%02x 0x%02x " + +static ssize_t codec_debug_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char lbuf[200]; + + RT_DBG("\n"); + + switch (read_size) + { + case 1: + snprintf(lbuf, sizeof(lbuf), "0x%02x\n", read_data[0]); + break; + case 4: + snprintf(lbuf, sizeof(lbuf), STR_4TIMES "\n", + read_data[0], read_data[1], read_data[2], read_data[3]); + break; + case 8: + snprintf(lbuf, sizeof(lbuf), STR_4TIMES STR_4TIMES "\n", + read_data[0], read_data[1], read_data[2], read_data[3], + read_data[4], read_data[5], read_data[6], read_data[7]); + break; + case 16: + snprintf(lbuf, sizeof(lbuf), STR_4TIMES STR_4TIMES STR_4TIMES STR_4TIMES "\n", + read_data[0], read_data[1], read_data[2], read_data[3], + read_data[4], read_data[5], read_data[6], read_data[7], + read_data[8], read_data[9], read_data[10], read_data[11], + read_data[12], read_data[13], read_data[14], read_data[15]); + break; + case 20: + snprintf(lbuf, sizeof(lbuf), STR_4TIMES STR_4TIMES STR_4TIMES STR_4TIMES STR_4TIMES "\n", + read_data[0], read_data[1], read_data[2], read_data[3], + read_data[4], read_data[5], read_data[6], read_data[7], + read_data[8], read_data[9], read_data[10], read_data[11], + read_data[12], read_data[13], read_data[14], read_data[15], + read_data[16], read_data[17], read_data[18], read_data[19]); + break; + default: + return 0; + } + return simple_read_from_buffer(ubuf, count, ppos, lbuf, strlen(lbuf)); +} + +static int rt5512_simulate_headset(struct i2c_client *client, int id) +{ + struct rt5512_codec_chip *chip = i2c_get_clientdata(client); + + RT_DBG("id = %d\n", id); + switch (id) + { + case 0: // simulate headset insert + snd_soc_jack_report(chip->rt_jack, SND_JACK_HEADSET, SND_JACK_HEADSET); + break; + case 1: // simulate headset plug out + snd_soc_jack_report(chip->rt_jack, 0, SND_JACK_HEADSET); + break; + case 2: // simulate headphone(no microphone) insert + snd_soc_jack_report(chip->rt_jack, SND_JACK_HEADPHONE, SND_JACK_HEADPHONE); + break; + case 3: // simulate headphone(no microphone) plug out + snd_soc_jack_report(chip->rt_jack, 0, SND_JACK_HEADPHONE); + break; + case 4: // simulate headset jack btn 0 click + snd_soc_jack_report(chip->rt_jack, SND_JACK_BTN_0, SND_JACK_BTN_0); + mdelay(1); + snd_soc_jack_report(chip->rt_jack, 0, SND_JACK_BTN_0); + break; + case 5: // simulate headset jack btn 1 click + snd_soc_jack_report(chip->rt_jack, SND_JACK_BTN_1, SND_JACK_BTN_1); + mdelay(1); + snd_soc_jack_report(chip->rt_jack, 0, SND_JACK_BTN_1); + break; + case 6: // simulate headset jack btn 2 click + snd_soc_jack_report(chip->rt_jack, SND_JACK_BTN_2, SND_JACK_BTN_2); + mdelay(1); + snd_soc_jack_report(chip->rt_jack, 0, SND_JACK_BTN_2); + break; + } + return 0; +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *access_str = filp->private_data; + char lbuf[200]; + int rc; + long int param[21]; + int i = 0; + int reg_dsize = 0; + + RT_DBG("\n"); + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + + if (!strcmp(access_str, "poke")) { + /* write */ + rc = get_parameters(lbuf, param, 21); + if (rc > 0 && param[0]<=RT5512_MAX_REG && rc == (rt5512_reg_priv[param[0]].reg_size+1)) + { + reg_dsize = rt5512_reg_priv[param[0]].reg_size; + switch (reg_dsize) + { + case 1: + case 4: + case 8: + case 16: + case 20: + for (i = 0; i 0 && param[0] <= RT5512_MAX_REG) + { + reg_dsize = rt5512_reg_priv[param[0]].reg_size; + switch (reg_dsize) + { + case 1: + case 4: + case 8: + case 16: + case 20: + lbuf[0] = (unsigned char)param[0]; + i2c_master_send(this_client, lbuf, 1); + i2c_master_recv(this_client, lbuf, reg_dsize); + for (i=0; i< reg_dsize; i++) + read_data[i] = lbuf[i]; + read_size = reg_dsize; + break; + default: + break; + } + } + else + rc = -EINVAL; + } else if (!strcmp(access_str, "headset_test")) { + rc = get_parameters(lbuf, param, 1); + if (rc > 0) + { + if (param[0] >= 0 && param[0] <= 6) + rt5512_simulate_headset(this_client, param[0]); + else + rc = -EINVAL; + } + else + rc = -EINVAL; + } + + if (rc > 0) + rc = cnt; + + return rc; +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, + .read = codec_debug_read +}; +#endif + +static int __devinit rt5512_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret; + struct rt5512_codec_chip *chip; + + RT_DBG("\n"); + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + { + pr_err("%s: could not allocate kernel memory\n", __func__); + return -ENOMEM; + } + + chip->dev = &client->dev; + chip->client = client; + chip->control_type = SND_SOC_I2C; + + i2c_set_clientdata(client, chip); + + ret = snd_soc_register_codec(&client->dev, + &soc_codec_dev_rt5512, rt5512_dai, ARRAY_SIZE(rt5512_dai)); + if (ret < 0) + { + dev_err(&client->dev, "register codec failed\n"); + goto codec_reg_fail; + } + + #ifdef CONFIG_DEBUG_FS + RT_DBG("add debugfs for core debug\n"); + this_client = client; + debugfs_rt_dent = debugfs_create_dir("rt5512_codec_dbg", 0); + if (!IS_ERR(debugfs_rt_dent)) { + debugfs_peek = debugfs_create_file("peek", + S_IFREG | S_IRUGO, debugfs_rt_dent, + (void *) "peek", &codec_debug_ops); + + debugfs_poke = debugfs_create_file("poke", + S_IFREG | S_IRUGO, debugfs_rt_dent, + (void *) "poke", &codec_debug_ops); + + debugfs_headset_test = debugfs_create_file("headset_test", + S_IFREG | S_IRUGO, debugfs_rt_dent, + (void *) "headset_test", &codec_debug_ops); + } + #endif + + pr_info("rt5512-codec driver successfully loaded\n"); + return 0; + +codec_reg_fail: + kfree(chip); + return ret; +} + +static int __devexit rt5512_i2c_remove(struct i2c_client *client) +{ + struct rt5512_codec_chip *chip = i2c_get_clientdata(client); + + if (!IS_ERR(debugfs_rt_dent)) + debugfs_remove_recursive(debugfs_rt_dent); + kfree(chip); + return 0; +} + +static const struct i2c_device_id rt5512_i2c_id[] = +{ + {"rt5512", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt5512_i2c_id); + +struct i2c_driver rt5512_i2c_driver = +{ + .driver = { + .name = "rt5512", + .owner = THIS_MODULE, + }, + .probe = rt5512_i2c_probe, + .remove = __devexit_p(rt5512_i2c_remove), + .id_table = rt5512_i2c_id, +}; + +static int __init rt5512_init(void) +{ + return i2c_add_driver(&rt5512_i2c_driver); +} + +static void __exit rt5512_exit(void) +{ + i2c_del_driver(&rt5512_i2c_driver); +} + +module_init(rt5512_init); +module_exit(rt5512_exit); + +MODULE_AUTHOR("cy_huang "); +MODULE_DESCRIPTION("RT5512 audio codec for Rockchip board"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(RT5512_DRV_VER); diff --git a/sound/soc/codecs/rt5512.h b/sound/soc/codecs/rt5512.h new file mode 100755 index 000000000000..93da472562f3 --- /dev/null +++ b/sound/soc/codecs/rt5512.h @@ -0,0 +1,49 @@ +#ifndef __SND_SOC_CODEC_RT5512_H +#define __SND_SOC_CODEC_RT5512_H + +#define RT5512_CODEC_NAME "rt5512-codec" +#define RT5512_DRV_VER "1.0.1_G" + +#define RT5512_CLK_DIV_ID 1 +#define FOR_MID 1 + + +struct rt5512_codec_chip +{ + struct device *dev; + struct i2c_client *client; + struct snd_soc_jack *rt_jack; + enum snd_soc_control_type control_type; + int curr_outpath; + int curr_inpath; +}; + +enum { + OFF, + RCV, + SPK_PATH, + HP_PATH, + HP_NO_MIC, + BT, + SPK_HP, + RING_SPK, + RING_HP, + RING_HP_NO_MIC, + RING_SPK_HP, +}; + +enum { + MIC_OFF, + Main_Mic, + Hands_Free_Mic, + BT_Sco_Mic, +}; + + +#if 1 +#define RT_DBG(format, args...) pr_info("%s:%s() line-%d: " format, RT5512_CODEC_NAME, __FUNCTION__, __LINE__, ##args) +#else +#define RT_DBG(format, args...) +#endif /* #if 1 */ + +#endif /* #ifndef __SND_SOC_CODEC_RT5512_H */ diff --git a/sound/soc/rk29/Kconfig b/sound/soc/rk29/Kconfig index cb95c2319a62..64ef61cc20b7 100755 --- a/sound/soc/rk29/Kconfig +++ b/sound/soc/rk29/Kconfig @@ -129,6 +129,15 @@ config SND_RK29_SOC_WM8900 help Say Y if you want to add support for SoC audio on rockchip with the WM8900. + +config SND_RK29_SOC_RT5512 + tristate "SoC I2S Audio support for rockchip - RICHTEK5512" + depends on SND_RK29_SOC + select SND_SOC_RT5512 + select SND_RK29_SOC_I2S + help + Say Y if you want to add support for SoC audio on the ODROID. + config SND_RK29_SOC_RT5621 tristate "SoC I2S Audio support for rockchip - rt5621" depends on SND_RK29_SOC diff --git a/sound/soc/rk29/Makefile b/sound/soc/rk29/Makefile index 97834810f6ab..892ca7143ef5 100755 --- a/sound/soc/rk29/Makefile +++ b/sound/soc/rk29/Makefile @@ -36,6 +36,7 @@ snd-soc-es8323-objs := rk29_es8323.o snd-soc-rk3026-objs := rk_rk3026.o snd-soc-hdmi-i2s-objs := rk_hdmi_i2s.o snd-soc-hdmi-spdif-objs := rk_hdmi_spdif.o +snd-soc-rt5512-objs := rk29_rt5512.o obj-$(CONFIG_SND_RK29_SOC_WM8994) += snd-soc-wm8994.o obj-$(CONFIG_SND_RK29_SOC_WM8988) += snd-soc-wm8988.o @@ -61,3 +62,4 @@ obj-$(CONFIG_SND_RK_SOC_HDMI_SPDIF) += snd-soc-hdmi-spdif.o obj-$(CONFIG_SND_RK_SOC_RK2928) += snd-soc-rk2928.o obj-$(CONFIG_SND_RK29_SOC_ES8323) += snd-soc-es8323.o obj-$(CONFIG_SND_RK_SOC_RK3026) += snd-soc-rk3026.o +obj-$(CONFIG_SND_RK29_SOC_RT5512) += snd-soc-rt5512.o diff --git a/sound/soc/rk29/rk29_rt5512.c b/sound/soc/rk29/rk29_rt5512.c new file mode 100755 index 000000000000..2fc769869ae6 --- /dev/null +++ b/sound/soc/rk29/rk29_rt5512.c @@ -0,0 +1,343 @@ +/* + * odroid_rt5512.c + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rk29_pcm.h" +#include "rk29_i2s.h" +#if 1 +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) +#endif + +#include "../codecs/rt5512.h" + +static struct platform_device *rk29_snd_device; + + +static int rk29_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; + unsigned int pll_out = 0; + unsigned int pll_div; + int ret; + + DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__); + + /* set cpu DAI configuration */ + #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE) + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + #endif + #if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER) + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + #endif + if (ret < 0) + return ret; + + + /* set codec DAI configuration */ + #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + #endif + #if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM ); + #endif + if (ret < 0) + return ret; + + + + + switch(params_rate(params)) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + pll_out = 12288000; + break; + case 11025: + case 22050: + case 44100: + pll_out = 11289600; + break; + case 96000: + case 192000: + pll_out = 12288000*2; + break; + case 88200: + case 176400: + pll_out = 11289600*2; + break; + default: + DBG("Enter:%s, %d, Error rate=%d\n",__FUNCTION__,__LINE__,params_rate(params)); + return -EINVAL; + break; + } +#if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE) + snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0); + snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_BCLK, 64-1);//bclk = 2*32*lrck; 2*32fs + switch(params_rate(params)) { + case 176400: + case 192000: + snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK, 1); + DBG("Enter:%s, %d, MCLK=%d BCLK=%d LRCK=%d\n", + __FUNCTION__,__LINE__,pll_out,pll_out/2,params_rate(params)); + break; + default: + snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK, 3); + DBG("default:%s, %d, MCLK=%d BCLK=%d LRCK=%d\n", + __FUNCTION__,__LINE__,pll_out,pll_out/4,params_rate(params)); + break; + } + + /*Set the system clk for codec*/ + ret=snd_soc_dai_set_sysclk(codec_dai, 0,pll_out,SND_SOC_CLOCK_IN); + if (ret < 0) + { + DBG("rk29_hw_params_rt5631:failed to set the sysclk for codec side\n"); + return ret; + } + + switch (params_rate(params)) + { + case 8000: + pll_div = 12; + break; + case 16000: + pll_div = 6; + break; + case 32000: + pll_div = 3; + break; + case 48000: + pll_div = 2; + break; + case 96000: + pll_div = 1; + break; + case 11025: + pll_div = 8; + break; + case 22050: + pll_div = 4; + break; + case 44100: + pll_div = 2; + break; + case 88200: + pll_div = 1; + break; + default: + printk("Not yet supported!\n"); + return -EINVAL; + } + ret = snd_soc_dai_set_clkdiv(codec_dai, RT5512_CLK_DIV_ID, pll_div*4); + if (ret < 0) + return ret; +#endif + +#if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER) + //snd_soc_dai_set_pll(codec_dai,0,pll_out, 22579200); + snd_soc_dai_set_sysclk(codec_dai,0,pll_out, SND_SOC_CLOCK_IN); +#endif + return 0; +} + +//--------------------------------------------------------------------------------- +/* + * rt5512 DAI operations. + */ +static struct snd_soc_ops rk29_ops = { + .hw_params = rk29_hw_params, +}; + +static const struct snd_soc_dapm_widget rt5512_dapm_widgets[] = { + // Input + SND_SOC_DAPM_MIC("Main Mic", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), + // Output + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_soc_dapm_route rt5512_audio_map[] = { + // Input + {"MicBias1", NULL,"Main Mic"}, + {"Mic2", NULL, "MicBias1"}, + {"Aux", NULL, "LineIn"}, + // Output + {"Ext Spk", NULL, "LSpeaker"}, + {"Ext Spk", NULL, "RSpeaker"}, + {"Headphone Jack", NULL, "LHeadphone"}, + {"Headphone Jack", NULL, "RHeadphone"}, +}; + +#if 0 + +static struct snd_soc_jack rk29_soc_jack; + + + +static struct snd_soc_jack_gpio odroid_soc_jack_gpio[] = { + { + .gpio = 28, + .name "headset event", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + }, +}; +#endif + +#if 0 +static int rt5512_headset_keys(struct snd_soc_jack *jack) +{ + int err = 0; + + err = snd_jack_set_key(jack->jack, SND_JACK_BTN_0, 0x80); + if (err) + return err; + + err = snd_jack_set_key(jack->jack, SND_JACK_BTN_1, 0x81); + if (err) + return err; + + err = snd_jack_set_key(jack->jack, SND_JACK_BTN_2, 0x82); + if (err) + return err; + + return 0; +} +#endif + +static int rt5512_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + //struct rt5512_codec_chip *chip = snd_soc_codec_get_drvdata(codec); + //int err = 0; + + snd_soc_dapm_new_controls(dapm, rt5512_dapm_widgets, + ARRAY_SIZE(rt5512_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, rt5512_audio_map, + ARRAY_SIZE(rt5512_audio_map)); +#if FOR_MID + snd_soc_dapm_disable_pin(dapm, "Main Mic"); + snd_soc_dapm_disable_pin(dapm, "LineIn"); + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); +#endif + +#if 0 + if (!chip->rt_jack) + { + err = snd_soc_jack_new(codec, "Headset Jack" , SND_JACK_HEADSET, &rk29_soc_jack); + if (err) + return err; + + #if 0 + // How-to use gpio, just declare snd_soc_jack_gpios, then it will + // help you to register a interrupt and set wakeup, and delayed schedule + // work + err = snd_soc_jack_add_gpios(&odroid_soc_jack, gpio_count, odroid_soc_jack_gpios); + if (err) + return err; + + // If use this, when trigger, just use snd_soc_jack_get_type + // then snd_soc_jack_report to send the event to upper layer + err = snd_soc_jack_add_zones(&odroid_soc_jack, zone_count, tcc_soc_zones); + if (err) + return err; + #endif + + err = rt5512_headset_keys(&rk29_soc_jack); + if (err) + return err; + + chip->rt_jack = &rk29_soc_jack; + } +#endif + snd_soc_dapm_sync(dapm); + return 0; +} + +static struct snd_soc_dai_link rk29_dai[] = { + { /* Primary DAI i/f */ + .name = "RT5512 AIF1", + .stream_name = "Playback", + .cpu_dai_name = "rk29_i2s.1", + .codec_dai_name = "RT5512-aif1", + .platform_name = "rockchip-audio", + .codec_name = "rt5512.1-0018", + .init = rt5512_init, + .ops = &rk29_ops, + }, + { /* Sec_Fifo DAI i/f */ + .name = "RT5512 AIF2", + .stream_name = "Capture", + .cpu_dai_name = "rk29_i2s.1", + .codec_dai_name = "RT5512-aif1", + .platform_name = "rockchip-audio", + .codec_name = "rt5512.1-0018", + .init = rt5512_init, + .ops = &rk29_ops, + }, +}; + +static struct snd_soc_card snd_soc_card_rk29 = { + .name = "RK29_RT5512", + .dai_link = rk29_dai, + + /* If you want to use sec_fifo device, + * changes the num_link = 2 or ARRAY_SIZE(odroid_dai). */ + .num_links = ARRAY_SIZE(rk29_dai), +}; + +static int __init audio_card_init(void) +{ + int ret; + rk29_snd_device = platform_device_alloc("soc-audio", -1); + if (!rk29_snd_device) + return -ENOMEM; + + platform_set_drvdata(rk29_snd_device, &snd_soc_card_rk29); + + ret = platform_device_add(rk29_snd_device); + if (ret) + platform_device_put(rk29_snd_device); + + return ret; +} +module_init(audio_card_init); + +static void __exit audio_card_exit(void) +{ + platform_device_unregister(rk29_snd_device); +} +module_exit(audio_card_exit); + +MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface"); +MODULE_AUTHOR("cy_huang "); +MODULE_LICENSE("GPL"); -- 2.34.1