| /* |
| * sound/soc/codecs/tlv320adc3xxx_amb.c |
| * |
| * Author: Dongge wu <dgwu@ambarella.com> |
| * |
| * History: |
| * 2015/10/28 - [Dongge wu] Created file |
| * |
| * Copyright (C) 2014-2018, Ambarella, Inc. |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| |
| /***************************** INCLUDES ************************************/ |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/io.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include <linux/pm.h> |
| #include <linux/i2c.h> |
| #include <linux/platform_device.h> |
| #include <linux/cdev.h> |
| #include <linux/of_gpio.h> |
| #include <linux/slab.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 "tlv320adc3xxx_amb.h" |
| |
| /* |
| * **************************************************************************** |
| * Macros |
| * **************************************************************************** |
| * |
| */ |
| |
| |
| /* codec private data */ |
| struct adc3xxx_priv { |
| void *control_data; |
| unsigned int rst_pin; |
| unsigned int rst_active; |
| unsigned int sysclk; |
| int master; |
| u8 page_no; |
| }; |
| |
| static int adc3xxx_set_bias_level(struct snd_soc_codec *, |
| enum snd_soc_bias_level); |
| /* |
| * ADC3xxx register cache |
| * We can't read the ADC3xxx register space when we are |
| * using 2 wire for device control, so we cache them instead. |
| * There is no point in caching the reset register |
| */ |
| static const u8 adc3xxx_reg[ADC3xxx_CACHEREGNUM] = { |
| 0x00, 0x00, 0x00, 0x00, /* 0 */ |
| 0x00, 0x11, 0x04, 0x00, /* 4 */ |
| 0x00, 0x00, 0x00, 0x00, /* 8 */ |
| 0x00, 0x00, 0x00, 0x00, /* 12 */ |
| 0x00, 0x00, 0x01, 0x01, /* 16 */ |
| 0x80, 0x80, 0x04, 0x00, /* 20 */ |
| 0x00, 0x00, 0x01, 0x00, /* 24 */ |
| 0x00, 0x02, 0x01, 0x00, /* 28 */ |
| 0x00, 0x10, 0x00, 0x00, /* 32 */ |
| 0x00, 0x00, 0x02, 0x00, /* 36 */ |
| 0x00, 0x00, 0x00, 0x00, /* 40 */ |
| 0x00, 0x00, 0x00, 0x00, /* 44 */ |
| 0x00, 0x00, 0x00, 0x00, /* 48 */ |
| 0x00, 0x12, 0x00, 0x00, /* 52 */ |
| 0x00, 0x00, 0x00, 0x44, /* 56 */ |
| 0x00, 0x01, 0x00, 0x00, /* 60 */ |
| 0x00, 0x00, 0x00, 0x00, /* 64 */ |
| 0x00, 0x00, 0x00, 0x00, /* 68 */ |
| 0x00, 0x00, 0x00, 0x00, /* 72 */ |
| 0x00, 0x00, 0x00, 0x00, /* 76 */ |
| 0x00, 0x00, 0x88, 0x00, /* 80 */ |
| 0x00, 0x00, 0x00, 0x00, /* 84 */ |
| 0x7F, 0x00, 0x00, 0x00, /* 88 */ |
| 0x00, 0x00, 0x00, 0x00, /* 92 */ |
| 0x7F, 0x00, 0x00, 0x00, /* 96 */ |
| 0x00, 0x00, 0x00, 0x00, /* 100 */ |
| 0x00, 0x00, 0x00, 0x00, /* 104 */ |
| 0x00, 0x00, 0x00, 0x00, /* 108 */ |
| 0x00, 0x00, 0x00, 0x00, /* 112 */ |
| 0x00, 0x00, 0x00, 0x00, /* 116 */ |
| 0x00, 0x00, 0x00, 0x00, /* 120 */ |
| 0x00, 0x00, 0x00, 0x00, /* 124 - PAGE0 Registers(127) ends here */ |
| 0x00, 0x00, 0x00, 0x00, /* 128, PAGE1-0 */ |
| 0x00, 0x00, 0x00, 0x00, /* 132, PAGE1-4 */ |
| 0x00, 0x00, 0x00, 0x00, /* 136, PAGE1-8 */ |
| 0x00, 0x00, 0x00, 0x00, /* 140, PAGE1-12 */ |
| 0x00, 0x00, 0x00, 0x00, /* 144, PAGE1-16 */ |
| 0x00, 0x00, 0x00, 0x00, /* 148, PAGE1-20 */ |
| 0x00, 0x00, 0x00, 0x00, /* 152, PAGE1-24 */ |
| 0x00, 0x00, 0x00, 0x00, /* 156, PAGE1-28 */ |
| 0x00, 0x00, 0x00, 0x00, /* 160, PAGE1-32 */ |
| 0x00, 0x00, 0x00, 0x00, /* 164, PAGE1-36 */ |
| 0x00, 0x00, 0x00, 0x00, /* 168, PAGE1-40 */ |
| 0x00, 0x00, 0x00, 0x00, /* 172, PAGE1-44 */ |
| 0x00, 0x00, 0x00, 0x00, /* 176, PAGE1-48 */ |
| 0xFF, 0x00, 0x3F, 0xFF, /* 180, PAGE1-52 */ |
| 0x00, 0x3F, 0x00, 0x80, /* 184, PAGE1-56 */ |
| 0x80, 0x00, 0x00, 0x00, /* 188, PAGE1-60 */ |
| }; |
| |
| /* |
| * adc3xxx initialization data |
| * This structure initialization contains the initialization required for |
| * ADC3xxx. |
| * These registers values (reg_val) are written into the respective ADC3xxx |
| * register offset (reg_offset) to initialize ADC3xxx. |
| * These values are used in adc3xxx_init() function only. |
| */ |
| struct adc3xxx_configs { |
| u8 reg_offset; |
| u8 reg_val; |
| }; |
| |
| /* The global Register Initialization sequence Array. During the Audio Driver |
| * Initialization, this array will be utilized to perform the default |
| * initialization of the audio Driver. |
| */ |
| static const struct adc3xxx_configs adc3xxx_reg_init[] = { |
| /* Select IN1L/IN1R Single Ended (0dB) inputs |
| * use value 10 for no connection, this enables dapm |
| * to switch ON/OFF inputs using MS Bit only. |
| * with this setup, -6dB input option is not used. |
| */ |
| {LEFT_PGA_SEL_1, 0xA8}, |
| {LEFT_PGA_SEL_2, 0x37}, |
| {RIGHT_PGA_SEL_1, 0x28}, |
| /* mute Left PGA + default gain */ |
| {LEFT_APGA_CTRL, 0xc6}, |
| /* mute Right PGA + default gain */ |
| {RIGHT_APGA_CTRL, 0xc6}, |
| {LEFT_CHN_AGC_1, 0x80}, |
| {RIGHT_CHN_AGC_1, 0x80}, |
| /* MICBIAS1=2.5V, MICBIAS2=2.5V */ |
| {MICBIAS_CTRL, 0x50}, |
| /* Use PLL for generating clocks from MCLK */ |
| {CLKGEN_MUX, USE_PLL}, |
| /* I2S, 16LE, codec as Master */ |
| {INTERFACE_CTRL_1, 0x0C}, |
| /*BDIV_CLKIN = ADC_CLK, BCLK & WCLK active when power-down */ |
| {INTERFACE_CTRL_2, 0x02}, |
| |
| #ifdef ADC3101_CODEC_SUPPORT |
| /* Use Primary BCLK and WCLK */ |
| {INTERFACE_CTRL_4, 0x0}, |
| #endif |
| |
| /* Left AGC Maximum Gain to 40db */ |
| {LEFT_CHN_AGC_3, 0x50}, |
| /* Right AGC Maximum Gain to 40db */ |
| {RIGHT_CHN_AGC_3, 0X50}, |
| /* ADC control, Gain soft-stepping disabled */ |
| {ADC_DIGITAL, 0x02}, |
| /* Fine Gain 0dB, Left/Right ADC Unmute */ |
| {ADC_FGA, 0x00}, |
| |
| #ifdef ADC3101_CODEC_SUPPORT |
| /* DMCLK output = ADC_MOD_CLK */ |
| {GPIO2_CTRL, 0x28}, |
| /* DMDIN is in Dig_Mic_In mode */ |
| {GPIO1_CTRL, 0x04}, |
| #endif |
| }; |
| |
| /* |
| * PLL and Clock settings |
| */ |
| static const struct adc3xxx_rate_divs adc3xxx_divs[] = { |
| /* mclk, rate, p, r, j, d, nadc, madc, aosr, bdiv */ |
| /* 8k rate */ |
| {12288000, 8000, 1, 1, 7, 0000, 42, 2, 128, 8, 188}, |
| /* 11.025k rate */ |
| //{12000000, 11025, 1, 1, 6, 8208, 29, 2, 128, 8, 188}, |
| /* 16k rate */ |
| {12288000, 16000, 1, 1, 7, 0000, 21, 2, 128, 8, 188}, |
| /* 22.05k rate */ |
| //{12000000, 22050, 1, 1, 7, 560, 15, 2, 128, 8, 188}, |
| /* 32k rate */ |
| {12288000, 32000, 1, 1, 8, 0000, 12, 2, 128, 8, 188}, |
| /* 44.1k rate */ |
| //{12000000, 44100, 1, 1, 7, 5264, 8, 2, 128, 8, 188}, |
| /* 48k rate */ |
| {12288000, 48000, 1, 1, 7, 0000, 7, 2, 128, 8, 188}, |
| /* 88.2k rate */ |
| //{12000000, 88200, 1, 1, 7, 5264, 4, 4, 64, 8, 188}, |
| /* 96k rate */ |
| //{12000000, 96000, 1, 1, 8, 1920, 4, 4, 64, 8, 188}, |
| }; |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_get_divs |
| * Purpose : This function is to get required divisor from the "adc3xxx_divs" |
| * table. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static inline int adc3xxx_get_divs(int mclk, int rate) |
| { |
| int i; |
| |
| printk(KERN_INFO "adc3xxx_get_divs mclk = %d, rate = %d\n", mclk, rate); |
| for (i = 0; i < ARRAY_SIZE(adc3xxx_divs); i++) { |
| if ((adc3xxx_divs[i].rate == rate) |
| && (adc3xxx_divs[i].mclk == mclk)) { |
| return i; |
| } |
| } |
| |
| printk("Master clock and sample rate is not supported\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_change_page |
| * Purpose : This function is to switch between page 0 and page 1. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| int adc3xxx_change_page(struct snd_soc_codec *codec, u8 new_page) |
| { |
| struct adc3xxx_priv *adc3xxx = snd_soc_codec_get_drvdata(codec); |
| |
| if (i2c_smbus_write_byte_data(codec->control_data, 0x0, new_page) < 0) { |
| printk("adc3xxx_change_page: I2C Wrte Error\n"); |
| return -1; |
| } |
| adc3xxx->page_no = new_page; |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_write_reg_cache |
| * Purpose : This function is to write adc3xxx register cache |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static inline void adc3xxx_write_reg_cache(struct snd_soc_codec *codec, |
| unsigned int reg, unsigned int value) |
| { |
| u8 *cache = codec->reg_cache; |
| if (reg >= ADC3xxx_CACHEREGNUM) |
| return; |
| cache[reg] = value & 0xFF; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_read_reg_cache |
| * Purpose : This function is to read adc3xxx register cache |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static inline unsigned int adc3xxx_read_reg_cache(struct snd_soc_codec *codec, |
| unsigned int reg) |
| { |
| u8 *cache = codec->reg_cache; |
| if (reg >= ADC3xxx_CACHEREGNUM) |
| return -1; |
| return cache[reg]; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_write |
| * Purpose : This function is to write to the adc3xxx register space. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| int adc3xxx_write(struct snd_soc_codec *codec, unsigned int reg, |
| unsigned int value) |
| { |
| struct adc3xxx_priv *adc3xxx = snd_soc_codec_get_drvdata(codec); |
| u8 data[2]; |
| u8 page; |
| |
| page = reg / ADC3xxx_PAGE_SIZE; |
| |
| if (adc3xxx->page_no != page) { |
| adc3xxx_change_page(codec, page); |
| } |
| |
| /* data is |
| * D15..D8 adc3xxx register offset |
| * D7...D0 register data |
| */ |
| data[0] = reg % ADC3xxx_PAGE_SIZE; |
| data[1] = value & 0xFF; |
| |
| if (i2c_smbus_write_byte_data(codec->control_data, data[0], data[1]) < 0) { |
| printk("adc3xxx_write: I2C write Error\n"); |
| return -EIO; |
| } |
| |
| /* Update register cache */ |
| if ((page == 0) || (page == 1)) { |
| adc3xxx_write_reg_cache(codec, reg, data[1]); |
| } |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_read |
| * Purpose : This function is to read the adc3xxx register space. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_read(struct snd_soc_codec *codec, unsigned int reg) |
| { |
| struct adc3xxx_priv *adc3xxx = snd_soc_codec_get_drvdata(codec); |
| u8 value, page; |
| |
| page = reg / ADC3xxx_PAGE_SIZE; |
| if (adc3xxx->page_no != page) { |
| adc3xxx_change_page(codec, page); |
| } |
| |
| /* write register address */ |
| reg = reg % ADC3xxx_PAGE_SIZE; |
| |
| /* read register value */ |
| value = i2c_smbus_read_word_data(codec->control_data, reg); |
| if (value < 0) { |
| printk("adc3xxx_read: I2C read Error\n"); |
| return -EIO; |
| } |
| |
| /* Update register cache */ |
| if ((page == 0) || (page == 1)) { |
| adc3xxx_write_reg_cache(codec, reg, value); |
| } |
| return value; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : snd_soc_adc3xxx_put_volsw |
| * Purpose : Callback to set the value of a mixer control. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int snd_soc_adc3xxx_put_volsw(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| s8 val1, val2; |
| u8 reg; |
| |
| val1 = ucontrol->value.integer.value[0]; |
| val2 = ucontrol->value.integer.value[1]; |
| |
| if ((val1 >= ADC_POS_VOL)) { |
| if (val1 > ADC_MAX_VOLUME) |
| val1 = ADC_MAX_VOLUME; |
| val1 = val1 - ADC_POS_VOL; |
| } else if ((val1 >= 0) && (val1 <= 23)) { |
| val1 = ADC_POS_VOL - val1; |
| val1 = 128 - val1; |
| } else |
| return -EINVAL; |
| |
| if (val2 >= ADC_POS_VOL) { |
| if (val2 > ADC_MAX_VOLUME) |
| val2 = ADC_MAX_VOLUME; |
| val2 = val2 - ADC_POS_VOL; |
| } else if ((val2 >= 0) && (val2 <= 23)) { |
| val2 = ADC_POS_VOL - val2; |
| val2 = 128 - val2; |
| } else |
| return -EINVAL; |
| |
| reg = adc3xxx_read_reg_cache(codec, LADC_VOL) & (~0x7F); |
| adc3xxx_write(codec, LADC_VOL, reg | (val1 << 0)); |
| reg = adc3xxx_read_reg_cache(codec, RADC_VOL) & (~0x7F); |
| adc3xxx_write(codec, RADC_VOL, reg | (val2 << 0)); |
| |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : snd_soc_adc3xxx_get_volsw |
| * Purpose : Callback to get the value of a mixer control. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int snd_soc_adc3xxx_get_volsw(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| u8 val1; |
| u8 val2; |
| |
| val1 = adc3xxx_read_reg_cache(codec, LADC_VOL) & (0x7F); |
| if ((val1 >= 0) && (val1 <= 40)) { |
| val1 = val1 + ADC_POS_VOL; |
| } else if ((val1 >= 104) && (val1 <= 127)) { |
| val1 = val1 - 104; |
| } else |
| return -EINVAL; |
| |
| val2 = adc3xxx_read_reg_cache(codec, RADC_VOL) & (0x7F); |
| if ((val2 >= 0) && (val2 <= 40)) { |
| val2 = val2 + ADC_POS_VOL; |
| } else if ((val2 >= 104) && (val2 <= 127)) { |
| val2 = val2 - 104; |
| } else |
| return -EINVAL; |
| |
| ucontrol->value.integer.value[0] = val1; |
| ucontrol->value.integer.value[1] = val2; |
| return 0; |
| |
| } |
| |
| #define SOC_ADC3xxx_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ |
| { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ |
| .get = snd_soc_adc3xxx_get_volsw, .put = snd_soc_adc3xxx_put_volsw, \ |
| .private_value = (unsigned long)&(struct soc_mixer_control) \ |
| {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ |
| .max = xmax, .invert = xinvert} } |
| |
| static const char *micbias_voltage[] = { "off", "2V", "2.5V", "AVDD" }; |
| static const char *linein_attenuation[] = { "0db", "-6db" }; |
| static const char *adc_softstepping[] = { "1 step", "2 step", "off" }; |
| |
| #define MICBIAS1_ENUM 0 |
| #define MICBIAS2_ENUM 1 |
| #define ATTLINEL1_ENUM 2 |
| #define ATTLINEL2_ENUM 3 |
| #define ATTLINEL3_ENUM 4 |
| #define ATTLINER1_ENUM 5 |
| #define ATTLINER2_ENUM 6 |
| #define ATTLINER3_ENUM 7 |
| #define ADCSOFTSTEP_ENUM 8 |
| |
| /* Creates an array of the Single Ended Widgets*/ |
| static const struct soc_enum adc3xxx_enum[] = { |
| SOC_ENUM_SINGLE(MICBIAS_CTRL, 5, 4, micbias_voltage), |
| SOC_ENUM_SINGLE(MICBIAS_CTRL, 3, 4, micbias_voltage), |
| SOC_ENUM_SINGLE(LEFT_PGA_SEL_1, 0, 2, linein_attenuation), |
| SOC_ENUM_SINGLE(LEFT_PGA_SEL_1, 2, 2, linein_attenuation), |
| SOC_ENUM_SINGLE(LEFT_PGA_SEL_1, 4, 2, linein_attenuation), |
| SOC_ENUM_SINGLE(RIGHT_PGA_SEL_1, 0, 2, linein_attenuation), |
| SOC_ENUM_SINGLE(RIGHT_PGA_SEL_1, 2, 2, linein_attenuation), |
| SOC_ENUM_SINGLE(RIGHT_PGA_SEL_1, 4, 2, linein_attenuation), |
| SOC_ENUM_SINGLE(ADC_DIGITAL, 0, 3, adc_softstepping), |
| }; |
| |
| /* Various Controls For adc3xxx */ |
| static const struct snd_kcontrol_new adc3xxx_snd_controls[] = { |
| /* PGA Gain Volume Control */ |
| SOC_DOUBLE_R("PGA Gain Volume Control (0=0dB, 80=40dB)", |
| LEFT_APGA_CTRL, RIGHT_APGA_CTRL, 0, 0x50, 0), |
| /* Audio gain control (AGC) */ |
| SOC_DOUBLE_R("Audio Gain Control (AGC)", LEFT_CHN_AGC_1, |
| RIGHT_CHN_AGC_1, 7, 0x01, 0), |
| /* AGC Target level control */ |
| SOC_DOUBLE_R("AGC Target Level Control", LEFT_CHN_AGC_1, |
| RIGHT_CHN_AGC_1, 4, 0x07, 1), |
| /* AGC Maximum PGA applicable */ |
| SOC_DOUBLE_R("AGC Maximum PGA Control", LEFT_CHN_AGC_3, |
| RIGHT_CHN_AGC_3, 0, 0x50, 0), |
| /* AGC Attack Time control */ |
| SOC_DOUBLE_R("AGC Attack Time control", LEFT_CHN_AGC_4, |
| RIGHT_CHN_AGC_4, 3, 0x1F, 0), |
| /* AGC Decay Time control */ |
| SOC_DOUBLE_R("AGC Decay Time control", LEFT_CHN_AGC_5, |
| RIGHT_CHN_AGC_5, 3, 0x1F, 0), |
| /* AGC Noise Bounce control */ |
| SOC_DOUBLE_R("AGC Noise bounce control", LEFT_CHN_AGC_6, |
| RIGHT_CHN_AGC_6, 0, 0x1F, 0), |
| /* AGC Signal Bounce control */ |
| SOC_DOUBLE_R("AGC Signal bounce control", LEFT_CHN_AGC_7, |
| RIGHT_CHN_AGC_7, 0, 0x0F, 0), |
| /* Mic Bias voltage */ |
| SOC_ENUM("Mic Bias 1 Voltage", adc3xxx_enum[MICBIAS1_ENUM]), |
| SOC_ENUM("Mic Bias 2 Voltage", adc3xxx_enum[MICBIAS2_ENUM]), |
| /* ADC soft stepping */ |
| SOC_ENUM("ADC soft stepping", adc3xxx_enum[ADCSOFTSTEP_ENUM]), |
| /* Left/Right Input attenuation */ |
| SOC_ENUM("Left Linein1 input attenuation", |
| adc3xxx_enum[ATTLINEL1_ENUM]), |
| SOC_ENUM("Left Linein2 input attenuation", |
| adc3xxx_enum[ATTLINEL2_ENUM]), |
| SOC_ENUM("Left Linein3 input attenuation", |
| adc3xxx_enum[ATTLINEL3_ENUM]), |
| SOC_ENUM("Right Linein1 input attenuation", |
| adc3xxx_enum[ATTLINER1_ENUM]), |
| SOC_ENUM("Right Linein2 input attenuation", |
| adc3xxx_enum[ATTLINER2_ENUM]), |
| SOC_ENUM("Right Linein3 input attenuation", |
| adc3xxx_enum[ATTLINER3_ENUM]), |
| /* ADC Volume */ |
| SOC_ADC3xxx_DOUBLE_R("ADC Volume Control (0=-12dB, 64=+20dB)", LADC_VOL, |
| RADC_VOL, 0, 64,0), |
| /* ADC Fine Volume */ |
| SOC_SINGLE("Left ADC Fine Volume (0=-0.4dB, 4=0dB)", ADC_FGA, 4, 4, 1), |
| SOC_SINGLE("Right ADC Fine Volume (0=-0.4dB, 4=0dB)", ADC_FGA, 0, 4, 1), |
| }; |
| |
| /* Left input selection, Single Ended inputs and Differential inputs */ |
| static const struct snd_kcontrol_new left_input_mixer_controls[] = { |
| SOC_DAPM_SINGLE("IN1_L switch", LEFT_PGA_SEL_1, 1, 0x1, 1), |
| SOC_DAPM_SINGLE("IN2_L switch", LEFT_PGA_SEL_1, 3, 0x1, 1), |
| SOC_DAPM_SINGLE("IN3_L switch", LEFT_PGA_SEL_1, 5, 0x1, 1), |
| SOC_DAPM_SINGLE("DIF1_L switch", LEFT_PGA_SEL_1, 7, 0x1, 1), |
| SOC_DAPM_SINGLE("DIF2_L switch", LEFT_PGA_SEL_2, 5, 0x1, 1), |
| SOC_DAPM_SINGLE("DIF3_L switch", LEFT_PGA_SEL_2, 3, 0x1, 1), //DIF3_L |
| SOC_DAPM_SINGLE("IN1_R switch", LEFT_PGA_SEL_2, 1, 0x1, 1), |
| }; |
| |
| /* Right input selection, Single Ended inputs and Differential inputs */ |
| static const struct snd_kcontrol_new right_input_mixer_controls[] = { |
| SOC_DAPM_SINGLE("IN1_R switch", RIGHT_PGA_SEL_1, 1, 0x1, 1), |
| SOC_DAPM_SINGLE("IN2_R switch", RIGHT_PGA_SEL_1, 3, 0x1, 1), |
| SOC_DAPM_SINGLE("IN3_R switch", RIGHT_PGA_SEL_1, 5, 0x1, 1), |
| SOC_DAPM_SINGLE("DIF1_R switch", RIGHT_PGA_SEL_1, 7, 0x1, 1), // DIF1_R |
| SOC_DAPM_SINGLE("DIF2_R switch", RIGHT_PGA_SEL_2, 5, 0x1, 1), |
| SOC_DAPM_SINGLE("DIF3_R switch", RIGHT_PGA_SEL_2, 3, 0x1, 1), |
| SOC_DAPM_SINGLE("IN1_L switch", RIGHT_PGA_SEL_2, 1, 0x1, 1), |
| }; |
| |
| /* Left Digital Mic input for left ADC */ |
| static const struct snd_kcontrol_new left_input_dmic_controls[] = { |
| SOC_DAPM_SINGLE("Left ADC switch", ADC_DIGITAL, 3, 0x1, 0), |
| }; |
| |
| /* Right Digital Mic input for Right ADC */ |
| static const struct snd_kcontrol_new right_input_dmic_controls[] = { |
| SOC_DAPM_SINGLE("Right ADC switch", ADC_DIGITAL, 2, 0x1, 0), |
| }; |
| |
| /* adc3xxx Widget structure */ |
| static const struct snd_soc_dapm_widget adc3xxx_dapm_widgets[] = { |
| |
| /* Left Input Selection */ |
| SND_SOC_DAPM_MIXER("Left Input Selection", SND_SOC_NOPM, 0, 0, |
| &left_input_mixer_controls[0], |
| ARRAY_SIZE(left_input_mixer_controls)), |
| /* Right Input Selection */ |
| SND_SOC_DAPM_MIXER("Right Input Selection", SND_SOC_NOPM, 0, 0, |
| &right_input_mixer_controls[0], |
| ARRAY_SIZE(right_input_mixer_controls)), |
| /*PGA selection */ |
| SND_SOC_DAPM_PGA("Left PGA", LEFT_APGA_CTRL, 7, 1, NULL, 0), |
| SND_SOC_DAPM_PGA("Right PGA", RIGHT_APGA_CTRL, 7, 1, NULL, 0), |
| |
| /*Digital Microphone Input Control for Left/Right ADC */ |
| SND_SOC_DAPM_MIXER("Left DMic Input", SND_SOC_NOPM, 0, 0, |
| &left_input_dmic_controls[0], |
| ARRAY_SIZE(left_input_dmic_controls)), |
| SND_SOC_DAPM_MIXER("Right DMic Input", SND_SOC_NOPM , 0, 0, |
| &right_input_dmic_controls[0], |
| ARRAY_SIZE(right_input_dmic_controls)), |
| |
| /* Left/Right ADC */ |
| SND_SOC_DAPM_ADC("Left ADC", "Left Capture", ADC_DIGITAL, 7, 0), |
| SND_SOC_DAPM_ADC("Right ADC", "Right Capture", ADC_DIGITAL, 6, 0), |
| |
| /* Inputs */ |
| SND_SOC_DAPM_INPUT("IN1_L"), |
| SND_SOC_DAPM_INPUT("IN1_R"), |
| SND_SOC_DAPM_INPUT("IN2_L"), |
| SND_SOC_DAPM_INPUT("IN2_R"), |
| SND_SOC_DAPM_INPUT("IN3_L"), |
| SND_SOC_DAPM_INPUT("IN3_R"), |
| SND_SOC_DAPM_INPUT("DIF1_L"), |
| SND_SOC_DAPM_INPUT("DIF2_L"), |
| SND_SOC_DAPM_INPUT("DIF3_L"), |
| SND_SOC_DAPM_INPUT("DIF1_R"), |
| SND_SOC_DAPM_INPUT("DIF2_R"), |
| SND_SOC_DAPM_INPUT("DIF3_R"), |
| SND_SOC_DAPM_INPUT("DMic_L"), |
| SND_SOC_DAPM_INPUT("DMic_R"), |
| |
| }; |
| |
| /* DAPM Routing related array declaratiom */ |
| static const struct snd_soc_dapm_route intercon[] = { |
| /* Left input selection from switchs */ |
| {"Left Input Selection", "IN1_L switch", "IN1_L"}, |
| {"Left Input Selection", "IN2_L switch", "IN2_L"}, |
| {"Left Input Selection", "IN3_L switch", "IN3_L"}, |
| {"Left Input Selection", "DIF1_L switch", "DIF1_L"}, |
| {"Left Input Selection", "DIF2_L switch", "DIF2_L"}, |
| {"Left Input Selection", "DIF3_L switch", "DIF3_L"}, |
| {"Left Input Selection", "IN1_R switch", "IN1_R"}, |
| |
| /* Left input selection to left PGA */ |
| {"Left PGA", NULL, "Left Input Selection"}, |
| |
| /* Left PGA to left ADC */ |
| {"Left ADC", NULL, "Left PGA"}, |
| |
| /* Right input selection from switchs */ |
| {"Right Input Selection", "IN1_R switch", "IN1_R"}, |
| {"Right Input Selection", "IN2_R switch", "IN2_R"}, |
| {"Right Input Selection", "IN3_R switch", "IN3_R"}, |
| {"Right Input Selection", "DIF1_R switch", "DIF1_R"}, |
| {"Right Input Selection", "DIF2_R switch", "DIF2_R"}, |
| {"Right Input Selection", "DIF3_R switch", "DIF3_R"}, |
| {"Right Input Selection", "IN1_L switch", "IN1_L"}, |
| |
| /* Right input selection to right PGA */ |
| {"Right PGA", NULL, "Right Input Selection"}, |
| |
| /* Right PGA to right ADC */ |
| {"Right ADC", NULL, "Right PGA"}, |
| |
| /* Left DMic Input selection from switch */ |
| {"Left DMic Input", "Left ADC switch", "DMic_L"}, |
| |
| /* Left DMic to left ADC */ |
| {"Left ADC", NULL, "Left DMic Input"}, |
| |
| /* Right DMic Input selection from switch */ |
| {"Right DMic Input", "Right ADC switch", "DMic_R"}, |
| |
| /* Right DMic to right ADC */ |
| {"Right ADC", NULL, "Right DMic Input"}, |
| }; |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_hw_params |
| * Purpose : This function is to set the hardware parameters for adc3xxx. |
| * The functions set the sample rate and audio serial data word |
| * length. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_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; |
| struct adc3xxx_priv *adc3xxx = snd_soc_codec_get_drvdata(codec); |
| int i, width = 16; |
| u8 data, bdiv; |
| |
| i = adc3xxx_get_divs(adc3xxx->sysclk, params_rate(params)); |
| |
| if (i < 0) { |
| printk("Clock configuration is not supported\n"); |
| return i; |
| } |
| |
| /* select data word length */ |
| data = |
| adc3xxx_read_reg_cache(codec, INTERFACE_CTRL_1) & (~WLENGTH_MASK); |
| switch (params_format(params)) { |
| case SNDRV_PCM_FORMAT_S16_LE: |
| width = 16; |
| break; |
| case SNDRV_PCM_FORMAT_S20_3LE: |
| data |= (0x01 << 4); |
| width = 20; |
| break; |
| case SNDRV_PCM_FORMAT_S24_LE: |
| data |= (0x02 << 4); |
| width = 24; |
| break; |
| case SNDRV_PCM_FORMAT_S32_LE: |
| data |= (0x03 << 4); |
| width = 32; |
| break; |
| } |
| adc3xxx_write(codec, INTERFACE_CTRL_1, data); |
| |
| /* BCLK is derived from ADC_CLK */ |
| if (width == 16) { |
| bdiv = adc3xxx_divs[i].bdiv_n; |
| } else { |
| bdiv = |
| (adc3xxx_divs[i].aosr * adc3xxx_divs[i].madc) / (2 * width); |
| } |
| |
| /* P & R values */ |
| adc3xxx_write(codec, PLL_PROG_PR, |
| (adc3xxx_divs[i].pll_p << PLLP_SHIFT) | (adc3xxx_divs[i]. |
| pll_r << |
| PLLR_SHIFT)); |
| /* J value */ |
| adc3xxx_write(codec, PLL_PROG_J, adc3xxx_divs[i].pll_j & PLLJ_MASK); |
| /* D value */ |
| adc3xxx_write(codec, PLL_PROG_D_LSB, |
| adc3xxx_divs[i].pll_d & PLLD_LSB_MASK); |
| adc3xxx_write(codec, PLL_PROG_D_MSB, |
| (adc3xxx_divs[i].pll_d >> 8) & PLLD_MSB_MASK); |
| /* NADC */ |
| adc3xxx_write(codec, ADC_NADC, adc3xxx_divs[i].nadc & NADC_MASK); |
| /* MADC */ |
| adc3xxx_write(codec, ADC_MADC, adc3xxx_divs[i].madc & MADC_MASK); |
| /* AOSR */ |
| adc3xxx_write(codec, ADC_AOSR, adc3xxx_divs[i].aosr & AOSR_MASK); |
| /* BDIV N Value */ |
| adc3xxx_write(codec, BCLK_N_DIV, bdiv & BDIV_MASK); |
| msleep(10); |
| |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_set_dai_sysclk |
| * Purpose : This function is to set the DAI system clock |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_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 adc3xxx_priv *adc3xxx = snd_soc_codec_get_drvdata(codec); |
| |
| adc3xxx->sysclk = freq; |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_set_dai_fmt |
| * Purpose : This function is to set the DAI format |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) |
| { |
| struct snd_soc_codec *codec = codec_dai->codec; |
| struct adc3xxx_priv *adc3xxx = snd_soc_codec_get_drvdata(codec); |
| u8 iface_reg; |
| |
| iface_reg = |
| adc3xxx_read_reg_cache(codec, INTERFACE_CTRL_1) & (~FMT_MASK); |
| |
| /* set master/slave audio interface */ |
| switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
| case SND_SOC_DAIFMT_CBM_CFM: |
| adc3xxx->master = 1; |
| iface_reg |= BCLK_MASTER | WCLK_MASTER; |
| break; |
| case SND_SOC_DAIFMT_CBS_CFS: |
| adc3xxx->master = 0; |
| iface_reg &= ~ (BCLK_MASTER | WCLK_MASTER ); //new repladec | with & |
| break; |
| case SND_SOC_DAIFMT_CBS_CFM: //new case..just for debugging |
| printk("%s: SND_SOC_DAIFMT_CBS_CFM\n", __FUNCTION__); |
| adc3xxx->master = 0; |
| /* BCLK by codec ie BCLK output */ |
| iface_reg |= (BCLK_MASTER); |
| iface_reg &= ~(WCLK_MASTER); |
| |
| break; |
| default: |
| printk("Invalid DAI master/slave interface\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * match both interface format and signal polarities since they |
| * are fixed |
| */ |
| switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK)) { |
| case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): |
| break; |
| case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF): |
| iface_reg |= (0x01 << 6); |
| break; |
| case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF): |
| iface_reg |= (0x01 << 6); |
| break; |
| case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF): |
| iface_reg |= (0x02 << 6); |
| break; |
| case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): |
| iface_reg |= (0x03 << 6); |
| break; |
| default: |
| printk("Invalid DAI format\n"); |
| return -EINVAL; |
| } |
| |
| /* set iface */ |
| adc3xxx_write(codec, INTERFACE_CTRL_1, iface_reg); |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_set_bias_level |
| * Purpose : This function is to get triggered when dapm events occurs. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_set_bias_level(struct snd_soc_codec *codec, |
| enum snd_soc_bias_level level) |
| { |
| struct adc3xxx_priv *adc3xxx = snd_soc_codec_get_drvdata(codec); |
| u8 reg; |
| |
| /* Check if the New Bias level is equal to the existing one, if so return */ |
| if (codec->dapm.bias_level == level) |
| return 0; |
| |
| /* all power is driven by DAPM system */ |
| switch (level) { |
| case SND_SOC_BIAS_ON: |
| if (adc3xxx->master) { |
| /* Enable pll */ |
| reg = adc3xxx_read_reg_cache(codec, PLL_PROG_PR); |
| adc3xxx_write(codec, PLL_PROG_PR, reg | ENABLE_PLL); |
| |
| /* 10msec delay needed after PLL power-up */ |
| mdelay(10); |
| |
| /* Switch on NADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_NADC); |
| adc3xxx_write(codec, ADC_NADC, reg | ENABLE_NADC); |
| |
| /* Switch on MADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_MADC); |
| adc3xxx_write(codec, ADC_MADC, reg | ENABLE_MADC); |
| |
| /* Switch on BCLK_N Divider */ |
| reg = adc3xxx_read_reg_cache(codec, BCLK_N_DIV); |
| adc3xxx_write(codec, BCLK_N_DIV, reg | ENABLE_BCLK); |
| } |
| else{ //new |
| /* Enable pll */ |
| reg = adc3xxx_read_reg_cache(codec, PLL_PROG_PR); |
| adc3xxx_write(codec, PLL_PROG_PR, reg | ENABLE_PLL); |
| |
| /* 10msec delay needed after PLL power-up */ |
| mdelay(10); |
| |
| /*Switch on NADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_NADC); |
| adc3xxx_write(codec, ADC_NADC, reg | ENABLE_NADC); |
| |
| /* Switch on MADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_MADC); |
| adc3xxx_write(codec, ADC_MADC, reg | ENABLE_MADC); |
| |
| /* Switch on BCLK_N Divider */ |
| reg = adc3xxx_read_reg_cache(codec, BCLK_N_DIV); |
| adc3xxx_write(codec, BCLK_N_DIV, reg | ~ENABLE_BCLK); |
| |
| } |
| msleep(350); |
| break; |
| |
| /* partial On */ |
| case SND_SOC_BIAS_PREPARE: |
| break; |
| |
| /* Off, with power */ |
| case SND_SOC_BIAS_STANDBY: |
| if (adc3xxx->master) { |
| /* switch off pll */ |
| reg = adc3xxx_read_reg_cache(codec, PLL_PROG_PR); |
| adc3xxx_write(codec, PLL_PROG_PR, reg & (~ENABLE_PLL)); |
| msleep(10); |
| |
| /* Switch off NADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_NADC); |
| adc3xxx_write(codec, ADC_NADC, reg & (~ENABLE_NADC)); |
| |
| /* Switch off MADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_MADC); |
| adc3xxx_write(codec, ADC_MADC, reg & (~ENABLE_MADC)); |
| |
| /* Switch off BCLK_N Divider */ |
| reg = adc3xxx_read_reg_cache(codec, BCLK_N_DIV); |
| adc3xxx_write(codec, BCLK_N_DIV, reg & (~ENABLE_BCLK)); |
| msleep(100); |
| } |
| break; |
| |
| /* Off without power */ |
| case SND_SOC_BIAS_OFF: |
| |
| /* power off Left/Right ADC channels */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_DIGITAL); |
| adc3xxx_write(codec, ADC_DIGITAL, |
| reg & ~(LADC_PWR_ON | RADC_PWR_ON)); |
| |
| /* Turn off PLLs */ |
| if (adc3xxx->master) { |
| /* switch off pll */ |
| reg = adc3xxx_read_reg_cache(codec, PLL_PROG_PR); |
| adc3xxx_write(codec, PLL_PROG_PR, reg & (~ENABLE_PLL)); |
| |
| /* Switch off NADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_NADC); |
| adc3xxx_write(codec, ADC_NADC, reg & (~ENABLE_NADC)); |
| |
| /* Switch off MADC Divider */ |
| reg = adc3xxx_read_reg_cache(codec, ADC_MADC); |
| adc3xxx_write(codec, ADC_MADC, reg & (~ENABLE_MADC)); |
| |
| /* Switch off BCLK_N Divider */ |
| reg = adc3xxx_read_reg_cache(codec, BCLK_N_DIV); |
| adc3xxx_write(codec, BCLK_N_DIV, reg & (~ENABLE_BCLK)); |
| } |
| break; |
| } |
| codec->dapm.bias_level = level; |
| |
| return 0; |
| } |
| |
| static struct snd_soc_dai_ops adc3xxx_dai_ops = { |
| .hw_params = adc3xxx_hw_params, |
| .set_sysclk = adc3xxx_set_dai_sysclk, |
| .set_fmt = adc3xxx_set_dai_fmt, |
| }; |
| |
| struct snd_soc_dai_driver adc3xxx_dai = { |
| .name = "tlv320adc3xxx-hifi", |
| .capture = { |
| .stream_name = "Capture", |
| .channels_min = 1, |
| .channels_max = 2, |
| .rates = ADC3xxx_RATES, |
| .formats = ADC3xxx_FORMATS, |
| }, |
| .ops = &adc3xxx_dai_ops, |
| }; |
| |
| EXPORT_SYMBOL_GPL(adc3xxx_dai); |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : tlv320adc3xxx_init |
| * Purpose : This function is to initialise the adc3xxx driver |
| * register the mixer and dsp interfaces with the kernel. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_init(struct snd_soc_codec *codec) |
| { |
| int i, ret = 0; |
| |
| codec->name = "tlv320adc3xxx"; |
| codec->num_dai = 1; |
| codec->reg_cache = |
| kmemdup(adc3xxx_reg, sizeof(adc3xxx_reg), GFP_KERNEL); |
| if (codec->reg_cache == NULL) |
| return -ENOMEM; |
| |
| /* Select Page 0 */ |
| adc3xxx_write(codec, PAGE_SELECT, 0); |
| /* Issue software reset to adc3xxx */ |
| adc3xxx_write(codec, RESET, SOFT_RESET); |
| |
| adc3xxx_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
| |
| for (i = 0; |
| i < sizeof(adc3xxx_reg_init) / sizeof(struct adc3xxx_configs); |
| i++) { |
| adc3xxx_write(codec, adc3xxx_reg_init[i].reg_offset, |
| adc3xxx_reg_init[i].reg_val); |
| } |
| |
| //adc3xxx_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
| |
| return ret; |
| |
| kfree(codec->reg_cache); |
| return ret; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_probe |
| * Purpose : This is first driver function called by the SoC core driver. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_probe(struct snd_soc_codec *codec) |
| { |
| struct adc3xxx_priv *adc3xxx = NULL; |
| int ret = 0; |
| |
| printk(KERN_INFO "ADC3xxx Audio Codec %s provided by Amba Dongge\n", ADC3xxx_VERSION); |
| #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) |
| codec->write = adc3xxx_write; |
| codec->read = adc3xxx_read; |
| #endif |
| |
| adc3xxx = (struct adc3xxx_priv *)snd_soc_codec_get_drvdata(codec); |
| codec->control_data = adc3xxx->control_data; |
| mutex_init(&codec->mutex); |
| |
| ret = devm_gpio_request(codec->dev, adc3xxx->rst_pin, "adc3xxx reset"); |
| if (ret < 0){ |
| dev_err(codec->dev, "Failed to request rst_pin: %d\n", ret); |
| return ret; |
| } |
| |
| /* Reset adc3xxx codec */ |
| gpio_direction_output(adc3xxx->rst_pin, adc3xxx->rst_active); |
| msleep(2); |
| gpio_direction_output(adc3xxx->rst_pin, !adc3xxx->rst_active); |
| |
| ret = adc3xxx_init(codec); |
| |
| if (ret < 0) { |
| printk(KERN_ERR "adc3xxx: failed to initialise ADC3xxx\n"); |
| devm_gpio_free(codec->dev, adc3xxx->rst_pin); |
| } |
| |
| return ret; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_remove |
| * Purpose : to remove adc3xxx soc device |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_remove(struct snd_soc_codec *codec) |
| { |
| adc3xxx_set_bias_level(codec, SND_SOC_BIAS_OFF); |
| |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_suspend |
| * Purpose : This function is to suspend the adc3xxx driver. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_suspend(struct snd_soc_codec *codec) |
| { |
| adc3xxx_set_bias_level(codec, SND_SOC_BIAS_OFF); |
| |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_resume |
| * Purpose : This function is to resume the ADC3xxx driver |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_resume(struct snd_soc_codec *codec) |
| { |
| snd_soc_cache_sync(codec); |
| adc3xxx_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
| |
| return 0; |
| } |
| |
| struct snd_soc_codec_driver soc_codec_dev_adc3xxx = { |
| .probe = adc3xxx_probe, |
| .remove = adc3xxx_remove, |
| .suspend = adc3xxx_suspend, |
| .resume = adc3xxx_resume, |
| .set_bias_level = adc3xxx_set_bias_level, |
| .reg_cache_default = adc3xxx_reg, |
| .reg_cache_size = ARRAY_SIZE(adc3xxx_reg), |
| .reg_word_size = sizeof(u8), |
| .reg_cache_step = 1, |
| |
| .controls = adc3xxx_snd_controls, |
| .num_controls = ARRAY_SIZE(adc3xxx_snd_controls), |
| .dapm_widgets = adc3xxx_dapm_widgets, |
| .num_dapm_widgets = ARRAY_SIZE(adc3xxx_dapm_widgets), |
| .dapm_routes = intercon, |
| .num_dapm_routes = ARRAY_SIZE(intercon), |
| }; |
| EXPORT_SYMBOL_GPL(soc_codec_dev_adc3xxx); |
| |
| |
| #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_i2c_probe |
| * Purpose : This function attaches the i2c client and initializes |
| * adc3xxx CODEC. |
| * NOTE: |
| * This function is called from i2c core when the I2C address is |
| * valid. |
| * If the i2c layer weren't so broken, we could pass this kind of |
| * data around |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int adc3xxx_i2c_probe(struct i2c_client *i2c, |
| const struct i2c_device_id *id) |
| { |
| struct device_node *np = i2c->dev.of_node; |
| struct adc3xxx_priv *adc3xxx = NULL; |
| enum of_gpio_flags flags; |
| int rst_pin; |
| int ret; |
| |
| adc3xxx = kzalloc(sizeof(struct adc3xxx_priv), GFP_KERNEL); |
| if (adc3xxx == NULL) { |
| return -ENOMEM; |
| } |
| |
| rst_pin = of_get_gpio_flags(np, 0, &flags); |
| if (rst_pin < 0 || !gpio_is_valid(rst_pin)) |
| return -ENXIO; |
| |
| adc3xxx->rst_pin = rst_pin; |
| adc3xxx->rst_active = !!(flags & OF_GPIO_ACTIVE_LOW); |
| adc3xxx->control_data = i2c; |
| |
| i2c_set_clientdata(i2c, adc3xxx); |
| ret = snd_soc_register_codec(&i2c->dev, |
| &soc_codec_dev_adc3xxx, &adc3xxx_dai, 1); |
| if (ret < 0){ |
| kfree(adc3xxx); |
| printk("\t[adc3xxx Error!] %s(%d)\n",__FUNCTION__,__LINE__); |
| } |
| |
| return ret; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : adc3xxx_i2c_remove |
| * Purpose : This function removes the i2c client and uninitializes |
| * adc3xxx CODEC. |
| * NOTE: |
| * This function is called from i2c core |
| * If the i2c layer weren't so broken, we could pass this kind of |
| * data around |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int __exit adc3xxx_i2c_remove(struct i2c_client *client) |
| { |
| snd_soc_unregister_codec(&client->dev); |
| kfree(i2c_get_clientdata(client)); |
| return 0; |
| } |
| |
| static struct of_device_id tlv320adc3xxx_of_match[] = { |
| { .compatible = "ambarella,tlv320adc3xxx",}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, tlv320adc3xxx_of_match); |
| |
| |
| static const struct i2c_device_id adc3xxx_i2c_id[] = { |
| {"tlv320adc3xxx", 0}, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, adc3xxx_i2c_id); |
| |
| /* machine i2c codec control layer */ |
| static struct i2c_driver adc3xxx_i2c_driver = { |
| .driver = { |
| .name = "tlv320adc3xxx-codec", |
| .owner = THIS_MODULE, |
| .of_match_table = tlv320adc3xxx_of_match, |
| }, |
| .probe = adc3xxx_i2c_probe, |
| .remove = adc3xxx_i2c_remove, |
| .id_table = adc3xxx_i2c_id, |
| }; |
| #endif |
| |
| static int __init tlv320adc3xxx_init(void) |
| { |
| return i2c_add_driver(&adc3xxx_i2c_driver); |
| } |
| |
| static void __exit tlv320adc3xxx_exit(void) |
| { |
| i2c_del_driver(&adc3xxx_i2c_driver); |
| } |
| |
| module_init(tlv320adc3xxx_init); |
| module_exit(tlv320adc3xxx_exit); |
| |
| MODULE_DESCRIPTION("ASoC TLV320ADC3xxx codec driver"); |
| MODULE_AUTHOR("dgwu@ambarella.com "); |
| MODULE_LICENSE("GPL"); |