diff options
author | Vinod G <vinodg@nvidia.com> | 2011-11-29 17:32:11 -0800 |
---|---|---|
committer | Varun Wadekar <vwadekar@nvidia.com> | 2011-12-08 17:04:07 +0530 |
commit | 5fac479d8dddb8b9bfada756379357726dc57ed0 (patch) | |
tree | a44fde297338237951eff0cbb23aa0cdce8497ef /sound | |
parent | 8d3d9f8fbde05efd16b159c3d059d643e74746fd (diff) |
kernel: sound: Adding TI codec support
Adding the code for supporting TI AIC3262 codec.
bug 816608
Change-Id: I19c3e03e8fd442c0c19a72a7efeddcd0ca05a7e1
Reviewed-on: http://git-master/r/67279
Reviewed-by: Vinod Gopalakrishnakurup <vinodg@nvidia.com>
Tested-by: Vinod Gopalakrishnakurup <vinodg@nvidia.com>
Reviewed-by: Scott Peterson <speterson@nvidia.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/Kconfig | 4 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 3 | ||||
-rwxr-xr-x | sound/soc/codecs/tlv320aic326x.c | 3896 | ||||
-rwxr-xr-x | sound/soc/codecs/tlv320aic326x.h | 629 | ||||
-rwxr-xr-x | sound/soc/codecs/tlv320aic326x_mini-dsp.c | 1587 | ||||
-rwxr-xr-x | sound/soc/codecs/tlv320aic326x_minidsp_config.c | 410 |
6 files changed, 6529 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 665d9240c4ae..f14d61e2834f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -48,6 +48,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TVL320AIC32X4 if I2C + select SND_SOC_TLV320AIC326X if I2C select SND_SOC_TLV320AIC3X if I2C select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C @@ -246,6 +247,9 @@ config SND_SOC_TVL320AIC32X4 config SND_SOC_TLV320AIC3X tristate +config SND_SOC_TLV320AIC326X + tristate "TI AIC326x Codec" + config SND_SOC_TLV320DAC33 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 5119a7e2c1a8..e866eee843b5 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -35,6 +35,8 @@ snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o +snd-soc-tlv320aic326x-objs := tlv320aic326x.o tlv320aic326x_minidsp_config.o +snd-soc-tlv320aic326x-objs += tlv320aic326x_mini-dsp.o snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-twl4030-objs := twl4030.o @@ -133,6 +135,7 @@ obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_TVL320AIC32X4) += snd-soc-tlv320aic32x4.o +obj-$(CONFIG_SND_SOC_TLV320AIC326X) += snd-soc-tlv320aic326x.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o diff --git a/sound/soc/codecs/tlv320aic326x.c b/sound/soc/codecs/tlv320aic326x.c new file mode 100755 index 000000000000..40b6093737e4 --- /dev/null +++ b/sound/soc/codecs/tlv320aic326x.c @@ -0,0 +1,3896 @@ +/* +* linux/sound/soc/codecs/tlv320aic3262.c +* +* Copyright (C) 2011 Mistral Solutions Pvt Ltd. +* +* Based on sound/soc/codecs/tlv320aic3262.c +* +* This package is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR +* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. +* +* The TLV320AIC3262 is a flexible, low-power, low-voltage stereo audio +* codec with digital microphone inputs and programmable outputs. +* +* History: +* +* Rev 0.1 ASoC driver support Mistral 20-01-2011 +* +* The AIC325x ASoC driver is ported for the codec AIC3262. +* Rev 0.2 ASoC driver support Mistral 21-03-2011 +* The AIC326x ASoC driver is updated abe changes. +* +* Rev 0.3 ASoC driver support Mistral 12.09.2011 +* fixed the compilation issues for Whistler support +* +* Rev 0.4 ASoC driver support Mistral 27.09.2011 +* The AIC326x driver ported for Nvidia cardhu. +* +* Rev 0.5 Modified to support Multiple ASI Ports 08-Nov-2011 +* Driver updated to support ASI Ports of AIC3262 +* +* Modified by Nvidia 23-Nov-2011 for K39 ASoC changes. +*/ + +/* + ***************************************************************************** + * INCLUDES + ***************************************************************************** + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <asm/div64.h> +#include <sound/tlv320aic326x.h> +#include <sound/jack.h> + +#include "tlv320aic326x.h" + +/* + ***************************************************************************** + * Global Variable + ***************************************************************************** + */ +static u8 aic3262_reg_ctl; + +u8 dac_reg = 0, adc_gain = 0, hpl = 0, hpr = 0; +u8 rec_amp = 0, rampr = 0, spk_amp = 0; +/* whenever aplay/arecord is run, aic3262_hw_params() function gets called. + * This function reprograms the clock dividers etc. this flag can be used to + * disable this when the clock dividers are programmed by pps config file + */ +static int soc_static_freq_config = 1; + +/* + ***************************************************************************** + * Macros + ***************************************************************************** + */ + +/* ASoC Widget Control definition for a single Register based Control */ +#define SOC_SINGLE_AIC3262(xname) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = __new_control_info, .get = __new_control_get,\ + .put = __new_control_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ +} +#define SOC_SINGLE_N(xname, xreg, xshift, xmax, xinvert) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = n_control_info, .get = n_control_get,\ + .put = n_control_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .private_value = ((unsigned long)&(struct soc_mixer_control)) \ + {.reg = xreg, .shift = xshift, .rshift = xshift, .max = xmax, \ + .invert = xinvert} } + +/* ASoC Widget Control definition for a Double Register based Control */ + +#define SOC_DOUBLE_R_N(xname, reg_left, reg_right, xshift, xmax, xinvert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = snd_soc_info_volsw_2r_n, \ + .get = snd_soc_get_volsw_2r_n, .put = snd_soc_put_volsw_2r_n, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ + .max = xmax, .invert = xinvert} } + +#define SND_SOC_DAPM_SWITCH_N(wname, wreg, wshift, winvert) \ +{ .id = snd_soc_dapm_switch, .name = wname, .reg = wreg, .shift = wshift,\ + .invert = winvert, .kcontrols = NULL, .num_kcontrols = 0} +/* + ***************************************************************************** + * Function Prototype + ***************************************************************************** + */ +static int aic3262_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); + +static int aic3262_mute(struct snd_soc_dai *dai, int mute); + +static int aic3262_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir); + +static int aic3262_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt); + +static int aic3262_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level); + +static int __new_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); + +static int __new_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static int __new_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static inline int aic3262_get_divs(int mclk, int rate); + +static int aic3262_multi_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); + +static int aic3262_multi_i2s_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir); +static int aic3262_multi_i2s_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, int source, unsigned int freq_in, + unsigned int freq_out); + +static int aic3262_multi_i2s_asi1_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt); + +static int aic3262_multi_i2s_asi2_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt); + +static int aic3262_multi_i2s_asi3_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt); + +static int aic3262_multi_i2s_asi1_mute(struct snd_soc_dai *dai, int mute); + +static int aic3262_multi_i2s_asi2_mute(struct snd_soc_dai *dai, int mute); + +static int aic3262_multi_i2s_asi3_mute(struct snd_soc_dai *dai, int mute); + +static const char *wclk1_pincontrol[] = { + "ASI1 Word Clock Input/Output", "CLKOUT output"}; +static const char *dout1_pincontrol[] = { + "disabled", "ASI1 data output", "gpio", "clock out", + "INT1", "INT2", "SAR ADC interrupt"}; + +static const char *din1_pincontrol[] = {"disabled", "enabled"}; + +static const char *wclk2_pincontrol[] = { + "diabled", "ASI1 secondary wclk", "general purpose input", + "general purpose output", "clkout", "INT1 interrupt", + "IN2 interrupt", "output digital microphone", + "SAR ADC interrupt", "data output for ASI1"}; + +static const char *bclk2_pincontrol[] = { + "diabled", "ASI1 secondary wclk", "general purpose input", + "general purpose output", "clkout", "INT1 interrupt", + "IN2 interrupt", "output digital microphone", + "SAR ADC interrupt", "data output for ASI1"}; + +static const char *dout2_pincontrol[] = { + "disabled", "ASI2 Data Output", "General Purpose Output", + "INT1 Interrupt", "INT2 Interrupt", "SAR ADC interrupt", + "Output for digital microphone", "Data Output for ASI1"}; + +static const char *din2_pincontrol[] = {"disabled", "enabled"}; + +static const char *wclk3_pincontrol[] = { + "Disabled", "ASI3 WCLK", "General Purpose Input", + "General Purpose output", "Data Output for ASI1"}; + +static const char *bclk3_pincontrol[] = { + "Disabled", "ASI3 BCLK", "General Purpose Input", + "General Purpose output", "Data Output for ASI1"}; + +static const char *dout3_pincontrol[] = { + "disabled", "ASI3 data ooutput", "General Purpose Output", + "ASI1 Word Clock Output", "Data Output for ASI1"}; + +static const char *din3_pincontrol[] = {"disabled", "enabled"}; + +static const char *clkin[] = { + "mclk1", "bclk1", "gpio1", "pll_clk", "bclk2", "gpi1", + "hf_ref_clk", "hf_osc_clk", "mclk2", "gpio2", "gpi2"}; + +/* List of SOC_ENUM structures for the AIC3262 PIN Control Amixer Controls */ +static const struct soc_enum aic326x_enum[] = { + SOC_ENUM_SINGLE(WCLK1_PIN_CNTL_REG, 2, 2, wclk1_pincontrol), + SOC_ENUM_SINGLE(DOUT1_PIN_CNTL_REG, 1, 7, dout1_pincontrol), + SOC_ENUM_SINGLE(DIN1_PIN_CNTL_REG, 5, 2, din1_pincontrol), + SOC_ENUM_SINGLE(WCLK2_PIN_CNTL_REG, 2, 10, wclk2_pincontrol), + SOC_ENUM_SINGLE(BCLK2_PIN_CNTL_REG, 2, 10, bclk2_pincontrol), + SOC_ENUM_SINGLE(DOUT2_PIN_CNTL_REG, 1, 8, dout2_pincontrol), + SOC_ENUM_SINGLE(DIN2_PIN_CNTL_REG, 5, 2, din2_pincontrol), + SOC_ENUM_SINGLE(WCLK3_PIN_CNTL_REG, 2, 5, wclk3_pincontrol), + SOC_ENUM_SINGLE(BCLK3_PIN_CNTL_REG, 2, 5, bclk3_pincontrol), + SOC_ENUM_SINGLE(DOUT3_PIN_CNTL_REG, 1, 5, dout3_pincontrol), + SOC_ENUM_SINGLE(DIN3_PIN_CNTL_REG, 5, 2, din3_pincontrol), + SOC_ENUM_DOUBLE(DAC_ADC_CLKIN_REG, 0, 4, 11, clkin), +}; + +#ifdef DAC_INDEPENDENT_VOL +/* + *---------------------------------------------------------------------------- + * Function : n_control_info + * Purpose : This function is to initialize data for new control required to + * program the AIC3262 registers. + * + *---------------------------------------------------------------------------- + */ +static int n_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = shift == rshift ? 1 : 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max; + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : n_control_get + * Purpose : This function is to read data of new control for + * program the AIC3262 registers. + * + *---------------------------------------------------------------------------- + */ +static int n_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u32 val; + unsigned short mask, shift; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + if (!strcmp(kcontrol->id.name, "Left DAC Volume")) { + mask = AIC3262_8BITS_MASK; + shift = 0; + val = aic3262_read(codec, mc->reg); + ucontrol->value.integer.value[0] = + (val <= 48) ? (val + 127) : (val - 129); + } + if (!strcmp(kcontrol->id.name, "Right DAC Volume")) { + mask = AIC3262_8BITS_MASK; + shift = 0; + val = aic3262_read(codec, mc->reg); + ucontrol->value.integer.value[0] = + (val <= 48) ? (val + 127) : (val - 129); + } + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : __new_control_put + * Purpose : new_control_put is called to pass data from user/application to + * the driver. + * + *---------------------------------------------------------------------------- + */ +static int n_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u8 val, val_mask; + int reg, err; + unsigned int invert = mc->invert; + int max = mc->max; + DBG("n_control_put\n"); + reg = mc->reg; + val = ucontrol->value.integer.value[0]; + if (invert) + val = max - val; + if (!strcmp(kcontrol->id.name, "Left DAC Volume")) { + DBG("LDAC\n"); + val = (val >= 127) ? (val - 127) : (val + 129); + val_mask = AIC3262_8BITS_MASK; + } + if (!strcmp(kcontrol->id.name, "Right DAC Volume")) { + DBG("RDAC\n"); + val = (val >= 127) ? (val - 127) : (val + 129); + val_mask = AIC3262_8BITS_MASK; + } + + err = snd_soc_update_bits_locked(codec, reg, val_mask, val); + if (err < 0) { + printk(KERN_ERR "Error while updating bits\n"); + return err; + } + + return 0; +} +#endif /*#ifdef DAC_INDEPENDENT_VOL*/ +/* + *------------------------------------------------------------------------------ + * snd_soc_info_volsw_2r_n - double mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a double mixer control that + * spans 2 codec registers. + * + * Returns 0 for success. + *------------------------------------------------------------------------------ + */ +int snd_soc_info_volsw_2r_n(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max; + return 0; +} + +/* + *------------------------------------------------------------------------------ + * snd_soc_get_volsw_2r_n - double mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a double mixer control that spans 2 registers. + * + * Returns 0 for success. + *------------------------------------------------------------------------------ + */ +int snd_soc_get_volsw_2r_n(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask; + unsigned int invert = mc->invert; + unsigned short val, val2; + + if (!strcmp(kcontrol->id.name, "PCM Playback Volume")) { + mask = AIC3262_8BITS_MASK; + shift = 0; + } else if (!strcmp(kcontrol->id.name, "HP Driver Gain")) { + mask = 0x3F; + shift = 0; + } else if (!strcmp(kcontrol->id.name, "PGA Capture Volume")) { + mask = 0x7F; + shift = 0; + } else if (!strcmp(kcontrol->id.name, "REC Driver Volume")) { + mask = 0x3F; + shift = 0; + } else if (!strcmp(kcontrol->id.name, "LO to HP Volume")) { + mask = 0x7F; + shift = 0; + } else if (!strcmp(kcontrol->id.name, "MA Volume")) { + mask = 0x7F; + shift = 0; + } else { + printk(KERN_ERR "Invalid kcontrol name\n"); + return -1; + } + + /* Read, update the corresponding Registers */ + val = (snd_soc_read(codec, reg) >> shift) & mask; + val2 = (snd_soc_read(codec, reg2) >> shift) & mask; + + if (!strcmp(kcontrol->id.name, "PCM Playback Volume")) { + ucontrol->value.integer.value[0] = + (val <= 48) ? (val + 127) : (val - 129); + ucontrol->value.integer.value[1] = + (val2 <= 48) ? (val2 + 127) : (val2 - 129); + } else if (!strcmp(kcontrol->id.name, "HP Driver Gain")) { + ucontrol->value.integer.value[0] = + (val >= 57) ? (val - 57) : (val + 7); + ucontrol->value.integer.value[1] = + (val2 >= 57) ? (val2 - 57) : (val2 + 7); + } else if (!strcmp(kcontrol->id.name, "PGA Capture Volume")) { + ucontrol->value.integer.value[0] = + (val <= 40) ? (val + 24) : (val - 104); + ucontrol->value.integer.value[1] = + (val2 <= 40) ? (val2 + 24) : (val2 - 104); + } else if (!strcmp(kcontrol->id.name, "REC Driver Volume")) { + ucontrol->value.integer.value[0] = ((val >= 0) & (val <= 29)) ? + (val + 7) : (val - 57); + ucontrol->value.integer.value[1] = ((val2 >= 0) & + (val2 <= 29)) ? (val2 + 7) : (val2 - 57); + + } else if (!strcmp(kcontrol->id.name, "LO to HP Volume")) { + ucontrol->value.integer.value[0] = ((val >= 0) & (val <= 116)) ? + (val + 1) : ((val == 127) ? (0) : (117)); + ucontrol->value.integer.value[1] = ((val2 >= 0) & (val2 <= 116)) + ? (val2 + 1) : ((val2 == 127) ? (0) : (117)); + + } else if (!strcmp(kcontrol->id.name, "MA Volume")) { + ucontrol->value.integer.value[0] = (val <= 40) ? + (41 - val) : (val = 0); + ucontrol->value.integer.value[1] = (val2 <= 40) ? + (41 - val2) : (val2 = 0); + } + + if (invert) { + ucontrol->value.integer.value[0] = + max - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = + max - ucontrol->value.integer.value[1]; + } + + return 0; +} +/* +*------------------------------------------------------------------------------- +* snd_soc_put_volsw_2r_n - double mixer set callback +* @kcontrol: mixer control +* @ucontrol: control element information +* +* Callback to set the value of a double mixer control that spans 2 registers. +* +* Returns 0 for success. +*------------------------------------------------------------------------------- +*/ +int snd_soc_put_volsw_2r_n(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask; + unsigned int invert = mc->invert; + int err; + unsigned short val, val2, val_mask; + + mask = 0x00FF; + + val = (ucontrol->value.integer.value[0] & mask); + val2 = (ucontrol->value.integer.value[1] & mask); + if (invert) { + val = max - val; + val2 = max - val2; + } + + /* Check for the string name of the kcontrol */ + if (!strcmp(kcontrol->id.name, "PCM Playback Volume")) { + val = (val >= 127) ? (val - 127) : (val + 129); + val2 = (val2 >= 127) ? (val2 - 127) : (val2 + 129); + val_mask = AIC3262_8BITS_MASK; /* 8 bits */ + } else if ((!strcmp(kcontrol->id.name, "HP Driver Gain")) || + (!strcmp(kcontrol->id.name, "LO Driver Gain"))) { + val = (val <= 6) ? (val + 57) : (val - 7); + val2 = (val2 <= 6) ? (val2 + 57) : (val2 - 7); + val_mask = 0x3F; /* 6 bits */ + DBG("val=%d, val2=%d", val, val2); + } else if (!strcmp(kcontrol->id.name, "PGA Capture Volume")) { + val = (val >= 24) ? ((val <= 64) ? + (val-24) : (40)) : (val + 104); + val2 = (val2 >= 24) ? + ((val2 <= 64) ? (val2 - 24) : (40)) : (val2 + 104); + val_mask = 0x7F; /* 7 bits */ + } else if (!strcmp(kcontrol->id.name, "LO to REC Volume")) { + + val = (val <= 116) ? + (val % 116) : ((val == 117) ? (127) : (117)); + val2 = (val2 <= 116) ? + (val2 % 116) : ((val2 == 117) ? (127) : (117)); + val_mask = 0x7F; + } else if (!strcmp(kcontrol->id.name, "REC Driver Volume")) { + + val = (val <= 7) ? (val + 57) : ((val < 36) ? (val - 7) : (29)); + val2 = (val2 <= 7) ? + (val2 + 57) : ((val2 < 36) ? (val2 - 7) : (29)); + val_mask = 0x3F; + } else if (!strcmp(kcontrol->id.name, "LO to HP Volume")) { + + val = ((val > 0) & (val <= 117)) ? + (val - 1) : ((val == 0) ? (127) : (116)); + val2 = ((val2 > 0) & (val2 <= 117)) ? + (val2 - 1) : ((val2 == 0) ? (127) : (116)); + val_mask = 0x7F; + } else if (!strcmp(kcontrol->id.name, "MA Volume")) { + + val = ((val <= 41) & (val > 0)) ? + (41 - val) : ((val > 41) ? (val = 41) : (63)); + val2 = ((val2 <= 41) & (val2 > 0)) ? + (41 - val2) : ((val2 > 41) ? (val2 = 41) : (63)); + val_mask = 0x7F; + } else { + printk(KERN_ERR "Invalid control name\n"); + return -1; + } + + val = val << shift; + val2 = val2 << shift; + + err = snd_soc_update_bits_locked(codec, reg, val_mask, val); + if (err < 0) + return err; + + err = snd_soc_update_bits_locked(codec, reg2, val_mask, val2); + return err; +} + +static int __new_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 65535; + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : __new_control_get + * Purpose : This function is to read data of new control for + * program the AIC3262 registers. + * + *---------------------------------------------------------------------------- + */ +static int __new_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u32 val; + val = aic3262_read(codec, aic3262_reg_ctl); + ucontrol->value.integer.value[0] = val; + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : __new_control_put + * Purpose : new_control_put is called to pass data from user/application to + * the driver. + * + *---------------------------------------------------------------------------- + */ +static int __new_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 data[2]; + int ret = 0; + + u32 data_from_user = ucontrol->value.integer.value[0]; + + aic3262_change_book(codec, 0); + aic3262_reg_ctl = data[0] = (u8) ((data_from_user & 0xFF00) >> 8); + data[1] = (u8) ((data_from_user & 0x00FF)); + + if (!data[0]) + aic3262->page_no = data[1]; + + DBG("reg = %d val = %x\n", data[0], data[1]); + + ret = snd_soc_write(codec, data[0], data[1]); + if (ret) + printk(KERN_ERR "Error in i2c write\n"); + + return ret; +} + + +/* + ***************************************************************************** + * Structure Initialization + ***************************************************************************** + */ +static const struct snd_kcontrol_new aic3262_snd_controls[] = { + /* Output */ + #ifndef DAC_INDEPENDENT_VOL + /* sound new kcontrol for PCM Playback volume control */ + SOC_DOUBLE_R_N("PCM Playback Volume", DAC_LVOL, DAC_RVOL, 0, 0xAf, 0), + #endif + /* sound new kcontrol for HP driver gain */ + SOC_DOUBLE_R_N("HP Driver Gain", HPL_VOL, HPR_VOL, 0, 21, 0), + /* sound new kcontrol for SPK driver gain*/ + SOC_DOUBLE("SPK Driver Gain", SPK_AMP_CNTL_R4, 0, 4, 5, 0), + /* Reveiver driver Gain volume*/ + SOC_DOUBLE_R_N("REC Driver Volume", + REC_AMP_CNTL_R5, RAMPR_VOL, 0, 36, 0), + /* sound new kcontrol for PGA capture volume */ + SOC_DOUBLE_R_N("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x3F, + 0), + SOC_DOUBLE("ADC Fine Gain ", ADC_FINE_GAIN, 4, 0, 4, 1), + SOC_DOUBLE_R("PGA MIC Volume", MICL_PGA, MICR_PGA, 0, 95, 0), + + SOC_DOUBLE_R("LO to REC Volume", RAMP_CNTL_R1, RAMP_CNTL_R2, 0, 116, 1), + SOC_DOUBLE_R_N("LO to HP Volume", + HP_AMP_CNTL_R2, HP_AMP_CNTL_R3, 0, 117, 0), + SOC_DOUBLE_R("LO to SPK Volume", + SPK_AMP_CNTL_R2, SPK_AMP_CNTL_R3, 0, 116, 1), + SOC_DOUBLE("ADC channel mute", ADC_FINE_GAIN, 7, 3, 1, 0), + + SOC_DOUBLE("DAC MUTE", DAC_MVOL_CONF, 2, 3, 1, 1), + + /* sound new kcontrol for Programming the registers from user space */ + SOC_SINGLE_AIC3262("Program Registers"), + + SOC_SINGLE("RESET", RESET_REG, 0 , 1, 0), + + SOC_SINGLE("DAC VOL SOFT STEPPING", DAC_MVOL_CONF, 0, 2, 0), + + #ifdef DAC_INDEPENDENT_VOL + SOC_SINGLE_N("Left DAC Volume", DAC_LVOL, 0, 0xAF, 0), + SOC_SINGLE_N("Right DAC Volume", DAC_RVOL, 0, 0xAF, 0), + #endif + + SOC_SINGLE("DAC AUTO MUTE CONTROL", DAC_MVOL_CONF, 4, 7, 0), + SOC_SINGLE("RIGHT MODULATOR SETUP", DAC_MVOL_CONF, 7, 1, 0), + + SOC_SINGLE("ADC Volume soft stepping", ADC_CHANNEL_POW, 0, 3, 0), + + SOC_DOUBLE_R_N("MA Volume", + LADC_PGA_MAL_VOL, RADC_PGA_MAR_VOL, 0, 42, 0), + + SOC_SINGLE("Mic Bias ext independent enable", MIC_BIAS_CNTL, 7, 1, 0), + SOC_SINGLE("MICBIAS_EXT ON", MIC_BIAS_CNTL, 6, 1, 0), + SOC_SINGLE("MICBIAS EXT Power Level", MIC_BIAS_CNTL, 4, 3, 0), + + SOC_SINGLE("MICBIAS_INT ON", MIC_BIAS_CNTL, 2, 1, 0), + SOC_SINGLE("MICBIAS INT Power Level", MIC_BIAS_CNTL, 0, 3, 0), + + SOC_DOUBLE("DRC_EN_CTL", DRC_CNTL_R1, 6, 5, 1, 0), + SOC_SINGLE("DRC_THRESHOLD_LEVEL", DRC_CNTL_R1, 2, 7, 1), + SOC_SINGLE("DRC_HYSTERISIS_LEVEL", DRC_CNTL_R1, 0, 7, 0), + + SOC_SINGLE("DRC_HOLD_LEVEL", DRC_CNTL_R2, 3, 0x0F, 0), + SOC_SINGLE("DRC_GAIN_RATE", DRC_CNTL_R2, 0, 4, 0), + SOC_SINGLE("DRC_ATTACK_RATE", DRC_CNTL_R3, 4, 0x0F, 1), + SOC_SINGLE("DRC_DECAY_RATE", DRC_CNTL_R3, 0, 0x0F, 1), + + SOC_SINGLE("BEEP_GEN_EN", BEEP_CNTL_R1, 7, 1, 0), + SOC_DOUBLE_R("BEEP_VOL_CNTL", BEEP_CNTL_R1, BEEP_CNTL_R2, 0, 0x0F, 1), + SOC_SINGLE("BEEP_MAS_VOL", BEEP_CNTL_R2, 6, 3, 0), + + SOC_DOUBLE_R("AGC_EN", LAGC_CNTL, RAGC_CNTL, 7, 1, 0), + SOC_DOUBLE_R("AGC_TARGET_LEVEL", LAGC_CNTL, RAGC_CNTL, 4, 7, 1), + + SOC_DOUBLE_R("AGC_GAIN_HYSTERESIS", LAGC_CNTL, RAGC_CNTL, 0, 3, 0), + SOC_DOUBLE_R("AGC_HYSTERESIS", LAGC_CNTL_R2, RAGC_CNTL_R2, 6, 3, 0), + SOC_DOUBLE_R("AGC_NOISE_THRESHOLD", + LAGC_CNTL_R2, RAGC_CNTL_R2, 1, 31, 1), + + SOC_DOUBLE_R("AGC_MAX_GAIN", LAGC_CNTL_R3, RAGC_CNTL_R3, 0, 116, 0), + SOC_DOUBLE_R("AGC_ATCK_TIME", LAGC_CNTL_R4, RAGC_CNTL_R4, 3, 31, 0), + SOC_DOUBLE_R("AGC_ATCK_SCALE_FACTOR", + LAGC_CNTL_R4, RAGC_CNTL_R4, 0, 7, 0), + + SOC_DOUBLE_R("AGC_DECAY_TIME", LAGC_CNTL_R5, RAGC_CNTL_R5, 3, 31, 0), + SOC_DOUBLE_R("AGC_DECAY_SCALE_FACTOR", + LAGC_CNTL_R5, RAGC_CNTL_R5, 0, 7, 0), + SOC_DOUBLE_R("AGC_NOISE_DEB_TIME", + LAGC_CNTL_R6, RAGC_CNTL_R6, 0, 31, 0), + + SOC_DOUBLE_R("AGC_SGL_DEB_TIME", + LAGC_CNTL_R7, RAGC_CNTL_R7, 0, 0x0F, 0), + SOC_SINGLE("DAC PRB Selection", DAC_PRB, 0, 25, 0), + + SOC_SINGLE("INTERRUPT FLAG - Read only", 46, 0, 255, 0), + SOC_SINGLE("INTERRUPT STICKY FLAG - Read only", 44, 0, 255, 0), + SOC_SINGLE("INT1 CONTROL", 48, 0, 255, 0), + SOC_SINGLE("GPIO1 CONTROL", (PAGE_4 + 86), 0, 255, 0), + SOC_SINGLE("HP_DEPOP", HP_DEPOP, 0, 255, 0), + + #if defined(FULL_IN_CNTL) + + SOC_SINGLE("IN1L_2_LMPGA_P_CTL", LMIC_PGA_PIN, 6, 3, 0), + SOC_SINGLE("IN2L_2_LMPGA_P_CTL", LMIC_PGA_PIN, 4, 3, 0), + SOC_SINGLE("IN3L_2_LMPGA_P_CTL", LMIC_PGA_PIN, 2, 3, 0), + SOC_SINGLE("IN1R_2_LMPGA_P_CTL", LMIC_PGA_PIN, 0, 3, 0), + + SOC_SINGLE("IN4L_2_LMPGA_P_CTL", LMIC_PGA_PM_IN4, 5, 1, 0), + SOC_SINGLE("IN4R_2_LMPGA_M_CTL", LMIC_PGA_PM_IN4, 4, 1, 0), + + SOC_SINGLE("CM1_2_LMPGA_M_CTL", LMIC_PGA_MIN, 6, 3, 0), + SOC_SINGLE("IN2R_2_LMPGA_M_CTL", LMIC_PGA_MIN, 4, 3, 0), + SOC_SINGLE("IN3R_2_LMPGA_M_CTL", LMIC_PGA_MIN, 2, 3, 0), + SOC_SINGLE("CM2_2_LMPGA_M_CTL", LMIC_PGA_MIN, 0, 3, 0), + + SOC_SINGLE("IN1R_2_RMPGA_P_CTL", RMIC_PGA_PIN, 6, 3, 0), + SOC_SINGLE("IN2R_2_RMPGA_P_CTL", RMIC_PGA_PIN, 4, 3, 0), + SOC_SINGLE("IN3R_2_RMPGA_P_CTL", RMIC_PGA_PIN, 2, 3, 0), + SOC_SINGLE("IN2L_2_RMPGA_P_CTL", RMIC_PGA_PIN, 0, 3, 0), + + SOC_SINGLE("IN4R_2_RMPGA_P_CTL", RMIC_PGA_PM_IN4, 5, 1, 0), + SOC_SINGLE("IN4L_2_RMPGA_M_CTL", RMIC_PGA_PM_IN4, 4, 1, 0), + + SOC_SINGLE("CM1_2_RMPGA_M_CTL", RMIC_PGA_MIN, 6, 3, 0), + SOC_SINGLE("IN1L_2_RMPGA_M_CTL", RMIC_PGA_MIN, 4, 3, 0), + SOC_SINGLE("IN3L_2_RMPGA_M_CTL", RMIC_PGA_MIN, 2, 3, 0), + SOC_SINGLE("CM2_2_RMPGA_M_CTL", RMIC_PGA_MIN, 0, 3, 0), + + #endif + + SOC_DOUBLE("MA_EN_CNTL", MA_CNTL, 3, 2, 1, 0), + SOC_DOUBLE("IN1 LO DIRECT BYPASS", MA_CNTL, 5, 4, 1, 0), + SOC_DOUBLE("IN1 LO BYPASS VOLUME" , LINE_AMP_CNTL_R2, 3, 0, 3, 1), + SOC_DOUBLE("MA LO BYPASS EN", LINE_AMP_CNTL_R2, 7, 6, 1, 0), + + /* Pin control Macros */ + SOC_ENUM("DOUT1 Pin Control", aic326x_enum[DOUT1_ENUM]), + SOC_ENUM("DIN1 Pin Control", aic326x_enum[DIN1_ENUM]), + SOC_ENUM("WCLK2 Pin Control", aic326x_enum[WCLK2_ENUM]), + SOC_ENUM("BCLK2 Pin Control", aic326x_enum[BCLK2_ENUM]), + SOC_ENUM("DOUT2 Pin Control", aic326x_enum[DOUT2_ENUM]), + SOC_ENUM("DIN2 Pin Control", aic326x_enum[DIN2_ENUM]), + SOC_ENUM("WCLK3 Pin Control", aic326x_enum[WCLK3_ENUM]), + SOC_ENUM("BCLK3 Pin Control", aic326x_enum[BCLK3_ENUM]), + SOC_ENUM("DOUT3 Pin Control", aic326x_enum[DOUT3_ENUM]), + SOC_ENUM("DIN3 Pin Control", aic326x_enum[DIN3_ENUM]), + SOC_ENUM("DAC CLK IN", aic326x_enum[CLKIN_ENUM]), + SOC_ENUM("ADC CLK IN", aic326x_enum[CLKIN_ENUM]), + +}; + +/* the sturcture contains the different values for mclk */ +static const struct aic3262_rate_divs aic3262_divs[] = { +/* + * mclk, rate, p_val, pll_j, pll_d, dosr, ndac, mdac, aosr, nadc, madc, blck_N, + * codec_speficic_initializations + */ + /* 8k rate */ +#ifdef CONFIG_MINI_DSP + {12000000, 8000, 1, 8, 1920, 768, 8, 2, 128, 8, 12, 4, + {{0, 60, 0}, {0, 61, 0} } }, +#else + {12000000, 8000, 1, 8, 1920, 128, 12, 8, 128, 8, 6, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {12288000, 8000, 1, 1, 3333, 128, 12, 8, 128, 8, 6, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {24000000, 8000, 1, 4, 96, 128, 12, 8, 128, 12, 8, 4, + {{0, 60, 1}, {0, 61, 1} } }, +#endif + /* 11.025k rate */ + {12000000, 11025, 1, 1, 8816, 1024, 8, 2, 128, 8, 2, 48, + {{0, 60, 1}, {0, 61, 1} } }, + {12288000, 11025, 1, 1, 8375, 1024, 8, 2, 128, 8, 2, 48, + {{0, 60, 1}, {0, 61, 1} } }, + {24000000, 11025, 1, 3, 7632, 128, 8, 8, 128, 8, 8, 4, + {{0, 60, 1}, {0, 61, 1} } }, + + /* 16k rate */ +#ifdef CONFIG_MINI_DSP + {12000000, 16000, 1, 8, 1920, 384, 4, 4, 128, 4, 12, 12, + {{0, 60, 0}, {0, 61, 0} } }, +#else + {12000000, 16000, 1, 8, 1920, 128, 8, 6, 128, 8, 6, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {12288000, 16000, 1, 2, 6667, 128, 8, 6, 128, 8, 6, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {24000000, 16000, 1, 4, 96, 128, 8, 6, 128, 8, 6, 4, + {{0, 60, 1}, {0, 61, 1} } }, +#endif + /* 22.05k rate */ + {12000000, 22050, 1, 3, 7632, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {12288000, 22050, 1, 3, 675, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {24000000, 22050, 1, 3, 7632, 128, 8, 3, 128, 8, 3, 4, + {{0, 60, 1}, {0, 61, 1} } }, + /* 32k rate */ + {12000000, 32000, 1, 5, 4613, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {12288000, 32000, 1, 5, 3333, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {24000000, 32000, 1, 4, 96, 128, 6, 4, 128, 6, 4, 4, + {{0, 60, 1}, {0, 61, 1} } }, + +#ifdef CONFIG_MINI_DSP + {12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4, + {{0, 60, 0}, {0, 61, 0} } }, + {12288000, 44100, 1, 7, 3548, 128, 2, 8, 128, 8, 2, 4, + {{0, 60, 0}, {0, 61, 0} } }, +#else + /* 44.1k rate */ + {12000000, 44100, 1, 7, 5264, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {12288000, 44100, 1, 7, 3548, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {24000000, 44100, 1, 3, 7632, 128, 4, 4, 64, 4, 4, 4, + {{0, 60, 1}, {0, 61, 1} } }, +#endif + +#ifdef CONFIG_MINI_DSP + {12288000, 48000, 1, 8, 52, 128, 2, 8, 128, 2, 8, 4, + {{0, 60, 0}, {0, 61, 0} } }, +#else + /* 48k rate */ + {12000000, 48000, 1, 8, 1920, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {12288000, 48000, 1, 8, 52, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 1}, {0, 61, 1} } }, + {24000000, 48000, 1, 4, 960, 128, 4, 4, 128, 4, 4, 4, + {{0, 60, 1}, {0, 61, 1} } }, +#endif + + /*96k rate */ + {12000000, 96000, 1, 16, 3840, 128, 8, 2, 128, 8, 2 , 4, + {{0, 60, 7}, {0, 61, 7} } }, + {24000000, 96000, 1, 4, 960, 128, 4, 2, 128, 4, 2, 2, + {{0, 60, 7}, {0, 61, 7} } }, + /*192k */ + {12000000, 192000, 1, 32, 7680, 128, 8, 2, 128, 8, 2, 4, + {{0, 60, 17}, {0, 61, 13} } }, + {24000000, 192000, 1, 4, 960, 128, 2, 2, 128, 2, 2, 4, + {{0, 60, 17}, {0, 61, 13} } }, +}; + + + +/* +*---------------------------------------------------------------------------- +* Function : aic3262_multi_i2s_dump_regs +* Purpose : This function is to mute or unmute the left and right DAC +* +*---------------------------------------------------------------------------- +*/ +static void aic3262_multi_i2s_dump_regs(struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + unsigned int counter; + + DBG(KERN_INFO "#%s: Dai Active %d ASI%d REGS DUMP\n", + __func__, aic3262->active_count, dai->id); + + aic3262_change_page(codec, 0); + aic3262_change_book(codec, 0); + + DBG(KERN_INFO "#Page0 REGS..\n"); + for (counter = 0; counter < 85; counter++) { + DBG(KERN_INFO "#%2d -> 0x%x\n", counter, + aic3262_read(codec, counter)); + } + + /*DBG(KERN_INFO "#Page1 REGS..\n"); + for (counter = 128; counter < 176; counter++) { + DBG(KERN_INFO "#%2d -> 0x%x\n", (counter % 128), + aic3262_read(codec, counter)); + }*/ + + DBG(KERN_INFO "#Page4 REGS..\n"); + for (counter = 512; counter < 631; counter++) { + DBG(KERN_INFO "#%2d -> 0x%x\n", + (counter % 128), aic3262_read(codec, counter)); + } + + for (counter = 0; counter < MAX_ASI_COUNT; counter++) { + DBG(KERN_INFO "#ASI%d Frame %s @ %dHz Playback %d Record %d\n", + (counter + 1), + (aic3262->asiCtxt[counter].master == 1) ? "Master" : "Slave", + aic3262->asiCtxt[counter].sampling_rate, + aic3262->asiCtxt[counter].playback_mode, + aic3262->asiCtxt[counter].capture_mode); + DBG(KERN_INFO "#DAC Option [%d,%d] ADC Option %d WLEN %d\n\n", + aic3262->asiCtxt[counter].left_dac_output, + aic3262->asiCtxt[counter].right_dac_output, + aic3262->asiCtxt[counter].adc_input, + aic3262->asiCtxt[counter].word_len); + } + return; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_multi_i2s_mute + * Purpose : This function is to mute or unmute the left and right DAC + * + *---------------------------------------------------------------------------- + */ +static int aic3262_multi_i2s_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#%s : mute entered with %d\n", __func__, mute); + + /* If we are doing both Recording and Playback on this DAI interface, + * do not MUTE the Codec. + */ + if (mute && (aic3262->asiCtxt[dai->id - 1].asi_active > 1)) { + DBG("#%s Cannot Mute the ASI%d Now..\n", + __func__, dai->id); + } else { + switch (dai->id) { + case 1: + aic3262_multi_i2s_asi1_mute(dai, mute); + break; + case 2: + aic3262_multi_i2s_asi2_mute(dai, mute); + break; + case 3: + aic3262_multi_i2s_asi3_mute(dai, mute); + break; + default: + printk(KERN_ERR "#%s: Invalid DAI id\n", __func__); + return -EINVAL; + } + } + DBG(KERN_INFO "#%s : mute ended\n", __func__); + return 0; +} + + +/* +*---------------------------------------------------------------------------- +* Function : aic3262_multi_i2s_asi1_mute +* Purpose : This function is to mute or unmute the left and right DAC +* +*---------------------------------------------------------------------------- +*/ +static int aic3262_multi_i2s_asi1_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#%s : mute %d started\n", __func__, mute); + + if (mute) { + DBG(KERN_INFO "Mute if part\n"); + + if (!aic3262->asiCtxt[0].port_muted) { + dac_reg = aic3262_read(codec, DAC_MVOL_CONF); + adc_gain = aic3262_read(codec, ADC_FINE_GAIN); + hpl = aic3262_read(codec, HPL_VOL); + hpr = aic3262_read(codec, HPR_VOL); + rec_amp = aic3262_read(codec, REC_AMP_CNTL_R5); + rampr = aic3262_read(codec, RAMPR_VOL); + spk_amp = aic3262_read(codec, SPK_AMP_CNTL_R4); + } + DBG(KERN_INFO "spk_reg = %2x\n\n", spk_amp); + + /* First check if both Playback and Recording is going on + * this interface. + */ + if (aic3262->asiCtxt[0].asi_active > 1) { + DBG("#%s Cannot Mute the ASI Now..\n", __func__); + } else if (!(aic3262->asiCtxt[1].playback_mode) && + !(aic3262->asiCtxt[2].playback_mode)) { + /* Before Muting, please check if any other + * ASI is active. if so, we cannot simply mute the + * DAC and ADC Registers. + */ + aic3262_write(codec, DAC_MVOL_CONF, + ((dac_reg & 0xF3) | 0x0C)); + aic3262_write(codec, ADC_FINE_GAIN, + ((adc_gain & 0x77) | 0x88)); + aic3262_write(codec, HPL_VOL, 0xB9); + aic3262_write(codec, HPR_VOL, 0xB9); + aic3262_write(codec, REC_AMP_CNTL_R5, 0x39); + aic3262_write(codec, RAMPR_VOL, 0x39); + aic3262_write(codec, SPK_AMP_CNTL_R4, 0x00); + aic3262->asiCtxt[0].port_muted = 1; + } else { + DBG(KERN_INFO + "#%s: Other ASI Active. Cannot MUTE Codec..\n", + __func__); + } + } else { + DBG(KERN_INFO "Mute else part\n"); + aic3262_write(codec, DAC_MVOL_CONF, (dac_reg & 0xF3)); + mdelay(5); + aic3262_write(codec, ADC_FINE_GAIN, (adc_gain & 0x77)); + mdelay(5); + aic3262_write(codec, HPL_VOL, hpl); + mdelay(5); + aic3262_write(codec, HPR_VOL, hpr); + mdelay(5); + aic3262_write(codec, REC_AMP_CNTL_R5, rec_amp); + mdelay(5); + aic3262_write(codec, RAMPR_VOL, rampr); + mdelay(5); + aic3262_write(codec, SPK_AMP_CNTL_R4, spk_amp); + mdelay(5); + + /* Basic hack */ + aic3262_write(codec, LINE_AMP_CNTL_R1, 0xc3); + aic3262_write(codec, HP_AMP_CNTL_R1, 0x33); + + aic3262->asiCtxt[0].port_muted = 0; + aic3262_multi_i2s_dump_regs(dai); + } + + DBG(KERN_INFO "#%s : mute %d ended\n", __func__, mute); + + return 0; +} + +/* +*---------------------------------------------------------------------------- +* Function : aic3262_multi_i2s_asi2_maic3262_asi3_clk_configute +* Purpose : This function is to mute or unmute the left and right DAC +* +*---------------------------------------------------------------------------- +*/ +static int aic3262_multi_i2s_asi2_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#%s : mute %d started\n", __func__, mute); + + if (mute) { + DBG(KERN_INFO "Mute if part\n"); + + if (!aic3262->asiCtxt[1].port_muted) { + dac_reg = aic3262_read(codec, DAC_MVOL_CONF); + adc_gain = aic3262_read(codec, ADC_FINE_GAIN); + hpl = aic3262_read(codec, HPL_VOL); + hpr = aic3262_read(codec, HPR_VOL); + rec_amp = aic3262_read(codec, REC_AMP_CNTL_R5); + rampr = aic3262_read(codec, RAMPR_VOL); + spk_amp = aic3262_read(codec, SPK_AMP_CNTL_R4); + DBG(KERN_INFO "spk_reg = %2x\n\n", spk_amp); + } + + /* First check if both Playback and Recording is going on + * this interface. + */ + if (aic3262->asiCtxt[1].asi_active > 1) { + DBG("#%s Cannot Mute the ASI Now..\n", __func__); + } else if (!(aic3262->asiCtxt[0].playback_mode) && + !(aic3262->asiCtxt[2].playback_mode)) { + /* Before Muting, please check if any other + * ASI is active. if so, we cannot simply mute the + * DAC and ADC Registers. + */ + aic3262_write(codec, DAC_MVOL_CONF, + ((dac_reg & 0xF3) | 0x0C)); + aic3262_write(codec, ADC_FINE_GAIN, + ((adc_gain & 0x77) | 0x88)); + aic3262_write(codec, HPL_VOL, 0xB9); + aic3262_write(codec, HPR_VOL, 0xB9); + aic3262_write(codec, REC_AMP_CNTL_R5, 0x39); + aic3262_write(codec, RAMPR_VOL, 0x39); + aic3262_write(codec, SPK_AMP_CNTL_R4, 0x00); + aic3262->asiCtxt[1].port_muted = 1; + } else { + DBG("#%s: Other ASI Active. Cannot MUTE Codec..\n", + __func__); + } + } else { + DBG(KERN_INFO "Mute else part\n"); + aic3262_write(codec, DAC_MVOL_CONF, (dac_reg & 0xF3)); + mdelay(5); + aic3262_write(codec, ADC_FINE_GAIN, (adc_gain & 0x77)); + mdelay(5); + aic3262_write(codec, HPL_VOL, hpl); + mdelay(5); + aic3262_write(codec, HPR_VOL, hpr); + mdelay(5); + aic3262_write(codec, REC_AMP_CNTL_R5, rec_amp); + mdelay(5); + aic3262_write(codec, RAMPR_VOL, rampr); + mdelay(5); + aic3262_write(codec, SPK_AMP_CNTL_R4, spk_amp); + mdelay(5); + + /* Basic hack */ + aic3262_write(codec, LINE_AMP_CNTL_R1, 0xc3); + aic3262_write(codec, HP_AMP_CNTL_R1, 0x33); + + aic3262->asiCtxt[1].port_muted = 0; + aic3262_multi_i2s_dump_regs(dai); + } + + DBG(KERN_INFO "#%s : mute %d ended\n", __func__, mute); + + return 0; +} +/* +*---------------------------------------------------------------------------- +* Function : aic3262_multi_i2s_asi3_mute +* Purpose : This function is to mute or unmute the left and right DAC +* +*---------------------------------------------------------------------------- +*/ +static int aic3262_multi_i2s_asi3_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#%s : mute %d started\n", __func__, mute); + + if (mute) { + DBG("Mute if part\n"); + + if (!aic3262->asiCtxt[2].port_muted) { + dac_reg = aic3262_read(codec, DAC_MVOL_CONF); + adc_gain = aic3262_read(codec, ADC_FINE_GAIN); + hpl = aic3262_read(codec, HPL_VOL); + hpr = aic3262_read(codec, HPR_VOL); + rec_amp = aic3262_read(codec, REC_AMP_CNTL_R5); + rampr = aic3262_read(codec, RAMPR_VOL); + spk_amp = aic3262_read(codec, SPK_AMP_CNTL_R4); + } + DBG("spk_reg = %2x\n\n", spk_amp); + + /* First check if both Playback and Recording is going on + * this interface. + */ + if (aic3262->asiCtxt[2].asi_active > 1) { + DBG("#%s Cannot Mute the ASI Now..\n", __func__); + } else if (!(aic3262->asiCtxt[0].playback_mode) && + !(aic3262->asiCtxt[1].playback_mode)) { + /* Before Muting, please check if any other + * ASI is active. if so, we cannot simply mute the + * DAC and ADC Registers. + */ + aic3262_write(codec, DAC_MVOL_CONF, + ((dac_reg & 0xF3) | 0x0C)); + aic3262_write(codec, ADC_FINE_GAIN, + ((adc_gain & 0x77) | 0x88)); + aic3262_write(codec, HPL_VOL, 0xB9); + aic3262_write(codec, HPR_VOL, 0xB9); + aic3262_write(codec, REC_AMP_CNTL_R5, 0x39); + aic3262_write(codec, RAMPR_VOL, 0x39); + aic3262_write(codec, SPK_AMP_CNTL_R4, 0x00); + aic3262->asiCtxt[2].port_muted = 1; + } else { + DBG("#%s: Other ASI Active. Cannot MUTE Codec..\n", + __func__); + } + } else { + DBG("Mute else part\n"); + aic3262_write(codec, DAC_MVOL_CONF, (dac_reg & 0xF3)); + mdelay(5); + aic3262_write(codec, ADC_FINE_GAIN, (adc_gain & 0x77)); + mdelay(5); + aic3262_write(codec, HPL_VOL, hpl); + mdelay(5); + aic3262_write(codec, HPR_VOL, hpr); + mdelay(5); + aic3262_write(codec, REC_AMP_CNTL_R5, rec_amp); + mdelay(5); + aic3262_write(codec, RAMPR_VOL, rampr); + mdelay(5); + aic3262_write(codec, SPK_AMP_CNTL_R4, spk_amp); + mdelay(5); + + /* Basic hack */ + aic3262_write(codec, LINE_AMP_CNTL_R1, 0xc3); + aic3262_write(codec, HP_AMP_CNTL_R1, 0x33); + + aic3262->asiCtxt[2].port_muted = 0; + aic3262_multi_i2s_dump_regs(dai); + } + + DBG(KERN_INFO "#%s : mute %d ended\n", __func__, mute); + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_multi_i2s_set_dai_fmt + * Purpose : This function is to set the DAI format + * + *---------------------------------------------------------------------------- + */ +static int aic3262_multi_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + /* Check the DAI Id and based on that switch the configuration for + * the Individual ASI Port. + */ + switch (codec_dai->id) { + case 1: + aic3262_multi_i2s_asi1_set_dai_fmt(codec_dai, fmt); + break; + case 2: + aic3262_multi_i2s_asi2_set_dai_fmt(codec_dai, fmt); + break; + case 3: + aic3262_multi_i2s_asi3_set_dai_fmt(codec_dai, fmt); + break; + default: + printk(KERN_ERR + "#%s: Invalid DAI interface format\n", __func__); + return -EINVAL; + } + return 0; +} + + + +/* +*---------------------------------------------------------------------------- +* Function : aic3262_multi_i2s_asi1_set_dai_fmt +* Purpose : This function is to set the DAI format for ASI1 Port +* +*---------------------------------------------------------------------------- +*/ +static int aic3262_multi_i2s_asi1_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 iface_reg, clk_reg; + u8 regvalue; + + DBG(KERN_INFO "%s: DAI_ID %d fmt %d\n", + __func__, codec_dai->id, fmt); + + /* Read the B0_P4_R4 and B0_P4_R10 Registers to configure the + * ASI1 Bus and Clock Formats depending on the PCM Format. + */ + iface_reg = aic3262_read(codec, ASI1_BUS_FMT); + clk_reg = aic3262_read(codec, ASI1_BWCLK_CNTL_REG); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + DBG(KERN_INFO "#%s: Configuring ASI%d as Frame Master..\n", + __func__, codec_dai->id); + aic3262->asiCtxt[0].master = 1; + clk_reg |= (BIT5 | BIT2); /* Codec Interface as Master */ + break; + case SND_SOC_DAIFMT_CBS_CFS: + DBG(KERN_INFO "#%s: Configuring ASI%d as Frame Slave..\n", + __func__, codec_dai->id); + clk_reg &= ~0xFC; /* Reset bits D[7:5] and D[4:2] to zero */ + aic3262->asiCtxt[0].master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + /* new case..just for debugging */ + DBG(KERN_INFO "%s: SND_SOC_DAIFMT_CBS_CFM\n", __func__); + aic3262->asiCtxt[0].master = 0; + clk_reg |= BIT5; /* Only WCLK1 Output from Codec */ + clk_reg &= ~0x1C; /* BCLK1 Input to Codec */ + break; + default: + printk(KERN_ERR "#%s: Invalid DAI master/slave interface\n", + __func__); + return -EINVAL; + } + aic3262->asiCtxt[0].pcm_format = (fmt & SND_SOC_DAIFMT_FORMAT_MASK); + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + DBG(KERN_INFO "#%s: Configuring ASI%d for I2s Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f); + break; + case SND_SOC_DAIFMT_DSP_A: + DBG(KERN_INFO "#%s: Configuring ASI%d for DSP_A Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f) | 0x20; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg = (iface_reg & 0x1f) | 0x40; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg = (iface_reg & 0x1f) | 0x60; + break; + case SND_SOC_DAIFMT_DSP_B: + DBG(KERN_INFO "#%s: Configuring ASI%d for DSP_B Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f) | 0x80; + /* voice call need data offset in 1 bitclock */ + aic3262_write(codec, ASI1_LCH_OFFSET, 1); + break; + default: + printk(KERN_ERR + "#%s: Invalid DAI interface format\n", __func__); + return -EINVAL; + } + /* Also Configure the Pin Control Registers before writing into + * the ASI specific Clock Control and Format Registers + */ + + /* Configure B0_P4_R65_D[5:2] to 001 This configures the + * WCLK1 Pin to ASI1 + */ + regvalue = aic3262_read(codec, WCLK1_PIN_CNTL_REG); + aic3262_write(codec, WCLK1_PIN_CNTL_REG, (regvalue | BIT2)); + + /* Configure B0_P4_R68_d[6:5] = 01 and B0_P4_R67_D[4:1] to 0001 + * to ensure that the DIN1 and DOUT1 Pins are configured + * correctly + */ + regvalue = aic3262_read(codec, DIN1_PIN_CNTL_REG); + aic3262_write(codec, DIN1_PIN_CNTL_REG, (regvalue | BIT5)); + regvalue = aic3262_read(codec, DOUT1_PIN_CNTL_REG); + aic3262_write(codec, DOUT1_PIN_CNTL_REG, (regvalue | BIT1)); + + aic3262_write(codec, ASI1_BWCLK_CNTL_REG, clk_reg); + + aic3262_write(codec, ASI1_BUS_FMT, iface_reg); + + return 0; +} + + +/* +*---------------------------------------------------------------------------- +* Function : aic3262_multi_i2s_asi2_set_dai_fmt +* Purpose : This function is to set the DAI format for ASI2 Port +* +*---------------------------------------------------------------------------- +*/ +static int aic3262_multi_i2s_asi2_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 iface_reg, clk_reg; + u8 regvalue; + + DBG(KERN_INFO "%s: DAI_ID %d fmt %d\n", + __func__, codec_dai->id, fmt); + + /* Read the B0_P4_R17 and B0_P4_R26 Registers to configure the + * ASI1 Bus and Clock Formats depending on the PCM Format. + */ + iface_reg = aic3262_read(codec, ASI2_BUS_FMT); + clk_reg = aic3262_read(codec, ASI2_BWCLK_CNTL_REG); + /* + iface_reg = ifce_reg & ~(3 << 6 | 3 << 2); + */ + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + DBG(KERN_INFO "#%s: Configuring ASI%d as Frame Master..\n", + __func__, codec_dai->id); + aic3262->asiCtxt[1].master = 1; + clk_reg |= (BIT5 | BIT2); + break; + case SND_SOC_DAIFMT_CBS_CFS: + DBG(KERN_INFO "#%s: Configuring ASI%d as Frame Slave..\n", + __func__, codec_dai->id); + + clk_reg &= ~0xFC; + aic3262->asiCtxt[1].master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + /*new case..just for debugging */ + DBG(KERN_INFO "%s: SND_SOC_DAIFMT_CBS_CFM\n", __func__); + aic3262->asiCtxt[1].master = 0; + clk_reg |= BIT5; + clk_reg &= ~0x1C; + break; + default: + printk(KERN_ERR "#%s:Invalid DAI master/slave interface\n", + __func__); + return -EINVAL; + } + aic3262->asiCtxt[1].pcm_format = (fmt & SND_SOC_DAIFMT_FORMAT_MASK); + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + DBG(KERN_INFO "#%s: Configuring ASI%d for I2S Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f); + break; + case SND_SOC_DAIFMT_DSP_A: + DBG(KERN_INFO "#%s: Configuring ASI%d for DSP_A Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f) | 0x20; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg = (iface_reg & 0x1f) | 0x40; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg = (iface_reg & 0x1f) | 0x60; + break; + case SND_SOC_DAIFMT_DSP_B: + DBG(KERN_INFO "#%s: Configuring ASI%d for DSP Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f) | 0x80; + /* voice call need data offset in 1 bitclock */ + aic3262_write(codec, ASI2_LCH_OFFSET, 1); + break; + default: + printk(KERN_ERR "#%s:Invalid DAI interface format\n", __func__); + return -EINVAL; + } + + /* Also Configure the Pin Control Registers before writing into + * the ASI2 specific Clock Control and Format Registers + */ + + /* Configure B0_P4_R69_D[5:2] to 001 This configures the + * WCLK2 Pin to ASI2 + */ + + regvalue = aic3262_read(codec, WCLK2_PIN_CNTL_REG); + aic3262_write(codec, WCLK2_PIN_CNTL_REG, (regvalue | BIT2)); + + regvalue = aic3262_read(codec, BCLK2_PIN_CNTL_REG); + aic3262_write(codec, BCLK2_PIN_CNTL_REG, (regvalue | BIT2)); + + /* Configure B0_P4_R72_d[6:5] = 01 and B0_P4_R71_D[4:1] to 0001 + * to ensure that the DIN2 and DOUT2 Pins are configured + * correctly + */ + regvalue = aic3262_read(codec, DIN2_PIN_CNTL_REG); + aic3262_write(codec, DIN2_PIN_CNTL_REG, (regvalue | BIT5)); + + regvalue = aic3262_read(codec, DOUT2_PIN_CNTL_REG); + aic3262_write(codec, DOUT2_PIN_CNTL_REG, (regvalue | BIT5 | BIT1)); + + aic3262_write(codec, ASI2_BWCLK_CNTL_REG, clk_reg); + + aic3262_write(codec, ASI2_BUS_FMT, iface_reg); + + return 0; +} + +/* +*---------------------------------------------------------------------------- +* Function : aic3262_multi_i2s_asi3_set_dai_fmt +* Purpose : This function is to set the DAI format for ASI3 Port +* +*---------------------------------------------------------------------------- +*/ +static int aic3262_multi_i2s_asi3_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 iface_reg, clk_reg; + u8 regvalue; + + DBG(KERN_INFO "%s: DAI_ID %d fmt %d\n", + __func__, codec_dai->id, fmt); + + /* Read the B0_P4_R33 and B0_P4_R42 Registers to configure the + * ASI1 Bus and Clock Formats depending on the PCM Format. + */ + iface_reg = aic3262_read(codec, ASI3_BUS_FMT); + clk_reg = aic3262_read(codec, ASI3_BWCLK_CNTL_REG); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + DBG(KERN_INFO "#%s: Configuring ASI%d as Frame Master..\n", + __func__, codec_dai->id); + aic3262->asiCtxt[2].master = 1; + clk_reg |= (BIT5 | BIT2); + break; + case SND_SOC_DAIFMT_CBS_CFS: + DBG(KERN_INFO "#%s: Configuring ASI%d as Frame Slave..\n", + __func__, codec_dai->id); + clk_reg &= ~0xFC; + aic3262->asiCtxt[2].master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + /* new case..just for debugging */ + DBG(KERN_INFO "%s: SND_SOC_DAIFMT_CBS_CFM\n", __func__); + aic3262->asiCtxt[2].master = 0; + clk_reg |= BIT5; + clk_reg &= ~0x1C; + break; + default: + printk(KERN_ERR "Invalid DAI master/slave interface\n"); + return -EINVAL; + } + aic3262->asiCtxt[2].pcm_format = (fmt & SND_SOC_DAIFMT_FORMAT_MASK); + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + DBG(KERN_INFO "#%s: Configuring ASI%d for I2S Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f); + break; + case SND_SOC_DAIFMT_DSP_A: + DBG(KERN_INFO "#%s: Configuring ASI%d for DSP_A Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f) | 0x20; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg = (iface_reg & 0x1f) | 0x40; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg = (iface_reg & 0x1f) | 0x60; + break; + case SND_SOC_DAIFMT_DSP_B: + DBG(KERN_INFO "#%s: Configuring ASI%d for DSP Mode..\n", + __func__, codec_dai->id); + iface_reg = (iface_reg & 0x1f) | 0x80; + /* voice call need data offset in 1 bitclock */ + aic3262_write(codec, ASI3_LCH_OFFSET, 1); + break; + default: + printk(KERN_ERR + "#%s: Invalid DAI interface format\n", __func__); + return -EINVAL; + } + + /* Also Configure the Pin Control Registers before writing into + * the ASI specific Clock Control and Format Registers + */ + /* Configure B0_P4_R73_D[5:2] to 0001 This configures the + * WCLK1 Pin to ASI1 + */ + regvalue = aic3262_read(codec, WCLK3_PIN_CNTL_REG); + aic3262_write(codec, WCLK3_PIN_CNTL_REG, (regvalue | BIT2)); + + regvalue = aic3262_read(codec, BCLK3_PIN_CNTL_REG); + aic3262_write(codec, BCLK3_PIN_CNTL_REG, (regvalue | BIT2)); + + /* Configure B0_P4_R76_d[6:5] = 01 and B0_P4_R75_D[4:1] to 0001 + * to ensure that the DIN1 and DOUT1 Pins are configured + * correctly + */ + regvalue = aic3262_read(codec, DIN3_PIN_CNTL_REG); + aic3262_write(codec, DIN3_PIN_CNTL_REG, (regvalue | BIT5)); + regvalue = aic3262_read(codec, DOUT3_PIN_CNTL_REG); + aic3262_write(codec, DOUT3_PIN_CNTL_REG, (regvalue | BIT1)); + + aic3262_write(codec, ASI3_BWCLK_CNTL_REG, clk_reg); + + aic3262_write(codec, ASI3_BUS_FMT, iface_reg); + + return 0; +} + +/* + * Clock after PLL and dividers + */ +static int aic3262_multi_i2s_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 aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#%s: DAI ID %d Freq %d Direction %d\n", + __func__, codec_dai->id, freq, dir); + switch (freq) { + case AIC3262_FREQ_12000000: + case AIC3262_FREQ_12288000: + aic3262->sysclk = freq; + return 0; + case AIC3262_FREQ_24000000: + aic3262->sysclk = freq; + return 0; + break; + } + printk(KERN_ERR "Invalid frequency to set DAI system clock\n"); + return -EINVAL; +} + +/* +* aic3262_multi_i2s_set_pll +* +* This function is invoked as part of the PLL call-back +* handler from the ALSA layer. +*/ +static int aic3262_multi_i2s_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, int source, unsigned int freq_in, + unsigned int freq_out) +{ + /*u16 reg, enable; + int offset; + struct snd_soc_codec *codec = codec_dai->codec;*/ + + printk(KERN_INFO "%s: DAI ID %d PLL_ID %d InFreq %d OutFreq %d\n", + __func__, pll_id, codec_dai->id, freq_in, freq_out); + + return 0; +} + +/* +* aic3262_asi1_clk_config +* +* This function is used to configure the BCLK1, WCLK1 pins which +* are specific to ASI1 Interface. This function just enables the +* BCLk and WCLK along with the miniDSP Port Control Registers. +* However, depending on the user requirement, this function can also be +* extended to configure the sourc for the BCLK and WCLK on a ASI basis. +*/ +static int aic3262_asi1_clk_config(struct snd_soc_codec *codec, + struct snd_pcm_hw_params *params) +{ + u8 bclk_N_value, wclk_N_value; + u8 minidspD_data, minidspA_data; + u8 regval; + + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "%s: Invoked\n", __func__); + + if (aic3262->asiCtxt[0].master == 1) { + DBG(KERN_INFO + "#%s: Codec Master on ASI1 Port. Enabling BCLK WCLK Divider.\n", + __func__); + bclk_N_value = aic3262->asiCtxt[0].bclk_div; + aic3262_write(codec, ASI1_BCLK_N, (bclk_N_value | 0x80)); + + wclk_N_value = aic3262_read(codec, ASI1_WCLK_N); + aic3262_write(codec, ASI1_WCLK_N, (wclk_N_value | 0xA0)); + } + /* Configure the BCLK and WCLK Output Mux Options */ + regval = aic3262_read(codec, ASI1_BWCLK_OUT_CNTL); + regval &= ~(AIC3262_ASI_BCLK_MUX_MASK | AIC3262_ASI_WCLK_MUX_MASK); + + regval |= (aic3262->asiCtxt[0].bclk_output << + AIC3262_ASI_BCLK_MUX_SHIFT); + regval |= aic3262->asiCtxt[0].wclk_output; + aic3262_write(codec, ASI1_BWCLK_OUT_CNTL, regval); + + /* Configure the corresponding miniDSP Data Ports */ + minidspD_data = aic3262_read(codec, MINIDSP_PORT_CNTL_REG); + minidspD_data &= ~(BIT5 | BIT4); + aic3262_write(codec, MINIDSP_PORT_CNTL_REG, minidspD_data); + + minidspA_data = aic3262_read(codec, ASI1_ADC_INPUT_CNTL); + minidspA_data &= ~(BIT2 | BIT1 | BIT0); + minidspA_data |= aic3262->asiCtxt[0].adc_input; + aic3262_write(codec, ASI1_ADC_INPUT_CNTL, minidspA_data); + + return 0; + +} + +/* +* aic3262_asi2_clk_config +* +* This function is used to configure the BCLK2, WCLK2 pins which +* are specific to ASI2 Interface. This function just enables the +* BCLk and WCLK along with the miniDSP Port Control Registers. +* However, depending on the user requirement, this function can also be +* extended to configure the sourc for the BCLK and WCLK on a ASI basis. +*/ +static int aic3262_asi2_clk_config(struct snd_soc_codec *codec, + struct snd_pcm_hw_params *params) +{ + u8 bclk_N_value, wclk_N_value, minidspD_data, minidspA_data; + u8 regval; + + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "%s: Invoked\n", __func__); + + if (aic3262->asiCtxt[1].master == 1) { + DBG(KERN_INFO + "#%s: Codec Master on ASI2 Port. Enabling BCLK WCLK Divider.\n", + __func__); + bclk_N_value = aic3262->asiCtxt[1].bclk_div; + aic3262_write(codec, ASI2_BCLK_N, (bclk_N_value | 0x80)); + + wclk_N_value = aic3262_read(codec, ASI2_WCLK_N); + aic3262_write(codec, ASI2_WCLK_N, (wclk_N_value | 0xA0)); + } + /* Configure the BCLK and WCLK Output Mux Options */ + regval = aic3262_read(codec, ASI2_BWCLK_OUT_CNTL); + regval &= ~(AIC3262_ASI_BCLK_MUX_MASK | AIC3262_ASI_WCLK_MUX_MASK); + regval |= (aic3262->asiCtxt[1].bclk_output << + AIC3262_ASI_BCLK_MUX_SHIFT); + regval |= aic3262->asiCtxt[1].wclk_output; + + aic3262_write(codec, ASI2_BWCLK_OUT_CNTL, regval); + /* Configure the corresponding miniDSP Data Ports */ + minidspD_data = aic3262_read(codec, MINIDSP_PORT_CNTL_REG); + minidspD_data |= (BIT2); + aic3262_write(codec, MINIDSP_PORT_CNTL_REG, minidspD_data); + + minidspA_data = aic3262_read(codec, ASI2_ADC_INPUT_CNTL); + minidspA_data &= ~(BIT2 | BIT1 | BIT0); + minidspA_data |= aic3262->asiCtxt[1].adc_input; + aic3262_write(codec, ASI2_ADC_INPUT_CNTL, minidspA_data); + + return 0; + +} + +/* +* aic3262_asi3_clk_config +* +* This function is used to configure the BCLK3, WCLK3 pins which +* are specific to ASI3 Interface. This function just enables the +* BCLk and WCLK along with the miniDSP Port Control Registers. +* However, depending on the user requirement, this function can also be +* extended to configure the sourc for the BCLK and WCLK on a ASI basis. +*/ +static int aic3262_asi3_clk_config(struct snd_soc_codec *codec, + struct snd_pcm_hw_params *params) +{ + u8 bclk_N_value, wclk_N_value, minidspD_data, minidspA_data; + u8 regval; + + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "%s:\n", __func__); + + if (aic3262->asiCtxt[2].master == 1) { + DBG(KERN_INFO + "#%s: Codec Master on ASI3 Port. Enabling BCLK WCLK Divider.\n", + __func__); + bclk_N_value = aic3262->asiCtxt[2].bclk_div; + aic3262_write(codec, ASI2_BCLK_N, (bclk_N_value | 0x80)); + + wclk_N_value = aic3262_read(codec, ASI3_WCLK_N); + aic3262_write(codec, ASI3_WCLK_N, (wclk_N_value | 0xA0)); + } + + /* Configure the BCLK and WCLK Output Mux Options */ + regval = aic3262_read(codec, ASI3_BWCLK_OUT_CNTL); + regval &= ~(AIC3262_ASI_BCLK_MUX_MASK | AIC3262_ASI_WCLK_MUX_MASK); + regval |= (aic3262->asiCtxt[2].bclk_output << + AIC3262_ASI_BCLK_MUX_SHIFT); + regval |= aic3262->asiCtxt[2].wclk_output; + aic3262_write(codec, ASI3_BWCLK_OUT_CNTL, regval); + + minidspD_data = aic3262_read(codec, MINIDSP_PORT_CNTL_REG); + minidspD_data |= (BIT1); + aic3262_write(codec, MINIDSP_PORT_CNTL_REG, minidspD_data); + + minidspA_data = aic3262_read(codec, ASI3_ADC_INPUT_CNTL); + minidspA_data &= ~(BIT2 | BIT1 | BIT0); + minidspA_data |= aic3262->asiCtxt[2].adc_input; + aic3262_write(codec, ASI3_ADC_INPUT_CNTL, minidspA_data); + + return 0; + +} + +/* +* aic3262_multi_i2s_hw_params +* +* This function is used to configure the individual ASI port registers +* depending on the configuration passed on by the snd_pcm_hw_params +* structure. +* This function internally configures the ASI specific pins and clock +* Control Registers. +*/ +static int aic3262_multi_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int i, j; + u8 data; + u16 regoffset = 0; + u8 dacpath = 0; + u8 adcpath = 0; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#%s: Invoked for ASI%d Port for %s Mode\n", + __func__, dai->id, + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ? "Playback" : "Record"); + + i = aic3262_get_divs(aic3262->sysclk, params_rate(params)); + + i2c_verify_book0(codec); + + if (i < 0) { + printk(KERN_ERR "#%s: Sampling rate %d not supported\n", + __func__, params_rate(params)); + return i; + } + + aic3262_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Configure the PLL J, R D values only if none of the ASI + * Interfaces are Active. + */ + /*if (!(AIC3262_MULTI_ASI_ACTIVE(aic3262))) {*/ + if (1) { + DBG(KERN_INFO "#%s: None of the ASIs active yet...\n", + __func__); + /*We will fix R value to 1 and make P & J=K.D as variable */ + /* Setting P & R values are set to 1 and 1 at init*/ + + /* J value */ + aic3262_write(codec, PLL_J_REG, aic3262_divs[i].pll_j); + + /* MSB & LSB for D value */ + + aic3262_write(codec, PLL_D_MSB, (aic3262_divs[i].pll_d >> 8)); + aic3262_write(codec, PLL_D_LSB, + (aic3262_divs[i].pll_d & AIC3262_8BITS_MASK)); + + /* NDAC divider value */ + data = aic3262_read(codec, NDAC_DIV_POW_REG); + DBG(KERN_INFO "# reading NDAC = %d , NDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].ndac, data); + aic3262_write(codec, NDAC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].ndac))); + DBG(KERN_INFO "# writing NDAC = %d , NDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].ndac, + ((data & 0x80)|(aic3262_divs[i].ndac))); + + /* MDAC divider value */ + data = aic3262_read(codec, MDAC_DIV_POW_REG); + DBG(KERN_INFO "# reading MDAC = %d , MDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].mdac, data); + aic3262_write(codec, MDAC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].mdac))); + DBG(KERN_INFO "# writing MDAC = %d , MDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].mdac, ((data & 0x80)|(aic3262_divs[i].mdac))); + + /* DOSR MSB & LSB values */ + aic3262_write(codec, DOSR_MSB_REG, aic3262_divs[i].dosr >> 8); + DBG(KERN_INFO "# writing DOSR_MSB_REG = %d\n", + (aic3262_divs[i].dosr >> 8)); + aic3262_write(codec, DOSR_LSB_REG, + aic3262_divs[i].dosr & AIC3262_8BITS_MASK); + DBG(KERN_INFO "# writing DOSR_LSB_REG = %d\n", + (aic3262_divs[i].dosr & AIC3262_8BITS_MASK)); + + /* NADC divider value */ + data = aic3262_read(codec, NADC_DIV_POW_REG); + aic3262_write(codec, NADC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].nadc))); + DBG(KERN_INFO "# writing NADC_DIV_POW_REG = %d\n", + aic3262_divs[i].nadc); + + /* MADC divider value */ + data = aic3262_read(codec, MADC_DIV_POW_REG); + aic3262_write(codec, MADC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].madc))); + DBG(KERN_INFO "# writing MADC_DIV_POW_REG = %d\n", + aic3262_divs[i].madc); + + /* AOSR value */ + aic3262_write(codec, AOSR_REG, aic3262_divs[i].aosr); + DBG(KERN_INFO "# writing AOSR = %d\n", aic3262_divs[i].aosr); + } else { + DBG(KERN_INFO "#Atleast 1 ASI Active. Cannot Program PLL..\n"); + } + /* Check for the DAI ID to know which ASI needs + * Configuration. + */ + switch (dai->id) { + case 1: + regoffset = ASI1_BUS_FMT; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + DBG(KERN_INFO "#%s: ASI1 DAC Inputs enabled..\n", + __func__); + /* Read the DAC Control Register and configure it + * as per the ASIContext Structure Settings. + */ + dacpath = aic3262_read(codec, ASI1_DAC_OUT_CNTL); + dacpath &= ~(AIC3262_ASI_LDAC_PATH_MASK | + AIC3262_ASI_RDAC_PATH_MASK); + dacpath |= (aic3262->asiCtxt[0].left_dac_output + << AIC3262_ASI_LDAC_PATH_SHIFT); + + dacpath |= (aic3262->asiCtxt[0].right_dac_output + << AIC3262_ASI_RDAC_PATH_SHIFT); + aic3262_write(codec, ASI1_DAC_OUT_CNTL, dacpath); + + aic3262->asiCtxt[0].playback_mode = 1; + aic3262->asiCtxt[0].bclk_div = + aic3262_divs[i].blck_N; + } else { + /* For Recording, Configure the DOUT Pin as per + * ASIContext Structure Settings. + */ + adcpath = aic3262_read(codec, ASI1_DATA_OUT); + adcpath &= ~(AIC3262_ASI_DOUT_MASK); + + adcpath |= aic3262->asiCtxt[0].dout_option; + aic3262_write(codec, ASI1_DATA_OUT, adcpath); + + aic3262->asiCtxt[0].capture_mode = 1; + } + break; + case 2: + regoffset = ASI2_BUS_FMT; + + /* Since we are configuring ASI2, please check if Playback + * is expected. If so, enable ASI2 Inputs to Left and + * Right DACs + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + DBG(KERN_INFO "#%s: ASI2 DAC Inputs enabled..\n", + __func__); + /* Read the DAC Control Register and configure it + * as per theASIContext Structure Settings. + */ + dacpath = aic3262_read(codec, ASI2_DAC_OUT_CNTL); + dacpath &= ~(AIC3262_ASI_LDAC_PATH_MASK | + AIC3262_ASI_RDAC_PATH_MASK); + dacpath |= (aic3262->asiCtxt[1].left_dac_output + << AIC3262_ASI_LDAC_PATH_SHIFT); + + dacpath |= (aic3262->asiCtxt[1].right_dac_output + << AIC3262_ASI_RDAC_PATH_SHIFT); + aic3262_write(codec, ASI2_DAC_OUT_CNTL, dacpath); + aic3262->asiCtxt[1].playback_mode = 1; + + aic3262->asiCtxt[1].bclk_div = + aic3262_divs[i].blck_N; + } else { + /* For Recording, Configure the DOUT Pin as per + * ASIContext Structure Settings. + */ + adcpath = aic3262_read(codec, ASI2_DATA_OUT); + adcpath &= ~(AIC3262_ASI_DOUT_MASK); + adcpath |= aic3262->asiCtxt[1].dout_option; + aic3262_write(codec, ASI2_DATA_OUT, adcpath); + + aic3262->asiCtxt[1].capture_mode = 1; + } + break; + case 3: + regoffset = ASI3_BUS_FMT; + /* Since we are configuring ASI3, please check if Playback + * is expected. If so, enable ASI3 Inputs to Left and + * Right DACs + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + DBG(KERN_INFO "#%s:ASI3 DAC Inputs enabled.\n", + __func__); + /* Read the DAC Control Register and configure + * it as per the ASIContext Structure Settings. + */ + dacpath = aic3262_read(codec, ASI3_DAC_OUT_CNTL); + dacpath &= ~(AIC3262_ASI_LDAC_PATH_MASK | + AIC3262_ASI_RDAC_PATH_MASK); + dacpath |= (aic3262->asiCtxt[2].left_dac_output + << AIC3262_ASI_LDAC_PATH_SHIFT); + dacpath |= (aic3262->asiCtxt[2].right_dac_output + << AIC3262_ASI_RDAC_PATH_SHIFT); + aic3262_write(codec, + ASI3_DAC_OUT_CNTL, dacpath); + + aic3262->asiCtxt[2].playback_mode = 1; + + aic3262->asiCtxt[2].bclk_div = + aic3262_divs[i].blck_N; + } else { + /* For Recording, Configure the DOUT Pin as per + * ASIContext Structure Settings. + */ + adcpath &= ~(AIC3262_ASI_DOUT_MASK); + adcpath |= aic3262->asiCtxt[2].dout_option; + aic3262_write(codec, ASI3_DATA_OUT, adcpath); + + aic3262->asiCtxt[2].capture_mode = 1; + } + break; + default: + printk(KERN_ERR "Invalid Dai ID %d in %s", + dai->id, __func__); + break; + } + DBG(KERN_INFO "#%s: Reading Pg %d Reg %d for Bus Format Control.\n", + __func__, (regoffset/128), (regoffset % 128)); + + /* Read the correspondig ASI DAI Interface Register */ + data = aic3262_read(codec, regoffset); + + data = data & 0xe7; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + DBG(KERN_INFO "#%s: Configuring ASI%d S16_LE Fmt..\n", + __func__, dai->id); + data = data | 0x00; + aic3262->asiCtxt[dai->id - 1].word_len = 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (0x08); + aic3262->asiCtxt[dai->id - 1].word_len = 20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + DBG(KERN_INFO "#%s: Configuring ASI%d S24_LE Fmt..\n", + __func__, dai->id); + data |= (0x10); + aic3262->asiCtxt[dai->id - 1].word_len = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + DBG(KERN_INFO "#%s: Configuring ASI%d S32_LE Fmt..\n", + __func__, dai->id); + data |= (0x18); + aic3262->asiCtxt[dai->id - 1].word_len = 32; + break; + } + + /* configure the respective Registers for the above configuration */ + aic3262_write(codec, regoffset, data); + + for (j = 0; j < NO_FEATURE_REGS; j++) { + aic3262_write(codec, + aic3262_divs[i].codec_specific_regs[j].reg_offset, + aic3262_divs[i].codec_specific_regs[j].reg_val); + } + + /* Enable the PLL, MDAC, NDAC, NADC, MADC and BCLK Dividers */ + aic3262_set_bias_level(codec, SND_SOC_BIAS_ON); + + /* Based on the DAI ID we enable the corresponding pins related to the + * ASI Port. + */ + switch (dai->id) { + case 1: + aic3262_asi1_clk_config(codec, params); + break; + case 2: + aic3262_asi2_clk_config(codec, params); + break; + case 3: + aic3262_asi3_clk_config(codec, params); + break; + default: + printk(KERN_ERR "Invalid Dai ID %d in %s", + dai->id, __func__); + break; + } + /* Depending on the DAI->ID update the local Flags */ + aic3262->asiCtxt[dai->id - 1].asi_active++; + aic3262->asiCtxt[dai->id - 1].sampling_rate = params_rate(params); + /* Update the active_count flag */ + aic3262->active_count++; + + return 0; +} + +/* +* +* aic3262_multi_i2s_hw_free +* +* This function is used to configure the Codec after the usage is completed. +* We can use this function to disable the DAC and ADC specific inputs from the +* individual ASI Ports of the Audio Codec. +*/ +static int aic3262_multi_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + u8 value; + u8 dacpath; + u8 adcpath; + u16 dacregoffset = 0; + u16 adcregoffset = 0; + + DBG(KERN_INFO "#%s: ASI%d Port for %s Mode\n", + __func__, dai->id, + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + "Playback" : "Record"); + + /* Check if this function was already executed earlier for the same + * ASI Port + */ + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + (aic3262->asiCtxt[dai->id - 1].playback_mode == 0)) { + DBG(KERN_INFO "#%s: Function Already Executed. Exiting..\n", + __func__); + goto err; + } else if ((substream->stream != SNDRV_PCM_STREAM_PLAYBACK) && + (aic3262->asiCtxt[dai->id - 1].capture_mode == 0)) { + DBG(KERN_INFO "#%s: Function Already Executed. Exiting..\n", + __func__); + goto err; + } + + switch (dai->id) { + case 1: + /* In case we are Frame Master on this Interface, Switch off + * the Bit Clock Divider and Word Clock Dividers + */ + if (aic3262->asiCtxt[0].master == 1) { + /* Also check if either Playback or Recording is still + * going on this ASI Interface + */ + + value = aic3262_read(codec, ASI1_BCLK_N); + aic3262_write(codec, ASI1_BCLK_N, (value & 0x7f)); + + value = aic3262_read(codec, ASI1_WCLK_N); + aic3262_write(codec, ASI1_WCLK_N, (value & 0x7f)); + } + + dacregoffset = ASI1_DAC_OUT_CNTL; + adcregoffset = ASI1_ADC_INPUT_CNTL; + break; + case 2: + /* In case we are Frame Master on this Interface, Switch off + * the Bit Clock Divider and Word Clock Dividers + */ + if (aic3262->asiCtxt[1].master == 1) { + value = aic3262_read(codec, ASI2_BCLK_N); + aic3262_write(codec, ASI2_BCLK_N, (value & 0x7f)); + + value = aic3262_read(codec, ASI2_WCLK_N); + aic3262_write(codec, ASI2_WCLK_N, (value & 0x7f)); + } + dacregoffset = ASI2_DAC_OUT_CNTL; + adcregoffset = ASI2_ADC_INPUT_CNTL; + break; + case 3: + /* In case we are Frame Master on this Interface, Switch off + * the Bit Clock Divider and Word Clock Dividers + */ + if (aic3262->asiCtxt[2].master == 1) { + value = aic3262_read(codec, ASI3_BCLK_N); + aic3262_write(codec, ASI3_BCLK_N, (value & 0x7f)); + + value = aic3262_read(codec, ASI3_WCLK_N); + aic3262_write(codec, ASI3_WCLK_N, (value & 0x7f)); + } + dacregoffset = ASI3_DAC_OUT_CNTL; + adcregoffset = ASI3_ADC_INPUT_CNTL; + break; + default: + printk(KERN_ERR "#%s: Invalid dai id\n", __func__); + } + /* If this was a Playback Stream Stop, then only + * switch off the DAC Inputs + */ + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + (dacregoffset != 0)) { + printk(KERN_INFO "#%s: Disabling Pg %d Reg %d DAC Inputs ..\n", + __func__, (dacregoffset/128), (dacregoffset % 128)); + + dacpath = aic3262_read(codec, dacregoffset); + aic3262_write(codec, dacregoffset, (dacpath & ~(BIT6 | BIT4))); + + aic3262->asiCtxt[dai->id - 1].playback_mode = 0; + } else { + /* Switch off the ADC Input Control Registers here */ + DBG(KERN_INFO "#%s: Disabling Pg %d Reg %d for ADC Inputs..\n", + __func__, (adcregoffset/128), (adcregoffset % 128)); + + adcpath = aic3262_read(codec, adcregoffset); + aic3262_write(codec, adcregoffset, + (adcpath & ~(BIT2 | BIT1 | BIT0))); + + aic3262->asiCtxt[dai->id - 1].capture_mode = 0; + } + + /* If we were configured in mono PCM Mode earlier, then reset the + * Left Channel and Right Channel offset Registers here. + */ + switch (dai->id) { + case 1: + if (aic3262->asiCtxt[0].pcm_format == SND_SOC_DAIFMT_DSP_B) { + aic3262_write(codec, ASI1_LCH_OFFSET, 0x00); + aic3262_write(codec, ASI1_RCH_OFFSET, 0x00); + } + break; + case 2: + if (aic3262->asiCtxt[1].pcm_format == SND_SOC_DAIFMT_DSP_B) { + aic3262_write(codec, ASI2_LCH_OFFSET, 0x00); + aic3262_write(codec, ASI2_RCH_OFFSET, 0x00); + } + + break; + case 3: + if (aic3262->asiCtxt[2].pcm_format == SND_SOC_DAIFMT_DSP_B) { + aic3262_write(codec, ASI3_LCH_OFFSET, 0x00); + aic3262_write(codec, ASI3_RCH_OFFSET, 0x00); + } + break; + } + /* Depending on the DAI->ID update the asi_active Flags */ + if (aic3262->asiCtxt[dai->id - 1].asi_active) { + aic3262->asiCtxt[dai->id - 1].asi_active--; + + /* Update the active_count flag */ + if (aic3262->active_count) + aic3262->active_count--; + } +err: + return 0; +} + +/* + *---------------------------------------------------------------------------- + * @struct snd_soc_codec_dai | + * It is SoC Codec DAI structure which has DAI capabilities viz., + * playback and capture, DAI runtime information viz. state of DAI + * and pop wait state, and DAI private data. + * The AIC3262 rates ranges from 8k to 192k + * The PCM bit format supported are 16, 20, 24 and 32 bits + *---------------------------------------------------------------------------- + */ +struct snd_soc_dai_ops aic3262_dai_ops = { + .hw_params = aic3262_hw_params, + .digital_mute = aic3262_mute, + .set_sysclk = aic3262_set_dai_sysclk, + .set_fmt = aic3262_set_dai_fmt, +}; + +struct snd_soc_dai_ops aic3262_multi_i2s_dai_ops = { + .hw_params = aic3262_multi_i2s_hw_params, + .digital_mute = aic3262_multi_i2s_mute, + .set_fmt = aic3262_multi_i2s_set_dai_fmt, + .set_pll = aic3262_multi_i2s_set_dai_pll, + .set_sysclk = aic3262_multi_i2s_set_dai_sysclk, + .hw_free = aic3262_multi_i2s_hw_free, +}; + + +static struct snd_soc_dai_driver tlv320aic3262_dai[] = { +/* AIC3262 ASI1 DAI */ +{ + .name = "aic3262-asi1", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3262_RATES, + .formats = AIC3262_FORMATS}, + .capture = { /* dummy for fast DAI switching */ + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3262_RATES, + .formats = AIC3262_FORMATS}, + .ops = &aic3262_multi_i2s_dai_ops, +}, +/* AIC3262 ASI2 DAI */ +{ + .name = "aic3262-asi2", + .id = 2, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3262_RATES, + .formats = AIC3262_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3262_RATES, + .formats = AIC3262_FORMATS,}, + .ops = &aic3262_multi_i2s_dai_ops, + +}, +/* AIC3262 ASI3 DAI */ +{ + .name = "aic3262-asi3", + .id = 3, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3262_RATES, + .formats = AIC3262_FORMATS, }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3262_RATES, + .formats = AIC3262_FORMATS, }, + .ops = &aic3262_multi_i2s_dai_ops, + +}, +}; + +/* + ***************************************************************************** + * Initializations + ***************************************************************************** + */ +/* + * AIC3262 register cache + * We are caching the registers here. + * There is no point in caching the reset register. + * + * NOTE: In AIC3262, there are 127 registers supported in both page0 and page1 + * The following table contains the page0 and page 1 and page 3 + * registers values. + */ +static const u8 aic3262_reg[AIC3262_CACHEREGNUM] = { + 0x00, 0x00, 0x10, 0x00, /* 0 */ + 0x03, 0x40, 0x11, 0x08, /* 4 */ + 0x00, 0x00, 0x00, 0x82, /* 8 */ + 0x88, 0x00, 0x80, 0x02, /* 12 */ + 0x00, 0x08, 0x01, 0x01, /* 16 */ + 0x80, 0x01, 0x00, 0x04, /* 20 */ + 0x00, 0x00, 0x01, 0x00, /* 24 */ + 0x00, 0x00, 0x01, 0x00, /* 28 */ + 0x00, 0x00, 0x00, 0x00, /* 32 */ + 0x00, 0x00, 0x00, 0x00, /* 36 */ + 0x00, 0x00, 0x00, 0x00, /* 40 */ + 0x00, 0x00, 0x00, 0x00, /* 44 */ + 0x00, 0x00, 0x00, 0x00, /* 48 */ + 0x00, 0x42, 0x02, 0x02, /* 52 */ + 0x42, 0x02, 0x02, 0x02, /* 56 */ + 0x00, 0x00, 0x00, 0x01, /* 60 */ + 0x01, 0x00, 0x14, 0x00, /* 64 */ + 0x0C, 0x00, 0x00, 0x00, /* 68 */ + 0x00, 0x00, 0x00, 0xEE, /* 72 */ + 0x10, 0xD8, 0x10, 0xD8, /* 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 */ + 0x01, 0x00, 0x08, 0x00, /* 128, PAGE1-0 */ + 0x00, 0x00, 0x00, 0x00, /* 132, PAGE1-4 */ + 0x00, 0x00, 0x00, 0x10, /* 136, PAGE1-8 */ + 0x00, 0x00, 0x00, 0x00, /* 140, PAGE1-12 */ + 0x40, 0x40, 0x40, 0x40, /* 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 */ + 0x00, 0x00, 0x00, 0x00, /* 180, PAGE1-52 */ + 0x00, 0x00, 0x00, 0x80, /* 184, PAGE1-56 */ + 0x80, 0x00, 0x00, 0x00, /* 188, PAGE1-60 */ + 0x00, 0x00, 0x00, 0x00, /* 192, PAGE1-64 */ + 0x00, 0x00, 0x00, 0x00, /* 196, PAGE1-68 */ + 0x00, 0x00, 0x00, 0x00, /* 200, PAGE1-72 */ + 0x00, 0x00, 0x00, 0x00, /* 204, PAGE1-76 */ + 0x00, 0x00, 0x00, 0x00, /* 208, PAGE1-80 */ + 0x00, 0x00, 0x00, 0x00, /* 212, PAGE1-84 */ + 0x00, 0x00, 0x00, 0x00, /* 216, PAGE1-88 */ + 0x00, 0x00, 0x00, 0x00, /* 220, PAGE1-92 */ + 0x00, 0x00, 0x00, 0x00, /* 224, PAGE1-96 */ + 0x00, 0x00, 0x00, 0x00, /* 228, PAGE1-100 */ + 0x00, 0x00, 0x00, 0x00, /* 232, PAGE1-104 */ + 0x00, 0x00, 0x00, 0x00, /* 236, PAGE1-108 */ + 0x00, 0x00, 0x00, 0x00, /* 240, PAGE1-112 */ + 0x00, 0x00, 0x00, 0x00, /* 244, PAGE1-116 */ + 0x00, 0x00, 0x00, 0x00, /* 248, PAGE1-120 */ + 0x00, 0x00, 0x00, 0x00, /* 252, PAGE1-124 Page 1 Registers Ends Here */ + 0x00, 0x00, 0x00, 0x00, /* 256, PAGE2-0 */ + 0x00, 0x00, 0x00, 0x00, /* 260, PAGE2-4 */ + 0x00, 0x00, 0x00, 0x00, /* 264, PAGE2-8 */ + 0x00, 0x00, 0x00, 0x00, /* 268, PAGE2-12 */ + 0x00, 0x00, 0x00, 0x00, /* 272, PAGE2-16 */ + 0x00, 0x00, 0x00, 0x00, /* 276, PAGE2-20 */ + 0x00, 0x00, 0x00, 0x00, /* 280, PAGE2-24 */ + 0x00, 0x00, 0x00, 0x00, /* 284, PAGE2-28 */ + 0x00, 0x00, 0x00, 0x00, /* 288, PAGE2-32 */ + 0x00, 0x00, 0x00, 0x00, /* 292, PAGE2-36 */ + 0x00, 0x00, 0x00, 0x00, /* 296, PAGE2-40 */ + 0x00, 0x00, 0x00, 0x00, /* 300, PAGE2-44 */ + 0x00, 0x00, 0x00, 0x00, /* 304, PAGE2-48 */ + 0x00, 0x00, 0x00, 0x00, /* 308, PAGE2-52 */ + 0x00, 0x00, 0x00, 0x00, /* 312, PAGE2-56 */ + 0x00, 0x00, 0x00, 0x00, /* 316, PAGE2-60 */ + 0x00, 0x00, 0x00, 0x00, /* 320, PAGE2-64 */ + 0x00, 0x00, 0x00, 0x00, /* 324, PAGE2-68 */ + 0x00, 0x00, 0x00, 0x00, /* 328, PAGE2-72 */ + 0x00, 0x00, 0x00, 0x00, /* 332, PAGE2-76 */ + 0x00, 0x00, 0x00, 0x00, /* 336, PAGE2-80 */ + 0x00, 0x00, 0x00, 0x00, /* 340, PAGE2-84 */ + 0x00, 0x00, 0x00, 0x00, /* 344, PAGE2-88 */ + 0x00, 0x00, 0x00, 0x00, /* 348, PAGE2-92 */ + 0x00, 0x00, 0x00, 0x00, /* 352, PAGE2-96 */ + 0x00, 0x00, 0x00, 0x00, /* 356, PAGE2-100 */ + 0x00, 0x00, 0x00, 0x00, /* 360, PAGE2-104 */ + 0x00, 0x00, 0x00, 0x00, /* 364, PAGE2-108 */ + 0x00, 0x00, 0x00, 0x00, /* 368, PAGE2-112*/ + 0x00, 0x00, 0x00, 0x00, /* 372, PAGE2-116*/ + 0x00, 0x00, 0x00, 0x00, /* 376, PAGE2-120*/ + 0x00, 0x00, 0x00, 0x00, /* 380, PAGE2-124 Page 2 Registers Ends Here */ + 0x00, 0x00, 0x00, 0x00, /* 384, PAGE3-0 */ + 0x00, 0x00, 0x00, 0x00, /* 388, PAGE3-4 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-8 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-12 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-16 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-20 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-24 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-28 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-32 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-36 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-40 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-44 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-48 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-52 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-56 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-60 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-64 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE3-68 */ + 0x00, 0x00, 0x00, 0x00, /* 328, PAGE3-72 */ + 0x00, 0x00, 0x00, 0x00, /* 332, PAGE3-76 */ + 0x00, 0x00, 0x00, 0x00, /* 336, PAGE3-80 */ + 0x00, 0x00, 0x00, 0x00, /* 340, PAGE3-84 */ + 0x00, 0x00, 0x00, 0x00, /* 344, PAGE3-88 */ + 0x00, 0x00, 0x00, 0x00, /* 348, PAGE3-92 */ + 0x00, 0x00, 0x00, 0x00, /* 352, PAGE3-96 */ + 0x00, 0x00, 0x00, 0x00, /* 356, PAGE3-100 */ + 0x00, 0x00, 0x00, 0x00, /* 360, PAGE3-104 */ + 0x00, 0x00, 0x00, 0x00, /* 364, PAGE3-108 */ + 0x00, 0x00, 0x00, 0x00, /* 368, PAGE3-112*/ + 0x00, 0x00, 0x00, 0x00, /* 372, PAGE3-116*/ + 0x00, 0x00, 0x00, 0x00, /* 376, PAGE3-120*/ + 0x00, 0x00, 0x00, 0x00, /* 380, PAGE3-124 Page 3 Registers Ends Here */ + 0x00, 0x00, 0x00, 0x00, /* 384, PAGE4-0 */ + 0x00, 0x00, 0x00, 0x00, /* 388, PAGE4-4 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-8 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-12 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-16 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-20 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-24 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-28 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-32 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-36 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-40 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-44 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-48 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-52 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-56 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-60 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-64 */ + 0x00, 0x00, 0x00, 0x00, /* 392, PAGE4-68 */ + 0x00, 0x00, 0x00, 0x00, /* 328, PAGE4-72 */ + 0x00, 0x00, 0x00, 0x00, /* 332, PAGE4-76 */ + 0x00, 0x00, 0x00, 0x00, /* 336, PAGE4-80 */ + 0x00, 0x00, 0x00, 0x00, /* 340, PAGE4-84 */ + 0x00, 0x00, 0x00, 0x00, /* 344, PAGE4-88 */ + 0x00, 0x00, 0x00, 0x00, /* 348, PAGE4-92 */ + 0x00, 0x00, 0x00, 0x00, /* 352, PAGE4-96 */ + 0x00, 0x00, 0x00, 0x00, /* 356, PAGE4-100 */ + 0x00, 0x00, 0x00, 0x00, /* 360, PAGE4-104 */ + 0x00, 0x00, 0x00, 0x00, /* 364, PAGE4-108 */ + 0x00, 0x00, 0x00, 0x00, /* 368, PAGE4-112*/ + 0x00, 0x00, 0x00, 0x00, /* 372, PAGE4-116*/ + 0x00, 0x00, 0x00, 0x00, /* 376, PAGE4-120*/ + 0x00, 0x00, 0x00, 0x00, /* 380, PAGE4-124 Page 2 Registers Ends Here */ + +}; + +/* + *------------------------------------------------------------------------------ + * aic3262 initialization data + * This structure initialization contains the initialization required for + * AIC326x. + * These registers values (reg_val) are written into the respective AIC3262 + * register offset (reg_offset) to initialize AIC326x. + * These values are used in aic3262_init() function only. + *------------------------------------------------------------------------------ + */ +static const struct aic3262_configs aic3262_reg_init[] = { + /* CLOCKING */ + + {0, RESET_REG, 1}, + {0, RESET_REG, 0}, + + {0, PASI_DAC_DP_SETUP, 0xc0}, /*DAC */ + {0, DAC_MVOL_CONF, 0x00}, /*DAC un-muted*/ + /* set default volumes */ + {0, DAC_LVOL, 0x01}, + {0, DAC_RVOL, 0x01}, + {0, HPL_VOL, 0x3a}, + {0, HPR_VOL, 0x3a}, + {0, SPK_AMP_CNTL_R2, 0x14}, + {0, SPK_AMP_CNTL_R3, 0x14}, + {0, SPK_AMP_CNTL_R4, 0x33}, + {0, REC_AMP_CNTL_R5, 0x82}, + {0, RAMPR_VOL, 20}, + {0, RAMP_CNTL_R1, 70}, + {0, RAMP_CNTL_R2, 70}, + + /* DRC Defaults */ + {0, DRC_CNTL_R1, 0x6c}, + {0, DRC_CNTL_R2, 16}, + + /* DEPOP SETTINGS */ + {0, HP_DEPOP, 0x14}, + {0, RECV_DEPOP, 0x14}, + + {0, POWER_CONF, 0x00}, /* Disconnecting AVDD-DVD weak link*/ + {0, REF_PWR_DLY, 0x01}, + {0, CM_REG, 0x00}, /*CM - default*/ + {0, LDAC_PTM, 0}, /*LDAC_PTM - default*/ + {0, RDAC_PTM, 0}, /*RDAC_PTM - default*/ + {0, HP_CTL, 0x30}, /*HP output percentage - at 75%*/ + {0, LADC_VOL, 0x01}, /*LADC volume*/ + {0, RADC_VOL, 0x01}, /*RADC volume*/ + + {0, DAC_ADC_CLKIN_REG, 0x33}, /*DAC ADC CLKIN*/ + {0, PLL_CLKIN_REG, 0x00}, /*PLL CLKIN*/ + {0, PLL_PR_POW_REG, 0x11}, /*PLL Power=0-down, P=1, R=1 vals*/ + {0, 0x3d, 1}, + + {0, LMIC_PGA_PIN, 0x55}, /*IN1_L select - - 10k -LMICPGA_P*/ + {0, LMIC_PGA_MIN, 0x40}, /*CM to LMICPGA-M*/ + {0, RMIC_PGA_PIN, 0x55}, /*IN1_R select - - 10k -RMIC_PGA_P*/ + {0, RMIC_PGA_MIN, 0x40}, /*CM to RMICPGA_M*/ + {0, (PAGE_1 + 0x79), 33}, /*LMIC-PGA-POWERUP-DELAY - default*/ + {0, (PAGE_1 + 0x7a), 1}, /*FIXMELATER*/ + + + {0, ADC_CHANNEL_POW, 0xc2}, /*ladc, radc ON , SOFT STEP disabled*/ + {0, ADC_FINE_GAIN, 0x00}, /*ladc - unmute, radc - unmute*/ + {0, MICL_PGA, 0x4f}, + {0, MICR_PGA, 0x4f}, + {0, MIC_BIAS_CNTL, 0xFC}, + /* ASI1 Configuration */ + {0, ASI1_BUS_FMT, 0}, + {0, ASI1_BWCLK_CNTL_REG, 0x00}, /* originaly 0x24*/ + {0, ASI1_BCLK_N_CNTL, 1}, + {0, ASI1_BCLK_N, 0x84}, + + {0, MA_CNTL, 0}, /* Mixer Amp disabled */ + {0, LINE_AMP_CNTL_R2, 0x00}, /* Line Amp Cntl disabled */ + + /* ASI2 Configuration */ + {0, ASI2_BUS_FMT, 0}, + {0, ASI2_BCLK_N_CNTL, 1}, + {0, ASI2_BCLK_N, 0x84}, + {0, ASI2_BWCLK_OUT_CNTL, 0x20}, + + {0, BEEP_CNTL_R1, 0x05}, + {0, BEEP_CNTL_R2, 0x04}, + + /* Interrupt config for headset detection */ + {0, INT1_CNTL, 0x80}, + {0, INT_FMT, 0x40}, + {0, GPIO1_IO_CNTL, 0x14}, + {0, HP_DETECT, 0x94}, + +#if defined(CONFIG_MINI_DSP) + {0, 60, 0}, + {0, 61, 0}, + /* Added the below set of values after consulting the miniDSP + * Program Section Array + */ + {0, MINIDSP_ACCESS_CTRL, 0x00}, +#endif + + +}; + +static int reg_init_size = + sizeof(aic3262_reg_init) / sizeof(struct aic3262_configs); + +static const struct snd_kcontrol_new aic3262_snd_controls2[] = { + + SOC_DOUBLE_R("IN1 MPGA Route", LMIC_PGA_PIN, RMIC_PGA_PIN, 6, 3, 0), + SOC_DOUBLE_R("IN2 MPGA Route", LMIC_PGA_PIN, RMIC_PGA_PIN, 4, 3, 0), + SOC_DOUBLE_R("IN3 MPGA Route", LMIC_PGA_PIN, RMIC_PGA_PIN, 2, 3, 0), + SOC_DOUBLE_R("IN4 MPGA Route", + LMIC_PGA_PM_IN4, RMIC_PGA_PM_IN4, 5, 1, 0), +}; +static const struct snd_soc_dapm_widget aic3262_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Playback", PASI_DAC_DP_SETUP, 7, 0), + SND_SOC_DAPM_DAC("Right DAC", "Playback", PASI_DAC_DP_SETUP, 6, 0), + + SND_SOC_DAPM_SWITCH_N("LDAC_2_HPL", HP_AMP_CNTL_R1, 5, 0), + SND_SOC_DAPM_SWITCH_N("RDAC_2_HPR", HP_AMP_CNTL_R1, 4, 0), + + SND_SOC_DAPM_PGA("HPL Driver", HP_AMP_CNTL_R1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HPR Driver", HP_AMP_CNTL_R1, 0, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH_N("LDAC_2_LOL", LINE_AMP_CNTL_R1, 7, 0), + SND_SOC_DAPM_SWITCH_N("RDAC_2_LOR", LINE_AMP_CNTL_R1, 6, 0), + + SND_SOC_DAPM_PGA("LOL Driver", LINE_AMP_CNTL_R1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("LOR Driver", LINE_AMP_CNTL_R1, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("SPKL Driver", SPK_AMP_CNTL_R1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPKR Driver", SPK_AMP_CNTL_R1, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("RECL Driver", REC_AMP_CNTL_R5, 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("RECR Driver", REC_AMP_CNTL_R5, 6, 0, NULL, 0), + + SND_SOC_DAPM_ADC("Left ADC", "Capture", ADC_CHANNEL_POW, 7, 0), + SND_SOC_DAPM_ADC("Right ADC", "Capture", ADC_CHANNEL_POW, 6, 0), + + SND_SOC_DAPM_SWITCH("IN1L Route", + LMIC_PGA_PIN, 6, 0, &aic3262_snd_controls2[0]), + SND_SOC_DAPM_SWITCH("IN2L Route", + LMIC_PGA_PIN, 4, 0, &aic3262_snd_controls2[1]), + SND_SOC_DAPM_SWITCH("IN3L Route", + LMIC_PGA_PIN, 2, 0, &aic3262_snd_controls2[2]), + SND_SOC_DAPM_SWITCH("IN4L Route", + LMIC_PGA_PM_IN4, 5, 0, &aic3262_snd_controls2[3]), + SND_SOC_DAPM_SWITCH("IN1R Route", + RMIC_PGA_PIN, 6, 0, &aic3262_snd_controls2[4]), + SND_SOC_DAPM_SWITCH("IN2R Route", + RMIC_PGA_PIN, 4, 0, &aic3262_snd_controls2[5]), + SND_SOC_DAPM_SWITCH("IN3R Route", + RMIC_PGA_PIN, 2, 0, &aic3262_snd_controls2[6]), + SND_SOC_DAPM_SWITCH("IN4R Route", + RMIC_PGA_PM_IN4, 5, 0, &aic3262_snd_controls2[7]), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LOL"), + SND_SOC_DAPM_OUTPUT("LOR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("RECL"), + SND_SOC_DAPM_OUTPUT("RECR"), + + SND_SOC_DAPM_INPUT("IN1L"), + SND_SOC_DAPM_INPUT("IN2L"), + SND_SOC_DAPM_INPUT("IN3L"), + SND_SOC_DAPM_INPUT("IN4L"), + + SND_SOC_DAPM_INPUT("IN1R"), + SND_SOC_DAPM_INPUT("IN2R"), + SND_SOC_DAPM_INPUT("IN3R"), + SND_SOC_DAPM_INPUT("IN4R"), +}; + +static const struct snd_soc_dapm_route aic3262_dapm_routes[] = { + {"LDAC_2_HPL", NULL, "Left DAC"}, + {"HPL Driver", NULL, "LDAC_2_HPL"}, + {"HPL", NULL, "HPL Driver"}, + {"RDAC_2_HPR", NULL, "Right DAC"}, + {"HPR Driver", NULL, "RDAC_2_HPR"}, + {"HPR", NULL, "HPR Driver"}, + + {"LDAC_2_LOL", NULL, "Left DAC"}, + {"LOL Driver", NULL, "LDAC_2_LOL"}, + {"LOL", NULL, "LOL Driver"}, + {"RDAC_2_LOR", NULL, "Right DAC"}, + {"LOR Driver", NULL, "RDAC_2_LOR"}, + {"LOR", NULL, "LOR Driver"}, + + {"SPKL Driver", NULL, "LOL"}, + {"SPKL", NULL, "SPKL Driver"}, + {"SPKR Driver", NULL, "LOR"}, + {"SPKR", NULL, "SPKR Driver"}, + + {"RECL Driver", NULL, "LOL"}, + {"RECL", NULL, "RECL Driver"}, + {"RECR Driver", NULL, "LOR"}, + {"RECR", NULL, "RECR Driver"}, + + {"Left ADC", "IN1L Route", "IN1L"}, + {"Left ADC", "IN2L Route", "IN2L"}, + {"Left ADC", "IN3L Route", "IN3L"}, + {"Left ADC", "IN4L Route", "IN4L"}, + + {"Right ADC", "IN1R Route", "IN1R"}, + {"Right ADC", "IN2R Route", "IN2R"}, + {"Right ADC", "IN3R Route", "IN3R"}, + {"Right ADC", "IN4R Route", "IN4R"}, +/* + {"LOL Driver", NULL, "IN1L"}, + {"LOR Driver", NULL, "IN1R"}, +*/ +}; + +/* + ***************************************************************************** + * Function Definitions + ***************************************************************************** + */ + + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_change_page + * Purpose : This function is to switch between page 0 and page 1. + * + *---------------------------------------------------------------------------- + */ +int aic3262_change_page(struct snd_soc_codec *codec, u8 new_page) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 data[2]; + int ret = 0; + + data[0] = 0; + data[1] = new_page; + aic3262->page_no = new_page; + + ret = snd_soc_write(codec, data[0], data[1]); + if (ret) + printk(KERN_ERR "Error in changing page to %d\n", new_page); + + DBG("# Changing page to %d\r\n", new_page); + + return ret; +} +/* + *---------------------------------------------------------------------------- + * Function : aic3262_change_book + * Purpose : This function is to switch between books + * + *---------------------------------------------------------------------------- + */ +int aic3262_change_book(struct snd_soc_codec *codec, u8 new_book) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 data[2]; + int ret = 0; + + data[0] = 0x7F; + data[1] = new_book; + aic3262->book_no = new_book; + + ret = aic3262_change_page(codec, 0); + if (ret) + return ret; + + ret = snd_soc_write(codec, data[0], data[1]); + if (ret) + printk(KERN_ERR "Error in changing Book\n"); + + DBG("# Changing book to %d\r\n", new_book); + + return ret; +} +/* + *---------------------------------------------------------------------------- + * Function : aic3262_write_reg_cache + * Purpose : This function is to write aic3262 register cache + * + *---------------------------------------------------------------------------- + */ +void aic3262_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, u8 value) +{ + u8 *cache = codec->reg_cache; + + if (reg >= AIC3262_CACHEREGNUM) + return; + + if (cache) + cache[reg] = value; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_read + * Purpose : This function is to read the aic3262 register space. + * + *---------------------------------------------------------------------------- + */ +u8 aic3262_read(struct snd_soc_codec *codec, u16 reg) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 value; + u8 page = reg / 128; + + reg = reg % 128; + + if (aic3262->page_no != page) + aic3262_change_page(codec, page); + + value = snd_soc_read(codec, reg); + /*DBG("r %2x %02x\r\n", reg, value);*/ + return value; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_write + * Purpose : This function is to write to the aic3262 register space. + * + *---------------------------------------------------------------------------- + */ +int aic3262_write(struct snd_soc_codec *codec, u16 reg, u8 value) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 data[2]; + u8 page; + int ret = 0; + + page = reg / 128; + data[AIC3262_REG_OFFSET_INDEX] = reg % 128; + if (aic3262->page_no != page) + aic3262_change_page(codec, page); + + /* data is + * D15..D8 aic3262 register offset + * D7...D0 register data + */ + data[AIC3262_REG_DATA_INDEX] = value & AIC3262_8BITS_MASK; +#if defined(EN_REG_CACHE) + if ((page >= 0) & (page <= 4)) + aic3262_write_reg_cache(codec, reg, value); + +#endif + if (!data[AIC3262_REG_OFFSET_INDEX]) { + /* if the write is to reg0 update aic3262->page_no */ + aic3262->page_no = value; + } + + /*DBG("w %2x %02x\r\n", + data[AIC3262_REG_OFFSET_INDEX], data[AIC3262_REG_DATA_INDEX]);*/ + ret = snd_soc_write(codec, data[AIC3262_REG_OFFSET_INDEX], + data[AIC3262_REG_DATA_INDEX]); + if (ret) + printk(KERN_ERR "Error in i2c write\n"); + + return ret; +} + +/* + *------------------------------------------------------------------------------ + * Function : aic3262_write__ + * Purpose : This function is to write to the aic3262 register space. + * (low level). + *------------------------------------------------------------------------------ + */ + +int aic3262_write__(struct i2c_client *client, const char *buf, int count) +{ + u8 data[3]; + int ret; + data[0] = *buf; + data[1] = *(buf+1); + data[2] = *(buf+2); + /*DBG("w %2x %02x\r\n", + data[AIC3262_REG_OFFSET_INDEX], data[AIC3262_REG_DATA_INDEX]);*/ + ret = i2c_master_send(client, data, 2); + if (ret < 2) { + printk( + KERN_ERR"I2C write Error : bytes written = %d\n\n", ret); + return -EIO; + } + + return ret; +} +/* + *---------------------------------------------------------------------------- + * Function : aic3262_reset_cache + * Purpose : This function is to reset the cache. + *---------------------------------------------------------------------------- + */ +int aic3262_reset_cache(struct snd_soc_codec *codec) +{ +#if defined(EN_REG_CACHE) + if (codec->reg_cache) { + memcpy(codec->reg_cache, aic3262_reg, sizeof(aic3262_reg)); + return 0; + } +#endif + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_get_divs + * Purpose : This function is to get required divisor from the "aic3262_divs" + * table. + * + *---------------------------------------------------------------------------- + */ +static inline int aic3262_get_divs(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(aic3262_divs); i++) { + if ((aic3262_divs[i].rate == rate) + && (aic3262_divs[i].mclk == mclk)) { + DBG(KERN_INFO "#%s: Found Entry %d in Clock_Array\n", + __func__, i); + return i; + } + } + printk(KERN_ERR "Master clock and sample rate is not supported\n"); + return -EINVAL; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_add_controls + * Purpose : This function is to add non dapm kcontrols. The different + * controls are in "aic3262_snd_controls" table. + * The following different controls are supported + * # PCM Playback volume control + * # PCM Playback Volume + * # HP Driver Gain + * # HP DAC Playback Switch + * # PGA Capture Volume + * # Program Registers + * + *---------------------------------------------------------------------------- + */ +static int aic3262_add_controls(struct snd_soc_codec *codec) +{ + int err; + + err = snd_soc_add_controls(codec, aic3262_snd_controls, + ARRAY_SIZE(aic3262_snd_controls)); + if (err < 0) { + printk(KERN_ERR "Invalid control\n"); + return err; + } + + err = snd_soc_add_controls(codec, aic3262_snd_controls2, + ARRAY_SIZE(aic3262_snd_controls2)); + if (err < 0) { + printk(KERN_ERR "Invalid control\n"); + return err; + } + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_add_widgets + * Purpose : This function is to add the dapm widgets + * The following are the main widgets supported + * # Left DAC to Left Outputs + * # Right DAC to Right Outputs + * # Left Inputs to Left ADC + * # Right Inputs to Right ADC + * + *---------------------------------------------------------------------------- + */ +static int aic3262_add_widgets(struct snd_soc_codec *codec) +{ + int ret; + struct snd_soc_dapm_context *dapm = &codec->dapm; +#ifndef AIC3262_MULTI_I2S + int i; + for (i = 0; i < ARRAY_SIZE(aic3262_dapm_widgets); i++) + ret = snd_soc_dapm_new_control(dapm, &aic3262_dapm_widgets[i]); +#else + ret = snd_soc_dapm_new_controls(dapm, aic3262_dapm_widgets, + ARRAY_SIZE(aic3262_dapm_widgets)); + if (ret != 0) { + printk(KERN_ERR "#%s: Unable to add DAPM Controls. Err %d\n", + __func__, ret); + } +#endif + /* set up audio path interconnects */ + DBG("#Completed adding new dapm widget controls size=%d\n", + ARRAY_SIZE(aic3262_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, aic3262_dapm_routes, + ARRAY_SIZE(aic3262_dapm_routes)); + DBG("#Completed adding DAPM routes\n"); + /*snd_soc_dapm_new_widgets(codec);*/ + DBG("#Completed updating dapm\n"); + return 0; +} +/* + *---------------------------------------------------------------------------- + * Function : reg_def_conf + * Purpose : This function is to reset the codec book 0 registers + * + *---------------------------------------------------------------------------- + */ +int reg_def_conf(struct snd_soc_codec *codec) +{ + int i = 0, ret; + + aic3262_change_page(codec, 0); + aic3262_change_book(codec, 0); + + /* Configure the Codec with the default Initialization Values */ + for (i = 0; i < reg_init_size; i++) { + ret = aic3262_write(codec, aic3262_reg_init[i].reg_offset, + aic3262_reg_init[i].reg_val); + if (ret) + break; + } + return ret; +} + +/* + * i2c_verify_book0 + * + * This function is used to dump the values of the Book 0 Pages. + */ +int i2c_verify_book0(struct snd_soc_codec *codec) +{ + int i, j, k = 0; + u8 val1; + + DBG("starting i2c_verify\n"); + DBG("Resetting page to 0\n"); + aic3262_change_book(codec, 0); + for (j = 0; j < 3; j++) { + if (j == 0) { + aic3262_change_page(codec, 0); + k = 0; + } + if (j == 1) { + aic3262_change_page(codec, 1); + k = 1; + } + /* + if (j == 2) { + aic3262_change_page(codec, 4); + k = 4; + }*/ + for (i = 0; i <= 127; i++) { + val1 = snd_soc_read(codec, i); + /* printk("[%d][%d]=[0x%2x]\n",k,i,val1); */ + } + } + return 0; +} + + + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_hw_params + * Purpose : This function is to set the hardware parameters for AIC3262. + * The functions set the sample rate and audio serial data word + * length. + * + *---------------------------------------------------------------------------- + */ +static int aic3262_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + int i, j; + u8 data; + + aic3262_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + i = aic3262_get_divs(aic3262->sysclk, params_rate(params)); + + i2c_verify_book0(codec); + + if (i < 0) { + printk(KERN_ERR "sampling rate not supported\n"); + return i; + } + + if (soc_static_freq_config) { + /*We will fix R value to 1 and make P & J=K.D as varialble */ + + /* Setting P & R values are set to 1 and 1 at init*/ + + /* J value */ + aic3262_write(codec, PLL_J_REG, aic3262_divs[i].pll_j); + + /* MSB & LSB for D value */ + + aic3262_write(codec, PLL_D_MSB, (aic3262_divs[i].pll_d >> 8)); + aic3262_write(codec, PLL_D_LSB, + (aic3262_divs[i].pll_d & AIC3262_8BITS_MASK)); + + /* NDAC divider value */ + data = aic3262_read(codec, NDAC_DIV_POW_REG); + DBG(KERN_INFO "# reading NDAC = %d , NDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].ndac, data); + aic3262_write(codec, NDAC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].ndac))); + DBG(KERN_INFO "# writing NDAC = %d , NDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].ndac, + ((data & 0x80)|(aic3262_divs[i].ndac))); + + /* MDAC divider value */ + data = aic3262_read(codec, MDAC_DIV_POW_REG); + DBG(KERN_INFO "# reading MDAC = %d , MDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].mdac, data); + aic3262_write(codec, MDAC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].mdac))); + DBG(KERN_INFO "# writing MDAC = %d , MDAC_DIV_POW_REG = %x\n", + aic3262_divs[i].mdac, ((data & 0x80)|(aic3262_divs[i].mdac))); + + /* DOSR MSB & LSB values */ + aic3262_write(codec, DOSR_MSB_REG, aic3262_divs[i].dosr >> 8); + DBG(KERN_INFO "# writing DOSR_MSB_REG = %d\n", + (aic3262_divs[i].dosr >> 8)); + aic3262_write(codec, DOSR_LSB_REG, + aic3262_divs[i].dosr & AIC3262_8BITS_MASK); + DBG(KERN_INFO "# writing DOSR_LSB_REG = %d\n", + (aic3262_divs[i].dosr & AIC3262_8BITS_MASK)); + + /* NADC divider value */ + data = aic3262_read(codec, NADC_DIV_POW_REG); + aic3262_write(codec, NADC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].nadc))); + DBG(KERN_INFO "# writing NADC_DIV_POW_REG = %d\n", + aic3262_divs[i].nadc); + + /* MADC divider value */ + data = aic3262_read(codec, MADC_DIV_POW_REG); + aic3262_write(codec, MADC_DIV_POW_REG, + ((data & 0x80)|(aic3262_divs[i].madc))); + DBG(KERN_INFO "# writing MADC_DIV_POW_REG = %d\n", + aic3262_divs[i].madc); + + /* AOSR value */ + aic3262_write(codec, AOSR_REG, aic3262_divs[i].aosr); + DBG(KERN_INFO "# writing AOSR = %d\n", aic3262_divs[i].aosr); + } + + + data = aic3262_read(codec, ASI1_BUS_FMT); + + data = data & 0xe7; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + data = data | 0x00; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (0x08); + break; + case SNDRV_PCM_FORMAT_S24_LE: + data |= (0x10); + break; + case SNDRV_PCM_FORMAT_S32_LE: + data |= (0x18); + break; + } + + /* configure the respective Registers for the above configuration */ + aic3262_write(codec, ASI1_BUS_FMT, data); + + for (j = 0; j < NO_FEATURE_REGS; j++) { + aic3262_write(codec, + aic3262_divs[i].codec_specific_regs[j].reg_offset, + aic3262_divs[i].codec_specific_regs[j].reg_val); + } + + aic3262_set_bias_level(codec, SND_SOC_BIAS_ON); + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_mute + * Purpose : This function is to mute or unmute the left and right DAC + * + *---------------------------------------------------------------------------- + */ +static int aic3262_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#aic3262 codec : mute started\n"); + if (mute) { + DBG("Mute if part\n"); + + if (!aic3262->mute_codec) { + dac_reg = aic3262_read(codec, DAC_MVOL_CONF); + adc_gain = aic3262_read(codec, ADC_FINE_GAIN); + hpl = aic3262_read(codec, HPL_VOL); + hpr = aic3262_read(codec, HPR_VOL); + rec_amp = aic3262_read(codec, REC_AMP_CNTL_R5); + rampr = aic3262_read(codec, RAMPR_VOL); + spk_amp = aic3262_read(codec, SPK_AMP_CNTL_R4); + } + DBG("spk_reg = %2x\n\n", spk_amp); + + aic3262_write(codec, DAC_MVOL_CONF, ((dac_reg & 0xF3) | 0x0C)); + aic3262_write(codec, ADC_FINE_GAIN, ((adc_gain & 0x77) | 0x88)); + aic3262_write(codec, HPL_VOL, 0xB9); + aic3262_write(codec, HPR_VOL, 0xB9); + aic3262_write(codec, REC_AMP_CNTL_R5, 0x39); + aic3262_write(codec, RAMPR_VOL, 0x39); + aic3262_write(codec, SPK_AMP_CNTL_R4, 0x00); + aic3262->mute_codec = 1; + } else { + DBG("Mute else part\n"); + aic3262_write(codec, DAC_MVOL_CONF, (dac_reg & 0xF3)); + mdelay(5); + aic3262_write(codec, ADC_FINE_GAIN, (adc_gain & 0x77)); + mdelay(5); + aic3262_write(codec, HPL_VOL, hpl); + mdelay(5); + aic3262_write(codec, HPR_VOL, hpr); + mdelay(5); + aic3262_write(codec, REC_AMP_CNTL_R5, rec_amp); + mdelay(5); + aic3262_write(codec, RAMPR_VOL, rampr); + mdelay(5); + aic3262_write(codec, SPK_AMP_CNTL_R4, spk_amp); + mdelay(5); + aic3262->mute_codec = 0; + } + DBG(KERN_INFO "#aic3262 codec : mute ended\n"); + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_set_dai_sysclk + * Purpose : This function is to set the DAI system clock + * + *---------------------------------------------------------------------------- + */ +static int aic3262_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 aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + switch (freq) { + case AIC3262_FREQ_12000000: + case AIC3262_FREQ_12288000: + aic3262->sysclk = freq; + return 0; + case AIC3262_FREQ_24000000: + aic3262->sysclk = freq; + return 0; + break; + } + printk(KERN_ERR "Invalid frequency to set DAI system clock\n"); + return -EINVAL; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_set_dai_fmt + * Purpose : This function is to set the DAI format + * + *---------------------------------------------------------------------------- + */ +static int aic3262_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 iface_reg, clk_reg; + + iface_reg = aic3262_read(codec, ASI1_BUS_FMT); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + DBG("setdai_fmt : SND_SOC_DAIFMT_CBM_CFM : master=1\n"); + aic3262->master = 1; + /*Test added */ + clk_reg = aic3262_read(codec, ASI1_BWCLK_CNTL_REG); + clk_reg = (clk_reg & 0x03); + aic3262_write(codec, ASI1_BWCLK_CNTL_REG, + (clk_reg | 0x24)); + break; + case SND_SOC_DAIFMT_CBS_CFS: + DBG("setdai_fmt : SND_SOC_DAIFMT_CBS_CFS : master=0\n"); + aic3262->master = 0; + /*Test added */ + clk_reg = aic3262_read(codec, ASI1_BWCLK_CNTL_REG); + aic3262_write(codec, ASI1_BWCLK_CNTL_REG, + (clk_reg & 0x03)); + break; + case SND_SOC_DAIFMT_CBS_CFM: /*new case..just for debugging*/ + DBG("SND_SOC_DAIFMT_CBS_CFM\n"); + aic3262->master = 0; + break; + default: + printk(KERN_ERR "Invalid DAI master/slave interface\n"); + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface_reg = (iface_reg & 0x1f); + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg = (iface_reg & 0x1f) | 0x20; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_reg = (iface_reg & 0x1f) | 0x40; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg = (iface_reg & 0x1f) | 0x60; + break; + case SND_SOC_DAIFMT_DSP_B: + iface_reg = (iface_reg & 0x1f) | 0x80; + /* voice call need data offset in 1 bitclock */ + aic3262_write(codec, ASI1_LCH_OFFSET, 1); + break; + default: + printk(KERN_ERR "Invalid DAI interface format\n"); + return -EINVAL; + } + + aic3262_write(codec, ASI1_BUS_FMT, iface_reg); + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_set_bias_level + * Purpose : This function is to get triggered when dapm events occurs. + * + *---------------------------------------------------------------------------- + */ +static int aic3262_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u8 value; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + DBG(KERN_INFO "#%s: Codec Active %d[%d]\n", + __func__, codec->active, aic3262->active_count); + switch (level) { + /* full On */ + case SND_SOC_BIAS_ON: + DBG(KERN_INFO "#aic3262 codec : set_bias_on started\n"); + case SND_SOC_BIAS_PREPARE: + /* all power is driven by DAPM system */ + DBG(KERN_INFO "#aic3262 codec : set_bias_prepare started\n"); + + /* Switch on PLL */ + value = aic3262_read(codec, PLL_PR_POW_REG); + aic3262_write(codec, PLL_PR_POW_REG, ((value | 0x80))); + + /* Switch on NDAC Divider */ + value = aic3262_read(codec, NDAC_DIV_POW_REG); + aic3262_write(codec, NDAC_DIV_POW_REG, + ((value & 0x7f) | (0x80))); + + /* Switch on MDAC Divider */ + value = aic3262_read(codec, MDAC_DIV_POW_REG); + aic3262_write(codec, MDAC_DIV_POW_REG, + ((value & 0x7f) | (0x80))); + + /* Switch on NADC Divider */ + value = aic3262_read(codec, NADC_DIV_POW_REG); + aic3262_write(codec, NADC_DIV_POW_REG, + ((value & 0x7f) | (0x80))); + + /* Switch on MADC Divider */ + value = aic3262_read(codec, MADC_DIV_POW_REG); + aic3262_write(codec, MADC_DIV_POW_REG, + ((value & 0x7f) | (0x80))); + + + aic3262_write(codec, ADC_CHANNEL_POW, 0xc2); + aic3262_write(codec, ADC_FINE_GAIN, 0x00); + + DBG("#aic3262 codec : set_bias_on complete\n"); + + break; + + + /* Off, with power */ + case SND_SOC_BIAS_STANDBY: + /* + * all power is driven by DAPM system, + * so output power is safe if bypass was set + */ + + DBG("#aic3262 codec : set_bias_stby inside if condn\n"); + + if (!aic3262->active_count) { + /* Switch off NDAC Divider */ + value = aic3262_read(codec, NDAC_DIV_POW_REG); + aic3262_write(codec, NDAC_DIV_POW_REG, + (value & 0x7f)); + + /* Switch off MDAC Divider */ + value = aic3262_read(codec, MDAC_DIV_POW_REG); + aic3262_write(codec, MDAC_DIV_POW_REG, + (value & 0x7f)); + + /* Switch off NADC Divider */ + value = aic3262_read(codec, NADC_DIV_POW_REG); + aic3262_write(codec, NADC_DIV_POW_REG, + (value & 0x7f)); + + /* Switch off MADC Divider */ + value = aic3262_read(codec, MADC_DIV_POW_REG); + aic3262_write(codec, MADC_DIV_POW_REG, + (value & 0x7f)); + + /* Switch off PLL */ + value = aic3262_read(codec, PLL_PR_POW_REG); + aic3262_write(codec, PLL_PR_POW_REG, (value & 0x7f)); + + DBG("#%s: set_bias_stby complete\n", __func__); + } else + DBG(KERN_INFO + "#%s: Another Stream Active. No STANDBY\n", __func__); + break; + + /* Off, without power */ + case SND_SOC_BIAS_OFF: + /* force all power off */ + + /* Switch off PLL */ + value = aic3262_read(codec, PLL_PR_POW_REG); + aic3262_write(codec, + PLL_PR_POW_REG, (value & ~(0x01 << 7))); + + /* Switch off NDAC Divider */ + value = aic3262_read(codec, NDAC_DIV_POW_REG); + aic3262_write(codec, NDAC_DIV_POW_REG, + (value & ~(0x01 << 7))); + + /* Switch off MDAC Divider */ + value = aic3262_read(codec, MDAC_DIV_POW_REG); + aic3262_write(codec, MDAC_DIV_POW_REG, + (value & ~(0x01 << 7))); + + /* Switch off NADC Divider */ + value = aic3262_read(codec, NADC_DIV_POW_REG); + aic3262_write(codec, NADC_DIV_POW_REG, + (value & ~(0x01 << 7))); + + /* Switch off MADC Divider */ + value = aic3262_read(codec, MADC_DIV_POW_REG); + aic3262_write(codec, MADC_DIV_POW_REG, + (value & ~(0x01 << 7))); + value = aic3262_read(codec, ASI1_BCLK_N); + + break; + } + codec->dapm.bias_level = level; + DBG(KERN_INFO "#aic3262 codec : set_bias exiting\n"); + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_suspend + * Purpose : This function is to suspend the AIC3262 driver. + * + *---------------------------------------------------------------------------- + */ +static int aic3262_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + if (aic3262) + disable_irq(aic3262->irq); + + aic3262_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_resume + * Purpose : This function is to resume the AIC3262 driver + * + *---------------------------------------------------------------------------- + */ +static int aic3262_resume(struct snd_soc_codec *codec) +{ + int i; + u8 data[2]; + int ret = 0; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u8 *cache = codec->reg_cache; + + ret = aic3262_change_page(codec, 0); + if (ret) + return ret; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(aic3262_reg); i++) { + data[0] = i % 128; + data[1] = cache[i]; + + ret = snd_soc_write(codec, data[0], data[1]); + if (ret) + break; + } + + if (!ret) { + aic3262_change_page(codec, 0); + aic3262_set_bias_level(codec, SND_SOC_BIAS_ON); + + if (aic3262) + enable_irq(aic3262->irq); + } + return ret; +} + +/* +* aic3262_jack_handler +* +* This function is called from the Interrupt Handler +* to check the status of the AIC3262 Registers related to Headset Detection +*/ +static irqreturn_t aic3262_jack_handler(int irq, void *data) +{ + struct snd_soc_codec *codec = data; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + unsigned int value; + unsigned int micbits, hsbits = 0; + + DBG("%s++\n", __func__); + + /* Read the Jack Status Register*/ + value = snd_soc_read(codec, STICKY_FLAG2); + DBG(KERN_INFO "reg44 0x%x\n", value); + + value = snd_soc_read(codec, INT_FLAG2); + DBG("reg46 0x%x\n", value); + + value = snd_soc_read(codec, DAC_FLAG_R1); + DBG("reg37 0x%x\n", value); + + micbits = value & DAC_FLAG_MIC_MASKBITS; + DBG("micbits 0x%x\n", micbits); + + hsbits = value & DAC_FLAG_HS_MASKBITS; + DBG("hsbits 0x%x\n", hsbits); + + /* sleep for debounce time */ + msleep(aic3262->pdata->debounce_time_ms); + + /* No Headphone or Headset*/ + if (!micbits && !hsbits) { + snd_soc_jack_report(aic3262->headset_jack, + 0, SND_JACK_HEADSET); + } + + /* Headphone Detected */ + if ((micbits == DAC_FLAG_R1_NOMIC) || (hsbits)) { + snd_soc_jack_report(aic3262->headset_jack, + SND_JACK_HEADPHONE, SND_JACK_HEADSET); + } + + /* Headset Detected - only with capless */ + if (micbits == DAC_FLAG_R1_MIC) { + snd_soc_jack_report(aic3262->headset_jack, + SND_JACK_HEADSET, SND_JACK_HEADSET); + } + + return IRQ_HANDLED; +} + +/* +* aic326x_headset_detect +* +* Call-back function called to check the status of Headset Pin. +*/ +int aic326x_headset_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, int jack_type) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + aic3262->headset_jack = jack; + + /*TODO*/ + aic3262_jack_handler(aic3262->irq, codec); + + return 0; +} +EXPORT_SYMBOL_GPL(aic326x_headset_detect); + + +#ifdef AIC3262_MULTI_I2S +/* +* aic3262_asi_default_config +* +* This function is used to perform the default pin configurations for +* the functionalities which are specific to each ASI Port of the AIC3262 +* Audio Codec Chipset. The user is encouraged to change these values +* if required on their platforms. +*/ +static void aic3262_asi_default_config(struct snd_soc_codec *codec) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u16 counter; + + DBG(KERN_INFO + "#%s: Invoked. Will Config ASI Registers to Defaults..\n", + __func__); + for (counter = 0; counter < MAX_ASI_COUNT; counter++) { + aic3262->asiCtxt[counter].asi_active = 0; + aic3262->asiCtxt[counter].bclk_div = 1; + aic3262->asiCtxt[counter].wclk_div = 1; + aic3262->asiCtxt[counter].port_muted = 1; + aic3262->asiCtxt[counter].bclk_div_option = + BDIV_CLKIN_DAC_MOD_CLK; + aic3262->asiCtxt[counter].offset1 = 0; + aic3262->asiCtxt[counter].offset2 = 0; + } + /* ASI1 Defaults */ + aic3262->asiCtxt[0].bclk_output = ASI1_BCLK_DIVIDER_OUTPUT; + aic3262->asiCtxt[0].wclk_output = GENERATED_DAC_FS; + aic3262->asiCtxt[0].left_dac_output = DAC_PATH_LEFT; + aic3262->asiCtxt[0].right_dac_output = DAC_PATH_LEFT; + aic3262->asiCtxt[0].adc_input = ADC_PATH_MINIDSP_1; + aic3262->asiCtxt[0].dout_option = ASI_OUTPUT; + + /* ASI2 Defaults */ + aic3262->asiCtxt[1].bclk_output = ASI2_BCLK_DIVIDER_OUTPUT; + aic3262->asiCtxt[1].wclk_output = GENERATED_DAC_FS; + aic3262->asiCtxt[1].left_dac_output = DAC_PATH_LEFT; + aic3262->asiCtxt[1].right_dac_output = DAC_PATH_LEFT; + aic3262->asiCtxt[1].adc_input = ADC_PATH_MINIDSP_2; + aic3262->asiCtxt[1].dout_option = ASI_OUTPUT; + + /* ASI3 Defaults */ + aic3262->asiCtxt[2].bclk_output = ASI3_BCLK_DIVIDER_OUTPUT; + aic3262->asiCtxt[2].wclk_output = GENERATED_DAC_FS; + aic3262->asiCtxt[2].left_dac_output = DAC_PATH_LEFT; + aic3262->asiCtxt[2].right_dac_output = DAC_PATH_LEFT; + aic3262->asiCtxt[2].adc_input = ADC_PATH_MINIDSP_3; + aic3262->asiCtxt[2].dout_option = ASI2_INPUT; + + return; +} + +#endif /* #ifdef AIC3262_MULTI_I2S */ + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_probe + * Purpose : This is first driver function called by the SoC core driver. + * + *---------------------------------------------------------------------------- + */ +static int aic3262_probe(struct snd_soc_codec *codec) +{ + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + DBG(KERN_INFO "#%s: Invoked..\n", __func__); + + /* Setting cache bypass - not to overwrite the cache registers, + Codec registers have 4 pages which is not handled in the common + cache code properly - bypass it in write value and save it + using separate call*/ + codec->cache_bypass = 1; + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + ret = reg_def_conf(codec); + if (ret != 0) { + printk(KERN_ERR "Failed to init TI codec: %d\n", ret); + return ret; + } + + if (aic3262->irq) { + /* audio interrupt */ + ret = request_threaded_irq(aic3262->irq, NULL, + aic3262_jack_handler, + IRQF_TRIGGER_FALLING, + "tlv320aic3262", codec); + if (ret) { + printk(KERN_INFO "#%s: IRQ Registration failed..[%d]", + __func__, ret); + dev_err(codec->dev, "Failed to request IRQ: %d\n", ret); + return ret; + } else + printk(KERN_INFO + "#%s: irq Registration for IRQ %d done..\n", + __func__, aic3262->irq); + } else { + printk(KERN_INFO "#%s: I2C IRQ Configuration is Wrong. \ + Please check it..\n", __func__); + } + + aic3262_asi_default_config(codec); + + /* off, with power on */ + aic3262_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + aic3262_add_controls(codec); + aic3262_add_widgets(codec); + /*TODO*/ + aic3262_write(codec, MIC_BIAS_CNTL, 0x66); + +#ifdef CONFIG_MINI_DSP + /* Program MINI DSP for ADC and DAC */ + aic3262_minidsp_program(codec); + aic3262_add_minidsp_controls(codec); + aic3262_change_book(codec, 0x0); +#endif + +#ifdef MULTIBYTE_CONFIG_SUPPORT + aic3262_add_multiconfig_controls(codec); +#endif + + return ret; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_remove + * Purpose : to remove aic3262 soc device + * + *---------------------------------------------------------------------------- + */ +static int aic3262_remove(struct snd_soc_codec *codec) +{ + + /* power down chip */ + aic3262_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + + +/* + *---------------------------------------------------------------------------- + * @struct snd_soc_codec_device | + * This structure is soc audio codec device sturecute which pointer + * to basic functions aic3262_probe(), aic3262_remove(), + * aic3262_suspend() and aic3262_resume() + *---------------------------------------------------------------------------- + */ +static struct snd_soc_codec_driver soc_codec_dev_aic3262 = { + .probe = aic3262_probe, + .remove = aic3262_remove, + .suspend = aic3262_suspend, + .resume = aic3262_resume, + .set_bias_level = aic3262_set_bias_level, + .reg_cache_size = ARRAY_SIZE(aic3262_reg), + .reg_word_size = sizeof(u16), + .reg_cache_default = aic3262_reg, + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + *---------------------------------------------------------------------------- + * Function : aic3262_codec_probe + * Purpose : This function attaches the i2c client and initializes + * AIC3262 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 __devinit int aic3262_codec_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret; + + struct aic3262_priv *aic3262; + + DBG(KERN_INFO "#%s: Entered\n", __func__); + + aic3262 = kzalloc(sizeof(struct aic3262_priv), GFP_KERNEL); + + if (!aic3262) { + printk(KERN_ERR "#%s: Unable to Allocate Priv struct..\n", + __func__); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, aic3262); + aic3262->control_type = SND_SOC_I2C; + aic3262->irq = i2c->irq; + aic3262->pdata = i2c->dev.platform_data; + + /* The Configuration Support will be by default to 3 which + * holds the MAIN Patch Configuration. + */ + aic3262->current_dac_config[0] = -1; + aic3262->current_dac_config[1] = -1; + aic3262->current_adc_config[0] = -1; + aic3262->current_adc_config[1] = -1; + + aic3262->mute_codec = 1; + + aic3262->page_no = 0; + aic3262->book_no = 0; + aic3262->active_count = 0; + aic3262->dac_clkin_option = 3; + aic3262->adc_clkin_option = 3; + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_aic3262, + tlv320aic3262_dai, ARRAY_SIZE(tlv320aic3262_dai)); + + if (ret < 0) + kfree(aic3262); + + return ret; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_i2c_remove + * Purpose : This function removes the i2c client and uninitializes + * AIC3262 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 __devexit int aic3262_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_codec(&i2c->dev); + kfree(i2c_get_clientdata(i2c)); + return 0; +} + +static const struct i2c_device_id tlv320aic3262_id[] = { + {"tlv320aic3262", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, tlv320aic3262_id); + +static struct i2c_driver tlv320aic3262_i2c_driver = { + .driver = { + .name = "tlv320aic3262", + .owner = THIS_MODULE, + }, + .probe = aic3262_codec_probe, + .remove = __devexit_p(aic3262_i2c_remove), + .id_table = tlv320aic3262_id, +}; +#endif /*#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)*/ + +static int __init tlv320aic3262_modinit(void) +{ + int ret = 0; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&tlv320aic3262_i2c_driver); + if (ret != 0) + printk(KERN_ERR "Failed to register aic326x i2c driver %d\n", + ret); +#endif + return ret; + +} +module_init(tlv320aic3262_modinit); + +static void __exit tlv320aic3262_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&tlv320aic3262_i2c_driver); +#endif +} +module_exit(tlv320aic3262_exit); + +MODULE_DESCRIPTION("ASoC TLV320AIC3262 codec driver"); +MODULE_AUTHOR("Barani Prashanth<gvbarani@mistralsolutions.com>"); +MODULE_AUTHOR("Ravindra<ravindra@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic326x.h b/sound/soc/codecs/tlv320aic326x.h new file mode 100755 index 000000000000..e6e67350a5e8 --- /dev/null +++ b/sound/soc/codecs/tlv320aic326x.h @@ -0,0 +1,629 @@ +/* + * linux/sound/soc/codecs/tlv320aic3262.h + * + * + * Copyright (C) 2011 Mistral Solutions Pvt Ltd. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * Rev 0.1 ASoC driver support Mistral 20-01-2011 + * + * The AIC3262 ASoC driver is ported for the codec AIC3262. + * + */ + +#ifndef _TLV320AIC3262_H +#define _TLV320AIC3262_H + +#define AUDIO_NAME "aic3262" +#define AIC3262_VERSION "1.1" + +/* Enable this macro allow for different ASI formats */ +/*#define ASI_MULTI_FMT*/ +#undef ASI_MULTI_FMT +/* Enable register caching on write */ +#define EN_REG_CACHE + +#define MULTIBYTE_CONFIG_SUPPORT + + +/* Macro enables or disables support for miniDSP in the driver */ +/* Enable the AIC3262_TiLoad macro first before enabling these macros */ +#define CONFIG_MINI_DSP +/*#undef CONFIG_MINI_DSP*/ + +/* Enable or disable controls to have Input routing*/ +/*#define FULL_IN_CNTL */ +#undef FULL_IN_CNTL +/* AIC3262 supported sample rate are 8k to 192k */ +#define AIC3262_RATES SNDRV_PCM_RATE_8000_192000 + +/* AIC3262 supports the word formats 16bits, 20bits, 24bits and 32 bits */ +#define AIC3262_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \ + | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +#define AIC3262_FREQ_12000000 12000000 +#define AIC3262_FREQ_12288000 12288000 +#define AIC3262_FREQ_24000000 24000000 + +/* Macro for enabling the Multi_I2S Support in Driver */ +#define AIC3262_MULTI_I2S 1 + +/* Driver Debug Messages Enabled */ +#define DEBUG + +#ifdef DEBUG + #define DBG(x...) printk(x) +#else + #define DBG(x...) +#endif + +/*Select the below macro to decide on the DAC master volume controls. + *2 independent or one combined + */ +/*#define DAC_INDEPENDENT_VOL*/ +#undef DAC_INDEPENDENT_VOL + +/* Audio data word length = 16-bits (default setting) */ +#define AIC3262_WORD_LEN_16BITS 0x00 +#define AIC3262_WORD_LEN_20BITS 0x01 +#define AIC3262_WORD_LEN_24BITS 0x02 +#define AIC3262_WORD_LEN_32BITS 0x03 + +/* sink: name of target widget */ +#define AIC3262_WIDGET_NAME 0 +/* control: mixer control name */ +#define AIC3262_CONTROL_NAME +/* source: name of source name */ +#define AIC3262_SOURCE_NAME 2 + +/* D15..D8 aic3262 register offset */ +#define AIC3262_REG_OFFSET_INDEX 0 +/* D7...D0 register data */ +#define AIC3262_REG_DATA_INDEX 1 + +/* Serial data bus uses I2S mode (Default mode) */ +#define AIC3262_I2S_MODE 0x00 +#define AIC3262_DSP_MODE 0x01 +#define AIC3262_RIGHT_JUSTIFIED_MODE 0x02 +#define AIC3262_LEFT_JUSTIFIED_MODE 0x03 + +/* 8 bit mask value */ +#define AIC3262_8BITS_MASK 0xFF + +/* shift value for CLK_REG_3 register */ +#define CLK_REG_3_SHIFT 6 +/* shift value for DAC_OSR_MSB register */ +#define DAC_OSR_MSB_SHIFT 4 + +/* number of codec specific register for configuration */ +#define NO_FEATURE_REGS 2 + +/* Total number of ASI Ports */ +#define MAX_ASI_COUNT 3 + +/* AIC3262 register space */ +/* Updated from 256 to support Page 3 registers */ +#define AIC3262_CACHEREGNUM 1024 +#define BIT7 (0x01 << 7) +#define BIT6 (0x01 << 6) +#define BIT5 (0x01 << 5) +#define BIT4 (0x01 << 4) +#define BIT3 (0x01 << 3) +#define BIT2 (0x01 << 2) +#define BIT1 (0x01 << 1) +#define BIT0 (0x01 << 0) + +#define DAC_FLAG_MIC_MASKBITS 0x30 +#define DAC_FLAG_HS_MASKBITS 0x03 +#define DAC_FLAG_R1_NOJACK 0 +#define DAC_FLAG_R1_NOMIC (0x1 << 4) +#define DAC_FLAG_R1_MIC (0x3 << 4) +#define DAC_FLAG_R1_NOHS 0 +#define DAC_FLAG_R1_MONOHS 1 +#define DAC_FLAG_R1_STEREOHS 2 +/* ****************** Book 0 Registers **************************************/ + +/* ****************** Page 0 Registers **************************************/ + +#define PAGE_SEL_REG 0 +#define RESET_REG 1 +#define DAC_ADC_CLKIN_REG 4 +#define PLL_CLKIN_REG 5 +#define PLL_CLK_RANGE_REG 5 +#define PLL_PR_POW_REG 6 +#define PLL_J_REG 7 +#define PLL_D_MSB 8 +#define PLL_D_LSB 9 +#define PLL_CKIN_DIV 10 + +#define NDAC_DIV_POW_REG 11 +#define MDAC_DIV_POW_REG 12 +#define DOSR_MSB_REG 13 +#define DOSR_LSB_REG 14 + +#define NADC_DIV_POW_REG 18 +#define MADC_DIV_POW_REG 19 +#define AOSR_REG 20 +#define CLKOUT_MUX 21 +#define CLKOUT_MDIV_VAL 22 +#define TIMER_REG 23 + +#define LF_CLK_CNTL 24 +#define HF_CLK_CNTL_R1 25 +#define HF_CLK_CNTL_R2 26 +#define HF_CLK_CNTL_R3 27 +#define HF_CLK_CNTL_R4 28 +#define HF_CLK_TRIM_R1 29 +#define HF_CLK_TRIM_R2 30 +#define HF_CLK_TRIM_R3 31 +#define HF_CLK_TRIM_R4 32 +#define DAC_FLAG_R1 37 +#define DAC_FLAG_R2 38 + +#define STICKY_FLAG1 42 +#define INT_FLAG1 43 +#define STICKY_FLAG2 44 +#define INT_FLAG2 46 +#define INT1_CNTL 48 +#define INT2_CNTL 49 +#define INT_FMT 51 + +#define DAC_PRB 60 +#define ADC_PRB 61 +#define PASI_DAC_DP_SETUP 63 +#define DAC_MVOL_CONF 64 +#define DAC_LVOL 65 +#define DAC_RVOL 66 +#define HP_DETECT 67 +#define DRC_CNTL_R1 68 +#define DRC_CNTL_R2 69 +#define DRC_CNTL_R3 70 +#define BEEP_CNTL_R1 71 +#define BEEP_CNTL_R2 72 + +#define ADC_CHANNEL_POW 81 +#define ADC_FINE_GAIN 82 +#define LADC_VOL 83 +#define RADC_VOL 84 +#define ADC_PHASE 85 + +#define LAGC_CNTL 86 +#define LAGC_CNTL_R2 87 +#define LAGC_CNTL_R3 88 +#define LAGC_CNTL_R4 89 +#define LAGC_CNTL_R5 90 +#define LAGC_CNTL_R6 91 +#define LAGC_CNTL_R7 92 +#define LAGC_CNTL_R8 93 + +#define RAGC_CNTL 94 +#define RAGC_CNTL_R2 95 +#define RAGC_CNTL_R3 96 +#define RAGC_CNTL_R4 97 +#define RAGC_CNTL_R5 98 +#define RAGC_CNTL_R6 99 +#define RAGC_CNTL_R7 100 +#define RAGC_CNTL_R8 101 +#define MINIDSP_ACCESS_CTRL 121 +/* ****************** Page 1 Registers **************************************/ +#define PAGE_1 128 + +#define POWER_CONF (PAGE_1 + 1) +#define LDAC_PTM (PAGE_1 + 3) +#define RDAC_PTM (PAGE_1 + 4) +#define CM_REG (PAGE_1 + 8) +#define HP_CTL (PAGE_1 + 9) +#define HP_DEPOP (PAGE_1 + 11) +#define RECV_DEPOP (PAGE_1 + 12) +#define MA_CNTL (PAGE_1 + 17) +#define LADC_PGA_MAL_VOL (PAGE_1 + 18) +#define RADC_PGA_MAR_VOL (PAGE_1 + 19) + + +#define LINE_AMP_CNTL_R1 (PAGE_1 + 22) +#define LINE_AMP_CNTL_R2 (PAGE_1 + 23) + +#define HP_AMP_CNTL_R1 (PAGE_1 + 27) +#define HP_AMP_CNTL_R2 (PAGE_1 + 28) +#define HP_AMP_CNTL_R3 (PAGE_1 + 29) + +#define HPL_VOL (PAGE_1 + 31) +#define HPR_VOL (PAGE_1 + 32) +#define INT1_SEL_L (PAGE_1 + 34) +#define RAMP_CNTL_R1 (PAGE_1 + 36) +#define RAMP_CNTL_R2 (PAGE_1 + 37) +#define INT1_SEL_RM (PAGE_1 + 39) +#define REC_AMP_CNTL_R5 (PAGE_1 + 40) +#define RAMPR_VOL (PAGE_1 + 41) +#define RAMP_TIME_CNTL (PAGE_1 + 42) +#define SPK_AMP_CNTL_R1 (PAGE_1 + 45) +#define SPK_AMP_CNTL_R2 (PAGE_1 + 46) +#define SPK_AMP_CNTL_R3 (PAGE_1 + 47) +#define SPK_AMP_CNTL_R4 (PAGE_1 + 48) +#define MIC_BIAS_CNTL (PAGE_1 + 51) + +#define LMIC_PGA_PIN (PAGE_1 + 52) +#define LMIC_PGA_PM_IN4 (PAGE_1 + 53) +#define LMIC_PGA_MIN (PAGE_1 + 54) +#define RMIC_PGA_PIN (PAGE_1 + 55) +#define RMIC_PGA_PM_IN4 (PAGE_1 + 56) +#define RMIC_PGA_MIN (PAGE_1 + 57) +/* MIC PGA Gain Registers */ +#define MICL_PGA (PAGE_1 + 59) +#define MICR_PGA (PAGE_1 + 60) +#define HEADSET_TUNING1_REG (PAGE_1 + 119) +#define HEADSET_TUNING2_REG (PAGE_1 + 120) +#define MIC_PWR_DLY (PAGE_1 + 121) +#define REF_PWR_DLY (PAGE_1 + 122) + +/* ****************** Page 4 Registers **************************************/ +#define PAGE_4 512 +#define ASI1_BUS_FMT (PAGE_4 + 1) +#define ASI1_LCH_OFFSET (PAGE_4 + 2) +#define ASI1_RCH_OFFSET (PAGE_4 + 3) +#define ASI1_CHNL_SETUP (PAGE_4 + 4) +#define ASI1_MULTI_CH_SETUP_R1 (PAGE_4 + 5) +#define ASI1_MULTI_CH_SETUP_R2 (PAGE_4 + 6) +#define ASI1_ADC_INPUT_CNTL (PAGE_4 + 7) +#define ASI1_DAC_OUT_CNTL (PAGE_4 + 8) +#define ASI1_ADC_OUT_TRISTATE (PAGE_4 + 9) +#define ASI1_BWCLK_CNTL_REG (PAGE_4 + 10) +#define ASI1_BCLK_N_CNTL (PAGE_4 + 11) +#define ASI1_BCLK_N (PAGE_4 + 12) +#define ASI1_WCLK_N (PAGE_4 + 13) +#define ASI1_BWCLK_OUT_CNTL (PAGE_4 + 14) +#define ASI1_DATA_OUT (PAGE_4 + 15) +#define ASI2_BUS_FMT (PAGE_4 + 17) +#define ASI2_LCH_OFFSET (PAGE_4 + 18) +#define ASI2_RCH_OFFSET (PAGE_4 + 19) +#define ASI2_ADC_INPUT_CNTL (PAGE_4 + 23) +#define ASI2_DAC_OUT_CNTL (PAGE_4 + 24) +#define ASI2_BWCLK_CNTL_REG (PAGE_4 + 26) +#define ASI2_BCLK_N_CNTL (PAGE_4 + 27) +#define ASI2_BCLK_N (PAGE_4 + 28) +#define ASI2_WCLK_N (PAGE_4 + 29) +#define ASI2_BWCLK_OUT_CNTL (PAGE_4 + 30) +#define ASI2_DATA_OUT (PAGE_4 + 31) +#define ASI3_BUS_FMT (PAGE_4 + 33) +#define ASI3_LCH_OFFSET (PAGE_4 + 34) +#define ASI3_RCH_OFFSET (PAGE_4 + 35) +#define ASI3_ADC_INPUT_CNTL (PAGE_4 + 39) +#define ASI3_DAC_OUT_CNTL (PAGE_4 + 40) +#define ASI3_BWCLK_CNTL_REG (PAGE_4 + 42) +#define ASI3_BCLK_N (PAGE_4 + 44) +#define ASI3_WCLK_N (PAGE_4 + 45) +#define ASI3_BWCLK_OUT_CNTL (PAGE_4 + 46) +#define ASI3_DATA_OUT (PAGE_4 + 47) +#define WCLK1_PIN_CNTL_REG (PAGE_4 + 65) +#define DOUT1_PIN_CNTL_REG (PAGE_4 + 67) +#define DIN1_PIN_CNTL_REG (PAGE_4 + 68) +#define WCLK2_PIN_CNTL_REG (PAGE_4 + 69) +#define BCLK2_PIN_CNTL_REG (PAGE_4 + 70) +#define DOUT2_PIN_CNTL_REG (PAGE_4 + 71) +#define DIN2_PIN_CNTL_REG (PAGE_4 + 72) +#define WCLK3_PIN_CNTL_REG (PAGE_4 + 73) +#define BCLK3_PIN_CNTL_REG (PAGE_4 + 74) +#define DOUT3_PIN_CNTL_REG (PAGE_4 + 75) +#define DIN3_PIN_CNTL_REG (PAGE_4 + 76) +#define MCLK2_PIN_CNTL_REG (PAGE_4 + 82) +#define GPIO1_IO_CNTL (PAGE_4 + 86) +#define GPIO2_IO_CNTL (PAGE_4 + 87) +#define GPI1_EN (PAGE_4 + 91) +#define GPO2_EN (PAGE_4 + 92) +#define GPO1_PIN_CNTL (PAGE_4 + 96) +#define MINIDSP_PORT_CNTL_REG (PAGE_4 + 118) + +/**************************************************************************** +* Mixer control related #defines +*************************************************************************** +*/ +#define WCLK1_ENUM 0 +#define DOUT1_ENUM 1 +#define DIN1_ENUM 2 +#define WCLK2_ENUM 3 +#define BCLK2_ENUM 4 +#define DOUT2_ENUM 5 +#define DIN2_ENUM 6 +#define WCLK3_ENUM 7 +#define BCLK3_ENUM 8 +#define DOUT3_ENUM 9 +#define DIN3_ENUM 10 +#define CLKIN_ENUM 11 +/* +***************************************************************************** +* Enumeration Definitions +***************************************************************************** +*/ +/* The below enumeration lists down all the possible inputs to the +* the PLL of the AIC3262. The Private structure will hold a member +* of this Enumeration Type. +*/ +enum AIC3262_PLL_OPTION { + PLL_CLKIN_MCLK1 = 0, /* 0000: (Device Pin) */ + PLL_CLKIN_BLKC1, /* 0001: (Device Pin) */ + PLL_CLKIN_GPIO1, /* 0010: (Device Pin)*/ + PLL_CLKIN_DIN1, /* 0011: (Device Pin)*/ + PLL_CLKIN_BCLK2, /* 0100: (Device Pin)*/ + PLL_CLKIN_GPI1, /* 0101: (Device Pin)*/ + PLL_CLKIN_HF_REF_CLK, /* 0110: (Device Pin)*/ + PLL_CLKIN_GPIO2, /* 0111: (Device Pin)*/ + PLL_CLKIN_GPI2, /* 1000: (Device Pin)*/ + PLL_CLKIN_MCLK2 /* 1001: (Device Pin)*/ +}; + +/* ASI Specific Bit Clock Divider Input Options. +* Please refer to Page 4 Reg 11, Reg 27 and Reg 43 +*/ +enum ASI_BDIV_CLKIN_OPTION { + BDIV_CLKIN_DAC_CLK = 0, /* 00 DAC_CLK */ + BDIV_CLKIN_DAC_MOD_CLK, /* 01 DAC_MOD_CLK */ + BDIV_CLKIN_ADC_CLK, /* 02 ADC_CLK */ + BDIV_CLKIN_ADC_MOD_CLK /* 03 ADC_MOD_CLK */ +}; + +/* ASI Specific Bit Clock Output Mux Options. +* Please refer to Page 4 Reg 14, Reg 30 and Reg 46 +* Please note that we are not handling the Reserved +* cases here. +*/ +enum ASI_BCLK_OPTION { + ASI1_BCLK_DIVIDER_OUTPUT = 0, /* 00 ASI1 Bit Clock Divider Output */ + ASI1_BCLK_INPUT, /* 01 ASI1 Bit Clock Input */ + ASI2_BCLK_DIVIDER_OUTPUT, /* 02 ASI2 Bit Clock Divider Output */ + ASI2_BCLK_INPUT, /* 03 ASI2 Bit Clock Input */ + ASI3_BCLK_DIVIDER_OUTPUT, /* 04 ASI3 Bit Clock Divider Output */ + ASI3_BBCLK_INPUT /* 05 ASi3 Bit Clock Input */ +}; + +/* Above bits are to be configured after Shifting 4 bits */ +#define AIC3262_ASI_BCLK_MUX_SHIFT 4 +#define AIC3262_ASI_BCLK_MUX_MASK (BIT6 | BIT5 | BIT4) +#define AIC3262_ASI_WCLK_MUX_MASK (BIT2 | BIT1 | BIT0) + +/* ASI Specific Word Clock Output Mux Options */ +enum ASI_WCLK_OPTION { + GENERATED_DAC_FS = 0, /* 00 WCLK = DAC_FS */ + GENERATED_ADC_FS = 1, /* 01 WCLK = ADC_FS */ + ASI1_WCLK_DIV_OUTPUT = 2, /* 02 WCLK = ASI1 WCLK_DIV_OUT */ + ASI1_WCLK_INPUT = 3, /* 03 WCLK = ASI1 WCLK Input */ + ASI2_WCLK_DIV_OUTPUT = 4, /* 04 WCLK = ASI2 WCLK_DIV_OUT */ + ASI2_WCLK_INPUT = 5, /* 05 WCLK = ASI2 WCLK Input */ + ASI3_WCLK_DIV_OUTPUT = 6, /* 06 WCLK = ASI3 WCLK_DIV_OUT */ + ASI3_WCLK_INPUT = 7 /* 07 WCLK = ASI3 WCLK Input */ +}; + +/* ASI DAC Output Control Options */ +enum ASI_DAC_OUTPUT_OPTION { + DAC_PATH_OFF = 0, /* 00 DAC Datapath Off */ + DAC_PATH_LEFT, /* 01 DAC Datapath left Data */ + DAC_PATH_RIGHT, /* 02 DAC Datapath Right Data */ +}; + +/* Shift the above options by so many bits */ +#define AIC3262_ASI_LDAC_PATH_SHIFT 6 +#define AIC3262_ASI_LDAC_PATH_MASK (BIT5 | BIT4) +#define AIC3262_ASI_RDAC_PATH_SHIFT 4 +#define AIC3262_ASI_RDAC_PATH_MASK (BIT7 | BIT6) + +/* ASI specific ADC Input Control Options */ +enum ASI_ADC_INPUT_OPTION { + ADC_PATH_OFF = 0, /* 00 ASI Digital Output Disabled */ + ADC_PATH_MINIDSP_1, /* 01 ASI Digital O/P from miniDSP_A(L1,R1) */ + ADC_PATH_ASI1, /* 02 ASI Digital Output from ASI1 */ + ADC_PATH_ASI2, /* 03 ASI Digital Output from ASI2 */ + ADC_PATH_ASI3, /* 04 ASI Digital Output from ASI3 */ + ADC_PATH_MINIDSP_2, /* 05 ASI Digital O/P from miniDSP_A(L2,R2) */ + ADC_PATH_MINIDSP_3 /* 05 ASI Digital O/P from miniDSP_A(L3,R3) */ +}; + +/* ASI Specific DOUT Pin Options */ +enum ASI_DOUT_OPTION { + ASI_OUTPUT = 0, /* 00 Default ASI Output */ + ASI1_INPUT, /* 01 ASI1 Data Input */ + ASI2_INPUT, /* 02 ASI2 Data Input */ + ASI3_INPUT /* 03 ASI3 Data Input */ +}; + +#define AIC3262_ASI_DOUT_MASK (BIT1 | BIT0) + +/* + ***************************************************************************** + * Structures Definitions + ***************************************************************************** + */ +#define AIC3262_MULTI_ASI_ACTIVE(x) (((x)->asiCtxt[0].asi_active) || \ + ((x)->asiCtxt[1].asi_active) || \ + ((x)->asiCtxt[2].asi_active)) + +/* +*---------------------------------------------------------------------------- +* @struct aic3262_setup_data | +* i2c specific data setup for AIC3262. +* @field unsigned short |i2c_address | +* Unsigned short for i2c address. +*---------------------------------------------------------------------------- +*/ + struct aic3262_setup_data { + unsigned short i2c_address; +}; + +/* +*---------------------------------------------------------------------------- +* @struct aic3262_asi_data +* ASI specific data stored for each ASI Interface +* +* +*--------------------------------------------------------------------------- +*/ +struct aic3262_asi_data { + u8 asi_active; /* ASI Active Flag */ + u8 master; /* Frame Master */ + u32 sampling_rate; /* Sampling Rate */ + enum ASI_BDIV_CLKIN_OPTION bclk_div_option; /* BCLK DIV Mux Option*/ + enum ASI_BCLK_OPTION bclk_output; /* BCLK Output Option*/ + enum ASI_WCLK_OPTION wclk_output; /* WCLK Output Option*/ + u8 bclk_div; /* BCLK Divider */ + u8 wclk_div; /* WCLK Divider */ + enum ASI_DAC_OUTPUT_OPTION left_dac_output; /* LDAC Path */ + enum ASI_DAC_OUTPUT_OPTION right_dac_output; /* RDAC Path */ + enum ASI_ADC_INPUT_OPTION adc_input; /* ADC Input Control */ + enum ASI_DOUT_OPTION dout_option; /* DOUT Option */ + u8 playback_mode; /* Playback Selected */ + u8 capture_mode; /* Record Selected */ + u8 port_muted; /* ASI Muted */ + u8 pcm_format; /* PCM Format */ + u8 word_len; /* Word Length */ + u8 offset1; /* Left Ch offset */ + u8 offset2; /* Right Ch Offset */ +}; + +/* +*---------------------------------------------------------------------------- +* @struct aic3262_priv | +* AIC3262 priviate data structure to set the system clock, mode and +* page number. +* @field u32 | sysclk | +* system clock +* @field s32 | master | +* master/slave mode setting for AIC3262 +* @field u8 | book_no | +* book number. +* @field u8 | page_no | +* page number. Here, page 0 and page 1 are used. +*---------------------------------------------------------------------------- +*/ +struct aic3262_priv { + enum snd_soc_control_type control_type; + struct aic326x_pdata *pdata; + u32 sysclk; + s32 master; + u8 book_no; + u8 page_no; + u8 process_flow; + u8 mute_codec; + u8 stream_status; + u32 active_count; + int current_dac_config[MAX_ASI_COUNT]; + int current_adc_config[MAX_ASI_COUNT]; + struct aic3262_asi_data asiCtxt[MAX_ASI_COUNT]; + enum AIC3262_PLL_OPTION aic3262_pllclkin_option; + u8 dac_clkin_option; + u8 adc_clkin_option; + int irq; + struct snd_soc_jack *headset_jack; +}; + +/* + *---------------------------------------------------------------------------- + * @struct aic3262_configs | + * AIC3262 initialization data which has register offset and register + * value. + * @field u8 | book_no | + * AIC3262 Book Number Offsets required for initialization.. + * @field u16 | reg_offset | + * AIC3262 Register offsets required for initialization.. + * @field u8 | reg_val | + * value to set the AIC3262 register to initialize the AIC3262. + *---------------------------------------------------------------------------- + */ +struct aic3262_configs { + u8 book_no; + u16 reg_offset; + u8 reg_val; +}; + +/* + *---------------------------------------------------------------------------- + * @struct aic3262_rate_divs | + * Setting up the values to get different freqencies + * @field u32 | mclk | + * Master clock + * @field u32 | rate | + * sample rate + * @field u8 | p_val | + * value of p in PLL + * @field u32 | pll_j | + * value for pll_j + * @field u32 | pll_d | + * value for pll_d + * @field u32 | dosr | + * value to store dosr + * @field u32 | ndac | + * value for ndac + * @field u32 | mdac | + * value for mdac + * @field u32 | aosr | + * value for aosr + * @field u32 | nadc | + * value for nadc + * @field u32 | madc | + * value for madc + * @field u32 | blck_N | + * value for block N + * @field u32 | aic3262_configs | + * configurations for aic3262 register value + *---------------------------------------------------------------------------- + */ +struct aic3262_rate_divs { + u32 mclk; + u32 rate; + u8 p_val; + u8 pll_j; + u16 pll_d; + u16 dosr; + u8 ndac; + u8 mdac; + u8 aosr; + u8 nadc; + u8 madc; + u8 blck_N; + struct aic3262_configs codec_specific_regs[NO_FEATURE_REGS]; +}; + +/* +***************************************************************************** +* EXTERN DECLARATIONS +***************************************************************************** +*/ +/* + *---------------------------------------------------------------------------- + * @func aic326x_headset_detect + * This function help to setup the needed registers to + * enable the headset detection + * + */ +extern int aic326x_headset_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, int jack_type); + + +extern u8 aic3262_read(struct snd_soc_codec *codec, u16 reg); +extern u16 aic3262_read_2byte(struct snd_soc_codec *codec, u16 reg); +extern int aic3262_reset_cache(struct snd_soc_codec *codec); +extern int aic3262_change_page(struct snd_soc_codec *codec, u8 new_page); +extern int aic3262_write(struct snd_soc_codec *codec, u16 reg, u8 value); +extern void aic3262_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, u8 value); +extern int aic3262_change_book(struct snd_soc_codec *codec, u8 new_book); +extern int reg_def_conf(struct snd_soc_codec *codec); +extern int i2c_verify_book0(struct snd_soc_codec *codec); + +#ifdef CONFIG_MINI_DSP +extern int aic3262_minidsp_program(struct snd_soc_codec *codec); +extern int aic3262_add_minidsp_controls(struct snd_soc_codec *codec); +#endif + + +#ifdef MULTIBYTE_CONFIG_SUPPORT +extern int aic3262_add_multiconfig_controls(struct snd_soc_codec *codec); +#endif + +#endif /* _TLV320AIC3262_H */ + diff --git a/sound/soc/codecs/tlv320aic326x_mini-dsp.c b/sound/soc/codecs/tlv320aic326x_mini-dsp.c new file mode 100755 index 000000000000..4d9c4de7e59a --- /dev/null +++ b/sound/soc/codecs/tlv320aic326x_mini-dsp.c @@ -0,0 +1,1587 @@ +/* + * linux/sound/soc/codecs/tlv320aic326x_mini-dsp.c + * + * Copyright (C) 2011 Mistral Solutions Pvt Ltd. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * The TLV320AIC3262 is a flexible, low-power, low-voltage stereo audio + * codec with digital microphone inputs and programmable outputs. + * + * History: + * + * Rev 0.1 Added the miniDSP Support Mistral 01-03-2011 + * + * Rev 0.2 Updated the code-base for miniDSP switching and + * mux control update. Mistral 21-03-2011 + * + * Rev 0.3 Updated the code-base to support Multi-Configuration feature + * of PPS GDE + */ + +/* + ***************************************************************************** + * INCLUDES + ***************************************************************************** + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/types.h> +#include <linux/kdev_t.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/soc-dapm.h> +#include <sound/control.h> +#include <linux/time.h> /* For timing computations */ +#include "tlv320aic326x.h" +#include "tlv320aic326x_mini-dsp.h" + +#include "first_rate_pps_driver.h" +#include "second_rate_pps_driver.h" + +#ifdef CONFIG_MINI_DSP + +#ifdef REG_DUMP_MINIDSP +static void aic3262_dump_page(struct i2c_client *i2c, u8 page); +#endif + +/* + ***************************************************************************** + * LOCAL STATIC DECLARATIONS + ***************************************************************************** + */ +static int m_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +static int m_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int m_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +/* + ***************************************************************************** + * MINIDSP RELATED GLOBALS + ***************************************************************************** + */ +/* The below variable is used to maintain the I2C Transactions + * to be carried out during miniDSP switching. + */ +minidsp_parser_data dsp_parse_data[MINIDSP_PARSER_ARRAY_SIZE*2]; + +struct i2c_msg i2c_transaction[MINIDSP_PARSER_ARRAY_SIZE * 2]; +/* Total count of I2C Messages are stored in the i2c_count */ +int i2c_count; + +/* The below array is used to store the burst array for I2C Multibyte + * Operations + */ +minidsp_i2c_page i2c_page_array[MINIDSP_PARSER_ARRAY_SIZE]; +int i2c_page_count; + +/* kcontrol structure used to register with ALSA Core layer */ +static struct snd_kcontrol_new snd_mux_controls[MAX_MUX_CONTROLS]; + +/* mode variables */ +static int amode; +static int dmode; + +/* k-control macros used for miniDSP related Kcontrols */ +#define SOC_SINGLE_VALUE_M(xmax, xinvert) \ + ((unsigned long)&(struct soc_mixer_control) \ + {.max = xmax, \ + .invert = xinvert}) +#define SOC_SINGLE_M(xname, max, invert) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = m_control_info, .get = m_control_get,\ + .put = m_control_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .private_value = SOC_SINGLE_VALUE_M(max, invert) } +#define SOC_SINGLE_AIC3262_M(xname) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = m_control_info, .get = m_control_get,\ + .put = m_control_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ +} + +/* + * aic3262_minidsp_controls + * + * Contains the list of the Kcontrol macros required for modifying the + * miniDSP behavior at run-time. + */ +static const struct snd_kcontrol_new aic3262_minidsp_controls[] = { + SOC_SINGLE_AIC3262_M("Minidsp mode") , + SOC_SINGLE_AIC3262_M("ADC Adaptive mode Enable") , + SOC_SINGLE_AIC3262_M("DAC Adaptive mode Enable") , + SOC_SINGLE_AIC3262_M("Dump Regs Book0") , + SOC_SINGLE_AIC3262_M("Verify minidsp program") , +}; + +#ifdef REG_DUMP_MINIDSP +/* + *---------------------------------------------------------------------------- + * Function : aic3262_dump_page + * Purpose : Read and display one codec register page, for debugging purpose + *---------------------------------------------------------------------------- + */ +static void aic3262_dump_page(struct i2c_client *i2c, u8 page) +{ + int i; + u8 data; + u8 test_page_array[256]; + + aic3262_change_page(codec, page); + + data = 0x0; + + i2c_master_send(i2c, data, 1); + i2c_master_recv(i2c, test_page_array, 128); + + DBG("\n------- MINI_DSP PAGE %d DUMP --------\n", page); + for (i = 0; i < 128; i++) + DBG(KERN_INFO " [ %d ] = 0x%x\n", i, test_page_array[i]); + +} +#endif + +/* + *---------------------------------------------------------------------------- + * Function : update_kcontrols + * Purpose : Given the miniDSP process flow, this function reads the + * corresponding Page Numbers and then performs I2C Read for those + * Pages. + *---------------------------------------------------------------------------- + */ +void update_kcontrols(struct snd_soc_codec *codec, int process_flow) +{ + int i, val1, array_size; + char **knames; + control *cntl; + + if (process_flow == 1) { + knames = Second_Rate_MUX_control_names; + cntl = Second_Rate_MUX_controls; + array_size = ARRAY_SIZE(Second_Rate_MUX_controls); + } else { + knames = main44_MUX_control_names; + cntl = main44_MUX_controls; + array_size = ARRAY_SIZE(main44_MUX_controls); + } + + DBG(KERN_INFO "%s: ARRAY_SIZE = %d\tmode=%d\n", __func__, + array_size, process_flow); + for (i = 0; i < array_size; i++) { + aic3262_change_book(codec, cntl[i].control_book); + aic3262_change_page(codec, cntl[i].control_page); + val1 = i2c_smbus_read_byte_data(codec->control_data, + cntl[i].control_base); + snd_mux_controls[i].private_value = 0; + } +} + +/* + *---------------------------------------------------------------------------- + * Function : byte_i2c_array_transfer + * Purpose : Function used only for debugging purpose. This function will + * be used while switching miniDSP Modes register by register. + * This needs to be used only during development. + *----------------------------------------------------------------------------- + */ +int byte_i2c_array_transfer(struct snd_soc_codec *codec, + reg_value *program_ptr, + int size) +{ + int j; + u8 buf[3]; + + for (j = 0; j < size; j++) { + /* Check if current Reg offset is zero */ + if (program_ptr[j].reg_off == 0) { + /* Check for the Book Change Request */ + if ((j < (size - 1)) && + (program_ptr[j+1].reg_off == 127)) { + aic3262_change_book(codec, + program_ptr[j+1].reg_val); + /* Increment for loop counter across Book Change */ + j++; + continue; + } + /* Check for the Page Change Request in Current book */ + aic3262_change_page(codec, program_ptr[j].reg_val); + continue; + } + + buf[AIC3262_REG_OFFSET_INDEX] = program_ptr[j].reg_off % 128; + buf[AIC3262_REG_DATA_INDEX] = + program_ptr[j].reg_val & AIC3262_8BITS_MASK; + + if (codec->hw_write(codec->control_data, buf, 2) != 2) { + printk(KERN_ERR "Error in i2c write\n"); + return -EIO; + } + } + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : byte_i2c_array_read + * Purpose : This function is used to perform Byte I2C Read. This is used + * only for debugging purposes to read back the Codec Page + * Registers after miniDSP Configuration. + *---------------------------------------------------------------------------- + */ +int byte_i2c_array_read(struct snd_soc_codec *codec, + reg_value *program_ptr, int size) +{ + int j; + u8 val1; + u8 cur_page = 0; + u8 cur_book = 0; + for (j = 0; j < size; j++) { + /* Check if current Reg offset is zero */ + if (program_ptr[j].reg_off == 0) { + /* Check for the Book Change Request */ + if ((j < (size - 1)) && + (program_ptr[j+1].reg_off == 127)) { + aic3262_change_book(codec, + program_ptr[j+1].reg_val); + cur_book = program_ptr[j+1].reg_val; + /* Increment for loop counter across Book Change */ + j++; + continue; + } + /* Check for the Page Change Request in Current book */ + aic3262_change_page(codec, program_ptr[j].reg_val); + cur_page = program_ptr[j].reg_val; + continue; + } + + val1 = i2c_smbus_read_byte_data(codec->control_data, + program_ptr[j].reg_off); + if (val1 < 0) + printk(KERN_ERR "Error in smbus read\n"); + + DBG(KERN_INFO "[%d][%d][%d]= %x\n", + cur_book, cur_page, program_ptr[j].reg_off, val1); + } + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : minidsp_get_burst + * Purpose : Format one I2C burst for transfer from mini dsp program array. + * This function will parse the program array and get next burst + * data for doing an I2C bulk transfer. + *---------------------------------------------------------------------------- + */ +static void +minidsp_get_burst(reg_value *program_ptr, + int program_size, + minidsp_parser_data *parse_data) +{ + int index = parse_data->current_loc; + int burst_write_count = 0; + + /*DBG("GET_BURST: start\n");*/ + /* check if first location is page register, and populate page addr */ + if (program_ptr[index].reg_off == 0) { + if ((index < (program_size - 1)) && + (program_ptr[index+1].reg_off == 127)) { + parse_data->book_change = 1; + parse_data->book_no = program_ptr[index+1].reg_val; + index += 2; + goto finish_out; + + } + parse_data->page_num = program_ptr[index].reg_val; + parse_data->burst_array[burst_write_count++] = + program_ptr[index].reg_off; + parse_data->burst_array[burst_write_count++] = + program_ptr[index].reg_val; + index++; + goto finish_out; + } + + parse_data->burst_array[burst_write_count++] = + program_ptr[index].reg_off; + parse_data->burst_array[burst_write_count++] = + program_ptr[index].reg_val; + index++; + + for (; index < program_size; index++) { + if (program_ptr[index].reg_off != + (program_ptr[index - 1].reg_off + 1)) + break; + else + parse_data->burst_array[burst_write_count++] = + program_ptr[index].reg_val; + + } +finish_out: + parse_data->burst_size = burst_write_count; + if (index == program_size) + /* parsing completed */ + parse_data->current_loc = MINIDSP_PARSING_END; + else + parse_data->current_loc = index; + /*DBG("GET_BURST: end\n");*/ +} +/* + *---------------------------------------------------------------------------- + * Function : minidsp_i2c_multibyte_transfer + * Purpose : Function used to perform multi-byte I2C Writes. Used to configure + * the miniDSP Pages. + *---------------------------------------------------------------------------- + */ +int +minidsp_i2c_multibyte_transfer(struct snd_soc_codec *codec, + reg_value *program_ptr, + int program_size) +{ + struct i2c_client *client = codec->control_data; + + minidsp_parser_data parse_data; + int count = 0; + +#ifdef DEBUG_MINIDSP_LOADING + int i = 0, j = 0; +#endif + /* point the current location to start of program array */ + parse_data.current_loc = 0; + parse_data.page_num = 0; + parse_data.book_change = 0; + parse_data.book_no = 0; + + DBG(KERN_INFO "size is : %d", program_size); + do { + do { + /* Get first burst data */ + minidsp_get_burst(program_ptr, program_size, + &parse_data); + if (parse_data.book_change == 1) + break; + dsp_parse_data[count] = parse_data; + + i2c_transaction[count].addr = client->addr; + i2c_transaction[count].flags = + client->flags & I2C_M_TEN; + i2c_transaction[count].len = + dsp_parse_data[count].burst_size; + i2c_transaction[count].buf = + dsp_parse_data[count].burst_array; + +#ifdef DEBUG_MINIDSP_LOADING + DBG(KERN_INFO + "i: %d\taddr: %d\tflags: %d\tlen: %d\tbuf:", + i, client->addr, client->flags & I2C_M_TEN, + dsp_parse_data[count].burst_size); + + for (j = 0; j <= dsp_parse_data[count].burst_size; j++) + DBG(KERN_INFO "%x ", + dsp_parse_data[i].burst_array[j]); + + DBG(KERN_INFO "\n\n"); + i++; +#endif + + count++; + /* Proceed to the next burst reg_addr_incruence */ + } while (parse_data.current_loc != MINIDSP_PARSING_END); + + if (count > 0) { + if (i2c_transfer(client->adapter, + i2c_transaction, count) != count) { + printk(KERN_ERR "Write burst i2c data error!\n"); + } + } + if (parse_data.book_change == 1) { + aic3262_change_book(codec, parse_data.book_no); + parse_data.book_change = 0; + } + } while (parse_data.current_loc != MINIDSP_PARSING_END); + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : set_minidsp_mode + * Purpose : Switch to the first minidsp mode. + *---------------------------------------------------------------------------- + */ +int +set_minidsp_mode(struct snd_soc_codec *codec, int new_mode) +{ + struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); + + DBG("%s: switch mode start\n", __func__); + aic3262_reset_cache(codec); + reg_def_conf(codec); + + if (new_mode == 0) { + + /* General Programming */ + DBG(KERN_INFO "$Writing reg_section_init_program\n"); + if (ARRAY_SIZE(main44_REG_Section_init_program) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_REG_Section_init_program, + ARRAY_SIZE(main44_REG_Section_init_program)); +#else + minidsp_i2c_multibyte_transfer(codec, + main44_REG_Section_init_program, + ARRAY_SIZE(main44_REG_Section_init_program)); +#endif + + } else { + printk(KERN_ERR + "_CODEC_REGS: Insufficient data for programming\n"); + } + + /* minidsp A programming */ + DBG(KERN_INFO "#Writing minidsp_A_reg_values\n"); + + if ((main44_miniDSP_A_reg_values_COEFF_SIZE + + main44_miniDSP_A_reg_values_INST_SIZE) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_miniDSP_A_reg_values, + (main44_miniDSP_A_reg_values_COEFF_SIZE + + main44_miniDSP_A_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + main44_miniDSP_A_reg_values, + (main44_miniDSP_A_reg_values_COEFF_SIZE + + main44_miniDSP_A_reg_values_INST_SIZE)); +#endif + } else { + printk(KERN_ERR + "MINI_DSP_A_second: Insufficient data for programming\n"); + } + /* minidsp D programming */ + DBG(KERN_INFO "#Writing minidsp_D_reg_values\n"); + if ((main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE) > 0) { + +#ifdef MULTIBYTE_CONFIG_SUPPORT + /*Multibyte for DAC */ +#endif + +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_miniDSP_D_reg_values, + (main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + main44_miniDSP_D_reg_values, + (main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE)); +#endif + + } else { + printk(KERN_ERR + "MINI_DSP_D_second: Insufficient data for programming\n"); + } + DBG(KERN_INFO "#Writing reg_section_post_program\n"); + if (ARRAY_SIZE(main44_REG_Section_post_program) > 0) { + #ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_REG_Section_post_program, + ARRAY_SIZE(REG_Section_post_program)); + #else + minidsp_i2c_multibyte_transfer(codec, + main44_REG_Section_post_program, + ARRAY_SIZE(main44_REG_Section_post_program)); + #endif + } else { + printk(KERN_ERR + "second_CODEC_REGS: Insufficient data for programming\n"); + } + } + + if (new_mode == 1) { + /* General Programming */ + DBG(KERN_INFO "#Writing reg_section_init_program\n"); + if (ARRAY_SIZE(Second_Rate_REG_Section_init_program) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + Second_Rate_REG_Section_init_program, + ARRAY_SIZE(Second_Rate_REG_Section_init_program)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_REG_Section_init_program, + ARRAY_SIZE(Second_Rate_REG_Section_init_program)); +#endif + + } else { + printk(KERN_ERR + "_CODEC_REGS: Insufficient data for programming\n"); + } + /* minidsp A programming */ + DBG(KERN_INFO "#Writing minidsp_A_reg_values\n"); + + if ((Second_Rate_miniDSP_A_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_A_reg_values_INST_SIZE) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + Second_Rate_miniDSP_A_reg_values, + (Second_Rate_miniDSP_A_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_A_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_miniDSP_A_reg_values, + (Second_Rate_miniDSP_A_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_A_reg_values_INST_SIZE)); +#endif + } else { + printk(KERN_ERR + "MINI_DSP_A_second: Insufficient data for programming\n"); + } + + /* minidsp D programming */ + DBG(KERN_INFO "#Writing minidsp_D_reg_values\n"); + + if ((Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_D_reg_values_INST_SIZE) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + Second_Rate_miniDSP_D_reg_values, + (Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_D_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_miniDSP_D_reg_values, + (Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_D_reg_values_INST_SIZE)); +#endif + } else { + printk(KERN_ERR + "MINI_DSP_D_second: Insufficient data for programming\n"); + } + DBG(KERN_INFO "Writing reg_section_post_program\n"); + if (ARRAY_SIZE(Second_Rate_REG_Section_post_program) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + Second_Rate_REG_Section_post_program, + ARRAY_SIZE(REG_Section_post_program)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_REG_Section_post_program, + ARRAY_SIZE(Second_Rate_REG_Section_post_program)); +#endif + } else { + printk(KERN_ERR + "second_CODEC_REGS: Insufficient data for programming\n"); + } + } + +#ifdef MULTIBYTE_CONFIG_SUPPORT + /*Multibyte for DAC */ + aic326x->process_flow = new_mode; + config_multibyte_for_mode(codec, new_mode); +#endif + DBG("%s: switch mode finished\n", __func__); + return 0; +} + +/* + * i2c_verify + * + * Function used to validate the contents written into the miniDSP + * pages after miniDSP Configuration. +*/ +int i2c_verify(struct snd_soc_codec *codec) +{ + + DBG(KERN_INFO "#%s: Invoked.. Resetting to page 0\n", __func__); + + aic3262_change_book(codec, 0); + DBG(KERN_INFO "#Reading reg_section_init_program\n"); + + byte_i2c_array_read(codec, main44_REG_Section_init_program, + ARRAY_SIZE(main44_REG_Section_init_program)); + + DBG(KERN_INFO "#Reading minidsp_A_reg_values\n"); + byte_i2c_array_read(codec, main44_miniDSP_A_reg_values, + (main44_miniDSP_A_reg_values_COEFF_SIZE + + main44_miniDSP_A_reg_values_INST_SIZE)); + + DBG(KERN_INFO "#Reading minidsp_D_reg_values\n"); + byte_i2c_array_read(codec, main44_miniDSP_D_reg_values, + (main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE)); + + DBG(KERN_INFO "#Reading reg_section_post_program\n"); + byte_i2c_array_read(codec, main44_REG_Section_post_program, + ARRAY_SIZE(main44_REG_Section_post_program)); + + DBG(KERN_INFO "i2c_verify completed\n"); + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : set_minidsp_mode1 + * Purpose : for laoding the default minidsp mode for the first time . + *---------------------------------------------------------------------------- + */ +int +set_minidsp_mode1(struct snd_soc_codec *codec, int new_mode) +{ + DBG("#%s: switch mode start\n", __func__); + aic3262_reset_cache(codec); + + if (new_mode == 0) { + /* General Programming */ + DBG(KERN_INFO "#Writing reg_section_init_program\n"); + if (ARRAY_SIZE(main44_REG_Section_init_program) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_REG_Section_init_program, + ARRAY_SIZE(main44_REG_Section_init_program)); +#else + minidsp_i2c_multibyte_transfer(codec, + main44_REG_Section_init_program, + ARRAY_SIZE(main44_REG_Section_init_program)); +#endif + + } else { + printk(KERN_ERR + "_CODEC_REGS: Insufficient data for programming\n"); + } + /* minidsp A programming */ + DBG(KERN_INFO "#Writing minidsp_A_reg_values\n"); + if ((main44_miniDSP_A_reg_values_COEFF_SIZE + + main44_miniDSP_A_reg_values_INST_SIZE) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_miniDSP_A_reg_values, + (main44_miniDSP_A_reg_values_COEFF_SIZE + + main44_miniDSP_A_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + main44_miniDSP_A_reg_values, + (main44_miniDSP_A_reg_values_COEFF_SIZE + + main44_miniDSP_A_reg_values_INST_SIZE)); +#endif + } else { + printk(KERN_ERR + "MINI_DSP_A_second: Insufficient data for programming\n"); + } + + /* minidsp D programming */ + DBG(KERN_INFO "#Writing minidsp_D_reg_values\n"); + if ((main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_miniDSP_D_reg_values, + (main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + main44_miniDSP_D_reg_values, + (main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE)); +#endif + } else { + printk(KERN_ERR + "MINI_DSP_D_second: Insufficient data for programming\n"); + } + + DBG(KERN_INFO "#Writing reg_section_post_program\n"); + if (ARRAY_SIZE(main44_REG_Section_post_program) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + main44_REG_Section_post_program, + ARRAY_SIZE(REG_Section_post_program)); +#else + minidsp_i2c_multibyte_transfer(codec, + main44_REG_Section_post_program, + ARRAY_SIZE(main44_REG_Section_post_program)); +#endif + } else { + printk(KERN_ERR + "second_CODEC_REGS: Insufficient data for programming\n"); + } + } + + if (new_mode == 1) { + /* General Programming */ + DBG(KERN_INFO "#Writing reg_section_init_program\n"); + if (ARRAY_SIZE(Second_Rate_REG_Section_init_program) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + second_Rate_REG_Section_init_program, + ARRAY_SIZE(Second_Rate_REG_Section_init_program)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_REG_Section_init_program, + ARRAY_SIZE(Second_Rate_REG_Section_init_program)); +#endif + } else { + printk(KERN_ERR + "_CODEC_REGS: Insufficient data for programming\n"); + } + /* minidsp A programming */ + DBG(KERN_INFO "#Writing minidsp_A_reg_values\n"); + if ((Second_Rate_miniDSP_A_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_A_reg_values_INST_SIZE) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + Second_Rate_miniDSP_A_reg_values, + (Second_Rate_miniDSP_A_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_A_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_miniDSP_A_reg_values, + (Second_Rate_miniDSP_A_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_A_reg_values_INST_SIZE)); +#endif + } else { + printk(KERN_ERR\ + "MINI_DSP_A_second: Insufficient data for programming\n"); + } + /* minidsp D programming */ + DBG(KERN_INFO "#Writing minidsp_D_reg_values\n"); + if ((Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_D_reg_values_INST_SIZE) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + Second_Rate_miniDSP_D_reg_values, + (Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_D_reg_values_INST_SIZE)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_miniDSP_D_reg_values, + (Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_D_reg_values_INST_SIZE)); +#endif + } else + printk(KERN_ERR "MINI_DSP_D_second: Insufficient data for programming\n"); + + DBG(KERN_INFO "#Writing reg_section_post_program\n"); + if (ARRAY_SIZE(Second_Rate_REG_Section_post_program) > 0) { +#ifndef MULTIBYTE_I2C + byte_i2c_array_transfer(codec, + Second_Rate_REG_Section_post_program, + ARRAY_SIZE(REG_Section_post_program)); +#else + minidsp_i2c_multibyte_transfer(codec, + Second_Rate_REG_Section_post_program, + ARRAY_SIZE(Second_Rate_REG_Section_post_program)); +#endif + } else + printk(KERN_ERR\ + "second_CODEC_REGS: Insufficient data for programming\n"); + + } + + DBG("#%s: switch mode completed\n", __func__); + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : aic3262_minidsp_program + * Purpose : Program mini dsp for AIC3262 codec chip. This routine is + * called from the aic3262 codec driver, if mini dsp programming + * is enabled. + *---------------------------------------------------------------------------- + */ +int aic3262_minidsp_program(struct snd_soc_codec *codec) +{ + DBG(KERN_INFO "#AIC3262: programming mini dsp\n"); + +#if defined(PROGRAM_MINI_DSP_first) + #ifdef DEBUG + DBG("#Verifying book 0\n"); + i2c_verify_book0(codec); +#endif + aic3262_change_book(codec, 0); + set_minidsp_mode1(codec, 0); + aic3262_change_book(codec, 0); +#ifdef DEBUG + DBG("#verifying book 0\n"); + i2c_verify_book0(codec); +#endif +#endif +#if defined(PROGRAM_MINI_DSP_second) +#ifdef DEBUG + DBG("#Verifying book 0\n"); + i2c_verify_book0(codec); +#endif + set_minidsp_mode1(codec, 1); +#ifdef DEBUG + DBG("#verifying book 0\n"); + i2c_verify_book0(codec); +#endif +#endif + return 0; +} +/* + *---------------------------------------------------------------------------- + * Function : m_control_info + * Purpose : This function is to initialize data for new control required to + * program the AIC3262 registers. + * + *---------------------------------------------------------------------------- + */ +static int m_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->count = 1; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : m_control_get + * Purpose : This function is to read data of new control for + * program the AIC3262 registers. + * + *---------------------------------------------------------------------------- + */ +static int m_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + u32 val; + u8 val1; + + if (!strcmp(kcontrol->id.name, "Minidsp mode")) { + val = aic3262->process_flow; + ucontrol->value.integer.value[0] = val; + DBG(KERN_INFO "control get : mode=%d\n", aic3262->process_flow); + } + if (!strcmp(kcontrol->id.name, "DAC Adaptive mode Enable")) { + aic3262_change_book(codec, 80); + val1 = i2c_smbus_read_byte_data(codec->control_data, 1); + ucontrol->value.integer.value[0] = ((val1>>1)&0x01); + DBG(KERN_INFO "control get : mode=%d\n", aic3262->process_flow); + } + if (!strcmp(kcontrol->id.name, "ADC Adaptive mode Enable")) { + aic3262_change_book(codec, 40); + val1 = i2c_smbus_read_byte_data(codec->control_data, 1); + ucontrol->value.integer.value[0] = ((val1>>1)&0x01); + DBG(KERN_INFO "control get : mode=%d\n", dmode); + } + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : m_new_control_put + * Purpose : new_control_put is called to pass data from user/application to + * the driver. + * + *---------------------------------------------------------------------------- + */ +static int m_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + u32 val; + u8 val1; + int mode = aic3262->process_flow; + + DBG("n_control_put\n"); + val = ucontrol->value.integer.value[0]; + if (!strcmp(kcontrol->id.name, "Minidsp mode")) { + DBG(KERN_INFO "\nMini dsp put\n mode = %d, val=%d\n", + aic3262->process_flow, val); + if (val != mode) { + if (aic3262->mute_codec == 1) { + i2c_verify_book0(codec); + aic3262_change_book(codec, 0); + set_minidsp_mode(codec, val); + + aic3262_change_book(codec, 0); + i2c_verify_book0(codec); + /* update_kcontrols(codec, val);*/ + } else { + printk(KERN_ERR + " Cant Switch Processflows, Playback in progress"); + } + } + } + + if (!strcmp(kcontrol->id.name, "DAC Adaptive mode Enable")) { + DBG(KERN_INFO "\nMini dsp put\n mode = %d, val=%d\n", + aic3262->process_flow, val); + if (val != amode) { + aic3262_change_book(codec, 80); + val1 = i2c_smbus_read_byte_data(codec->control_data, 1); + aic3262_write(codec, 1, (val1&0xfb)|(val<<1)); + } + amode = val; + } + + if (!strcmp(kcontrol->id.name, "ADC Adaptive mode Enable")) { + DBG(KERN_INFO "\nMini dsp put\n mode = %d, val=%d\n", + aic3262->process_flow, val); + if (val != dmode) { + aic3262_change_book(codec, 40); + val1 = i2c_smbus_read_byte_data(codec->control_data, 1); + aic3262_write(codec, 1, (val1&0xfb)|(val<<1)); + } + dmode = val; + } + + if (!strcmp(kcontrol->id.name, "Dump Regs Book0")) + i2c_verify_book0(codec); + + + if (!strcmp(kcontrol->id.name, "Verify minidsp program")) { + if (mode == 0) { + DBG("Current mod=%d\nVerifying minidsp_D_regs", mode); + byte_i2c_array_read(codec, main44_miniDSP_D_reg_values, + (main44_miniDSP_D_reg_values_COEFF_SIZE + + main44_miniDSP_D_reg_values_INST_SIZE)); + } else { + byte_i2c_array_read(codec, + Second_Rate_miniDSP_D_reg_values, + (Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + + Second_Rate_miniDSP_D_reg_values_INST_SIZE)); + } + } + DBG("\nmode = %d\n", mode); + return mode; +} + +/************************** MUX CONTROL section *****************************/ +/* + *---------------------------------------------------------------------------- + * Function : __new_control_info_minidsp_mux + * Purpose : info routine for mini dsp mux control amixer kcontrols + *---------------------------------------------------------------------------- + */ +static int __new_control_info_minidsp_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int index; + int ret_val = -1; + + for (index = 0; index < ARRAY_SIZE(main44_MUX_controls); index++) { + if (strstr(kcontrol->id.name, main44_MUX_control_names[index])) + break; + } + + if (index < ARRAY_SIZE(main44_MUX_controls)) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = MIN_MUX_CTRL; + uinfo->value.integer.max = MAX_MUX_CTRL; + ret_val = 0; + } + return ret_val; +} + +/* + *---------------------------------------------------------------------------- + * Function : __new_control_get_minidsp_mux + * + * Purpose : get routine for mux control amixer kcontrols, + * read current register values to user. + * Used for for mini dsp 'MUX control' amixer controls. + *---------------------------------------------------------------------------- + */ +static int __new_control_get_minidsp_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : __new_control_put_minidsp_mux + * + * Purpose : put routine for amixer kcontrols, write user values to registers + * values. Used for for mini dsp 'MUX control' amixer controls. + *---------------------------------------------------------------------------- + */ +static int __new_control_put_minidsp_mux(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 data[MUX_CTRL_REG_SIZE + 1]; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int index = 1; + int user_value = ucontrol->value.integer.value[0]; + struct i2c_client *i2c; + u8 value[2], swap_reg_pre, swap_reg_post; + u8 page; + int ret_val = -1, array_size; + control *array; + char **array_names; + char *control_name, *control_name1; + struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); + i2c = codec->control_data; + + if (aic326x->process_flow == 0) { + DBG("#the current process flow is %d", aic326x->process_flow); + array = main44_MUX_controls; + array_size = ARRAY_SIZE(main44_MUX_controls); + array_names = main44_MUX_control_names; + control_name = "Stereo_Mux_TwoToOne_1"; + control_name1 = "Mono_Mux_1_1"; + + /* Configure only for process flow 1 controls */ + if (strcmp(kcontrol->id.name, control_name) && + strcmp(kcontrol->id.name, control_name1)) + return 0; + } else { + array = Second_Rate_MUX_controls; + array_size = ARRAY_SIZE(Second_Rate_MUX_controls); + array_names = Second_Rate_MUX_control_names; + control_name = "Stereo_Mux_TwoToOne_1_Second"; + control_name1 = "Mono_Mux_1_1_Second"; + + /* Configure only for process flow 2 controls */ + if (strcmp(kcontrol->id.name, control_name) && + strcmp(kcontrol->id.name, control_name1)) + return 0; + } + + page = array[index].control_page; + + DBG("#user value = 0x%x\n", user_value); + for (index = 0; index < array_size; index++) { + if (strstr(kcontrol->id.name, array_names[index])) + break; + } + if (index < array_size) { + DBG(KERN_INFO "#Index %d Changing to Page %d\n", index, + array[index].control_page); + + aic3262_change_book(codec, + array[index].control_book); + aic3262_change_page(codec, + array[index].control_page); + + if (!strcmp(array_names[index], control_name)) { + if (user_value > 0) { + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + } else { + data[1] = 0xFF; + data[2] = 0xFf; + data[3] = 0xFF; + } + } else { + if (user_value > 0) { + data[1] = + (u8) ((user_value >> 16) & + AIC3262_8BITS_MASK); + data[2] = + (u8) ((user_value >> 8) & + AIC3262_8BITS_MASK); + data[3] = + (u8)((user_value) & AIC3262_8BITS_MASK); + } + } + /* start register address */ + data[0] = array[index].control_base; + + DBG(KERN_INFO + "#Writing %d %d %d \r\n", data[0], data[1], data[2]); + + ret_val = i2c_master_send(i2c, data, MUX_CTRL_REG_SIZE + 1); + + if (ret_val != MUX_CTRL_REG_SIZE + 1) + printk(KERN_ERR "i2c_master_send transfer failed\n"); + else { + /* store the current level */ + kcontrol->private_value = user_value; + ret_val = 0; + /* Enable adaptive filtering for ADC/DAC */ + } + + /* Perform a BUFFER SWAP Command. Check if we are currently not + * in Page 8, if so, swap to Page 8 first + */ + + value[0] = 1; + + if (i2c_master_send(i2c, value, 1) != 1) + printk(KERN_ERR "Can not write register address\n"); + + /* Read the Value of the Page 8 Register 1 which controls the + Adaptive Switching Mode */ + if (i2c_master_recv(i2c, value, 1) != 1) + printk(KERN_ERR "Can not read codec registers\n"); + + swap_reg_pre = value[0]; + /* Write the Register bit updates */ + value[1] = value[0] | 1; + value[0] = 1; + + if (i2c_master_send(i2c, value, 2) != 2) + printk(KERN_ERR "Can not write register address\n"); + + value[0] = 1; + /* verify buffer swap */ + if (i2c_master_send(i2c, value, 1) != 1) + printk(KERN_ERR "Can not write register address\n"); + + /* Read the Value of the Page 8 Register 1 which controls the + Adaptive Switching Mode */ + if (i2c_master_recv(i2c, &swap_reg_post, 1) != 1) + printk(KERN_ERR "Can not read codec registers\n"); + + if ((swap_reg_pre == 4 && swap_reg_post == 6) + || (swap_reg_pre == 6 && swap_reg_post == 4)) + DBG("Buffer swap success\n"); + else + printk(KERN_ERR + "Buffer swap...FAILED\nswap_reg_pre=%x, \ + swap_reg_post=%x\n", swap_reg_pre, swap_reg_post); + + } + /* update the new buffer value in the old, just swapped out buffer */ + aic3262_change_book(codec, array[index].control_book); + aic3262_change_page(codec, array[index].control_page); + ret_val = i2c_master_send(i2c, data, MUX_CTRL_REG_SIZE + 1); + ret_val = 0; + + aic3262_change_book(codec, 0); + return ret_val; +} + +/* + *---------------------------------------------------------------------------- + * Function : minidsp_mux_ctrl_mixer_controls + * + * Purpose : Add amixer kcontrols for mini dsp mux controls, + *---------------------------------------------------------------------------- + */ +static int minidsp_mux_ctrl_mixer_controls(struct snd_soc_codec *codec, + int size, control *cntl, + char **name) +{ + int i, err; + int val1; + + /* DBG("%d mixer controls for mini dsp MUX\n", no_mux_controls);*/ + + if (size) { + for (i = 0; i < size; i++) { + + snd_mux_controls[i].name = name[i]; + snd_mux_controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_mux_controls[i].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_mux_controls[i].info = + __new_control_info_minidsp_mux; + snd_mux_controls[i].get = __new_control_get_minidsp_mux; + snd_mux_controls[i].put = __new_control_put_minidsp_mux; + /* + * TBD: read volume reg and update the index number + */ + aic3262_change_book(codec, cntl[i].control_book); + aic3262_change_page(codec, cntl[i].control_page); + val1 = i2c_smbus_read_byte_data(codec->control_data, + cntl[i].control_base); + DBG(KERN_INFO "Control data %x\n", val1); + /* + if( val1 >= 0 ) + snd_mux_controls[i].private_value = val1; + else + snd_mux_controls[i].private_value = 0; + */ + DBG(KERN_INFO + "the value of amixer control mux=%d", val1); + if (val1 >= 0 && val1 != 255) + snd_mux_controls[i].private_value = val1; + else + snd_mux_controls[i].private_value = 0; + + snd_mux_controls[i].count = 0; + + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_mux_controls[i], + codec)); + if (err < 0) + printk(KERN_ERR + "%s:Invalid control %s\n", __FILE__, + snd_mux_controls[i].name); + } + } + return 0; +} + +/*------------------------- Volume Controls -----------------------*/ +static int volume_lite_table[] = { + + 0x00000D, 0x00000E, 0x00000E, 0x00000F, + 0x000010, 0x000011, 0x000012, 0x000013, + 0x000015, 0x000016, 0x000017, 0x000018, + 0x00001A, 0x00001C, 0x00001D, 0x00001F, + 0x000021, 0x000023, 0x000025, 0x000027, + 0x000029, 0x00002C, 0x00002F, 0x000031, + 0x000034, 0x000037, 0x00003B, 0x00003E, + 0x000042, 0x000046, 0x00004A, 0x00004F, + 0x000053, 0x000058, 0x00005D, 0x000063, + 0x000069, 0x00006F, 0x000076, 0x00007D, + 0x000084, 0x00008C, 0x000094, 0x00009D, + 0x0000A6, 0x0000B0, 0x0000BB, 0x0000C6, + 0x0000D2, 0x0000DE, 0x0000EB, 0x0000F9, + 0x000108, 0x000118, 0x000128, 0x00013A, + 0x00014D, 0x000160, 0x000175, 0x00018B, + 0x0001A3, 0x0001BC, 0x0001D6, 0x0001F2, + 0x000210, 0x00022F, 0x000250, 0x000273, + 0x000298, 0x0002C0, 0x0002E9, 0x000316, + 0x000344, 0x000376, 0x0003AA, 0x0003E2, + 0x00041D, 0x00045B, 0x00049E, 0x0004E4, + 0x00052E, 0x00057C, 0x0005D0, 0x000628, + 0x000685, 0x0006E8, 0x000751, 0x0007C0, + 0x000836, 0x0008B2, 0x000936, 0x0009C2, + 0x000A56, 0x000AF3, 0x000B99, 0x000C49, + 0x000D03, 0x000DC9, 0x000E9A, 0x000F77, + 0x001062, 0x00115A, 0x001262, 0x001378, + 0x0014A0, 0x0015D9, 0x001724, 0x001883, + 0x0019F7, 0x001B81, 0x001D22, 0x001EDC, + 0x0020B0, 0x0022A0, 0x0024AD, 0x0026DA, + 0x002927, 0x002B97, 0x002E2D, 0x0030E9, + 0x0033CF, 0x0036E1, 0x003A21, 0x003D93, + 0x004139, 0x004517, 0x00492F, 0x004D85, + 0x00521D, 0x0056FA, 0x005C22, 0x006197, + 0x006760, 0x006D80, 0x0073FD, 0x007ADC, + 0x008224, 0x0089DA, 0x009205, 0x009AAC, + 0x00A3D7, 0x00B7D4, 0x00AD8C, 0x00C2B9, + 0x00CE43, 0x00DA7B, 0x00E76E, 0x00F524, + 0x0103AB, 0x01130E, 0x01235A, 0x01349D, + 0x0146E7, 0x015A46, 0x016ECA, 0x018486, + 0x019B8C, 0x01B3EE, 0x01CDC3, 0x01E920, + 0x02061B, 0x0224CE, 0x024553, 0x0267C5, + 0x028C42, 0x02B2E8, 0x02DBD8, 0x030736, + 0x033525, 0x0365CD, 0x039957, 0x03CFEE, + 0x0409C2, 0x044703, 0x0487E5, 0x04CCA0, + 0x05156D, 0x05628A, 0x05B439, 0x060ABF, + 0x066666, 0x06C77B, 0x072E50, 0x079B3D, + 0x080E9F, 0x0888D7, 0x090A4D, 0x09936E, + 0x0A24B0, 0x0ABE8D, 0x0B6188, 0x0C0E2B, + 0x0CC509, 0x0D86BD, 0x0E53EB, 0x0F2D42, + 0x101379, 0x110754, 0x1209A3, 0x131B40, + 0x143D13, 0x157012, 0x16B543, 0x180DB8, + 0x197A96, 0x1AFD13, 0x1C9676, 0x1E481C, + 0x201373, 0x21FA02, 0x23FD66, 0x261F54, + 0x28619A, 0x2AC625, 0x2D4EFB, 0x2FFE44, + 0x32D646, 0x35D96B, 0x390A41, 0x3C6B7E, + 0x400000, 0x43CAD0, 0x47CF26, 0x4C106B, + 0x50923B, 0x55586A, 0x5A6703, 0x5FC253, + 0x656EE3, 0x6B7186, 0x71CF54, 0x788DB4, + 0x7FB260, +}; + +static struct snd_kcontrol_new snd_vol_controls[MAX_VOLUME_CONTROLS]; +/* + *---------------------------------------------------------------------------- + * Function : __new_control_info_main44_minidsp_volume + * Purpose : info routine for volumeLite amixer kcontrols + *---------------------------------------------------------------------------- + */ + +static int +__new_control_info_minidsp_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int index, index8; + int ret_val = -1; + + for (index = 0; index < ARRAY_SIZE(main44_VOLUME_controls); index++) { + if (strstr + (kcontrol->id.name, main44_VOLUME_control_names[index])) + break; + } + for (index8 = 0; index8 < ARRAY_SIZE(Second_Rate_VOLUME_controls); + index8++) { + if (strstr + (kcontrol->id.name, + Second_Rate_VOLUME_control_names[index])) + break; + } + if ((index < ARRAY_SIZE(main44_VOLUME_controls)) + || (index8 < ARRAY_SIZE(Second_Rate_VOLUME_controls))) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = MIN_VOLUME; + uinfo->value.integer.max = MAX_VOLUME; + ret_val = 0; + } + return ret_val; +} + +/* + *---------------------------------------------------------------------------- + * Function : __new_control_get_main44_minidsp_vol + * Purpose : get routine for amixer kcontrols, read current register + * values. Used for for mini dsp 'VolumeLite' amixer controls. + *---------------------------------------------------------------------------- + */ +static int +__new_control_get_minidsp_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : __new_control_put_main44_minidsp_volume + * Purpose : put routine for amixer kcontrols, write user values to registers + * values. Used for for mini dsp 'VolumeLite' amixer controls. + *---------------------------------------------------------------------------- + */ +static int +__new_control_put_minidsp_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 data[4]; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int user_value = ucontrol->value.integer.value[0]; + struct i2c_client *i2c = codec->control_data; + int ret_val = -1; + int coeff; + u8 value[2], swap_reg_pre, swap_reg_post; + struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); + + control *volume_controls = NULL; + printk(KERN_INFO "user value = 0x%x\n", user_value); + + if (aic3262->process_flow == 0) + volume_controls = main44_VOLUME_controls; + else + volume_controls = Second_Rate_VOLUME_controls; + + aic3262_change_book(codec, volume_controls->control_book); + aic3262_change_page(codec, volume_controls->control_page); + + coeff = volume_lite_table[user_value << 1]; + + data[1] = (u8) ((coeff >> 16) & AIC3262_8BITS_MASK); + data[2] = (u8) ((coeff >> 8) & AIC3262_8BITS_MASK); + data[3] = (u8) ((coeff) & AIC3262_8BITS_MASK); + + /* Start register address */ + data[0] = volume_controls->control_base; + ret_val = i2c_master_send(i2c, data, VOLUME_REG_SIZE + 1); + if (ret_val != VOLUME_REG_SIZE + 1) + printk(KERN_ERR "i2c_master_send transfer failed\n"); + else { + /* store the current level */ + kcontrol->private_value = user_value; + ret_val = 0; + } + /* Initiate buffer swap */ + value[0] = 1; + + if (i2c_master_send(i2c, value, 1) != 1) + printk(KERN_ERR "Can not write register address\n"); + + /* Read the Value of the Page 8 Register 1 which controls the + Adaptive Switching Mode */ + if (i2c_master_recv(i2c, value, 1) != 1) + printk(KERN_ERR "Can not read codec registers\n"); + + swap_reg_pre = value[0]; + /* Write the Register bit updates */ + value[1] = value[0] | 1; + value[0] = 1; + if (i2c_master_send(i2c, value, 2) != 2) + printk(KERN_ERR "Can not write register address\n"); + + value[0] = 1; + /* verify buffer swap */ + if (i2c_master_send(i2c, value, 1) != 1) + printk(KERN_ERR "Can not write register address\n"); + + /* Read the Value of the Page 8 Register 1 which controls the + Adaptive Switching Mode */ + if (i2c_master_recv(i2c, &swap_reg_post, 1) != 1) + printk(KERN_ERR "Can not read codec registers\n"); + + if ((swap_reg_pre == 4 && swap_reg_post == 6) + || (swap_reg_pre == 6 && swap_reg_post == 4)) + DBG("Buffer swap success\n"); + else + DBG("Buffer swap...FAILED\nswap_reg_pre=%x, swap_reg_post=%x\n", + swap_reg_pre, swap_reg_post); + + /* update the new buffer value in the old, just swapped out buffer */ + aic3262_change_book(codec, volume_controls->control_book); + aic3262_change_page(codec, volume_controls->control_page); + i2c_master_send(i2c, data, MUX_CTRL_REG_SIZE + 1); + + aic3262_change_book(codec, 0); + + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : minidsp_volume_main44_mixer_controls + * Purpose : Add amixer kcontrols for mini dsp volume Lite controls, + *---------------------------------------------------------------------------- + */ +static int minidsp_volume_mixer_controls(struct snd_soc_codec *codec) +{ + int i, err, no_volume_controls; + static char volume_control_name[MAX_VOLUME_CONTROLS][40]; + + /* ADD first process volume controls */ + no_volume_controls = ARRAY_SIZE(main44_VOLUME_controls); + + printk(KERN_INFO " %d mixer controls for mini dsp 'volumeLite'\n", + no_volume_controls); + + if (no_volume_controls) { + + for (i = 0; i < no_volume_controls; i++) { + strcpy(volume_control_name[i], + main44_VOLUME_control_names[i]); + strcat(volume_control_name[i], VOLUME_KCONTROL_NAME); + + printk(KERN_ERR "Volume controls: %s\n", + volume_control_name[i]); + + snd_vol_controls[i].name = volume_control_name[i]; + snd_vol_controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_vol_controls[i].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_vol_controls[i].info = + __new_control_info_minidsp_volume; + snd_vol_controls[i].get = + __new_control_get_minidsp_volume; + snd_vol_controls[i].put = + __new_control_put_minidsp_volume; + /* + * TBD: read volume reg and update the index number + */ + snd_vol_controls[i].private_value = 0; + snd_vol_controls[i].count = 0; + + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_vol_controls[i], + codec)); + if (err < 0) { + printk(KERN_ERR + "%s:Invalid control %s\n", __FILE__, + snd_vol_controls[i].name); + } + } + } + /* ADD second process volume controls */ + no_volume_controls = ARRAY_SIZE(Second_Rate_VOLUME_controls); + + printk(KERN_ERR " %d mixer controls for mini dsp 'volumeLite'\n", + no_volume_controls); + + if (no_volume_controls) { + + for (i = 0; i < no_volume_controls; i++) { + strcpy(volume_control_name[i], + Second_Rate_VOLUME_control_names[i]); + strcat(volume_control_name[i], VOLUME_KCONTROL_NAME); + + printk(KERN_ERR "Volume controls: %s\n", + volume_control_name[i]); + + snd_vol_controls[i].name = volume_control_name[i]; + snd_vol_controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_vol_controls[i].access = + SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_vol_controls[i].info = + __new_control_info_minidsp_volume; + snd_vol_controls[i].get = + __new_control_get_minidsp_volume; + snd_vol_controls[i].put = + __new_control_put_minidsp_volume; + /* + * TBD: read volume reg and update the index number + */ + snd_vol_controls[i].private_value = 0; + snd_vol_controls[i].count = 0; + + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_vol_controls[i], + codec)); + if (err < 0) { + printk(KERN_ERR + "%s:Invalid control %s\n", __FILE__, + snd_vol_controls[i].name); + } + } + } + return 0; +} + +/* + *-------------------------------------------------------------------------- + * Function : aic3262_add_minidsp_controls + * Purpose : Configures the AMIXER Control Interfaces that can be exercised by + * the user at run-time. Utilizes the the snd_adaptive_controls[] + * array to specify two run-time controls. + *--------------------------------------------------------------------------- + */ +int aic3262_add_minidsp_controls(struct snd_soc_codec *codec) +{ +#ifdef ADD_MINI_DSP_CONTROLS + int i, err, no_mux_controls; + /* add mode k control */ + for (i = 0; i < ARRAY_SIZE(aic3262_minidsp_controls); i++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&aic3262_minidsp_controls[i], codec)); + if (err < 0) { + printk(KERN_ERR "Invalid control\n"); + return err; + } + } + + /* add mux controls */ + no_mux_controls = ARRAY_SIZE(main44_MUX_controls); + minidsp_mux_ctrl_mixer_controls(codec, no_mux_controls, + main44_MUX_controls, main44_MUX_control_names); + + no_mux_controls = ARRAY_SIZE(Second_Rate_MUX_controls); + minidsp_mux_ctrl_mixer_controls(codec, no_mux_controls, + Second_Rate_MUX_controls, Second_Rate_MUX_control_names); + + /* add volume controls*/ + minidsp_volume_mixer_controls(codec); +#endif /* ADD_MINI_DSP_CONTROLS */ + return 0; +} + +MODULE_DESCRIPTION("ASoC TLV320AIC3262 miniDSP driver"); +MODULE_AUTHOR("Y Preetam Sashank Reddy <preetam@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); +#endif /* End of CONFIG_MINI_DSP */ diff --git a/sound/soc/codecs/tlv320aic326x_minidsp_config.c b/sound/soc/codecs/tlv320aic326x_minidsp_config.c new file mode 100755 index 000000000000..07b762aeedbc --- /dev/null +++ b/sound/soc/codecs/tlv320aic326x_minidsp_config.c @@ -0,0 +1,410 @@ +/* + * linux/sound/soc/codecs/tlv320aic326x_minidsp_config.c + * + * Copyright (C) 2011 Mistral Solutions Pvt Ltd. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * The TLV320AIC3262 is a flexible, low-power, low-voltage stereo audio + * codec with digital microphone inputs and programmable outputs. + * + * History: + * + * Rev 0.1 Added the multiconfig support Mistral 17-08-2011 + * + * Rev 0.2 Migrated for aic3262 nVidia + * Mistral 21-10-2011 + */ + +/* + ***************************************************************************** + * INCLUDES + ***************************************************************************** + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/types.h> +#include <linux/kdev_t.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/core.h> +#include <sound/soc-dapm.h> +#include <sound/control.h> +#include <linux/time.h> /* For timing computations */ +#include "tlv320aic326x.h" +#include "tlv320aic326x_mini-dsp.h" + +#include "Patch_base_jazz_Rate48_pps_driver.h" +#include "Patch_base_main_Rate48_pps_driver.h" +#include "Patch_base_pop_Rate48_pps_driver.h" +#include "Patch_base_rock_Rate48_pps_driver.h" + +#ifdef CONFIG_MINI_DSP + +#define MAX_CONFIG_D_ARRAYS 4 +#define MAX_CONFIG_A_ARRAYS 0 +#define MAX_CONFIG_ARRAYS 4 +#define MINIDSP_DMODE 0 +#define MINIDSP_AMODE 1 + +/* + ***************************************************************************** + * LOCAL STATIC DECLARATIONS + ***************************************************************************** + */ +static int multibyte_coeff_change(struct snd_soc_codec *codec, int); + +static int m_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int m_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +/* k-control macros used for miniDSP related Kcontrols */ +#define SOC_SINGLE_VALUE_M(xmax, xinvert) \ + ((unsigned long)&(struct soc_mixer_control) \ + {.max = xmax, \ + .invert = xinvert}) +#define SOC_SINGLE_M(xname, max, invert) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = m_control_info, .get = m_control_get,\ + .put = m_control_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .private_value = SOC_SINGLE_VALUE_M(max, invert) } +#define SOC_SINGLE_AIC3262_M(xname) \ +{\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = m_control_info, .get = m_control_get,\ + .put = m_control_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ +} + + +/* The Multi-Configurations generated through PPS GDE has been + * named as Rock, Pop, Jazz and Main. These were the Configurations + * that were used while testing this AUdio Driver. If the user + * creates Process-flow with different names, it is advised to + * modify the names present in the below array. + */ +static const char *multi_config_support_DAC[] = { + "ROCK", + "POP", + "JAZZ", + "MAIN", +}; + +/* SOC_ENUM Declaration and kControl for switching Configurations + * at run-time. + */ +static const struct soc_enum aic3262_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(multi_config_support_DAC), + multi_config_support_DAC); + +static const struct snd_kcontrol_new aic3262_minidsp_controls1[] = { + + SOC_ENUM_EXT("Multiconfig support for DAC", + aic3262_enum, m_control_get, m_control_put), + +}; + + +/* + * multibyte_config + * + * This structure has been devised to maintain information about each + * configuration provided with the PPS GDE Processflow. For each + * configuration, the Driver needs to know where the starting offset + * of the Coefficient change, Total Size of Coefficients being affected, + * and the Instruction Sizes. + * This has to be replicated for both miniDSP_A and miniDSP_D + */ +struct multibyte_config { + reg_value *regs; + unsigned int d_coeff_start; + unsigned int d_coeff_size; + unsigned int d_inst_start; + unsigned int d_inst_size; + unsigned int a_coeff_start; + unsigned int a_coeff_size; + unsigned int a_inst_start; + unsigned int a_inst_size; +} config_array[][2][MAX_CONFIG_ARRAYS] = { + /* Process flow 1 */ + { + { + /* DAC */ + {rock_D_reg_values, 0, 67, 67, 0, 0, 0, 0, 0}, + {pop_D_reg_values, 0, 67, 67, 0, 0, 0, 0, 0}, + {jazz_D_reg_values, 0, 67, 67, 0, 0, 0, 0, 0}, + {main_D_reg_values, 0, 67, 67, 0, 0, 0, 0, 0}, + }, + /* ADC */ + {}, + }, + + /* Process flow 2 */ + { +#if 0 + { + {main, 0, 0, 0, 0, 0, 0, 0, 0}, + {pop, 0, 0, 0, 0, 0, 0, 0, 0}, + {jazz, 0, 0, 0, 0, 0, 0, 0, 0}, + {rock, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + /* ADC */ + {}, +#endif + }, +}; + +/* + *---------------------------------------------------------------------------- + * Function : m_control_get + * Purpose : This function is to read data of new control for + * program the AIC3262 registers. + * + *---------------------------------------------------------------------------- + */ +static int m_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); + u32 val = 0; + u32 mode = aic326x->process_flow; + + + if (!strcmp(kcontrol->id.name, "Multiconfig support for DAC")) + val = aic326x->current_dac_config[mode]; + else if (!strcmp(kcontrol->id.name, "Multiconfig support for ADC")) + val = aic326x->current_adc_config[mode]; + + + ucontrol->value.integer.value[0] = val; + return 0; +} + +/* + *---------------------------------------------------------------------------- + * Function : m_new_control_put + * Purpose : new_control_put is called to pass data from user/application to + * the driver. + * + *---------------------------------------------------------------------------- + */ +static int m_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); + u32 val; + u8 value; + int pf = aic326x->process_flow; + struct multibyte_config *array; + + val = ucontrol->value.integer.value[0]; + if (!strcmp(kcontrol->id.name, "Multiconfig support for DAC")) { + if (aic326x->process_flow == MINIDSP_DMODE) { + if (val > MAX_CONFIG_D_ARRAYS) { + dev_err(codec->dev, "Value not in range\n"); + return -1; + } + + value = aic3262_read(codec, ADC_CHANNEL_POW); + value = aic3262_read(codec, PASI_DAC_DP_SETUP); + + array = &config_array[pf][MINIDSP_DMODE][val]; + minidsp_i2c_multibyte_transfer(codec, + array->regs, + (array->d_inst_size + array->d_coeff_size)); + + + /*coefficent buffer change*/ + multibyte_coeff_change(codec, 0x50); + + minidsp_i2c_multibyte_transfer(codec, + array->regs, + (array->d_inst_size + array->d_coeff_size)); + + + + value = aic3262_read(codec, ADC_CHANNEL_POW); + value = aic3262_read(codec, PASI_DAC_DP_SETUP); + } + aic326x->current_dac_config[pf] = val; + + } else { + if (aic326x->process_flow == MINIDSP_AMODE) { + if (val > MAX_CONFIG_A_ARRAYS) { + dev_err(codec->dev, "Value not in range\n"); + return -1; + } + array = &config_array[pf][MINIDSP_AMODE][val]; + minidsp_i2c_multibyte_transfer(codec, + array->regs, + (array->a_inst_size + array->a_coeff_size)); + } + aic326x->current_adc_config[pf] = val; + } + return val; +} + + +/* + *-------------------------------------------------------------------------- + * Function : aic3262_add_multiconfig_controls + * Purpose : Configures the AMIXER Control Interfaces that can be exercised by + * the user at run-time. Utilizes the the snd_adaptive_controls[] + * array to specify two run-time controls. + *--------------------------------------------------------------------------- + */ +int aic3262_add_multiconfig_controls(struct snd_soc_codec *codec) +{ + int i, err; + + DBG(KERN_INFO + "#%s: Invoked to add controls for Multi-Configuration\n", + __func__); + + /* add mode k control */ + for (i = 0; i < ARRAY_SIZE(aic3262_minidsp_controls1); i++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&aic3262_minidsp_controls1[i], + codec)); + if (err < 0) { + printk(KERN_ERR + "Cannot add controls for mulibyte configuration\n"); + return err; + } + } + DBG(KERN_INFO "#%s: Completed control addition.\n", __func__); + return 0; +} + +/* + *-------------------------------------------------------------------------- + * Function : config_multibyte_for_mode + * Purpose : Function which is invoked when user changes the configuration + * at run-time. Internally configures/switches both + * miniDSP_D and miniDSP_A Coefficient arrays. + *--------------------------------------------------------------------------- + */ +void config_multibyte_for_mode(struct snd_soc_codec *codec, int mode) +{ + int val; + int pf = mode; + struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); + struct multibyte_config *array; + + DBG(KERN_INFO "#%s: Invoked for miniDSP Mode %d\n", __func__, mode); + + array = config_array[pf][MINIDSP_DMODE]; + if ((aic326x->current_dac_config[pf] >= 0) && + (aic326x->current_dac_config[pf] < MAX_CONFIG_ARRAYS)) { + val = aic326x->current_dac_config[pf]; + array = &config_array[pf][MINIDSP_DMODE][val]; + byte_i2c_array_transfer(codec, + array->regs, + (array->d_inst_size + + array->d_coeff_size)); + } else { + DBG(KERN_INFO "#%s: Invalid Configuration ID %d specified.\n", + __func__, aic326x->current_dac_config[pf]); + } + + array = config_array[pf][MINIDSP_AMODE]; + if ((aic326x->current_adc_config[pf] >= 0) && + (aic326x->current_adc_config[pf] < MAX_CONFIG_ARRAYS)) { + val = aic326x->current_adc_config[pf]; + minidsp_i2c_multibyte_transfer(codec, + array[val].regs, + array[val].a_inst_size + + array[val].a_coeff_size); + } else { + DBG(KERN_INFO "#%s: Invalid Configuration ID %d specified.\n", + __func__, aic326x->current_dac_config[pf]); + } + return; +} + +/* + *-------------------------------------------------------------------------- + * Function : config_multibyte_for_mode + * Purpose : Function which is invoked when user changes the configuration + * at run-time. Internally configures/switches both + * miniDSP_D and miniDSP_A Coefficient arrays. + *--------------------------------------------------------------------------- + */ +static int multibyte_coeff_change(struct snd_soc_codec *codec, int bk) +{ + + u8 value[2], swap_reg_pre, swap_reg_post; + struct i2c_client *i2c; + i2c = codec->control_data; + + aic3262_change_book(codec, bk); + + value[0] = 1; + + if (i2c_master_send(i2c, value, 1) != 1) + printk(KERN_ERR "Can not write register address\n"); + else { + /* Read the Value of the Page 8 Register 1 which controls the + Adaptive Switching Mode */ + if (i2c_master_recv(i2c, value, 1) != 1) { + printk(KERN_ERR "Can not read codec registers\n"); + goto err; + } + swap_reg_pre = value[0]; + + /* Write the Register bit updates */ + value[1] = value[0] | 1; + value[0] = 1; + + if (i2c_master_send(i2c, value, 2) != 2) { + printk(KERN_ERR "Can not write register address\n"); + goto err; + } + value[0] = 1; + /* verify buffer swap */ + if (i2c_master_send(i2c, value, 1) != 1) + printk(KERN_ERR "Can not write register address\n"); + + /* Read the Value of the Page 8 Register 1 which controls the + Adaptive Switching Mode */ + if (i2c_master_recv(i2c, &swap_reg_post, 1) != 1) + printk(KERN_ERR "Can not read codec registers\n"); + + if ((swap_reg_pre == 4 && swap_reg_post == 6) + || (swap_reg_pre == 6 && swap_reg_post == 4)) + DBG(KERN_INFO "Buffer swap success\n"); + else + printk(KERN_ERR + "Buffer swap...FAILED\nswap_reg_pre=%x, swap_reg_post=%x\n", + swap_reg_pre, swap_reg_post); + } + +err: + return 0; +} + +#endif + +MODULE_DESCRIPTION("ASoC AIC3262 miniDSP multi-configuration"); +MODULE_AUTHOR("Barani Prashanth <gvbarani@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); |