/* * linux/sound/soc/codecs/tlv320aic326x.c * * Copyright (C) 2011 Texas Instruments Inc., * * 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 TI 20-01-2011 * * The AIC325x ASoC driver is ported for the codec AIC3262. * Rev 0.2 ASoC driver support TI 21-03-2011 * The AIC326x ASoC driver is updated for linux 2.6.32 Kernel. * Rev 0.3 ASoC driver support TI 20-04-2011 * The AIC326x ASoC driver is ported to 2.6.35 omap4 kernel */ /* ***************************************************************************** * INCLUDES ***************************************************************************** */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aic3xxx/aic3xxx_cfw.h" #include "aic3xxx/aic3xxx_cfw_ops.h" #include "tlv320aic326x.h" #define SOC_DOUBLE_R_SX_TLV3262(xname, xreg_left, xreg_right, xshift, \ xmin, xmax, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .tlv.p = (tlv_array), \ .info = snd_soc_info_volsw_2r_sx, \ .get = snd_soc_get_volsw_2r_sx, \ .put = snd_soc_put_volsw_2r_sx_aic3262, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg_left, \ .rreg = xreg_right, .shift = xshift, \ .min = xmin, .max = xmax} } /****************************************************************************** Macros ****************************************************************************** ****************************************************************************** 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_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt); static int aic3262_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, unsigned int Fin, unsigned int Fout); static int aic3262_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level); static int aic3262_set_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); static int aic3262_set_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); static int aic326x_adc_dsp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); static int aic3262_get_runstate(struct snd_soc_codec *codec); static int aic3262_dsp_pwrdwn_status(struct snd_soc_codec *codec); static int aic3262_dsp_pwrup(struct snd_soc_codec *codec, int state); static int aic3262_restart_dsps_sync(struct snd_soc_codec *codec, int rs); static inline unsigned int dsp_non_sync_mode(unsigned int state) { return (!((state & 0x03) && (state & 0x30))); } /** * snd_soc_put_volsw_2r_sx - double with tlv and variable data size * mixer put callback * @kcontrol: mixer control * @uinfo: control element information * * Returns 0 for success. */ int snd_soc_put_volsw_2r_sx_aic3262(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 mask = (1<shift)-1; int min = mc->min; int ret; unsigned int val, valr; val = ((ucontrol->value.integer.value[0]+min) & 0xff); val &= mask; valr = ((ucontrol->value.integer.value[1]+min) & 0xff); valr &= mask; ret = 0; ret = snd_soc_update_bits_locked(codec, mc->reg, mask, val); if (ret < 0) return ret; ret = snd_soc_update_bits_locked(codec, mc->rreg, mask, valr); if (ret < 0) return ret; return 0; } static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -6350, 50, 0); static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -1200, 50, 0); static const DECLARE_TLV_DB_SCALE(spk_gain_tlv, 600, 600, 0); static const DECLARE_TLV_DB_SCALE(output_gain_tlv, -600, 100, 1); static const DECLARE_TLV_DB_SCALE(micpga_gain_tlv, 0, 50, 0); static const DECLARE_TLV_DB_SCALE(adc_fine_gain_tlv, -40, 10, 0); static const DECLARE_TLV_DB_SCALE(beep_gen_volume_tlv, -6300, 100, 0); /* Chip-level Input and Output CM Mode Controls */ static const char * const input_common_mode_text[] = { "0.9v", "0.75v" }; static const char * const output_common_mode_text[] = { "Input CM", "1.25v", "1.5v", "1.65v" }; static const struct soc_enum input_cm_mode = SOC_ENUM_SINGLE(AIC3262_CM_REG, 2, 2, input_common_mode_text); static const struct soc_enum output_cm_mode = SOC_ENUM_SINGLE(AIC3262_CM_REG, 0, 4, output_common_mode_text); /* ***************************************************************************** * 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_SX_TLV3262("PCM Playback Volume", AIC3262_DAC_LVOL, AIC3262_DAC_RVOL, 8, 0xffffff81, 0x30, dac_vol_tlv), #endif /*HP Driver Gain Control */ SOC_DOUBLE_R_SX_TLV3262("HeadPhone Driver Amplifier Volume", AIC3262_HPL_VOL, AIC3262_HPR_VOL, 6, 0xffffffb9, 0xffffffce, output_gain_tlv), /*LO Driver Gain Control */ SOC_DOUBLE_TLV("Speaker Amplifier Volume", AIC3262_SPK_AMP_CNTL_R4, 4, 0, 5, 0, spk_gain_tlv), SOC_DOUBLE_R_SX_TLV3262("Receiver Amplifier Volume", AIC3262_REC_AMP_CNTL_R5, AIC3262_RAMPR_VOL, 6, 0xffffffb9, 0xffffffd6, output_gain_tlv), SOC_DOUBLE_R_SX_TLV3262("PCM Capture Volume", AIC3262_LADC_VOL, AIC3262_RADC_VOL, 7, 0xffffff68, 0xffffffa8, adc_vol_tlv), SOC_DOUBLE_R_TLV("MicPGA Volume Control", AIC3262_MICL_PGA, AIC3262_MICR_PGA, 0, 0x5F, 0, micpga_gain_tlv), SOC_DOUBLE_TLV("PCM Capture Fine Gain Volume", AIC3262_ADC_FINE_GAIN, 4, 0, 5, 1, adc_fine_gain_tlv), SOC_DOUBLE("ADC channel mute", AIC3262_ADC_FINE_GAIN, 7, 3, 1, 0), SOC_DOUBLE("DAC MUTE", AIC3262_DAC_MVOL_CONF, 2, 3, 1, 1), SOC_SINGLE("RESET", AIC3262_RESET_REG, 0, 1, 0), SOC_SINGLE("DAC VOL SOFT STEPPING", AIC3262_DAC_MVOL_CONF, 0, 2, 0), SOC_SINGLE("DAC AUTO MUTE CONTROL", AIC3262_DAC_MVOL_CONF, 4, 7, 0), SOC_SINGLE("RIGHT MODULATOR SETUP", AIC3262_DAC_MVOL_CONF, 7, 1, 0), SOC_SINGLE("ADC Volume soft stepping", AIC3262_ADC_CHANNEL_POW, 0, 3, 0), SOC_SINGLE("Mic Bias ext independent enable", AIC3262_MIC_BIAS_CNTL, 7, 1, 0), SOC_SINGLE("MICBIAS EXT Power Level", AIC3262_MIC_BIAS_CNTL, 4, 3, 0), SOC_SINGLE("MICBIAS INT Power Level", AIC3262_MIC_BIAS_CNTL, 0, 3, 0), SOC_SINGLE("BEEP_GEN_EN", AIC3262_BEEP_CNTL_R1, 7, 1, 0), SOC_DOUBLE_R("BEEP_VOL_CNTL", AIC3262_BEEP_CNTL_R1, AIC3262_BEEP_CNTL_R2, 0, 0x0F, 1), SOC_SINGLE("BEEP_MAS_VOL", AIC3262_BEEP_CNTL_R2, 6, 3, 0), SOC_SINGLE("DAC PRB Selection", AIC3262_DAC_PRB, 0, 26, 0), SOC_SINGLE("ADC PRB Selection", AIC3262_ADC_PRB, 0, 18, 0), SOC_ENUM("Input CM mode", input_cm_mode), SOC_ENUM("Output CM mode", output_cm_mode), SOC_SINGLE_EXT("FIRMWARE SET MODE", SND_SOC_NOPM, 0, 0xffff, 0, aic3262_set_mode_get, aic3262_set_mode_put), }; /* *---------------------------------------------------------------------------- * @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_asi1_dai_ops = { .hw_params = aic3262_hw_params, .digital_mute = aic3262_mute, .set_fmt = aic3262_set_dai_fmt, .set_pll = aic3262_dai_set_pll, }; struct snd_soc_dai_ops aic3262_asi2_dai_ops = { .hw_params = aic3262_hw_params, .digital_mute = aic3262_mute, .set_fmt = aic3262_set_dai_fmt, .set_pll = aic3262_dai_set_pll, }; struct snd_soc_dai_ops aic3262_asi3_dai_ops = { .hw_params = aic3262_hw_params, .digital_mute = aic3262_mute, .set_fmt = aic3262_set_dai_fmt, .set_pll = aic3262_dai_set_pll, }; struct snd_soc_dai_driver aic326x_dai_driver[] = { { .name = "aic326x-asi1", .playback = { .stream_name = "ASI1 Playback", .channels_min = 1, .channels_max = 8, .rates = AIC3262_RATES, .formats = AIC3262_FORMATS, }, .capture = { .stream_name = "ASI1 Capture", .channels_min = 1, .channels_max = 8, .rates = AIC3262_RATES, .formats = AIC3262_FORMATS, }, .ops = &aic3262_asi1_dai_ops, }, { .name = "aic326x-asi2", .playback = { .stream_name = "ASI2 Playback", .channels_min = 1, .channels_max = 2, .rates = AIC3262_RATES, .formats = AIC3262_FORMATS, }, .capture = { .stream_name = "ASI2 Capture", .channels_min = 1, .channels_max = 2, .rates = AIC3262_RATES, .formats = AIC3262_FORMATS, }, .ops = &aic3262_asi2_dai_ops, }, { .name = "aic326x-asi3", .playback = { .stream_name = "ASI3 Playback", .channels_min = 1, .channels_max = 2, .rates = AIC3262_RATES, .formats = AIC3262_FORMATS, }, .capture = { .stream_name = "ASI3 Capture", .channels_min = 1, .channels_max = 2, .rates = AIC3262_RATES, .formats = AIC3262_FORMATS, }, .ops = &aic3262_asi3_dai_ops, }, }; static const unsigned int adc_ma_tlv[] = { TLV_DB_RANGE_HEAD(7), 1, 1, TLV_DB_SCALE_ITEM(-3610, 0, 0), 2, 2, TLV_DB_SCALE_ITEM(-3010, 0, 0), 3, 3, TLV_DB_SCALE_ITEM(-2660, 0, 0), 4, 4, TLV_DB_SCALE_ITEM(-2410, 0, 0), 5, 7, TLV_DB_SCALE_ITEM(-2210, 1500, 0), 8, 11, TLV_DB_SCALE_ITEM(-1810, 1000, 0), 12, 41 , TLV_DB_SCALE_ITEM(-1450, 500, 0) }; static const DECLARE_TLV_DB_SCALE(lo_hp_tlv, -7830, 50, 0); static const struct snd_kcontrol_new mal_pga_mixer_controls[] = { SOC_DAPM_SINGLE("IN1 Left Capture Switch", AIC3262_MA_CNTL, 5, 1, 0), SOC_DAPM_SINGLE_TLV("Left MicPGA Volume", AIC3262_LADC_PGA_MAL_VOL, 0, 0x3f, 1, adc_ma_tlv), }; static const struct snd_kcontrol_new mar_pga_mixer_controls[] = { SOC_DAPM_SINGLE("IN1 Right Capture Switch", AIC3262_MA_CNTL, 4, 1, 0), SOC_DAPM_SINGLE_TLV("Right MicPGA Volume", AIC3262_RADC_PGA_MAR_VOL, 0, 0x3f, 1, adc_ma_tlv), }; /* Left HPL Mixer */ static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { SOC_DAPM_SINGLE("MA Left Playback Switch", AIC3262_HP_AMP_CNTL_R1, 7, 1, 0), SOC_DAPM_SINGLE("Left DAC Playback Switch", AIC3262_HP_AMP_CNTL_R1, 5, 1, 0), SOC_DAPM_SINGLE_TLV("LO Left-B1 Playback Volume", AIC3262_HP_AMP_CNTL_R2, 0, 0x7f, 1, lo_hp_tlv), }; /* Right HPR Mixer */ static const struct snd_kcontrol_new hpr_output_mixer_controls[] = { SOC_DAPM_SINGLE_TLV("LO Right-B1 Playback Volume", AIC3262_HP_AMP_CNTL_R3, 0, 0x7f, 1, lo_hp_tlv), SOC_DAPM_SINGLE("Left DAC Playback Switch", AIC3262_HP_AMP_CNTL_R1, 2, 1, 0), SOC_DAPM_SINGLE("Right DAC Playback Switch", AIC3262_HP_AMP_CNTL_R1, 4, 1, 0), SOC_DAPM_SINGLE("MA Right Playback Switch", AIC3262_HP_AMP_CNTL_R1, 6, 1, 0), }; /* Left LOL Mixer */ static const struct snd_kcontrol_new lol_output_mixer_controls[] = { SOC_DAPM_SINGLE("MA Left Playback Switch", AIC3262_LINE_AMP_CNTL_R2, 7, 1, 0), SOC_DAPM_SINGLE("IN1 Left-B Capture Switch", AIC3262_LINE_AMP_CNTL_R2, 3, 1, 0), SOC_DAPM_SINGLE("Left DAC Playback Switch", AIC3262_LINE_AMP_CNTL_R1, 7, 1, 0), SOC_DAPM_SINGLE("Right DAC Playback Switch", AIC3262_LINE_AMP_CNTL_R1, 5, 1, 0), }; /* Right LOR Mixer */ static const struct snd_kcontrol_new lor_output_mixer_controls[] = { SOC_DAPM_SINGLE("LO Left Playback Switch", AIC3262_LINE_AMP_CNTL_R1, 2, 1, 0), SOC_DAPM_SINGLE("Right DAC Playback Switch", AIC3262_LINE_AMP_CNTL_R1, 6, 1, 0), SOC_DAPM_SINGLE("MA Right Playback Switch", AIC3262_LINE_AMP_CNTL_R2, 6, 1, 0), SOC_DAPM_SINGLE("IN1 Right-B Capture Switch", AIC3262_LINE_AMP_CNTL_R2, 0, 1, 0), }; /* Left SPKL Mixer */ static const struct snd_kcontrol_new spkl_output_mixer_controls[] = { SOC_DAPM_SINGLE("MA Left Playback Switch", AIC3262_SPK_AMP_CNTL_R1, 7, 1, 0), SOC_DAPM_SINGLE_TLV("LO Left Playback Volume", AIC3262_SPK_AMP_CNTL_R2, 0, 0x7f, 1, lo_hp_tlv), SOC_DAPM_SINGLE("SPR_IN Switch", AIC3262_SPK_AMP_CNTL_R1, 2, 1, 0), }; /* Right SPKR Mixer */ static const struct snd_kcontrol_new spkr_output_mixer_controls[] = { SOC_DAPM_SINGLE_TLV("LO Right Playback Volume", AIC3262_SPK_AMP_CNTL_R3, 0, 0x7f, 1, lo_hp_tlv), SOC_DAPM_SINGLE("MA Right Playback Switch", AIC3262_SPK_AMP_CNTL_R1, 6, 1, 0), }; /* REC Mixer */ static const struct snd_kcontrol_new rec_output_mixer_controls[] = { SOC_DAPM_SINGLE_TLV("LO Left-B2 Playback Volume", AIC3262_RAMP_CNTL_R1, 0, 0x7f, 1, lo_hp_tlv), SOC_DAPM_SINGLE_TLV("IN1 Left Capture Volume", AIC3262_IN1L_SEL_RM, 0, 0x7f, 1, lo_hp_tlv), SOC_DAPM_SINGLE_TLV("IN1 Right Capture Volume", AIC3262_IN1R_SEL_RM, 0, 0x7f, 1, lo_hp_tlv), SOC_DAPM_SINGLE_TLV("LO Right-B2 Playback Volume", AIC3262_RAMP_CNTL_R2, 0, 0x7f, 1, lo_hp_tlv), }; /* Left Input Mixer */ static const struct snd_kcontrol_new left_input_mixer_controls[] = { SOC_DAPM_SINGLE("IN1 Left Capture Switch", AIC3262_LMIC_PGA_PIN, 6, 3, 0), SOC_DAPM_SINGLE("IN2 Left Capture Switch", AIC3262_LMIC_PGA_PIN, 4, 3, 0), SOC_DAPM_SINGLE("IN3 Left Capture Switch", AIC3262_LMIC_PGA_PIN, 2, 3, 0), SOC_DAPM_SINGLE("IN4 Left Capture Switch", AIC3262_LMIC_PGA_PM_IN4, 5, 1, 0), SOC_DAPM_SINGLE("IN1 Right Capture Switch", AIC3262_LMIC_PGA_PIN, 0, 3, 0), SOC_DAPM_SINGLE("IN2 Right Capture Switch", AIC3262_LMIC_PGA_MIN, 4, 3, 0), SOC_DAPM_SINGLE("IN3 Right Capture Switch", AIC3262_LMIC_PGA_MIN, 2, 3, 0), SOC_DAPM_SINGLE("IN4 Right Capture Switch", AIC3262_LMIC_PGA_PM_IN4, 4, 1, 0), SOC_DAPM_SINGLE("CM2 Left Capture Switch", AIC3262_LMIC_PGA_MIN, 0, 3, 0), SOC_DAPM_SINGLE("CM1 Left Capture Switch", AIC3262_LMIC_PGA_MIN, 6, 3, 0), }; /* Right Input Mixer */ static const struct snd_kcontrol_new right_input_mixer_controls[] = { SOC_DAPM_SINGLE("IN1 Right Capture Switch", AIC3262_RMIC_PGA_PIN, 6, 3, 0), SOC_DAPM_SINGLE("IN2 Right Capture Switch", AIC3262_RMIC_PGA_PIN, 4, 3, 0), SOC_DAPM_SINGLE("IN3 Right Capture Switch", AIC3262_RMIC_PGA_PIN, 2, 3, 0), SOC_DAPM_SINGLE("IN4 Right Capture Switch", AIC3262_RMIC_PGA_PM_IN4, 5, 1, 0), SOC_DAPM_SINGLE("IN2 Left Capture Switch", AIC3262_RMIC_PGA_PIN, 0, 3, 0), SOC_DAPM_SINGLE("IN1 Left Capture Switch", AIC3262_RMIC_PGA_MIN, 4, 3, 0), SOC_DAPM_SINGLE("IN3 Left Capture Switch", AIC3262_RMIC_PGA_MIN, 2, 3, 0), SOC_DAPM_SINGLE("IN4 Left Capture Switch", AIC3262_RMIC_PGA_PM_IN4, 4, 1, 0), SOC_DAPM_SINGLE("CM1 Right Capture Switch", AIC3262_RMIC_PGA_MIN, 6, 3, 0), SOC_DAPM_SINGLE("CM2 Right Capture Switch", AIC3262_RMIC_PGA_MIN, 0, 3, 0), }; static const char * const asi1lin_text[] = { "Off", "ASI1 Left In", "ASI1 Right In", "ASI1 MonoMix In" }; SOC_ENUM_SINGLE_DECL(asi1lin_enum, AIC3262_ASI1_DAC_OUT_CNTL, 6, asi1lin_text); static const struct snd_kcontrol_new asi1lin_control = SOC_DAPM_ENUM("ASI1LIN Route", asi1lin_enum); static const char * const asi1rin_text[] = { "Off", "ASI1 Right In", "ASI1 Left In", "ASI1 MonoMix In" }; SOC_ENUM_SINGLE_DECL(asi1rin_enum, AIC3262_ASI1_DAC_OUT_CNTL, 4, asi1rin_text); static const struct snd_kcontrol_new asi1rin_control = SOC_DAPM_ENUM("ASI1RIN Route", asi1rin_enum); static const char * const asi2lin_text[] = { "Off", "ASI2 Left In", "ASI2 Right In", "ASI2 MonoMix In" }; SOC_ENUM_SINGLE_DECL(asi2lin_enum, AIC3262_ASI2_DAC_OUT_CNTL, 6, asi2lin_text); static const struct snd_kcontrol_new asi2lin_control = SOC_DAPM_ENUM("ASI2LIN Route", asi2lin_enum); static const char * const asi2rin_text[] = { "Off", "ASI2 Right In", "ASI2 Left In", "ASI2 MonoMix In" }; SOC_ENUM_SINGLE_DECL(asi2rin_enum, AIC3262_ASI2_DAC_OUT_CNTL, 4, asi2rin_text); static const struct snd_kcontrol_new asi2rin_control = SOC_DAPM_ENUM("ASI2RIN Route", asi2rin_enum); static const char * const asi3lin_text[] = { "Off", "ASI3 Left In", "ASI3 Right In", "ASI3 MonoMix In" }; SOC_ENUM_SINGLE_DECL(asi3lin_enum, AIC3262_ASI3_DAC_OUT_CNTL, 6, asi3lin_text); static const struct snd_kcontrol_new asi3lin_control = SOC_DAPM_ENUM("ASI3LIN Route", asi3lin_enum); static const char * const asi3rin_text[] = { "Off", "ASI3 Right In", "ASI3 Left In", "ASI3 MonoMix In" }; SOC_ENUM_SINGLE_DECL(asi3rin_enum, AIC3262_ASI3_DAC_OUT_CNTL, 4, asi3rin_text); static const struct snd_kcontrol_new asi3rin_control = SOC_DAPM_ENUM("ASI3RIN Route", asi3rin_enum); static const char * const dacminidspin1_text[] = { "ASI1 In", "ASI2 In", "ASI3 In", "ADC MiniDSP Out" }; SOC_ENUM_SINGLE_DECL(dacminidspin1_enum, AIC3262_MINIDSP_DATA_PORT_CNTL, 4, dacminidspin1_text); static const struct snd_kcontrol_new dacminidspin1_control = SOC_DAPM_ENUM("DAC MiniDSP IN1 Route", dacminidspin1_enum); static const char * const dacminidspin2_text[] = { "ASI1 In", "ASI2 In", "ASI3 In" }; SOC_ENUM_SINGLE_DECL(dacminidspin2_enum, AIC3262_MINIDSP_DATA_PORT_CNTL, 2, dacminidspin2_text); static const struct snd_kcontrol_new dacminidspin2_control = SOC_DAPM_ENUM("DAC MiniDSP IN2 Route", dacminidspin2_enum); static const char * const dacminidspin3_text[] = { "ASI1 In", "ASI2 In", "ASI3 In" }; SOC_ENUM_SINGLE_DECL(dacminidspin3_enum, AIC3262_MINIDSP_DATA_PORT_CNTL, 0, dacminidspin3_text); static const struct snd_kcontrol_new dacminidspin3_control = SOC_DAPM_ENUM("DAC MiniDSP IN3 Route", dacminidspin3_enum); static const char * const adcdac_route_text[] = { "Off", "On", }; SOC_ENUM_SINGLE_DECL(adcdac_enum, 0, 2, adcdac_route_text); static const struct snd_kcontrol_new adcdacroute_control = SOC_DAPM_ENUM_VIRT("ADC DAC Route", adcdac_enum); static const char * const dout1_text[] = { "ASI1 Out", "DIN1 Bypass", "DIN2 Bypass", "DIN3 Bypass", }; SOC_ENUM_SINGLE_DECL(dout1_enum, AIC3262_ASI1_DOUT_CNTL, 0, dout1_text); static const struct snd_kcontrol_new dout1_control = SOC_DAPM_ENUM("DOUT1 Route", dout1_enum); static const char * const dout2_text[] = { "ASI2 Out", "DIN1 Bypass", "DIN2 Bypass", "DIN3 Bypass", }; SOC_ENUM_SINGLE_DECL(dout2_enum, AIC3262_ASI2_DOUT_CNTL, 0, dout2_text); static const struct snd_kcontrol_new dout2_control = SOC_DAPM_ENUM("DOUT2 Route", dout2_enum); static const char * const dout3_text[] = { "ASI3 Out", "DIN1 Bypass", "DIN2 Bypass", "DIN3 Bypass", }; SOC_ENUM_SINGLE_DECL(dout3_enum, AIC3262_ASI3_DOUT_CNTL, 0, dout3_text); static const struct snd_kcontrol_new dout3_control = SOC_DAPM_ENUM("DOUT3 Route", dout3_enum); static const char * const asi1out_text[] = { "Off", "ADC MiniDSP Out1", "ASI1In Bypass", "ASI2In Bypass", "ASI3In Bypass", }; SOC_ENUM_SINGLE_DECL(asi1out_enum, AIC3262_ASI1_ADC_INPUT_CNTL, 0, asi1out_text); static const struct snd_kcontrol_new asi1out_control = SOC_DAPM_ENUM("ASI1OUT Route", asi1out_enum); static const char * const asi2out_text[] = { "Off", "ADC MiniDSP Out1", "ASI1In Bypass", "ASI2In Bypass", "ASI3In Bypass", "ADC MiniDSP Out2", }; SOC_ENUM_SINGLE_DECL(asi2out_enum, AIC3262_ASI2_ADC_INPUT_CNTL, 0, asi2out_text); static const struct snd_kcontrol_new asi2out_control = SOC_DAPM_ENUM("ASI2OUT Route", asi2out_enum); static const char * const asi3out_text[] = { "Off", "ADC MiniDSP Out1", "ASI1In Bypass", "ASI2In Bypass", "ASI3In Bypass", "Reserved", "ADC MiniDSP Out3", }; SOC_ENUM_SINGLE_DECL(asi3out_enum, AIC3262_ASI3_ADC_INPUT_CNTL, 0, asi3out_text); static const struct snd_kcontrol_new asi3out_control = SOC_DAPM_ENUM("ASI3OUT Route", asi3out_enum); static const char * const asibclk_text[] = { "DAC_CLK", "DAC_MOD_CLK", "ADC_CLK", "ADC_MOD_CLK", }; SOC_ENUM_SINGLE_DECL(asi1bclk_enum, AIC3262_ASI1_BCLK_N_CNTL, 0, asibclk_text); static const struct snd_kcontrol_new asi1bclk_control = SOC_DAPM_ENUM("ASI1_BCLK Route", asi1bclk_enum); SOC_ENUM_SINGLE_DECL(asi2bclk_enum, AIC3262_ASI2_BCLK_N_CNTL, 0, asibclk_text); static const struct snd_kcontrol_new asi2bclk_control = SOC_DAPM_ENUM("ASI2_BCLK Route", asi2bclk_enum); SOC_ENUM_SINGLE_DECL(asi3bclk_enum, AIC3262_ASI3_BCLK_N_CNTL, 0, asibclk_text); static const struct snd_kcontrol_new asi3bclk_control = SOC_DAPM_ENUM("ASI3_BCLK Route", asi3bclk_enum); static const char * const adc_mux_text[] = { "Analog", "Digital", }; SOC_ENUM_SINGLE_DECL(adcl_enum, AIC3262_ADC_CHANNEL_POW, 4, adc_mux_text); SOC_ENUM_SINGLE_DECL(adcr_enum, AIC3262_ADC_CHANNEL_POW, 2, adc_mux_text); static const struct snd_kcontrol_new adcl_mux = SOC_DAPM_ENUM("Left ADC Route", adcl_enum); static const struct snd_kcontrol_new adcr_mux = SOC_DAPM_ENUM("Right ADC Route", adcr_enum); /** * aic326x_hp_event: - To handle headphone related task before and after * headphone powrup and power down * @w: pointer variable to dapm_widget * @kcontrol: mixer control * @event: event element information * * Returns 0 for success. */ static int aic326x_hp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { int reg_mask = 0; int mute_reg = 0; int ret_wbits = 0; u8 hpl_hpr; if (w->shift == 1) { reg_mask = AIC3262_HPL_POWER_STATUS_MASK; mute_reg = AIC3262_HPL_VOL; } if (w->shift == 0) { reg_mask = AIC3262_HPR_POWER_STATUS_MASK; mute_reg = AIC3262_HPR_VOL; } switch (event) { case SND_SOC_DAPM_PRE_PMU: snd_soc_update_bits(w->codec, AIC3262_CHARGE_PUMP_CNTL, AIC3262_DYNAMIC_OFFSET_CALIB_MASK, AIC3262_DYNAMIC_OFFSET_CALIB); snd_soc_write(w->codec, mute_reg, 0x80); snd_soc_update_bits(w->codec, AIC3262_HP_CTL, AIC3262_HP_STAGE_MASK , AIC3262_HP_STAGE_25 << AIC3262_HP_STAGE_SHIFT); break; case SND_SOC_DAPM_POST_PMU: ret_wbits = aic3xxx_wait_bits(w->codec->control_data, AIC3262_HP_FLAG, reg_mask, reg_mask, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); if (!ret_wbits) { dev_err(w->codec->dev, "HP POST_PMU timedout\n"); return -1; } snd_soc_update_bits(w->codec, AIC3262_HP_CTL, AIC3262_HP_STAGE_MASK , AIC3262_HP_STAGE_100 << AIC3262_HP_STAGE_SHIFT); break; case SND_SOC_DAPM_PRE_PMD: snd_soc_update_bits(w->codec, AIC3262_HP_CTL, AIC3262_HP_STAGE_MASK , AIC3262_HP_STAGE_25 << AIC3262_HP_STAGE_SHIFT); hpl_hpr = snd_soc_read(w->codec, AIC3262_HP_AMP_CNTL_R1); if ((hpl_hpr & 0x3) == 0x3) { snd_soc_update_bits(w->codec, AIC3262_HP_AMP_CNTL_R1, AIC3262_HPL_POWER_MASK, 0x0); mdelay(1); snd_soc_update_bits(w->codec, AIC3262_HP_AMP_CNTL_R1, AIC3262_HPR_POWER_MASK, 0x0); } break; case SND_SOC_DAPM_POST_PMD: ret_wbits = aic3xxx_wait_bits(w->codec->control_data, AIC3262_HP_FLAG, reg_mask, 0, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); if (!ret_wbits) { dev_err(w->codec->dev, "HP POST_PMD timedout\n"); return -1; } snd_soc_write(w->codec, mute_reg, 0xb9); snd_soc_write(w->codec, AIC3262_POWER_CONF, snd_soc_read(w->codec, AIC3262_POWER_CONF)); break; default: BUG(); return -EINVAL; } return 0; } /** *aic326x_dac_event: Headset popup reduction and powering up dsps together * when they are in sync mode * @w: pointer variable to dapm_widget * @kcontrol: pointer to sound control * @event: event element information * * Returns 0 for success. */ static int aic326x_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { int reg_mask = 0; int ret_wbits = 0; int run_state_mask; struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(w->codec); int sync_needed = 0, non_sync_state = 0; int other_dsp = 0, run_state = 0; if (w->shift == 7) { reg_mask = AIC3262_LDAC_POWER_STATUS_MASK; run_state_mask = AIC3XXX_COPS_MDSP_D_L; } if (w->shift == 6) { reg_mask = AIC3262_RDAC_POWER_STATUS_MASK; run_state_mask = AIC3XXX_COPS_MDSP_D_R; } switch (event) { case SND_SOC_DAPM_POST_PMU: ret_wbits = aic3xxx_wait_bits(w->codec->control_data, AIC3262_DAC_FLAG, reg_mask, reg_mask, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); sync_needed = aic3xxx_reg_read(w->codec->control_data, AIC3262_DAC_PRB); non_sync_state = dsp_non_sync_mode(aic3262->dsp_runstate); other_dsp = aic3262->dsp_runstate & AIC3XXX_COPS_MDSP_A; if (sync_needed && non_sync_state && other_dsp) { run_state = aic3262_get_runstate( aic3262->codec); aic3262_dsp_pwrdwn_status(aic3262->codec); aic3262_dsp_pwrup(aic3262->codec, run_state); } aic3262->dsp_runstate |= run_state_mask; if (!ret_wbits) { dev_err(w->codec->dev, "DAC POST_PMU timedout\n"); return -1; } break; case SND_SOC_DAPM_POST_PMD: ret_wbits = aic3xxx_wait_bits(w->codec->control_data, AIC3262_DAC_FLAG, reg_mask, 0, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); aic3262->dsp_runstate = (aic3262->dsp_runstate & ~run_state_mask); if (!ret_wbits) { dev_err(w->codec->dev, "DAC POST_PMD timedout\n"); return -1; } break; default: BUG(); return -EINVAL; } return 0; } /** * aic326x_spk_event: Speaker related task before and after * headphone powrup and power down$ * @w: pointer variable to dapm_widget, * @kcontrolr: pointer variable to sound control, * @event: integer to event, * * Return value: 0 for success */ static int aic326x_spk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { int reg_mask; if (w->shift == 1) reg_mask = AIC3262_SPKL_POWER_STATUS_MASK; if (w->shift == 0) reg_mask = AIC3262_SPKR_POWER_STATUS_MASK; switch (event) { case SND_SOC_DAPM_POST_PMU: mdelay(1); break; case SND_SOC_DAPM_POST_PMD: mdelay(1); break; default: BUG(); return -EINVAL; } return 0; } /**$ * pll_power_on_event: provide delay after widget power up * @w: pointer variable to dapm_widget, * @kcontrolr: pointer variable to sound control, * @event: integer to event, * * Return value: 0 for success */ static int pll_power_on_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { if (event == SND_SOC_DAPM_POST_PMU) mdelay(10); return 0; } /** * aic3262_set_mode_get: To get different mode of Firmware through tinymix * @kcontrolr: pointer to sound control, * ucontrol: pointer to control element value, * * Return value: 0 for success */ static int aic3262_set_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct aic3262_priv *priv_ds = snd_soc_codec_get_drvdata(codec); ucontrol->value.integer.value[0] = ((priv_ds->cfw_p->cur_mode << 8) | priv_ds->cfw_p->cur_cfg); return 0; } /** * aic3262_set_mode_put: To set different mode of Firmware through tinymix * @kcontrolr: pointer to sound control, * ucontrol: pointer to control element value, * * Return value: 0 for success */ static int aic3262_set_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct aic3262_priv *priv_ds = snd_soc_codec_get_drvdata(codec); int next_mode = 0, next_cfg = 0; int ret = 0; next_mode = (ucontrol->value.integer.value[0] >> 8); next_cfg = (ucontrol->value.integer.value[0]) & 0xFF; if (priv_ds == NULL) dev_err(codec->dev, "failed to load firmware\n"); else ret = aic3xxx_cfw_setmode_cfg(priv_ds->cfw_p, next_mode, next_cfg); return ret; } /** * aic326x_adc_dsp_event: To get DSP run state to perform synchronization * @w: pointer variable to dapm_widget * @kcontrol: pointer to sound control * @event: event element information * * Returns 0 for success. */ static int aic326x_adc_dsp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { int run_state = 0; int non_sync_state = 0, sync_needed = 0; int other_dsp = 0; int run_state_mask = 0; struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(w->codec); int reg_mask = 0; int ret_wbits = 0; if (w->shift == 7) { reg_mask = AIC3262_LADC_POWER_MASK; run_state_mask = AIC3XXX_COPS_MDSP_A_L; } if (w->shift == 6) { reg_mask = AIC3262_RADC_POWER_MASK; run_state_mask = AIC3XXX_COPS_MDSP_A_R; } switch (event) { case SND_SOC_DAPM_POST_PMU: ret_wbits = aic3xxx_wait_bits(w->codec->control_data, AIC3262_ADC_FLAG, reg_mask, reg_mask, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); sync_needed = aic3xxx_reg_read(w->codec->control_data, AIC3262_DAC_PRB); non_sync_state = dsp_non_sync_mode(aic3262->dsp_runstate); other_dsp = aic3262->dsp_runstate & AIC3XXX_COPS_MDSP_D; if (sync_needed && non_sync_state && other_dsp) { run_state = aic3262_get_runstate( aic3262->codec); aic3262_dsp_pwrdwn_status(aic3262->codec); aic3262_dsp_pwrup(aic3262->codec, run_state); } aic3262->dsp_runstate |= run_state_mask; if (!ret_wbits) { dev_err(w->codec->dev, "ADC POST_PMU timedout\n"); return -1; } break; case SND_SOC_DAPM_POST_PMD: ret_wbits = aic3xxx_wait_bits(w->codec->control_data, AIC3262_ADC_FLAG, reg_mask, 0, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); aic3262->dsp_runstate = (aic3262->dsp_runstate & ~run_state_mask); if (!ret_wbits) { dev_err(w->codec->dev, "ADC POST_PMD timedout\n"); return -1; } break; default: BUG(); return -EINVAL; } return 0; } static const struct snd_soc_dapm_widget aic3262_dapm_widgets[] = { /* TODO: Can we switch these off ? */ SND_SOC_DAPM_AIF_IN("DIN1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("DIN2", "ASI2 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("DIN3", "ASI3 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC_E("Left DAC", NULL, AIC3262_PASI_DAC_DP_SETUP, 7, 0, aic326x_dac_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_DAC_E("Right DAC", NULL, AIC3262_PASI_DAC_DP_SETUP, 6, 0, aic326x_dac_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), /* dapm widget (path domain) for HPL Output Mixer */ SND_SOC_DAPM_MIXER("HP Left Mixer", SND_SOC_NOPM, 0, 0, &hpl_output_mixer_controls[0], ARRAY_SIZE(hpl_output_mixer_controls)), /* dapm widget (path domain) for HPR Output Mixer */ SND_SOC_DAPM_MIXER("HP Right Mixer", SND_SOC_NOPM, 0, 0, &hpr_output_mixer_controls[0], ARRAY_SIZE(hpr_output_mixer_controls)), SND_SOC_DAPM_PGA_S("HP Left Playback Driver", 3, AIC3262_HP_AMP_CNTL_R1, 1, 0, aic326x_hp_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_S("HP Right Playback Driver", 3, AIC3262_HP_AMP_CNTL_R1, 0, 0, aic326x_hp_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), /* dapm widget (path domain) for LOL Output Mixer */ SND_SOC_DAPM_MIXER("LO Left Mixer", SND_SOC_NOPM, 0, 0, &lol_output_mixer_controls[0], ARRAY_SIZE(lol_output_mixer_controls)), /* dapm widget (path domain) for LOR Output Mixer mixer */ SND_SOC_DAPM_MIXER("LO Right Mixer", SND_SOC_NOPM, 0, 0, &lor_output_mixer_controls[0], ARRAY_SIZE(lor_output_mixer_controls)), SND_SOC_DAPM_PGA_S("LO Left Playback Driver", 2, AIC3262_LINE_AMP_CNTL_R1, 1, 0, NULL, 0), SND_SOC_DAPM_PGA_S("LO Right Playback Driver", 2, AIC3262_LINE_AMP_CNTL_R1, 0, 0, NULL, 0), /* dapm widget (path domain) for SPKL Output Mixer */ SND_SOC_DAPM_MIXER("SPK Left Mixer", SND_SOC_NOPM, 0, 0, &spkl_output_mixer_controls[0], ARRAY_SIZE(spkl_output_mixer_controls)), /* dapm widget (path domain) for SPKR Output Mixer */ SND_SOC_DAPM_MIXER("SPK Right Mixer", SND_SOC_NOPM, 0, 0, &spkr_output_mixer_controls[0], ARRAY_SIZE(spkr_output_mixer_controls)), SND_SOC_DAPM_PGA_S("SPK Left Playback Driver", 3, AIC3262_SPK_AMP_CNTL_R1, 1, 0, aic326x_spk_event, SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_PGA_S("SPK Right Playback Driver", 3, AIC3262_SPK_AMP_CNTL_R1, 0, 0, aic326x_spk_event, SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), /* dapm widget (path domain) for SPKR Output Mixer */ SND_SOC_DAPM_MIXER("REC Mixer", SND_SOC_NOPM, 0, 0, &rec_output_mixer_controls[0], ARRAY_SIZE(rec_output_mixer_controls)), SND_SOC_DAPM_PGA_S("RECP Playback Driver", 3, AIC3262_REC_AMP_CNTL_R5, 7, 0, NULL, 0), SND_SOC_DAPM_PGA_S("RECM Playback Driver", 3, AIC3262_REC_AMP_CNTL_R5, 6, 0, NULL, 0), SND_SOC_DAPM_MUX("ASI1LIN Route", SND_SOC_NOPM, 0, 0, &asi1lin_control), SND_SOC_DAPM_MUX("ASI1RIN Route", SND_SOC_NOPM, 0, 0, &asi1rin_control), SND_SOC_DAPM_MUX("ASI2LIN Route", SND_SOC_NOPM, 0, 0, &asi2lin_control), SND_SOC_DAPM_MUX("ASI2RIN Route", SND_SOC_NOPM, 0, 0, &asi2rin_control), SND_SOC_DAPM_MUX("ASI3LIN Route", SND_SOC_NOPM, 0, 0, &asi3lin_control), SND_SOC_DAPM_MUX("ASI3RIN Route", SND_SOC_NOPM, 0, 0, &asi3rin_control), SND_SOC_DAPM_PGA("ASI1LIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI1RIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI2LIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI2RIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI3LIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI3RIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI1MonoMixIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI2MonoMixIN", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI3MonoMixIN", SND_SOC_NOPM, 0, 0, NULL, 0), /* TODO: Can we switch the ASIxIN off? */ SND_SOC_DAPM_PGA("ASI1IN Port", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI2IN Port", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI3IN Port", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MUX("DAC MiniDSP IN1 Route", SND_SOC_NOPM, 0, 0, &dacminidspin1_control), SND_SOC_DAPM_MUX("DAC MiniDSP IN2 Route", SND_SOC_NOPM, 0, 0, &dacminidspin2_control), SND_SOC_DAPM_MUX("DAC MiniDSP IN3 Route", SND_SOC_NOPM, 0, 0, &dacminidspin3_control), SND_SOC_DAPM_VIRT_MUX("ADC DAC Route", SND_SOC_NOPM, 0, 0, &adcdacroute_control), SND_SOC_DAPM_PGA("CM", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("CM1 Left Capture", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("CM2 Left Capture", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("CM1 Right Capture", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("CM2 Right Capture", SND_SOC_NOPM, 0, 0, NULL, 0), /* TODO: Can we switch these off ? */ SND_SOC_DAPM_AIF_OUT("DOUT1", "ASI1 Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("DOUT2", "ASI2 Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("DOUT3", "ASI3 Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("DOUT1 Route", SND_SOC_NOPM, 0, 0, &dout1_control), SND_SOC_DAPM_MUX("DOUT2 Route", SND_SOC_NOPM, 0, 0, &dout2_control), SND_SOC_DAPM_MUX("DOUT3 Route", SND_SOC_NOPM, 0, 0, &dout3_control), SND_SOC_DAPM_PGA("ASI1OUT", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI2OUT", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ASI3OUT", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MUX("ASI1OUT Route", SND_SOC_NOPM, 0, 0, &asi1out_control), SND_SOC_DAPM_MUX("ASI2OUT Route", SND_SOC_NOPM, 0, 0, &asi2out_control), SND_SOC_DAPM_MUX("ASI3OUT Route", SND_SOC_NOPM, 0, 0, &asi3out_control), /* TODO: Can we switch the ASI1 OUT1 off? */ /* TODO: Can we switch them off? */ SND_SOC_DAPM_PGA("ADC MiniDSP OUT1", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ADC MiniDSP OUT2", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ADC MiniDSP OUT3", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MUX("Left ADC Route", SND_SOC_NOPM, 0, 0, &adcl_mux), SND_SOC_DAPM_MUX("Right ADC Route", SND_SOC_NOPM, 0, 0, &adcr_mux), SND_SOC_DAPM_ADC_E("Left ADC", NULL, AIC3262_ADC_CHANNEL_POW, 7, 0, aic326x_adc_dsp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("Right ADC", NULL, AIC3262_ADC_CHANNEL_POW, 6, 0, aic326x_adc_dsp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_S("Left MicPGA", 0, AIC3262_MICL_PGA, 7, 1, NULL, 0), SND_SOC_DAPM_PGA_S("Right MicPGA", 0, AIC3262_MICR_PGA, 7, 1, NULL, 0), SND_SOC_DAPM_PGA_S("MA Left Playback PGA", 1, AIC3262_MA_CNTL, 3, 0, NULL, 0), SND_SOC_DAPM_PGA_S("MA Right Playback PGA", 1, AIC3262_MA_CNTL, 2, 0, NULL, 0), /* dapm widget for MAL PGA Mixer */ SND_SOC_DAPM_MIXER("MA Left PGA Mixer", SND_SOC_NOPM, 0, 0, &mal_pga_mixer_controls[0], ARRAY_SIZE(mal_pga_mixer_controls)), /* dapm widget for MAR PGA Mixer */ SND_SOC_DAPM_MIXER("MA Right PGA Mixer", SND_SOC_NOPM, 0, 0, &mar_pga_mixer_controls[0], ARRAY_SIZE(mar_pga_mixer_controls)), /* dapm widget for Left Input Mixer */ SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0, &left_input_mixer_controls[0], ARRAY_SIZE(left_input_mixer_controls)), /* dapm widget for Right Input Mixer */ SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0, &right_input_mixer_controls[0], ARRAY_SIZE(right_input_mixer_controls)), SND_SOC_DAPM_OUTPUT("HP Left Playback"), SND_SOC_DAPM_OUTPUT("HP Right Playback"), SND_SOC_DAPM_OUTPUT("LO Left Playback"), SND_SOC_DAPM_OUTPUT("LO Right Playback"), SND_SOC_DAPM_OUTPUT("SPK Left Playback"), SND_SOC_DAPM_OUTPUT("SPK Right Playback"), SND_SOC_DAPM_OUTPUT("RECP Playback"), SND_SOC_DAPM_OUTPUT("RECM Playback"), SND_SOC_DAPM_INPUT("IN1 Left Capture"), SND_SOC_DAPM_INPUT("IN2 Left Capture"), SND_SOC_DAPM_INPUT("IN3 Left Capture"), SND_SOC_DAPM_INPUT("IN4 Left Capture"), SND_SOC_DAPM_INPUT("IN1 Right Capture"), SND_SOC_DAPM_INPUT("IN2 Right Capture"), SND_SOC_DAPM_INPUT("IN3 Right Capture"), SND_SOC_DAPM_INPUT("IN4 Right Capture"), SND_SOC_DAPM_INPUT("Left DMIC Capture"), SND_SOC_DAPM_INPUT("Right DMIC Capture"), SND_SOC_DAPM_MICBIAS("Mic Bias Ext", AIC3262_MIC_BIAS_CNTL, 6, 0), SND_SOC_DAPM_MICBIAS("Mic Bias Int", AIC3262_MIC_BIAS_CNTL, 2, 0), SND_SOC_DAPM_SUPPLY_S("PLLCLK", 0, AIC3262_PLL_PR_POW_REG, 7, 0, pll_power_on_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_SUPPLY_S("DACCLK", 2, AIC3262_NDAC_DIV_POW_REG, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("CODEC_CLK_IN", 1, SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("DAC_MOD_CLK", 3, AIC3262_MDAC_DIV_POW_REG, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ADCCLK", 2, AIC3262_NADC_DIV_POW_REG, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ADC_MOD_CLK", 3, AIC3262_MADC_DIV_POW_REG, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ASI1_BCLK", 4, AIC3262_ASI1_BCLK_N, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ASI1_WCLK", 4, AIC3262_ASI1_WCLK_N, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ASI2_BCLK", 4, AIC3262_ASI2_BCLK_N, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ASI2_WCLK", 4, AIC3262_ASI2_WCLK_N, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ASI3_BCLK", 4, AIC3262_ASI3_BCLK_N, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("ASI3_WCLK", 4, AIC3262_ASI3_WCLK_N, 7, 0, NULL, 0), SND_SOC_DAPM_MUX("ASI1_BCLK Route", SND_SOC_NOPM, 0, 0, &asi1bclk_control), SND_SOC_DAPM_MUX("ASI2_BCLK Route", SND_SOC_NOPM, 0, 0, &asi2bclk_control), SND_SOC_DAPM_MUX("ASI3_BCLK Route", SND_SOC_NOPM, 0, 0, &asi3bclk_control), }; static const struct snd_soc_dapm_route aic3262_dapm_routes[] = { /* TODO: Do we need only DACCLK for ASIIN's and ADCCLK for ASIOUT??? */ /* Clock portion */ {"CODEC_CLK_IN", NULL, "PLLCLK"}, {"DACCLK", NULL, "CODEC_CLK_IN"}, {"ADCCLK", NULL, "CODEC_CLK_IN"}, {"DAC_MOD_CLK", NULL, "DACCLK"}, #ifdef AIC3262_SYNC_MODE {"ADC_MOD_CLK", NULL, "DACCLK"}, #else {"ADC_MOD_CLK", NULL, "ADCCLK"}, #endif {"ASI1_BCLK Route", "DAC_CLK", "DACCLK"}, {"ASI1_BCLK Route", "DAC_MOD_CLK", "DAC_MOD_CLK"}, {"ASI1_BCLK Route", "ADC_CLK", "ADCCLK"}, {"ASI1_BCLK Route", "ADC_MOD_CLK", "ADC_MOD_CLK"}, {"ASI2_BCLK Route", "DAC_CLK", "DACCLK"}, {"ASI2_BCLK Route", "DAC_MOD_CLK", "DAC_MOD_CLK"}, {"ASI2_BCLK Route", "ADC_CLK", "ADCCLK"}, {"ASI2_BCLK Route", "ADC_MOD_CLK", "ADC_MOD_CLK"}, {"ASI3_BCLK Route", "DAC_CLK", "DACCLK"}, {"ASI3_BCLK Route", "DAC_MOD_CLK", "DAC_MOD_CLK"}, {"ASI3_BCLK Route", "ADC_CLK", "ADCCLK"}, {"ASI3_BCLK Route", "ADC_MOD_CLK", "ADC_MOD_CLK"}, {"ASI1_BCLK", NULL, "ASI1_BCLK Route"}, {"ASI2_BCLK", NULL, "ASI2_BCLK Route"}, {"ASI3_BCLK", NULL, "ASI3_BCLK Route"}, #ifdef AIC3262_ASI1_MASTER {"DIN1", NULL, "ASI1_BCLK"}, {"DOUT1", NULL, "ASI1_BCLK"}, {"DIN1", NULL, "ASI1_WCLK"}, {"DOUT1", NULL, "ASI1_WCLK"}, #endif #ifdef AIC3262_ASI2_MASTER {"DIN2", NULL, "ASI2_BCLK"}, {"DOUT2", NULL, "ASI2_BCLK"}, {"DIN2", NULL, "ASI2_WCLK"}, {"DOUT2", NULL, "ASI2_WCLK"}, #endif #ifdef AIC3262_ASI3_MASTER {"DIN3", NULL, "ASI3_BCLK"}, {"DOUT3", NULL, "ASI3_BCLK"}, {"DIN3", NULL, "ASI3_WCLK"}, {"DOUT3", NULL, "ASI3_WCLK"}, #endif {"Left DAC", NULL, "DAC_MOD_CLK"}, {"Right DAC", NULL, "DAC_MOD_CLK"}, /* When we are master, ASI bclk and wclk are generated by * DAC_MOD_CLK, so we put them as dependency for ADC too. */ {"Left ADC", NULL, "DAC_MOD_CLK"}, {"Right ADC", NULL, "DAC_MOD_CLK"}, {"Left ADC", NULL, "ADC_MOD_CLK"}, {"Right ADC", NULL, "ADC_MOD_CLK"}, /* Playback (DAC) Portion */ {"HP Left Mixer", "Left DAC Playback Switch", "Left DAC"}, {"HP Left Mixer", "MA Left Playback Switch", "MA Left Playback PGA"}, {"HP Left Mixer", "LO Left-B1 Playback Volume", "LO Left Playback"}, {"HP Right Mixer", "LO Right-B1 Playback Volume", "LO Right Playback"}, {"HP Right Mixer", "Left DAC Playback Switch", "Left DAC"}, {"HP Right Mixer", "Right DAC Playback Switch", "Right DAC"}, {"HP Right Mixer", "MA Right Playback Switch", "MA Right Playback PGA"}, {"HP Left Playback Driver", NULL, "HP Left Mixer"}, {"HP Right Playback Driver", NULL, "HP Right Mixer"}, {"HP Left Playback", NULL, "HP Left Playback Driver"}, {"HP Right Playback", NULL, "HP Right Playback Driver"}, {"LO Left Mixer", "MA Left Playback Switch", "MA Left Playback PGA"}, {"LO Left Mixer", "IN1 Left-B Capture Switch", "IN1 Left Capture"}, {"LO Left Mixer", "Left DAC Playback Switch", "Left DAC"}, {"LO Left Mixer", "Right DAC Playback Switch", "Right DAC"}, {"LO Right Mixer", "LO Left Playback Switch", "LO Left Playback"}, {"LO Right Mixer", "Right DAC Playback Switch", "Right DAC"}, {"LO Right Mixer", "MA Right Playback Switch", "MA Right Playback PGA"}, {"LO Right Mixer", "IN1 Right-B Capture Switch", "IN1 Right Capture"}, {"LO Left Playback Driver", NULL, "LO Left Mixer"}, {"LO Right Playback Driver", NULL, "LO Right Mixer"}, {"LO Left Playback", NULL, "LO Left Playback Driver"}, {"LO Right Playback", NULL, "LO Right Playback Driver"}, {"REC Mixer", "LO Left-B2 Playback Volume", "LO Left Playback"}, {"REC Mixer", "IN1 Left Capture Volume", "IN1 Left Capture"}, {"REC Mixer", "IN1 Right Capture Volume", "IN1 Right Capture"}, {"REC Mixer", "LO Right-B2 Playback Volume", "LO Right Playback"}, {"RECP Playback Driver", NULL, "REC Mixer"}, {"RECM Playback Driver", NULL, "REC Mixer"}, {"RECP Playback", NULL, "RECP Playback Driver"}, {"RECM Playback", NULL, "RECM Playback Driver"}, {"SPK Left Mixer", "MA Left Playback Switch", "MA Left Playback PGA"}, {"SPK Left Mixer", "LO Left Playback Volume", "LO Left Playback"}, {"SPK Left Mixer", "SPR_IN Switch", "SPK Right Mixer"}, {"SPK Right Mixer", "LO Right Playback Volume", "LO Right Playback"}, {"SPK Right Mixer", "MA Right Playback Switch", "MA Right Playback PGA"}, {"SPK Left Playback Driver", NULL, "SPK Left Mixer"}, {"SPK Right Playback Driver", NULL, "SPK Right Mixer"}, {"SPK Left Playback", NULL, "SPK Left Playback Driver"}, {"SPK Right Playback", NULL, "SPK Right Playback Driver"}, /* ASI Input routing */ {"ASI1LIN", NULL, "DIN1"}, {"ASI1RIN", NULL, "DIN1"}, {"ASI1MonoMixIN", NULL, "DIN1"}, {"ASI2LIN", NULL, "DIN2"}, {"ASI2RIN", NULL, "DIN2"}, {"ASI2MonoMixIN", NULL, "DIN2"}, {"ASI3LIN", NULL, "DIN3"}, {"ASI3RIN", NULL, "DIN3"}, {"ASI3MonoMixIN", NULL, "DIN3"}, {"ASI1LIN Route", "ASI1 Left In", "ASI1LIN"}, {"ASI1LIN Route", "ASI1 Right In", "ASI1RIN"}, {"ASI1LIN Route", "ASI1 MonoMix In", "ASI1MonoMixIN"}, {"ASI1RIN Route", "ASI1 Right In", "ASI1RIN"}, {"ASI1RIN Route", "ASI1 Left In", "ASI1LIN"}, {"ASI1RIN Route", "ASI1 MonoMix In", "ASI1MonoMixIN"}, {"ASI2LIN Route", "ASI2 Left In", "ASI2LIN"}, {"ASI2LIN Route", "ASI2 Right In", "ASI2RIN"}, {"ASI2LIN Route", "ASI2 MonoMix In", "ASI2MonoMixIN"}, {"ASI2RIN Route", "ASI2 Right In", "ASI2RIN"}, {"ASI2RIN Route", "ASI2 Left In", "ASI2LIN"}, {"ASI2RIN Route", "ASI2 MonoMix In", "ASI2MonoMixIN"}, {"ASI3LIN Route", "ASI3 Left In", "ASI3LIN"}, {"ASI3LIN Route", "ASI3 Right In", "ASI3RIN"}, {"ASI3LIN Route", "ASI3 MonoMix In", "ASI3MonoMixIN"}, {"ASI3RIN Route", "ASI3 Right In", "ASI3RIN"}, {"ASI3RIN Route", "ASI3 Left In", "ASI3LIN"}, {"ASI3RIN Route", "ASI3 MonoMix In", "ASI3MonoMixIN"}, {"ASI1IN Port", NULL, "ASI1LIN Route"}, {"ASI1IN Port", NULL, "ASI1RIN Route"}, {"ASI2IN Port", NULL, "ASI2LIN Route"}, {"ASI2IN Port", NULL, "ASI2RIN Route"}, {"ASI3IN Port", NULL, "ASI3LIN Route"}, {"ASI3IN Port", NULL, "ASI3RIN Route"}, {"DAC MiniDSP IN1 Route", "ASI1 In", "ASI1IN Port"}, {"DAC MiniDSP IN1 Route", "ASI2 In", "ASI2IN Port"}, {"DAC MiniDSP IN1 Route", "ASI3 In", "ASI3IN Port"}, {"DAC MiniDSP IN1 Route", "ADC MiniDSP Out", "ADC MiniDSP OUT1"}, {"DAC MiniDSP IN2 Route", "ASI1 In", "ASI1IN Port"}, {"DAC MiniDSP IN2 Route", "ASI2 In", "ASI2IN Port"}, {"DAC MiniDSP IN2 Route", "ASI3 In", "ASI3IN Port"}, {"DAC MiniDSP IN3 Route", "ASI1 In", "ASI1IN Port"}, {"DAC MiniDSP IN3 Route", "ASI2 In", "ASI2IN Port"}, {"DAC MiniDSP IN3 Route", "ASI3 In", "ASI3IN Port"}, {"Left DAC", "NULL", "DAC MiniDSP IN1 Route"}, {"Right DAC", "NULL", "DAC MiniDSP IN1 Route"}, {"Left DAC", "NULL", "DAC MiniDSP IN2 Route"}, {"Right DAC", "NULL", "DAC MiniDSP IN2 Route"}, {"Left DAC", "NULL", "DAC MiniDSP IN3 Route"}, {"Right DAC", "NULL", "DAC MiniDSP IN3 Route"}, /* Mixer Amplifier */ {"MA Left PGA Mixer", "IN1 Left Capture Switch", "IN1 Left Capture"}, {"MA Left PGA Mixer", "Left MicPGA Volume", "Left MicPGA"}, {"MA Left Playback PGA", NULL, "MA Left PGA Mixer"}, {"MA Right PGA Mixer", "IN1 Right Capture Switch", "IN1 Right Capture"}, {"MA Right PGA Mixer", "Right MicPGA Volume", "Right MicPGA"}, {"MA Right Playback PGA", NULL, "MA Right PGA Mixer"}, /* Virtual connection between DAC and ADC for miniDSP IPC */ {"ADC DAC Route", "On", "Left ADC"}, {"ADC DAC Route", "On", "Right ADC"}, {"Left DAC", NULL, "ADC DAC Route"}, {"Right DAC", NULL, "ADC DAC Route"}, /* Capture (ADC) portions */ /* Left Positive PGA input */ {"Left Input Mixer", "IN1 Left Capture Switch", "IN1 Left Capture"}, {"Left Input Mixer", "IN2 Left Capture Switch", "IN2 Left Capture"}, {"Left Input Mixer", "IN3 Left Capture Switch", "IN3 Left Capture"}, {"Left Input Mixer", "IN4 Left Capture Switch", "IN4 Left Capture"}, {"Left Input Mixer", "IN1 Right Capture Switch", "IN1 Right Capture"}, /* Left Negative PGA input */ {"Left Input Mixer", "IN2 Right Capture Switch", "IN2 Right Capture"}, {"Left Input Mixer", "IN3 Right Capture Switch", "IN3 Right Capture"}, {"Left Input Mixer", "IN4 Right Capture Switch", "IN4 Right Capture"}, {"Left Input Mixer", "CM2 Left Capture Switch", "CM2 Left Capture"}, {"Left Input Mixer", "CM1 Left Capture Switch", "CM1 Left Capture"}, /* Right Positive PGA Input */ {"Right Input Mixer", "IN1 Right Capture Switch", "IN1 Right Capture"}, {"Right Input Mixer", "IN2 Right Capture Switch", "IN2 Right Capture"}, {"Right Input Mixer", "IN3 Right Capture Switch", "IN3 Right Capture"}, {"Right Input Mixer", "IN4 Right Capture Switch", "IN4 Right Capture"}, {"Right Input Mixer", "IN2 Left Capture Switch", "IN2 Left Capture"}, /* Right Negative PGA Input */ {"Right Input Mixer", "IN1 Left Capture Switch", "IN1 Left Capture"}, {"Right Input Mixer", "IN3 Left Capture Switch", "IN3 Left Capture"}, {"Right Input Mixer", "IN4 Left Capture Switch", "IN4 Left Capture"}, {"Right Input Mixer", "CM1 Right Capture Switch", "CM1 Right Capture"}, {"Right Input Mixer", "CM2 Right Capture Switch", "CM2 Right Capture"}, {"CM1 Left Capture", NULL, "CM"}, {"CM2 Left Capture", NULL, "CM"}, {"CM1 Right Capture", NULL, "CM"}, {"CM2 Right Capture", NULL, "CM"}, {"Left MicPGA", NULL, "Left Input Mixer"}, {"Right MicPGA", NULL, "Right Input Mixer"}, {"Left ADC Route", "Analog", "Left MicPGA"}, {"Left ADC Route", "Digital", "Left DMIC Capture"}, {"Right ADC Route", "Analog", "Right MicPGA"}, {"Right ADC Route", "Digital", "Right DMIC Capture"}, {"Left ADC", NULL, "Left ADC Route"}, {"Right ADC", NULL, "Right ADC Route"}, /* ASI Output Routing */ {"ADC MiniDSP OUT1", NULL, "Left ADC"}, {"ADC MiniDSP OUT1", NULL, "Right ADC"}, {"ADC MiniDSP OUT2", NULL, "Left ADC"}, {"ADC MiniDSP OUT2", NULL, "Right ADC"}, {"ADC MiniDSP OUT3", NULL, "Left ADC"}, {"ADC MiniDSP OUT3", NULL, "Right ADC"}, {"ASI1OUT Route", "ADC MiniDSP Out1", "ADC MiniDSP OUT1"}, {"ASI1OUT Route", "ASI1In Bypass", "ASI1IN Port"}, {"ASI1OUT Route", "ASI2In Bypass", "ASI2IN Port"}, {"ASI1OUT Route", "ASI3In Bypass", "ASI3IN Port"}, {"ASI2OUT Route", "ADC MiniDSP Out1", "ADC MiniDSP OUT1"}, {"ASI2OUT Route", "ASI1In Bypass", "ASI1IN Port"}, {"ASI2OUT Route", "ASI2In Bypass", "ASI2IN Port"}, {"ASI2OUT Route", "ASI3In Bypass", "ASI3IN Port"}, {"ASI2OUT Route", "ADC MiniDSP Out2", "ADC MiniDSP OUT2"}, {"ASI3OUT Route", "ADC MiniDSP Out1", "ADC MiniDSP OUT1"}, {"ASI3OUT Route", "ASI1In Bypass", "ASI1IN Port"}, {"ASI3OUT Route", "ASI2In Bypass", "ASI2IN Port"}, {"ASI3OUT Route", "ASI3In Bypass", "ASI3IN Port"}, {"ASI3OUT Route", "ADC MiniDSP Out3", "ADC MiniDSP OUT3"}, {"ASI1OUT", NULL, "ASI1OUT Route"}, {"ASI2OUT", NULL, "ASI2OUT Route"}, {"ASI3OUT", NULL, "ASI3OUT Route"}, {"DOUT1 Route", "ASI1 Out", "ASI1OUT"}, {"DOUT1 Route", "DIN1 Bypass", "DIN1"}, {"DOUT1 Route", "DIN2 Bypass", "DIN2"}, {"DOUT1 Route", "DIN3 Bypass", "DIN3"}, {"DOUT2 Route", "ASI2 Out", "ASI2OUT"}, {"DOUT2 Route", "DIN1 Bypass", "DIN1"}, {"DOUT2 Route", "DIN2 Bypass", "DIN2"}, {"DOUT2 Route", "DIN3 Bypass", "DIN3"}, {"DOUT3 Route", "ASI3 Out", "ASI3OUT"}, {"DOUT3 Route", "DIN1 Bypass", "DIN1"}, {"DOUT3 Route", "DIN2 Bypass", "DIN2"}, {"DOUT3 Route", "DIN3 Bypass", "DIN3"}, {"DOUT1", NULL, "DOUT1 Route"}, {"DOUT2", NULL, "DOUT2 Route"}, {"DOUT3", NULL, "DOUT3 Route"}, }; #define AIC3262_DAPM_ROUTE_NUM (ARRAY_SIZE(aic3262_dapm_routes)/ \ sizeof(struct snd_soc_dapm_route)) /* aic3262_firmware_load: This function is called by the * request_firmware_nowait function as soon * as the firmware has been loaded from the file. * The firmware structure contains the data and$ * the size of the firmware loaded. * @fw: pointer to firmware file to be dowloaded * @context: pointer variable to codec * * Returns 0 for success. */ static void aic3262_firmware_load(const struct firmware *fw, void *context) { struct snd_soc_codec *codec = context; struct aic3262_priv *private_ds = snd_soc_codec_get_drvdata(codec); int ret = 0; aic3xxx_cfw_lock(private_ds->cfw_p, 1); if (private_ds->cur_fw != NULL) release_firmware(private_ds->cur_fw); private_ds->cur_fw = NULL; if (fw != NULL) { dev_dbg(codec->dev, "Firmware binary load\n"); private_ds->cur_fw = (void *)fw; ret = aic3xxx_cfw_reload(private_ds->cfw_p, (void *)fw->data, fw->size); if (ret < 0) { /* reload failed */ dev_err(codec->dev, "Firmware binary load failed\n"); release_firmware(private_ds->cur_fw); private_ds->cur_fw = NULL; fw = NULL; } } else { /* request_firmware failed*/ /* could not locate file tlv320aic3262_fw_v1.bin under /vendor/firmare */ dev_err(codec->dev, "request_firmware failed\n"); ret = -1; } aic3xxx_cfw_lock(private_ds->cfw_p, 0); if (ret >= 0) { /*init function for transition */ aic3xxx_cfw_transition(private_ds->cfw_p, "INIT"); /* add firmware modes */ aic3xxx_cfw_add_modes(codec, private_ds->cfw_p); /* add runtime controls */ aic3xxx_cfw_add_controls(codec, private_ds->cfw_p); /* set the default firmware mode */ aic3xxx_cfw_setmode_cfg(private_ds->cfw_p, 0, 0); } } /*========================================================= headset work and headphone/headset jack interrupt handlers ========================================================*/ enum headset_accessory_state { BIT_NO_ACCESSORY = 0, BIT_HEADSET = (1 << 0), BIT_HEADPHONE = (1 << 1), }; /** * aic3262_hs_jack_report: Report jack notication to upper layor * @codec: pointer variable to codec having information related to codec * @jack: Pointer variable to snd_soc_jack having information of codec * and pin number$ * @report: Provides informaton of whether it is headphone or microphone * */ static void aic3262_hs_jack_report(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int report) { struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); int status, state = 0, switch_state = BIT_NO_ACCESSORY; mutex_lock(&aic3262->mutex); /* Sync status */ status = snd_soc_read(codec, AIC3262_DAC_FLAG); /* We will check only stereo MIC and headphone */ switch (status & AIC3262_JACK_TYPE_MASK) { case AIC3262_JACK_WITH_MIC: state |= SND_JACK_HEADSET; break; case AIC3262_JACK_WITHOUT_MIC: state |= SND_JACK_HEADPHONE; } mutex_unlock(&aic3262->mutex); snd_soc_jack_report(jack, state, report); if ((state & SND_JACK_HEADSET) == SND_JACK_HEADSET) switch_state |= BIT_HEADSET; else if (state & SND_JACK_HEADPHONE) switch_state |= BIT_HEADPHONE; } /** * aic3262_hs_jack_detect: Detect headphone jack during boot time * @codec: pointer variable to codec having information related to codec * @jack: Pointer variable to snd_soc_jack having information of codec * and pin number$ * @report: Provides informaton of whether it is headphone or microphone * */ void aic3262_hs_jack_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int report) { struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); struct aic3262_jack_data *hs_jack = &aic3262->hs_jack; hs_jack->jack = jack; hs_jack->report = report; aic3262_hs_jack_report(codec, hs_jack->jack, hs_jack->report); } EXPORT_SYMBOL_GPL(aic3262_hs_jack_detect); /** * aic3262_accessory_work: Finished bottom half work from headphone jack * insertion interupt * @work: pionter variable to work_struct which is maintaining work queqe * */ static void aic3262_accessory_work(struct work_struct *work) { struct aic3262_priv *aic3262 = container_of(work, struct aic3262_priv, delayed_work.work); struct snd_soc_codec *codec = aic3262->codec; struct aic3262_jack_data *hs_jack = &aic3262->hs_jack; aic3262_hs_jack_report(codec, hs_jack->jack, hs_jack->report); } /** * aic3262_audio_handler: audio interrupt handler called * when interupt is generated * @irq: provides interupt number which is assigned by aic3262_request_irq, * @data having information of data passed by aic3262_request_irq last arg, * * Return IRQ_HANDLED(means interupt handeled successfully) */ static irqreturn_t aic3262_audio_handler(int irq, void *data) { struct snd_soc_codec *codec = data; struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); queue_delayed_work(aic3262->workqueue, &aic3262->delayed_work, msecs_to_jiffies(200)); return IRQ_HANDLED; } /** * Methods for CFW Operations * * Due to incompatibilites between structures used by MFD and CFW * we need to transform the register format before linking to * CFW operations. */ static inline unsigned int aic3262_ops_cfw2reg(unsigned int reg) { union cfw_register *c = (union cfw_register *) ® union aic3xxx_reg_union mreg; mreg.aic3xxx_register.offset = c->offset; mreg.aic3xxx_register.page = c->page; mreg.aic3xxx_register.book = c->book; mreg.aic3xxx_register.reserved = 0; return mreg.aic3xxx_register_int; } static int aic3262_ops_reg_read(struct snd_soc_codec *codec, unsigned int reg) { return aic3xxx_reg_read(codec->control_data, aic3262_ops_cfw2reg(reg)); } static int aic3262_ops_reg_write(struct snd_soc_codec *codec, unsigned int reg, unsigned char val) { return aic3xxx_reg_write(codec->control_data, aic3262_ops_cfw2reg(reg), val); } static int aic3262_ops_set_bits(struct snd_soc_codec *codec, unsigned int reg, unsigned char mask, unsigned char val) { return aic3xxx_set_bits(codec->control_data, aic3262_ops_cfw2reg(reg), mask, val); } static int aic3262_ops_bulk_read(struct snd_soc_codec *codec, unsigned int reg, int count, u8 *buf) { return aic3xxx_bulk_read(codec->control_data, aic3262_ops_cfw2reg(reg), count, buf); } static int aic3262_ops_bulk_write(struct snd_soc_codec *codec, unsigned int reg, int count, const u8 *buf) { return aic3xxx_bulk_write(codec->control_data, aic3262_ops_cfw2reg(reg), count, buf); } /** * aic3262_ops_dlock_lock: To Read the run state of the DAC and ADC * by reading the codec and returning the run state * @pv: pointer argument to the codec * * Run state Bit format * * ------------------------------------------------------ * D31|..........| D7 | D6| D5 | D4 | D3 | D2 | D1 | D0 | * R R R LADC RADC R R LDAC RDAC * ------------------------------------------------------ * * R- Reserved * LDAC- Left DAC * RDAC- Right DAC * * Return value : Integer */ static int aic3262_ops_lock(struct snd_soc_codec *codec) { mutex_lock(&codec->mutex); /* Reading the run state of adc and dac */ return aic3262_get_runstate(codec); } /** * aic3262_ops_dlock_unlock: To unlock the mutex acqiured for reading * run state of the codec * @pv: pointer argument to the codec * * Return Value: integer returning 0 */ static int aic3262_ops_unlock(struct snd_soc_codec *codec) { /*Releasing the lock of mutex */ mutex_unlock(&codec->mutex); return 0; } /** * aic3262_ops_dlock_stop: * @pv: pointer Argument to the codec * @mask: tells us the bit format of the codec running state * * Bit Format: * ------------------------------------------------------ * D31|..........| D7 | D6| D5 | D4 | D3 | D2 | D1 | D0 | * R R R AL AR R R DL DR * ------------------------------------------------------ * R - Reserved * A - minidsp_A * D - minidsp_D * * Return: return run state */ static int aic3262_ops_stop(struct snd_soc_codec *codec, int mask) { int run_state = 0; run_state = aic3262_get_runstate(codec); if (mask & AIC3XXX_COPS_MDSP_A) aic3xxx_set_bits(codec->control_data, AIC3262_ADC_DATAPATH_SETUP, 0xC0, 0); if (mask & AIC3XXX_COPS_MDSP_D) aic3xxx_set_bits(codec->control_data, AIC3262_DAC_DATAPATH_SETUP, 0xC0, 0); if ((mask & AIC3XXX_COPS_MDSP_A) && !aic3xxx_wait_bits(codec->control_data, AIC3262_ADC_FLAG, AIC3262_ADC_POWER_MASK, 0, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER)) goto err; if ((mask & AIC3XXX_COPS_MDSP_D) && !aic3xxx_wait_bits(codec->control_data, AIC3262_DAC_FLAG, AIC3262_DAC_POWER_MASK, 0, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER)) goto err; return run_state; err: dev_err(codec->dev, "Unable to turn off ADCs or DACs at [%s:%d]", __FILE__, __LINE__); return -EINVAL; } /** * aic3262_ops_dlock_restore: To unlock the mutex acqiured for reading * @pv: pointer argument to the codec,run_state * @run_state: run state of the codec and to restore the states of the dsp * * Return Value : integer returning 0 */ static int aic3262_ops_restore(struct snd_soc_codec *codec, int run_state) { int sync_state; /* This is for read the sync mode register state */ sync_state = aic3xxx_reg_read(codec->control_data, AIC3262_DAC_PRB); /*checking whether the sync mode has been set or not and checking the current state */ if (((run_state & 0x30) && (run_state & 0x03)) && (sync_state & 0x80)) aic3262_restart_dsps_sync(codec, run_state); else aic3262_dsp_pwrup(codec, run_state); return 0; } /** * aic3262_ops_adaptivebuffer_swap: To swap the coefficient buffers * of minidsp according to mask * @pv: pointer argument to the codec, * @mask: tells us which dsp has to be chosen for swapping * * Return Value : returning 0 on success */ int aic3262_ops_adaptivebuffer_swap(struct snd_soc_codec *codec, int mask) { const int sbuf[][2] = { { AIC3XXX_ABUF_MDSP_A, AIC3262_ADC_ADAPTIVE_CRAM_REG }, { AIC3XXX_ABUF_MDSP_D1, AIC3262_DAC_ADAPTIVE_BANK1_REG }, { AIC3XXX_ABUF_MDSP_D2, AIC3262_DAC_ADAPTIVE_BANK2_REG }, }; int i; for (i = 0; i < sizeof(sbuf)/sizeof(sbuf[0]); ++i) { if (!(mask & sbuf[i][0])) continue; aic3xxx_set_bits(codec->control_data, sbuf[i][1], 0x1, 0x1); if (!aic3xxx_wait_bits(codec->control_data, sbuf[i][1], 0x1, 0, 15, 1)) goto err; } return 0; err: dev_err(codec->dev, "miniDSP buffer swap failure at [%s:%d]", __FILE__, __LINE__); return -EINVAL; } /** * get_runstate: To read the current state of the dac's and adc's * @ps: pointer argument to the codec * * Return Value : returning the runstate */ static int aic3262_get_runstate(struct snd_soc_codec *codec) { unsigned int dac, adc; /* Read the run state */ dac = aic3xxx_reg_read(codec->control_data, AIC3262_DAC_FLAG); adc = aic3xxx_reg_read(codec->control_data, AIC3262_ADC_FLAG); return (((adc>>6)&1)<<5) | (((adc>>2)&1)<<4) | (((dac>>7)&1)<<1) | (((dac>>3)&1)<<0); } /** * aic3262_dsp_pwrdwn_status: To read the status of dsp's * @pv: pointer argument to the codec , cur_state of dac's and adc's * * Return Value : integer returning 0 */ static int aic3262_dsp_pwrdwn_status(struct snd_soc_codec *codec) { aic3xxx_set_bits(codec->control_data, AIC3262_ADC_DATAPATH_SETUP, 0XC0, 0); aic3xxx_set_bits(codec->control_data, AIC3262_DAC_DATAPATH_SETUP, 0XC0, 0); if (!aic3xxx_wait_bits(codec->control_data, AIC3262_ADC_FLAG, AIC3262_ADC_POWER_MASK, 0, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER)) goto err; if (!aic3xxx_wait_bits(codec->control_data, AIC3262_DAC_FLAG, AIC3262_DAC_POWER_MASK, 0, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER)) goto err; return 0; err: dev_err(codec->dev, "DAC/ADC Power down timedout at [%s:%d]", __FILE__, __LINE__); return -EINVAL; } static int aic3262_dsp_pwrup(struct snd_soc_codec *codec, int state) { int adc_reg_mask = 0; int adc_power_mask = 0; int dac_reg_mask = 0; int dac_power_mask = 0; int ret_wbits; if (state & AIC3XXX_COPS_MDSP_A_L) { adc_reg_mask |= 0x80; adc_power_mask |= AIC3262_LADC_POWER_MASK; } if (state & AIC3XXX_COPS_MDSP_A_R) { adc_reg_mask |= 0x40; adc_power_mask |= AIC3262_RADC_POWER_MASK; } if (state & AIC3XXX_COPS_MDSP_A) aic3xxx_set_bits(codec->control_data, AIC3262_ADC_DATAPATH_SETUP, 0XC0, adc_reg_mask); if (state & AIC3XXX_COPS_MDSP_D_L) { dac_reg_mask |= 0x80; dac_power_mask |= AIC3262_LDAC_POWER_STATUS_MASK; } if (state & AIC3XXX_COPS_MDSP_D_R) { dac_reg_mask |= 0x40; dac_power_mask |= AIC3262_RDAC_POWER_STATUS_MASK; } if (state & AIC3XXX_COPS_MDSP_D) aic3xxx_set_bits(codec->control_data, AIC3262_DAC_DATAPATH_SETUP, 0XC0, dac_reg_mask); if (state & AIC3XXX_COPS_MDSP_A) { ret_wbits = aic3xxx_wait_bits(codec->control_data, AIC3262_ADC_FLAG, AIC3262_ADC_POWER_MASK, adc_power_mask, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); if (!ret_wbits) dev_err(codec->dev, "ADC Power down timedout\n"); } if (state & AIC3XXX_COPS_MDSP_D) { ret_wbits = aic3xxx_wait_bits(codec->control_data, AIC3262_DAC_FLAG, AIC3262_DAC_POWER_MASK, dac_power_mask, AIC326X_TIME_DELAY, AIC326X_DELAY_COUNTER); if (!ret_wbits) dev_err(codec->dev, "ADC Power down timedout\n"); } return 0; } static int aic3262_restart_dsps_sync(struct snd_soc_codec *codec, int run_state) { aic3262_dsp_pwrdwn_status(codec); aic3262_dsp_pwrup(codec, run_state); return 0; } static const struct aic3xxx_codec_ops aic3262_cfw_codec_ops = { .reg_read = aic3262_ops_reg_read, .reg_write = aic3262_ops_reg_write, .set_bits = aic3262_ops_set_bits, .bulk_read = aic3262_ops_bulk_read, .bulk_write = aic3262_ops_bulk_write, .lock = aic3262_ops_lock, .unlock = aic3262_ops_unlock, .stop = aic3262_ops_stop, .restore = aic3262_ops_restore, .bswap = aic3262_ops_adaptivebuffer_swap, }; /** * aic3262_codec_read: provide read api to read aic3262 registe space * @codec: pointer variable to codec having codec information, * @reg: register address, * * Return: Return value will be value read. */ unsigned int aic3262_codec_read(struct snd_soc_codec *codec, unsigned int reg) { u8 value; union aic3xxx_reg_union *aic_reg = (union aic3xxx_reg_union *) ® value = aic3xxx_reg_read(codec->control_data, reg); dev_dbg(codec->dev, "p %d , r 30 %x %x\n", aic_reg->aic3xxx_register.page, aic_reg->aic3xxx_register.offset, value); return value; } /** * aic3262_codec_write: provide write api to write at aic3262 registe space * @codec: Pointer variable to codec having codec information, * @reg: Register address, * @value: Value to be written to address space * * Return: Total no of byte written to address space. */ int aic3262_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { union aic3xxx_reg_union *aic_reg = (union aic3xxx_reg_union *) ® dev_dbg(codec->dev, "p %d, w 30 %x %x\n", aic_reg->aic3xxx_register.page, aic_reg->aic3xxx_register.offset, value); return aic3xxx_reg_write(codec->control_data, reg, value); } /** * aic3262_set_interface_fmt: Setting interface ASI1/2/3 data format * @dai: ponter to dai Holds runtime data for a DAI, * @fmt: asi format info, * @channel: number of channel, * * Return: On success return 0. */ static int aic3262_set_interface_fmt(struct snd_soc_dai *dai, unsigned int fmt, unsigned int channel) { int aif_interface_reg; int aif_bclk_offset_reg; struct snd_soc_codec *codec = dai->codec; u8 iface_val = 0; u8 dsp_a_val = 0; switch (dai->id) { case 0: aif_interface_reg = AIC3262_ASI1_BUS_FMT; aif_bclk_offset_reg = AIC3262_ASI1_LCH_OFFSET; break; case 1: aif_interface_reg = AIC3262_ASI2_BUS_FMT; aif_bclk_offset_reg = AIC3262_ASI2_LCH_OFFSET; break; case 2: aif_interface_reg = AIC3262_ASI3_BUS_FMT; aif_bclk_offset_reg = AIC3262_ASI3_LCH_OFFSET; break; default: return -EINVAL; } /* interface format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: iface_val = 0; break; case SND_SOC_DAIFMT_DSP_A: dsp_a_val = 0x1; /* Intentionally falling through */ case SND_SOC_DAIFMT_DSP_B: if (channel == 1) iface_val = 0x80; /* Choose mono PCM */ else if (channel <= 8) iface_val = 0x20; /* choose multichannel PCM */ else return -EINVAL; break; case SND_SOC_DAIFMT_RIGHT_J: iface_val = 0x40; break; case SND_SOC_DAIFMT_LEFT_J: iface_val = 0x60; break; default: dev_err(codec->dev, "Invalid DAI interface format\n"); return -EINVAL; } snd_soc_update_bits(codec, aif_interface_reg, AIC3262_ASI_INTERFACE_MASK, iface_val); snd_soc_update_bits(codec, aif_bclk_offset_reg, AIC3262_BCLK_OFFSET_MASK, dsp_a_val); return 0; } /** * aic3262_hw_params: This function is to set the hardware parameters * for AIC3262. * The functions set the sample rate and audio serial data word * length. * @substream: pointer variable to sn_pcm_substream, * @params: pointer to snd_pcm_hw_params structure, * @dai: ponter to dai Holds runtime data for a DAI, * * Return: Return 0 on success. */ 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 asi_reg, ret = 0; u8 data = 0, value = 0, val = 0, wclk_div = 0, bclk_div = 0; unsigned int channels = params_channels(params); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) aic3262->stream_status = 1; else aic3262->stream_status = 0; switch (dai->id) { case 0: asi_reg = AIC3262_ASI1_BUS_FMT; break; case 1: asi_reg = AIC3262_ASI2_BUS_FMT; break; case 2: asi_reg = AIC3262_ASI3_BUS_FMT; break; default: return -EINVAL; } 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 TDM for multi chennels */ switch (channels) { case 4: value = value | 0x40; bclk_div = 0x03; wclk_div = 0x40; break; case 6: bclk_div = 0x02; wclk_div = 0x60; value = value | 0x80; break; case 8: bclk_div = 0x01; wclk_div = 0x00; value = value | 0xC0; break; default: bclk_div = 0x04; wclk_div = 0x20; } snd_soc_update_bits(codec, AIC3262_ASI1_CHNL_SETUP, AIC3262_ASI1_CHNL_MASK, value); snd_soc_update_bits(codec, AIC3262_ASI1_BCLK_N, AIC3262_ASI1_BCLK_N_MASK, bclk_div); snd_soc_update_bits(codec, AIC3262_ASI1_WCLK_N, AIC3262_ASI1_WCLK_N_MASK, wclk_div); val = snd_soc_read(codec, AIC3262_ASI1_BUS_FMT); val = snd_soc_read(codec, AIC3262_ASI1_CHNL_SETUP); /* configure the respective Registers for the above configuration */ snd_soc_update_bits(codec, asi_reg, AIC3262_ASI_DATA_WORD_LENGTH_MASK, data); ret = aic3262_set_interface_fmt(dai, aic3262->asi_fmt[dai->id], channels); if (ret < 0) { dev_err(codec->dev, "failed to set hardware params for AIC3262\n"); return ret; } return 0; } /** * aic3262_mute: This function is to mute or unmute the left and right DAC * @dai: ponter to dai Holds runtime data for a DAI, * @mute: integer value one if we using mute else unmute, * * Return: return 0 on success. */ 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); dev_dbg(codec->dev, "codec : %s : started\n", __func__); if (dai->id > 2) return -EINVAL; if (mute) { aic3262->mute_asi &= ~((0x1) << dai->id); if (aic3262->mute_asi == 0) /* Mute only when all asi's are muted */ snd_soc_update_bits_locked(codec, AIC3262_DAC_MVOL_CONF, AIC3262_DAC_LR_MUTE_MASK, AIC3262_DAC_LR_MUTE); } else { /* Unmute */ if (aic3262->mute_asi == 0) /* Unmute for the first asi that need to unmute. rest unmute will pass */ snd_soc_update_bits_locked(codec, AIC3262_DAC_MVOL_CONF, AIC3262_DAC_LR_MUTE_MASK, 0x0); aic3262->mute_asi |= ((0x1) << dai->id); } dev_dbg(codec->dev, "codec : %s : ended\n", __func__); return 0; } /** * aic3262_set_dai_fmt: This function is to set the DAI format * @codec_dai: ponter to dai Holds runtime data for a DAI, * @fmt: asi format info, * * return: return 0 on success. */ static int aic3262_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct aic3262_priv *aic3262; struct snd_soc_codec *codec; u8 iface_val, master; int aif_bclk_wclk_reg; codec = codec_dai->codec; aic3262 = snd_soc_codec_get_drvdata(codec); iface_val = 0x00; master = 0x0; switch (codec_dai->id) { case 0: aif_bclk_wclk_reg = AIC3262_ASI1_BWCLK_CNTL_REG; break; case 1: aif_bclk_wclk_reg = AIC3262_ASI2_BWCLK_CNTL_REG; break; case 2: aif_bclk_wclk_reg = AIC3262_ASI3_BWCLK_CNTL_REG; break; default: return -EINVAL; } aic3262->asi_fmt[codec_dai->id] = fmt; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: aic3262->master = 1; master |= (AIC3262_WCLK_OUT_MASK | AIC3262_BCLK_OUT_MASK); break; case SND_SOC_DAIFMT_CBS_CFS: aic3262->master = 0; break; case SND_SOC_DAIFMT_CBS_CFM: /* new case..for debug purpose */ master |= (AIC3262_WCLK_OUT_MASK); aic3262->master = 0; break; case SND_SOC_DAIFMT_CBM_CFS: master |= (AIC3262_BCLK_OUT_MASK); aic3262->master = 0; break; default: dev_err(codec->dev, "Invalid DAI master/slave" " interface\n"); return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_IB_NF: master |= AIC3262_BCLK_INV_MASK; break; default: return -EINVAL; } break; case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_LEFT_J: switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_IB_NF: master |= AIC3262_BCLK_INV_MASK; break; default: return -EINVAL; } break; default: return -EINVAL; } snd_soc_update_bits(codec, aif_bclk_wclk_reg, AIC3262_WCLK_BCLK_MASTER_MASK, master); return 0; } /** * aic3262_dai_set_pll: This function is to Set pll for aic3262 codec dai * @dai: ponter to dai Holds runtime data for a DAI,$ * @pll_id: integer pll_id * @fin: frequency in, * @fout: Frequency out, * * Return: return 0 on success */ static int aic3262_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, unsigned int Fin, unsigned int Fout) { struct snd_soc_codec *codec = dai->codec; struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); dev_dbg(codec->dev, "In aic3262: dai_set_pll\n"); dev_dbg(codec->dev, "%d, %s, dai->id = %d\n", __LINE__, __func__, dai->id); /* select the PLL_CLKIN */ snd_soc_update_bits(codec, AIC3262_PLL_CLKIN_REG, AIC3262_PLL_CLKIN_MASK, source << AIC3262_PLL_CLKIN_SHIFT); /* TODO: How to select low/high clock range? */ aic3xxx_cfw_set_pll(aic3262->cfw_p, dai->id); return 0; } /** * * aic3262_set_bias_level: This function is to get triggered * when dapm events occurs. * @codec: pointer variable to codec having informaton related to codec, * @level: Bias level-> ON, PREPARE, STANDBY, OFF. * * Return: Return 0 on success. */ static int aic3262_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { switch (level) { /* full On */ case SND_SOC_BIAS_ON: dev_dbg(codec->dev, "set_bias_on\n"); break; /* partial On */ case SND_SOC_BIAS_PREPARE: dev_dbg(codec->dev, "set_bias_prepare\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 */ dev_dbg(codec->dev, "set_bias_stby\n"); if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { pm_runtime_get_sync(codec->dev); snd_soc_update_bits(codec, AIC3262_POWER_CONF, (AIC3262_AVDD_TO_DVDD_MASK | AIC3262_EXT_ANALOG_SUPPLY_MASK), 0x0); snd_soc_update_bits(codec, AIC3262_REF_PWR_DLY, AIC3262_CHIP_REF_PWR_ON_MASK, AIC3262_CHIP_REF_PWR_ON); mdelay(40); snd_soc_update_bits(codec, AIC3262_CHARGE_PUMP_CNTL, AIC3262_DYNAMIC_OFFSET_CALIB_MASK, AIC3262_DYNAMIC_OFFSET_CALIB); } break; /* Off, without power */ case SND_SOC_BIAS_OFF: dev_dbg(codec->dev, "set_bias_off\n"); /* force all power off */ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { snd_soc_update_bits(codec, AIC3262_REF_PWR_DLY, AIC3262_CHIP_REF_PWR_ON_MASK, 0x0); snd_soc_update_bits(codec, AIC3262_POWER_CONF, (AIC3262_AVDD_TO_DVDD_MASK | AIC3262_EXT_ANALOG_SUPPLY_MASK), (AIC3262_AVDD_TO_DVDD | AIC3262_EXT_ANALOG_SUPPLY_OFF)); pm_runtime_put(codec->dev); } break; } codec->dapm.bias_level = level; return 0; } /** * * aic3262_suspend; This function is to suspend the AIC3262 driver. * @codec: pointer variable to codec having informaton related to codec, * * Return: Return 0 on success. */ static int aic3262_suspend(struct snd_soc_codec *codec) { aic3262_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } /** * aic3262_resume: This function is to resume the AIC3262 driver * from off state to standby * @codec: pointer variable to codec having informaton related to codec, * * Return: Return 0 on success. */ static int aic3262_resume(struct snd_soc_codec *codec) { aic3262_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } /** * aic3262_probe: This is first driver function called by the SoC core driver. * @codec: pointer variable to codec having informaton related to codec, * * Return: Return 0 on success. */ static int aic3262_codec_probe(struct snd_soc_codec *codec) { int ret = 0; struct aic3xxx *control; struct aic3262_priv *aic3262; if (codec == NULL) dev_err(codec->dev, "codec pointer is NULL.\n"); codec->control_data = dev_get_drvdata(codec->dev->parent); control = codec->control_data; aic3262 = kzalloc(sizeof(struct aic3262_priv), GFP_KERNEL); if (aic3262 == NULL) return -ENOMEM; snd_soc_codec_set_drvdata(codec, aic3262); aic3262->pdata = dev_get_platdata(codec->dev->parent); aic3262->codec = codec; aic3262->cur_fw = NULL; aic3262->cfw_p = &(aic3262->cfw_ps); aic3xxx_cfw_init(aic3262->cfw_p, &aic3262_cfw_codec_ops, aic3262->codec); aic3262->workqueue = create_singlethread_workqueue("aic3262-codec"); if (!aic3262->workqueue) { ret = -ENOMEM; goto work_err; } INIT_DELAYED_WORK(&aic3262->delayed_work, aic3262_accessory_work); mutex_init(&aic3262->mutex); mutex_init(&codec->mutex); mutex_init(&aic3262->cfw_mutex); pm_runtime_enable(codec->dev); pm_runtime_resume(codec->dev); aic3262->dsp_runstate = 0; if (control->irq) { ret = aic3xxx_request_irq(codec->control_data, AIC3262_IRQ_HEADSET_DETECT, aic3262_audio_handler, IRQF_NO_SUSPEND, "aic3262_irq_headset", codec); if (ret) { dev_err(codec->dev, "HEADSET detect irq request" "failed: %d\n", ret); goto irq_err; } else { /* Dynamic Headset Detection Enabled */ snd_soc_update_bits(codec, AIC3262_HP_DETECT, AIC3262_HEADSET_IN_MASK, AIC3262_HEADSET_IN_MASK); } } /* Keep the reference voltage ON while in$ STANDBY mode for fast power up */ snd_soc_update_bits(codec, AIC3262_REF_PWR_DLY, AIC3262_CHIP_REF_PWR_ON_MASK, AIC3262_CHIP_REF_PWR_ON); mdelay(40); snd_soc_update_bits(codec, AIC3262_CHARGE_PUMP_CNTL, AIC3262_DYNAMIC_OFFSET_CALIB_MASK, AIC3262_DYNAMIC_OFFSET_CALIB); aic3262_set_bias_level(codec, SND_SOC_BIAS_STANDBY); aic3262->mute_asi = 0; ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, "tlv320aic3262_fw_v1.bin", codec->dev, GFP_KERNEL, codec, aic3262_firmware_load); if (ret < 0) { dev_err(codec->dev, "Firmware request failed\n"); goto firm_err; } return 0; firm_err: aic3xxx_free_irq(control, AIC3262_IRQ_HEADSET_DETECT, codec); irq_err: destroy_workqueue(aic3262->workqueue); work_err: kfree(aic3262); return 0; } /* * aic3262_remove: Cleans up and Remove aic3262 soc device * @codec: pointer variable to codec having informaton related to codec, * * Return: Return 0 on success. */ static int aic3262_codec_remove(struct snd_soc_codec *codec) { /* power down chip */ struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); struct aic3xxx *control = codec->control_data; aic3262_set_bias_level(codec, SND_SOC_BIAS_OFF); /* free_irq if any */ switch (control->type) { case TLV320AIC3262: if (control->irq) aic3xxx_free_irq(control, AIC3262_IRQ_HEADSET_DETECT, codec); break; default: dev_info(codec->dev, "Coded is not TLV320AIC3262\n"); } /* release firmware if any */ if (aic3262->cur_fw != NULL) release_firmware(aic3262->cur_fw); /* destroy workqueue for jac dev */ destroy_workqueue(aic3262->workqueue); kfree(aic3262); return 0; } static struct snd_soc_codec_driver soc_codec_driver_aic326x = { .probe = aic3262_codec_probe, .remove = aic3262_codec_remove, .suspend = aic3262_suspend, .resume = aic3262_resume, .read = aic3262_codec_read, .write = aic3262_codec_write, .controls = aic3262_snd_controls, .num_controls = ARRAY_SIZE(aic3262_snd_controls), .dapm_widgets = aic3262_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(aic3262_dapm_widgets), .dapm_routes = aic3262_dapm_routes, .num_dapm_routes = ARRAY_SIZE(aic3262_dapm_routes), .set_bias_level = aic3262_set_bias_level, .reg_cache_size = 0, .reg_word_size = sizeof(u8), .reg_cache_default = NULL, }; static int aic326x_probe(struct platform_device *pdev) { return snd_soc_register_codec(&pdev->dev, &soc_codec_driver_aic326x, aic326x_dai_driver, ARRAY_SIZE(aic326x_dai_driver)); } static int aic326x_remove(struct platform_device *pdev) { snd_soc_unregister_codec(&pdev->dev); return 0; } static const struct of_device_id aic3262_of_match[] = { { .compatible = "ti,aic3262", }, { }, }; MODULE_DEVICE_TABLE(of, aic3262_of_match); static const struct platform_device_id aic3262_i2c_id[] = { { "tlv320aic3262-codec", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, aic3262_i2c_id); static struct platform_driver aic326x_codec_driver = { .driver = { .name = "tlv320aic3262-codec", .owner = THIS_MODULE, .of_match_table = aic3262_of_match, }, .probe = aic326x_probe, .remove = __devexit_p(aic326x_remove), .id_table = aic3262_i2c_id, }; module_platform_driver(aic326x_codec_driver); MODULE_ALIAS("platform:tlv320aic3262-codec"); MODULE_DESCRIPTION("ASoC TLV320AIC3262 codec driver"); MODULE_AUTHOR("Y Preetam Sashank Reddy "); MODULE_AUTHOR("Barani Prashanth "); MODULE_AUTHOR("Mukund Navada K "); MODULE_AUTHOR("Naren Vasanad "); MODULE_LICENSE("GPL");