summaryrefslogtreecommitdiff
path: root/sound/soc/codecs/ab5500.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs/ab5500.c')
-rw-r--r--sound/soc/codecs/ab5500.c1632
1 files changed, 1632 insertions, 0 deletions
diff --git a/sound/soc/codecs/ab5500.c b/sound/soc/codecs/ab5500.c
new file mode 100644
index 00000000000..e5f0ecce3a0
--- /dev/null
+++ b/sound/soc/codecs/ab5500.c
@@ -0,0 +1,1632 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Xie Xiaolei <xie.xiaolei@etericsson.com>,
+ * Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Roger Nilsson <roger.xr.nilsson@stericsson.com>
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program 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.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/mfd/abx500.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
+#include <asm/atomic.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include "ab5500.h"
+
+#define AB5500_CODEC_DEBUG 0
+
+/* codec private data */
+struct ab5500_codec_dai_data {
+};
+
+static struct device *ab5500_dev;
+
+static u8 virtual_regs[] = {
+ 0, 0, 0, 0, 0
+};
+
+#define set_reg(reg, val) mask_set_reg((reg), 0xff, (val))
+
+static void mask_set_reg(u8 reg, u8 mask, u8 val)
+{
+ u8 newval = mask & val;
+ u8 oldval, diff;
+
+ if (!ab5500_dev) {
+ pr_err("%s: The AB5500 codec driver not initialized.\n",
+ __func__);
+ return;
+ }
+ if (reg < AB5500_FIRST_REG)
+ return;
+ if (reg <= AB5500_LAST_REG) {
+ abx500_mask_and_set_register_interruptible(
+ ab5500_dev, AB5500_BANK_AUDIO_HEADSETUSB,
+ reg, mask, val);
+ return;
+ }
+ if (reg - AB5500_LAST_REG - 1 >= ARRAY_SIZE(virtual_regs))
+ return;
+
+ /* treatment of virtual registers follows */
+ oldval = virtual_regs[reg - AB5500_LAST_REG - 1];
+ diff = (val ^ oldval) & mask;
+ if (!diff)
+ return;
+
+ switch (reg) {
+ case AB5500_VIRTUAL_REG3:
+ if ((diff & (1 << SPKR1_PWR_SHIFT))) {
+ if ((val & (1 << SPKR1_PWR_SHIFT)) == 0)
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK, 0);
+ else
+ switch (oldval & SPKR1_MODE_MASK) {
+ case 0:
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK,
+ SPKRx_PWR_VBR_VALUE);
+ break;
+ case 1:
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK,
+ SPKRx_PWR_CLS_D_VALUE);
+ break;
+ case 2:
+ mask_set_reg(SPKR1, SPKRx_PWR_MASK,
+ SPKRx_PWR_CLS_AB_VALUE);
+ break;
+ }
+ }
+ if ((diff & (1 << SPKR2_PWR_SHIFT))) {
+ if ((val & (1 << SPKR2_PWR_SHIFT)) == 0)
+ mask_set_reg(SPKR2, SPKRx_PWR_MASK, 0);
+ else
+ switch (oldval & SPKR2_MODE_MASK) {
+ case 0:
+ mask_set_reg(SPKR2, SPKRx_PWR_MASK,
+ SPKRx_PWR_VBR_VALUE);
+ break;
+ case 1:
+ mask_set_reg(SPKR2, SPKRx_PWR_MASK,
+ SPKRx_PWR_CLS_D_VALUE);
+ break;
+ }
+ }
+
+ break;
+ case AB5500_VIRTUAL_REG4:
+ ;
+ /* configure PWMCTRL_SPKR1, PWMCTRL_SPKR2, etc. */
+ }
+ virtual_regs[reg - AB5500_LAST_REG - 1] &= ~mask;
+ virtual_regs[reg - AB5500_LAST_REG - 1] |= newval;
+}
+
+static u8 read_reg(u8 reg)
+{
+ if (!ab5500_dev) {
+ pr_err("%s: The AB5500 codec driver not initialized.\n",
+ __func__);
+ return 0;
+ }
+ if (reg < AB5500_FIRST_REG)
+ return 0;
+ else if (reg <= AB5500_LAST_REG) {
+ u8 val;
+ abx500_get_register_interruptible(
+ ab5500_dev, AB5500_BANK_AUDIO_HEADSETUSB, reg, &val);
+ return val;
+ } else if (reg - AB5500_LAST_REG - 1 < ARRAY_SIZE(virtual_regs))
+ return virtual_regs[reg - AB5500_LAST_REG - 1];
+ dev_warn(ab5500_dev, "%s: out-of-scope reigster %u.\n",
+ __func__, reg);
+ return 0;
+}
+
+/* Components that can be powered up/down */
+enum enum_widget {
+ widget_ear = 0,
+
+ widget_auxo1,
+ widget_auxo2,
+ widget_auxo3,
+ widget_auxo4,
+
+ widget_spkr1,
+ widget_spkr2,
+ widget_spkr1_adder,
+ widget_spkr2_adder,
+
+ widget_pwm_spkr1,
+ widget_pwm_spkr2,
+
+ widget_pwm_spkr1n,
+ widget_pwm_spkr1p,
+ widget_pwm_spkr2n,
+ widget_pwm_spkr2p,
+
+ widget_line1,
+ widget_line2,
+
+ widget_dac1,
+ widget_dac2,
+ widget_dac3,
+
+ widget_rx1,
+ widget_rx2,
+ widget_rx3,
+
+ widget_mic1,
+ widget_mic2,
+
+ widget_micbias1,
+ widget_micbias2,
+
+ widget_apga1,
+ widget_apga2,
+
+ widget_tx1,
+ widget_tx2,
+
+ widget_adc1,
+ widget_adc2,
+
+ widget_if0_dld_l,
+ widget_if0_dld_r,
+ widget_if0_uld_l,
+ widget_if0_uld_r,
+ widget_if1_dld_l,
+ widget_if1_dld_r,
+ widget_if1_uld_l,
+ widget_if1_uld_r,
+
+ widget_mic1p1,
+ widget_mic1n1,
+ widget_mic1p2,
+ widget_mic1n2,
+
+ widget_mic2p1,
+ widget_mic2n1,
+ widget_mic2p2,
+ widget_mic2n2,
+
+ widget_clock,
+
+ number_of_widgets
+};
+
+#if AB5500_CODEC_DEBUG
+static const char *widget_names[] = {
+ "EAR", "AUXO1", "AUXO2", "AUXO3", "AUXO4",
+ "SPKR1", "SPKR2", "SPKR1_ADDER", "SPKR2_ADDER",
+ "PWM_SPKR1", "PWM_SPKR2",
+ "PWM_SPKR1N", "PWM_SPKR1P",
+ "PWM_SPKR2N", "PWM_SPKR2P",
+ "LINE1", "LINE2",
+ "DAC1", "DAC2", "DAC3",
+ "RX1", "RX2", "RX3",
+ "MIC1", "MIC2",
+ "MIC-BIAS1", "MIC-BIAS2",
+ "APGA1", "APGA2",
+ "TX1", "TX2",
+ "ADC1", "ADC2",
+ "IF0-DLD-L", "IF0-DLD-R", "IF0-ULD-L", "IF0-ULD-R",
+ "IF1-DLD-L", "IF1-DLD-R", "IF1-ULD-L", "IF1-ULD-R",
+ "MIC1P1", "MIC1N1", "MIC1P2", "MIC1N2",
+ "MIC2P1", "MIC2N1", "MIC2P2", "MIC2N2",
+ "CLOCK"
+};
+#endif
+
+struct widget_pm {
+ enum enum_widget widget;
+ u8 reg;
+ u8 shift;
+
+ unsigned long source_list[BIT_WORD(number_of_widgets) + 1];
+ unsigned long sink_list[BIT_WORD(number_of_widgets) + 1];
+};
+
+static struct widget_pm widget_pm_array[] = {
+ {.widget = widget_ear, .reg = EAR_PWR, .shift = EAR_PWR_SHIFT},
+
+ {.widget = widget_auxo1, .reg = AUXO1, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_auxo2, .reg = AUXO2, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_auxo3, .reg = AUXO3, .shift = AUXOx_PWR_SHIFT},
+ {.widget = widget_auxo4, .reg = AUXO4, .shift = AUXOx_PWR_SHIFT},
+
+ {.widget = widget_spkr1, .reg = AB5500_VIRTUAL_REG3,
+ .shift = SPKR1_PWR_SHIFT},
+ {.widget = widget_spkr2, .reg = AB5500_VIRTUAL_REG3,
+ .shift = SPKR2_PWR_SHIFT},
+
+ {.widget = widget_spkr1_adder, .reg = AB5500_VIRTUAL_REG3,
+ .shift = SPKR1_ADDER_PWR_SHIFT},
+ {.widget = widget_spkr2_adder, .reg = AB5500_VIRTUAL_REG3,
+ .shift = SPKR2_ADDER_PWR_SHIFT},
+
+ {.widget = widget_pwm_spkr1, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR1_PWR_SHIFT},
+ {.widget = widget_pwm_spkr2, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR2_PWR_SHIFT},
+
+ {.widget = widget_pwm_spkr1n, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR1N_PWR_SHIFT},
+ {.widget = widget_pwm_spkr1p, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR1P_PWR_SHIFT},
+
+ {.widget = widget_pwm_spkr2n, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR2N_PWR_SHIFT},
+ {.widget = widget_pwm_spkr2p, .reg = AB5500_VIRTUAL_REG4,
+ .shift = PWM_SPKR2P_PWR_SHIFT},
+
+
+ {.widget = widget_line1, .reg = LINE1, .shift = LINEx_PWR_SHIFT},
+ {.widget = widget_line2, .reg = LINE2, .shift = LINEx_PWR_SHIFT},
+
+ {.widget = widget_dac1, .reg = RX1, .shift = DACx_PWR_SHIFT},
+ {.widget = widget_dac2, .reg = RX2, .shift = DACx_PWR_SHIFT},
+ {.widget = widget_dac3, .reg = RX3, .shift = DACx_PWR_SHIFT},
+
+ {.widget = widget_rx1, .reg = RX1, .shift = RXx_PWR_SHIFT},
+ {.widget = widget_rx2, .reg = RX2, .shift = RXx_PWR_SHIFT},
+ {.widget = widget_rx3, .reg = RX3, .shift = RXx_PWR_SHIFT},
+
+ {.widget = widget_mic1, .reg = MIC1_GAIN, .shift = MICx_PWR_SHIFT},
+ {.widget = widget_mic2, .reg = MIC2_GAIN, .shift = MICx_PWR_SHIFT},
+
+ {.widget = widget_micbias1, .reg = MIC_BIAS1,
+ .shift = MBIASx_PWR_SHIFT},
+ {.widget = widget_micbias2, .reg = MIC_BIAS2,
+ .shift = MBIASx_PWR_SHIFT},
+
+ {.widget = widget_apga1, .reg = ANALOG_LOOP_PGA1,
+ .shift = APGAx_PWR_SHIFT},
+ {.widget = widget_apga2, .reg = ANALOG_LOOP_PGA2,
+ .shift = APGAx_PWR_SHIFT},
+
+ {.widget = widget_tx1, .reg = TX1, .shift = TXx_PWR_SHIFT},
+ {.widget = widget_tx2, .reg = TX2, .shift = TXx_PWR_SHIFT},
+
+ {.widget = widget_adc1, .reg = TX1, .shift = ADCx_PWR_SHIFT},
+ {.widget = widget_adc2, .reg = TX2, .shift = ADCx_PWR_SHIFT},
+
+ {.widget = widget_if0_dld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_DLD_L_PW_SHIFT},
+ {.widget = widget_if0_dld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_DLD_R_PW_SHIFT},
+ {.widget = widget_if0_uld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_ULD_L_PW_SHIFT},
+ {.widget = widget_if0_uld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF0_ULD_R_PW_SHIFT},
+
+ {.widget = widget_if1_dld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_DLD_L_PW_SHIFT},
+ {.widget = widget_if1_dld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_DLD_R_PW_SHIFT},
+ {.widget = widget_if1_uld_l, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_ULD_L_PW_SHIFT},
+ {.widget = widget_if1_uld_r, .reg = AB5500_VIRTUAL_REG1,
+ .shift = IF1_ULD_R_PW_SHIFT},
+
+ {.widget = widget_mic1p1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1P1_PW_SHIFT},
+ {.widget = widget_mic1n1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1N1_PW_SHIFT},
+ {.widget = widget_mic1p2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1P2_PW_SHIFT},
+ {.widget = widget_mic1n2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC1N2_PW_SHIFT},
+
+ {.widget = widget_mic2p1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2P1_PW_SHIFT},
+ {.widget = widget_mic2n1, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2N1_PW_SHIFT},
+ {.widget = widget_mic2p2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2P2_PW_SHIFT},
+ {.widget = widget_mic2n2, .reg = AB5500_VIRTUAL_REG2,
+ .shift = MIC2N2_PW_SHIFT},
+
+ {.widget = widget_clock, .reg = CLOCK, .shift = CLOCK_ENABLE_SHIFT},
+};
+
+DEFINE_MUTEX(ab5500_pm_mutex);
+
+static struct {
+ enum enum_widget stack[number_of_widgets];
+ int p;
+} pm_stack;
+
+#define pm_stack_as_bitmap ({ \
+ unsigned long bitmap[BIT_WORD(number_of_widgets) + 1]; \
+ int i; \
+ memset(bitmap, 0, sizeof(bitmap)); \
+ for (i = 0; i < pm_stack.p; i++) { \
+ set_bit(pm_stack.stack[i], bitmap); \
+ } \
+ bitmap; \
+ })
+
+/* These are only meant to meet the obligations of DAPM */
+static const struct snd_soc_dapm_widget ab5500_dapm_widgets[] = {
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+};
+
+
+struct codec_dai_private {
+ unsigned active_flags;
+} privates[] = {
+ {0},
+ {0}
+};
+
+static const char *enum_rx_input_select[] = {
+ "Mute", "TX1", "TX2", "I2S0_DLD_L",
+ "I2S0_DLD_R", "I2S1_DLD_L", "I2S1_DLD_R"
+};
+
+static const char *enum_i2s_uld_select[] = {
+ "Mute", "TX1", "TX2", "I2S0_DLD_L",
+ "I2S0_DLD_R", "I2S1_DLD_L", "I2S1_DLD_R", "tri-state"
+};
+static const char *enum_apga1_source[] = {"LINEIN1", "MIC1", "MIC2", "None"};
+static const char *enum_apga2_source[] = {"LINEIN2", "MIC1", "MIC2", "None"};
+static const char *enum_rx_side_tone[] = {"TX1", "TX2"};
+static const char *enum_dac_power_mode[] = {"100%", "75%", "55%"};
+static const char *enum_ear_power_mode[] = {"100%", "70%", "50%"};
+static const char *enum_auxo_power_mode[] = {
+ "100%", "67%", "50%", "25%", "auto"
+};
+static const char *enum_onoff[] = {"Off", "On"};
+static const char *enum_mbias_pdn_imp[] = {"GND", "HiZ"};
+static const char *enum_mbias2_out_v[] = {"2.0v", "2.2v"};
+static const char *enum_mic_in_imp[] = {
+ "12.5 kohm", "25 kohm", "50 kohm"
+};
+static const char *enum_hp_filter[] = {"HP3", "HP1", "bypass"};
+static const char *enum_i2s_word_length[] = {"16 bits", "24 bits"};
+static const char *enum_i2s_mode[] = {"Master Mode", "Slave Mode"};
+static const char *enum_i2s_tristate[] = {"Normal", "Tri-state"};
+static const char *enum_optional_resistor[] = {"disconnected", "connected"};
+static const char *enum_i2s_sample_rate[] = {
+ "8 kHz", "16 kHz", "44.1 kHz", "48 kHz"
+};
+static const char *enum_tx1_input_select[] = {
+ "ADC1", "DIGMIC1", "DIGMIC2"
+};
+static const char *enum_tx2_input_select[] = {
+ "ADC2", "DIGMIC1", "DIGMIC2"
+};
+static const char *enum_signal_inversion[] = {"normal", "inverted"};
+static const char *enum_spkr1_mode[] = {
+ "Vibra PWM", "class D amplifier", "class AB amplifier"
+};
+static const char *enum_spkr2_mode[] = {
+ "Vibra PWM", "class D amplifier",
+};
+static const char *enum_pwm_pol[] = {
+ "GND", "VDD"
+};
+/* RX1 Input Select */
+static struct soc_enum soc_enum_rx1_in_sel =
+ SOC_ENUM_SINGLE(RX1, RXx_DATA_SHIFT,
+ ARRAY_SIZE(enum_rx_input_select),
+ enum_rx_input_select);
+
+/* RX2 Input Select */
+static struct soc_enum soc_enum_rx2_in_sel =
+ SOC_ENUM_SINGLE(RX2, RXx_DATA_SHIFT,
+ ARRAY_SIZE(enum_rx_input_select),
+ enum_rx_input_select);
+/* RX3 Input Select */
+static struct soc_enum soc_enum_rx3_in_sel =
+ SOC_ENUM_SINGLE(RX3, RXx_DATA_SHIFT,
+ ARRAY_SIZE(enum_rx_input_select),
+ enum_rx_input_select);
+/* TX1 Input Select */
+static struct soc_enum soc_enum_tx1_in_sel =
+ SOC_ENUM_SINGLE(TX1, TXx_MUX_SHIFT,
+ ARRAY_SIZE(enum_tx1_input_select),
+ enum_tx1_input_select);
+/* TX2 Input Select */
+static struct soc_enum soc_enum_tx2_in_sel =
+ SOC_ENUM_SINGLE(TX2, TXx_MUX_SHIFT,
+ ARRAY_SIZE(enum_tx2_input_select),
+ enum_tx2_input_select);
+
+/* I2S0 ULD Select */
+static struct soc_enum soc_enum_i2s0_input_select =
+ SOC_ENUM_DOUBLE(INTERFACE0_ULD, 0, 4,
+ ARRAY_SIZE(enum_i2s_uld_select),
+ enum_i2s_uld_select);
+/* I2S1 ULD Select */
+static struct soc_enum soc_enum_i2s1_input_select =
+ SOC_ENUM_DOUBLE(INTERFACE1_ULD, 0, 4,
+ ARRAY_SIZE(enum_i2s_uld_select),
+ enum_i2s_uld_select);
+
+/* APGA1 Source */
+static struct soc_enum soc_enum_apga1_source =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA1, APGAx_MUX_SHIFT,
+ ARRAY_SIZE(enum_apga1_source),
+ enum_apga1_source);
+
+/* APGA2 Source */
+static struct soc_enum soc_enum_apga2_source =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA2, APGAx_MUX_SHIFT,
+ ARRAY_SIZE(enum_apga2_source),
+ enum_apga2_source);
+
+static struct soc_enum soc_enum_apga1_enable =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA1, APGAx_PWR_SHIFT,
+ ARRAY_SIZE(enum_onoff), enum_onoff);
+
+static struct soc_enum soc_enum_apga2_enable =
+ SOC_ENUM_SINGLE(ANALOG_LOOP_PGA2, APGAx_PWR_SHIFT,
+ ARRAY_SIZE(enum_onoff), enum_onoff);
+
+/* RX1 Side Tone */
+static struct soc_enum soc_enum_dac1_side_tone =
+ SOC_ENUM_SINGLE(ST1_PGA, STx_MUX_SHIFT,
+ ARRAY_SIZE(enum_rx_side_tone),
+ enum_rx_side_tone);
+
+/* RX2 Side Tone */
+static struct soc_enum soc_enum_dac2_side_tone =
+ SOC_ENUM_SINGLE(ST2_PGA, STx_MUX_SHIFT,
+ ARRAY_SIZE(enum_rx_side_tone),
+ enum_rx_side_tone);
+
+/* DAC1 Power Mode */
+static struct soc_enum soc_enum_dac1_power_mode =
+ SOC_ENUM_SINGLE(RX1, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode),
+ enum_dac_power_mode);
+
+/* DAC2 Power Mode */
+static struct soc_enum soc_enum_dac2_power_mode =
+ SOC_ENUM_SINGLE(RX2, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode),
+ enum_dac_power_mode);
+
+/* DAC3 Power Mode */
+static struct soc_enum soc_enum_dac3_power_mode =
+ SOC_ENUM_SINGLE(RX3, DACx_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_dac_power_mode),
+ enum_dac_power_mode);
+
+/* EAR Power Mode */
+static struct soc_enum soc_enum_ear_power_mode =
+ SOC_ENUM_SINGLE(EAR_PWR, EAR_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_ear_power_mode),
+ enum_ear_power_mode);
+
+/* AUXO12 Power Mode */
+static struct soc_enum soc_enum_auxo12_power_mode =
+ SOC_ENUM_SINGLE(AUXO12_PWR_MODE, AUXOxy_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_auxo_power_mode),
+ enum_auxo_power_mode);
+
+/* AUXO34 Power Mode */
+static struct soc_enum soc_enum_auxo34_power_mode =
+ SOC_ENUM_SINGLE(AUXO34_PWR_MODE, AUXOxy_PWR_MODE_SHIFT,
+ ARRAY_SIZE(enum_auxo_power_mode),
+ enum_auxo_power_mode);
+
+/* MBIAS1 PDN Impedance */
+static struct soc_enum soc_enum_mbias1_pdn_imp =
+ SOC_ENUM_SINGLE(MIC_BIAS1, MBIASx_PDN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mbias_pdn_imp),
+ enum_mbias_pdn_imp);
+
+/* MBIAS2 PDN Impedance */
+static struct soc_enum soc_enum_mbias2_pdn_imp =
+ SOC_ENUM_SINGLE(MIC_BIAS2, MBIASx_PDN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mbias_pdn_imp),
+ enum_mbias_pdn_imp);
+
+/* MBIAS2 Output voltage */
+static struct soc_enum soc_enum_mbias2_out_v =
+ SOC_ENUM_SINGLE(MIC_BIAS2, MBIAS2_OUT_V_SHIFT,
+ ARRAY_SIZE(enum_mbias2_out_v),
+ enum_mbias2_out_v);
+
+static struct soc_enum soc_enum_mbias2_int_r =
+ SOC_ENUM_SINGLE(MIC_BIAS2_VAD, MBIAS2_R_INT_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_mic1_in_imp =
+ SOC_ENUM_SINGLE(MIC1_GAIN, MICx_IN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mic_in_imp),
+ enum_mic_in_imp);
+
+static struct soc_enum soc_enum_mic2_in_imp =
+ SOC_ENUM_SINGLE(MIC2_GAIN, MICx_IN_IMP_SHIFT,
+ ARRAY_SIZE(enum_mic_in_imp),
+ enum_mic_in_imp);
+
+static struct soc_enum soc_enum_tx1_hp_filter =
+ SOC_ENUM_SINGLE(TX1, TXx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_tx2_hp_filter =
+ SOC_ENUM_SINGLE(TX2, TXx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_st1_hp_filter =
+ SOC_ENUM_SINGLE(ST1_PGA, STx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_st2_hp_filter =
+ SOC_ENUM_SINGLE(ST2_PGA, STx_HP_FILTER_SHIFT,
+ ARRAY_SIZE(enum_hp_filter),
+ enum_hp_filter);
+
+static struct soc_enum soc_enum_i2s0_word_length =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_WORDLENGTH_SHIFT,
+ ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length);
+
+static struct soc_enum soc_enum_i2s1_word_length =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_WORDLENGTH_SHIFT,
+ ARRAY_SIZE(enum_i2s_word_length),
+ enum_i2s_word_length);
+
+static struct soc_enum soc_enum_i2s0_mode =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_MODE_SHIFT,
+ ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode);
+
+static struct soc_enum soc_enum_i2s1_mode =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_MODE_SHIFT,
+ ARRAY_SIZE(enum_i2s_mode),
+ enum_i2s_mode);
+
+static struct soc_enum soc_enum_i2s0_tristate =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_TRISTATE_SHIFT,
+ ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate);
+
+static struct soc_enum soc_enum_i2s1_tristate =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_TRISTATE_SHIFT,
+ ARRAY_SIZE(enum_i2s_tristate),
+ enum_i2s_tristate);
+
+static struct soc_enum soc_enum_i2s0_pulldown_resistor =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_i2s1_pulldown_resistor =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_i2s0_sample_rate =
+ SOC_ENUM_SINGLE(INTERFACE0, I2Sx_SR_SHIFT,
+ ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate);
+
+static struct soc_enum soc_enum_i2s1_sample_rate =
+ SOC_ENUM_SINGLE(INTERFACE1, I2Sx_SR_SHIFT,
+ ARRAY_SIZE(enum_i2s_sample_rate),
+ enum_i2s_sample_rate);
+
+static struct soc_enum soc_enum_line1_inversion =
+ SOC_ENUM_SINGLE(LINE1, LINEx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_line2_inversion =
+ SOC_ENUM_SINGLE(LINE2, LINEx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo1_inversion =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo2_inversion =
+ SOC_ENUM_SINGLE(AUXO2, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo3_inversion =
+ SOC_ENUM_SINGLE(AUXO3, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo4_inversion =
+ SOC_ENUM_SINGLE(AUXO4, AUXOx_INV_SHIFT,
+ ARRAY_SIZE(enum_signal_inversion),
+ enum_signal_inversion);
+
+static struct soc_enum soc_enum_auxo1_pulldown_resistor =
+ SOC_ENUM_SINGLE(AUXO1, AUXOx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_auxo2_pulldown_resistor =
+ SOC_ENUM_SINGLE(AUXO2, AUXOx_PULLDOWN_SHIFT,
+ ARRAY_SIZE(enum_optional_resistor),
+ enum_optional_resistor);
+
+static struct soc_enum soc_enum_spkr1_mode =
+ SOC_ENUM_SINGLE(AB5500_VIRTUAL_REG3, SPKR1_MODE_SHIFT,
+ ARRAY_SIZE(enum_spkr1_mode),
+ enum_spkr1_mode);
+
+static struct soc_enum soc_enum_spkr2_mode =
+ SOC_ENUM_SINGLE(AB5500_VIRTUAL_REG3, SPKR2_MODE_SHIFT,
+ ARRAY_SIZE(enum_spkr2_mode),
+ enum_spkr2_mode);
+
+static struct soc_enum soc_enum_pwm_spkr1n_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR1, PWMCTRL_SPKRx_N1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct soc_enum soc_enum_pwm_spkr1p_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR1, PWMCTRL_SPKRx_P1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct soc_enum soc_enum_pwm_spkr2n_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR2, PWMCTRL_SPKRx_N1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct soc_enum soc_enum_pwm_spkr2p_pol =
+ SOC_ENUM_SINGLE(PWMCTRL_SPKR2, PWMCTRL_SPKRx_P1_POL_SHIFT,
+ ARRAY_SIZE(enum_pwm_pol), enum_pwm_pol);
+
+static struct snd_kcontrol_new ab5500_snd_controls[] = {
+ /* RX Routing */
+ SOC_ENUM("RX1 Input Select", soc_enum_rx1_in_sel),
+ SOC_ENUM("RX2 Input Select", soc_enum_rx2_in_sel),
+ SOC_ENUM("RX3 Input Select", soc_enum_rx3_in_sel),
+ SOC_SINGLE("LINE1 Adder", LINE1_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("LINE2 Adder", LINE2_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("EAR Adder", EAR_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("SPKR1 Adder", SPKR1_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("SPKR2 Adder", SPKR2_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO1 Adder", AUXO1_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO2 Adder", AUXO2_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO3 Adder", AUXO3_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("AUXO4 Adder", AUXO4_ADDER, 0, 0x1F, 0),
+ SOC_SINGLE("SPKR1 PWM Select", AB5500_VIRTUAL_REG5, 0, 0x03, 0),
+ SOC_SINGLE("SPKR2 PWM Select", AB5500_VIRTUAL_REG5, 2, 0x0C, 0),
+ /* TX Routing */
+ SOC_ENUM("TX1 Input Select", soc_enum_tx1_in_sel),
+ SOC_ENUM("TX2 Input Select", soc_enum_tx2_in_sel),
+ SOC_SINGLE("MIC1 Input Select", MIC1_INPUT_SELECT, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 Input Select", MIC2_INPUT_SELECT, 0, 0xff, 0),
+ SOC_SINGLE("MIC2 to MIC1", MIC2_TO_MIC1, 0, 0x03, 0),
+ SOC_ENUM("I2S0 Input Select", soc_enum_i2s0_input_select),
+ SOC_ENUM("I2S1 Input Select", soc_enum_i2s1_input_select),
+ /* Routing of Side Tone and Analop Loop */
+ SOC_ENUM("APGA1 Source", soc_enum_apga1_source),
+ SOC_ENUM("APGA2 Source", soc_enum_apga2_source),
+ SOC_ENUM("APGA1 Enable", soc_enum_apga1_enable),
+ SOC_ENUM("APGA2 Enable", soc_enum_apga2_enable),
+ SOC_ENUM("DAC1 Side Tone", soc_enum_dac1_side_tone),
+ SOC_ENUM("DAC2 Side Tone", soc_enum_dac2_side_tone),
+ /* RX Volume Control */
+ SOC_SINGLE("RX-DPGA1 Gain", RX1_DPGA, 0, 0x43, 0),
+ SOC_SINGLE("RX-DPGA2 Gain", RX1_DPGA, 0, 0x43, 0),
+ SOC_SINGLE("RX-DPGA3 Gain", RX3_DPGA, 0, 0x43, 0),
+ SOC_SINGLE("LINE1 Gain", LINE1, LINEx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("LINE2 Gain", LINE2, LINEx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("SPKR1 Gain", SPKR1, SPKRx_GAIN_SHIFT, 0x16, 0),
+ SOC_SINGLE("SPKR2 Gain", SPKR2, SPKRx_GAIN_SHIFT, 0x16, 0),
+ SOC_SINGLE("EAR Gain", EAR_GAIN, EAR_GAIN_SHIFT, 0x12, 0),
+ SOC_SINGLE("AUXO1 Gain", AUXO1, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ SOC_SINGLE("AUXO2 Gain", AUXO2, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ SOC_SINGLE("AUXO3 Gain", AUXO3, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ SOC_SINGLE("AUXO4 Gain", AUXO4, AUXOx_GAIN_SHIFT, 0x0c, 0),
+ /* TX Volume Control */
+ SOC_SINGLE("MIC1 Gain", MIC1_GAIN, MICx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("MIC2 Gain", MIC2_GAIN, MICx_GAIN_SHIFT, 0x0a, 0),
+ SOC_SINGLE("TX-DPGA1 Gain", TX_DPGA1, TX_DPGAx_SHIFT, 0x0f, 0),
+ SOC_SINGLE("TX-DPGA2 Gain", TX_DPGA2, TX_DPGAx_SHIFT, 0x0f, 0),
+ /* Volume Control of Side Tone and Analog Loop */
+ SOC_SINGLE("ST-PGA1 Gain", ST1_PGA, STx_PGA_SHIFT, 0x0a, 0),
+ SOC_SINGLE("ST-PGA2 Gain", ST2_PGA, STx_PGA_SHIFT, 0x0a, 0),
+ SOC_SINGLE("APGA1 Gain", ANALOG_LOOP_PGA1, APGAx_GAIN_SHIFT, 0x1d, 0),
+ SOC_SINGLE("APGA2 Gain", ANALOG_LOOP_PGA2, APGAx_GAIN_SHIFT, 0x1d, 0),
+ /* RX Properties */
+ SOC_ENUM("DAC1 Power Mode", soc_enum_dac1_power_mode),
+ SOC_ENUM("DAC2 Power Mode", soc_enum_dac2_power_mode),
+ SOC_ENUM("DAC3 Power Mode", soc_enum_dac3_power_mode),
+ SOC_ENUM("EAR Power Mode", soc_enum_ear_power_mode),
+ SOC_ENUM("AUXO12 Power Mode", soc_enum_auxo12_power_mode),
+ SOC_ENUM("AUXO34 Power Mode", soc_enum_auxo34_power_mode),
+ SOC_ENUM("LINE1 Inversion", soc_enum_line1_inversion),
+ SOC_ENUM("LINE2 Inversion", soc_enum_line2_inversion),
+ SOC_ENUM("AUXO1 Inversion", soc_enum_auxo1_inversion),
+ SOC_ENUM("AUXO2 Inversion", soc_enum_auxo2_inversion),
+ SOC_ENUM("AUXO3 Inversion", soc_enum_auxo3_inversion),
+ SOC_ENUM("AUXO4 Inversion", soc_enum_auxo4_inversion),
+ SOC_ENUM("AUXO1 Pulldown Resistor", soc_enum_auxo1_pulldown_resistor),
+ SOC_ENUM("AUXO2 Pulldown Resistor", soc_enum_auxo2_pulldown_resistor),
+ SOC_ENUM("SPKR1 Mode", soc_enum_spkr1_mode),
+ SOC_ENUM("SPKR2 Mode", soc_enum_spkr2_mode),
+ SOC_ENUM("PWM SPKR1N POL", soc_enum_pwm_spkr1n_pol),
+ SOC_ENUM("PWM SPKR1P POL", soc_enum_pwm_spkr1p_pol),
+ SOC_ENUM("PWM SPKR2N POL", soc_enum_pwm_spkr2n_pol),
+ SOC_ENUM("PWM SPKR2P POL", soc_enum_pwm_spkr2p_pol),
+ /* TX Properties */
+ SOC_SINGLE("MIC1 VMID", MIC1_VMID_SELECT, 0, 0x3f, 0),
+ SOC_SINGLE("MIC2 VMID", MIC2_VMID_SELECT, 0, 0x3f, 0),
+ SOC_ENUM("MBIAS1 PDN Impedance", soc_enum_mbias1_pdn_imp),
+ SOC_ENUM("MBIAS2 PDN Impedance", soc_enum_mbias2_pdn_imp),
+ SOC_ENUM("MBIAS2 Output Voltage", soc_enum_mbias2_out_v),
+ SOC_ENUM("MBIAS2 Internal Resistor", soc_enum_mbias2_int_r),
+ SOC_ENUM("MIC1 Input Impedance", soc_enum_mic1_in_imp),
+ SOC_ENUM("MIC2 Input Impedance", soc_enum_mic2_in_imp),
+ SOC_ENUM("TX1 HP Filter", soc_enum_tx1_hp_filter),
+ SOC_ENUM("TX2 HP Filter", soc_enum_tx2_hp_filter),
+ /* Side Tone and Analog Loop Properties */
+ SOC_ENUM("ST1 HP Filter", soc_enum_st1_hp_filter),
+ SOC_ENUM("ST2 HP Filter", soc_enum_st2_hp_filter),
+ /* I2S Interface Properties */
+ SOC_ENUM("I2S0 Word Length", soc_enum_i2s0_word_length),
+ SOC_ENUM("I2S1 Word Length", soc_enum_i2s1_word_length),
+ SOC_ENUM("I2S0 Mode", soc_enum_i2s0_mode),
+ SOC_ENUM("I2S1 Mode", soc_enum_i2s1_mode),
+ SOC_ENUM("I2S0 tri-state", soc_enum_i2s0_tristate),
+ SOC_ENUM("I2S1 tri-state", soc_enum_i2s1_tristate),
+ SOC_ENUM("I2S0 Pulldown Resistor", soc_enum_i2s0_pulldown_resistor),
+ SOC_ENUM("I2S1 Pulldown Resistor", soc_enum_i2s1_pulldown_resistor),
+ SOC_ENUM("I2S0 Sample Rate", soc_enum_i2s0_sample_rate),
+ SOC_ENUM("I2S1 Sample Rate", soc_enum_i2s1_sample_rate),
+ SOC_SINGLE("Interface Swap", INTERFACE_SWAP, 0, 0x03, 0),
+ /* Miscellaneous */
+ SOC_SINGLE("Negative Charge Pump", NEG_CHARGE_PUMP, 0, 0x03, 0)
+};
+
+enum enum_power {
+ POWER_OFF = 0,
+ POWER_ON = 1
+};
+
+enum enum_link {
+ UNLINK = 0,
+ LINK = 1
+};
+
+static enum enum_power get_widget_power_status(enum enum_widget widget)
+{
+ u8 val;
+
+ if (widget >= number_of_widgets)
+ return POWER_OFF;
+ val = read_reg(widget_pm_array[widget].reg);
+ if (val & (1 << widget_pm_array[widget].shift))
+ return POWER_ON;
+ else
+ return POWER_OFF;
+}
+
+static int count_powered_neighbors(const unsigned long *neighbors)
+{
+ unsigned long i;
+ int n = 0;
+ for_each_set_bit(i, neighbors, number_of_widgets) {
+ if (get_widget_power_status(i) == POWER_ON)
+ n++;
+ }
+ return n;
+}
+
+static int has_powered_neighbors(const unsigned long *neighbors)
+{
+ unsigned int i;
+ for_each_set_bit(i, neighbors, number_of_widgets) {
+ if (get_widget_power_status(i) == POWER_ON)
+ return 1;
+ }
+ return 0;
+}
+
+
+static int has_stacked_neighbors(const unsigned long *neighbors)
+{
+ unsigned long *stack_map = pm_stack_as_bitmap;
+ return bitmap_intersects(stack_map, neighbors, number_of_widgets);
+}
+
+static void power_widget_unlocked(enum enum_power onoff,
+ enum enum_widget widget)
+{
+ enum enum_widget w;
+ int done;
+
+ if (widget >= number_of_widgets)
+ return;
+ if (get_widget_power_status(widget) == onoff)
+ return;
+ for (w = widget, done = 0; !done;) {
+ unsigned long i;
+ unsigned long *srcs = widget_pm_array[w].source_list;
+ unsigned long *sinks = widget_pm_array[w].sink_list;
+ if (onoff == POWER_ON &&
+ !bitmap_empty(srcs, number_of_widgets) &&
+ !has_powered_neighbors(srcs)) {
+ pm_stack.stack[pm_stack.p++] = w;
+ for_each_set_bit(i, srcs, number_of_widgets) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else if (onoff == POWER_OFF &&
+ has_powered_neighbors(sinks)) {
+ int n = 0;
+ pm_stack.stack[pm_stack.p++] = w;
+ for_each_set_bit(i, sinks, number_of_widgets) {
+ if (count_powered_neighbors(
+ widget_pm_array[i].source_list)
+ == 1 &&
+ get_widget_power_status(i) == POWER_ON) {
+ pm_stack.stack[pm_stack.p++] = i;
+ n++;
+ }
+ }
+ if (n) {
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else
+ --pm_stack.p;
+ }
+ mask_set_reg(widget_pm_array[w].reg,
+ 1 << widget_pm_array[w].shift,
+ onoff == POWER_ON ? 0xff : 0);
+ if (onoff == POWER_ON &&
+ !bitmap_empty(sinks, number_of_widgets) &&
+ !has_powered_neighbors(sinks) &&
+ !has_stacked_neighbors(sinks)) {
+ for_each_set_bit(i, sinks, number_of_widgets) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ w = pm_stack.stack[--pm_stack.p];
+ continue;
+ } else if (onoff == POWER_OFF) {
+ for_each_set_bit(i, srcs, number_of_widgets) {
+ if (!has_powered_neighbors(
+ widget_pm_array[i].sink_list)
+ && get_widget_power_status(i) == POWER_ON
+ && !test_bit(i, pm_stack_as_bitmap)) {
+ pm_stack.stack[pm_stack.p++] = i;
+ }
+ }
+ }
+ if (pm_stack.p > 0)
+ w = pm_stack.stack[--pm_stack.p];
+ else
+ done = 1;
+ }
+}
+
+static void power_widget_locked(enum enum_power onoff,
+ enum enum_widget widget)
+{
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, widget);
+ mutex_unlock(&ab5500_pm_mutex);
+}
+
+#if AB5500_CODEC_DEBUG
+static void dump_registers(const char *where, ...)
+{
+ va_list ap;
+ va_start(ap, where);
+ do {
+ short reg = va_arg(ap, int);
+ if (reg < 0)
+ break;
+ dev_info(ab5500_dev, "%s from %s> 0x%02X : 0x%02X.\n",
+ __func__, where, reg, read_reg(reg));
+ } while (1);
+ va_end(ap);
+}
+#endif
+
+/**
+ * update the link two widgets.
+ * @op: 1 - connect; 0 - disconnect
+ * @src: source of the connection
+ * @sink: sink of the connection
+ */
+static int update_widgets_link(enum enum_link op, enum enum_widget src,
+ enum enum_widget sink,
+ u8 reg, u8 mask, u8 newval)
+{
+ int ret = 0;
+
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev, "%s: A signal is received while waiting on"
+ " the PM mutex.\n", __func__);
+ return -EINTR;
+ }
+
+ switch (op << 2 | test_bit(sink, widget_pm_array[src].sink_list) << 1 |
+ test_bit(src, widget_pm_array[sink].source_list)) {
+ case 3: /* UNLINK, sink in sink_list, src in source_list */
+ case 4: /* LINK, sink not in sink_list, src not in source_list */
+ break;
+ default:
+ ret = -EINVAL;
+ goto end;
+ }
+ switch (((int)op) << 2 | get_widget_power_status(src) << 1 |
+ get_widget_power_status(sink)) {
+ case 3: /* op = 0, src on, sink on */
+ if (count_powered_neighbors(widget_pm_array[sink].source_list)
+ == 1)
+ power_widget_unlocked(POWER_OFF, sink);
+ mask_set_reg(reg, mask, newval);
+ break;
+ case 6: /* op = 1, src on, sink off */
+ mask_set_reg(reg, mask, newval);
+ power_widget_unlocked(POWER_ON, sink);
+ break;
+ default:
+ /* op = 0, src off, sink off */
+ /* op = 0, src off, sink on */
+ /* op = 0, src on, sink off */
+ /* op = 1, src off, sink off */
+ /* op = 1, src off, sink on */
+ /* op = 1, src on, sink on */
+ mask_set_reg(reg, mask, newval);
+ }
+ change_bit(sink, widget_pm_array[src].sink_list);
+ change_bit(src, widget_pm_array[sink].source_list);
+end:
+ mutex_unlock(&ab5500_pm_mutex);
+ return ret;
+};
+
+static enum enum_widget apga_source_translate(u8 reg_value)
+{
+ switch (reg_value) {
+ case 1:
+ return widget_mic1;
+ case 2:
+ return widget_mic2;
+ default:
+ return number_of_widgets;
+ }
+}
+
+static enum enum_widget adder_sink_translate(u8 reg)
+{
+ switch (reg) {
+ case EAR_ADDER:
+ return widget_ear;
+ case AUXO1_ADDER:
+ return widget_auxo1;
+ case AUXO2_ADDER:
+ return widget_auxo2;
+ case AUXO3_ADDER:
+ return widget_auxo3;
+ case AUXO4_ADDER:
+ return widget_auxo4;
+ case SPKR1_ADDER:
+ return widget_spkr1;
+ case SPKR2_ADDER:
+ return widget_spkr2;
+ case LINE1_ADDER:
+ return widget_line1;
+ case LINE2_ADDER:
+ return widget_line2;
+ default:
+ return number_of_widgets;
+ }
+}
+
+static int ab5500_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ab5500_dapm_widgets,
+ ARRAY_SIZE(ab5500_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static void power_for_playback(enum enum_power onoff, int ifsel)
+{
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_dld_l : widget_if1_dld_l);
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_dld_r : widget_if1_dld_r);
+ mutex_unlock(&ab5500_pm_mutex);
+}
+
+static void power_for_capture(enum enum_power onoff, int ifsel)
+{
+ if (mutex_lock_interruptible(&ab5500_pm_mutex)) {
+ dev_warn(ab5500_dev,
+ "%s: Signal received while waiting on the PM mutex.\n",
+ __func__);
+ return;
+ }
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_uld_l : widget_if1_uld_l);
+ power_widget_unlocked(onoff, ifsel == 0 ?
+ widget_if0_uld_r : widget_if1_uld_r);
+ mutex_unlock(&ab5500_pm_mutex);
+}
+
+static int ab5500_add_controls(struct snd_soc_codec *codec)
+{
+ int err = 0, i, n = ARRAY_SIZE(ab5500_snd_controls);
+
+ for (i = 0; i < n; i++) {
+ err = snd_ctl_add(codec->card->snd_card, snd_ctl_new1(
+ &ab5500_snd_controls[i], codec));
+ if (err < 0) {
+ pr_err("%s failed to add control No.%d of %d.\n",
+ __func__, i, n);
+ return err;
+ }
+ }
+ return err;
+}
+
+static int ab5500_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ dai->playback_active : dai->capture_active) {
+ dev_err(dai->dev, "A %s stream is already active.\n",
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int ab5500_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *dai)
+{
+ u8 val;
+ u8 reg = dai->id == 0 ? INTERFACE0 : INTERFACE1;
+
+ if (!ab5500_dev) {
+ pr_err("%s: The AB5500 codec driver not initialized.\n",
+ __func__);
+ return -EAGAIN;
+ }
+ switch (params_rate(hw_params)) {
+ case 8000:
+ val = I2Sx_SR_8000Hz;
+ break;
+ case 16000:
+ val = I2Sx_SR_16000Hz;
+ break;
+ case 44100:
+ val = I2Sx_SR_44100Hz;
+ break;
+ case 48000:
+ val = I2Sx_SR_48000Hz;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ !dai->capture_active : !dai->playback_active) {
+
+ mask_set_reg(reg, I2Sx_SR_MASK, val << I2Sx_SR_SHIFT);
+ if ((read_reg(reg) & I2Sx_MODE_MASK) == 0) {
+ mask_set_reg(reg, MASTER_GENx_PWR_MASK,
+ 1 << MASTER_GENx_PWR_SHIFT);
+ }
+ }
+ return 0;
+}
+
+static int ab5500_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ /* Configure registers for either playback or capture */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ power_for_playback(POWER_ON, dai->id);
+ else
+ power_for_capture(POWER_ON, dai->id);
+ return 0;
+}
+
+static void ab5500_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ u8 iface = dai->id == 0 ? INTERFACE0 : INTERFACE1;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ power_for_playback(POWER_OFF, dai->id);
+ else
+ power_for_capture(POWER_OFF, dai->id);
+ if (!dai->playback_active && !dai->capture_active &&
+ (read_reg(iface) & I2Sx_MODE_MASK) == 0)
+ mask_set_reg(iface, MASTER_GENx_PWR_MASK, 0);
+}
+
+static int ab5500_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ return 0;
+}
+
+static int ab5500_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ u8 iface = (codec_dai->id == 0) ? INTERFACE0 : INTERFACE1;
+ u8 val = 0;
+
+ switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
+ SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ val |= 1 << I2Sx_MODE_SHIFT;
+ break;
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ break;
+
+ default:
+ dev_warn(ab5500_dev, "AB5500_dai: unsupported DAI format "
+ "0x%x\n", fmt);
+ return -EINVAL;
+ }
+ if (codec_dai->playback_active && codec_dai->capture_active) {
+ if ((read_reg(iface) & I2Sx_MODE_MASK) == val)
+ return 0;
+ else {
+ dev_err(ab5500_dev,
+ "%s: DAI format set differently "
+ "by an existing stream.\n", __func__);
+ return -EINVAL;
+ }
+ }
+ mask_set_reg(iface, I2Sx_MODE_MASK, val);
+ return 0;
+}
+
+struct snd_soc_dai_driver ab5500_dai_drv[] = {
+ {
+ .name = "ab5500-codec-dai.0",
+ .id = 0,
+ .playback = {
+ .stream_name = "ab5500.0 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "ab5500.0 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab5500_pcm_startup,
+ .prepare = ab5500_pcm_prepare,
+ .hw_params = ab5500_pcm_hw_params,
+ .shutdown = ab5500_pcm_shutdown,
+ .set_sysclk = ab5500_set_dai_sysclk,
+ .set_fmt = ab5500_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1,
+ },
+ {
+ .name = "ab5500-codec-dai.1",
+ .id = 1,
+ .playback = {
+ .stream_name = "ab5500.1 Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .capture = {
+ .stream_name = "ab5500.1 Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = AB5500_SUPPORTED_RATE,
+ .formats = AB5500_SUPPORTED_FMT,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .startup = ab5500_pcm_startup,
+ .prepare = ab5500_pcm_prepare,
+ .hw_params = ab5500_pcm_hw_params,
+ .shutdown = ab5500_pcm_shutdown,
+ .set_sysclk = ab5500_set_dai_sysclk,
+ .set_fmt = ab5500_set_dai_fmt,
+ }
+ },
+ .symmetric_rates = 1,
+ }
+};
+
+static int ab5500_codec_probe(struct snd_soc_codec *codec)
+{
+ int ret = ab5500_add_controls(codec);
+ if (ret < 0)
+ return ret;
+ ab5500_add_widgets(codec);
+ return 0;
+}
+
+static int ab5500_codec_remove(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_free(codec);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ab5500_codec_suspend(struct snd_soc_codec *codec,
+ pm_message_t state)
+{
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0);
+ return 0;
+}
+
+static int ab5500_codec_resume(struct snd_soc_codec *codec)
+{
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0xff);
+ return 0;
+}
+#else
+#define ab5500_codec_resume NULL
+#define ab5500_codec_suspend NULL
+#endif
+
+/**
+ This function is only called by the SOC framework to
+ set registers associated to the mixer controls.
+*/
+static int ab5500_codec_write_reg(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ if (reg < MIC_BIAS1 || reg > INTERFACE_SWAP)
+ return -EINVAL;
+ switch (reg) {
+ u8 diff, oldval;
+ case ANALOG_LOOP_PGA1:
+ case ANALOG_LOOP_PGA2: {
+ enum enum_widget apga = reg == ANALOG_LOOP_PGA1 ?
+ widget_apga1 : widget_apga2;
+
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+
+ /*
+ * The APGA is to be turned on/off. The power bit and the
+ * other bits in the same register won't be changed at the
+ * same time since they belong to different controls.
+ */
+ if (diff & (1 << APGAx_PWR_SHIFT)) {
+ power_widget_locked(value >> APGAx_PWR_SHIFT & 1,
+ apga);
+ } else if (diff & APGAx_MUX_MASK) {
+ enum enum_widget old_source =
+ apga_source_translate(oldval);
+ enum enum_widget new_source =
+ apga_source_translate(value);
+ update_widgets_link(UNLINK, old_source, apga,
+ reg, APGAx_MUX_MASK, 0);
+ update_widgets_link(LINK, new_source, apga,
+ reg, APGAx_MUX_MASK, value);
+ } else {
+ set_reg(reg, value);
+ }
+ break;
+ }
+
+ case EAR_ADDER:
+ case AUXO1_ADDER:
+ case AUXO2_ADDER:
+ case AUXO3_ADDER:
+ case AUXO4_ADDER:
+ case SPKR1_ADDER:
+ case SPKR2_ADDER:
+ case LINE1_ADDER:
+ case LINE2_ADDER: {
+ int i;
+ enum enum_widget widgets[] = {
+ widget_dac1, widget_dac2, widget_dac3,
+ widget_apga1, widget_apga2
+ };
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+ for (i = 0; diff; i++) {
+ if (!(diff & 1 << i))
+ continue;
+ diff ^= 1 << i;
+ update_widgets_link(value >> i & 1, widgets[i],
+ adder_sink_translate(reg),
+ reg, 1 << i, value);
+ }
+ break;
+ }
+ case AB5500_VIRTUAL_REG3:
+ oldval = read_reg(reg);
+ diff = value ^ oldval;
+ /*
+ * The following changes won't take place in the same call,
+ * since they are arranged into different mixer controls.
+ */
+
+ /* changed between the two amplifier modes */
+ if (hweight8(diff & SPKR1_MODE_MASK) == 2) {
+ set_reg(reg, value);
+ break;
+ }
+
+ if (diff & SPKR1_MODE_MASK) {
+ update_widgets_link(
+ UNLINK,
+ (oldval & SPKR1_MODE_MASK) == 0 ?
+ widget_pwm_spkr1 : widget_spkr1_adder,
+ widget_spkr1,
+ reg, SPKR1_MODE_MASK, value);
+ update_widgets_link(
+ LINK,
+ (value & SPKR1_MODE_MASK) == 0 ?
+ widget_pwm_spkr1 : widget_spkr1_adder,
+ widget_spkr1,
+ DUMMY_REG, 0, 0);
+
+ }
+ if (diff & SPKR2_MODE_MASK) {
+ update_widgets_link(
+ UNLINK,
+ (oldval & SPKR2_MODE_MASK) == 0 ?
+ widget_pwm_spkr2 : widget_spkr2_adder,
+ widget_spkr2,
+ reg, SPKR2_MODE_MASK, value);
+ update_widgets_link(
+ LINK,
+ (value & SPKR2_MODE_MASK) == 0 ?
+ widget_pwm_spkr2 : widget_spkr2_adder,
+ widget_spkr2,
+ DUMMY_REG, 0, 0);
+
+ }
+ break;
+
+ case AB5500_VIRTUAL_REG4:
+ /* configure PWMCTRL_SPKR1, PWMCTRL_SPKR2, etc. */
+ break;
+ default:
+ set_reg(reg, value);
+ }
+ return 0;
+}
+
+static unsigned int ab5500_codec_read_reg(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ return read_reg(reg);
+}
+
+
+static struct snd_soc_codec_driver ab5500_codec_drv = {
+ .probe = ab5500_codec_probe,
+ .remove = ab5500_codec_remove,
+ .suspend = ab5500_codec_suspend,
+ .resume = ab5500_codec_resume,
+ .read = ab5500_codec_read_reg,
+ .write = ab5500_codec_write_reg,
+}; EXPORT_SYMBOL_GPL(ab5500_codec_drv);
+
+static inline void init_playback_route(void)
+{
+ /* if0_dld_l -> rx1 -> dac1 -> auxo1 */
+ update_widgets_link(LINK, widget_if0_dld_l, widget_rx1,
+ RX1, RXx_DATA_MASK, 0x03 << RXx_DATA_SHIFT);
+ update_widgets_link(LINK, widget_rx1, widget_dac1, 0, 0, 0);
+ update_widgets_link(LINK, widget_dac1, widget_auxo1,
+ AUXO1_ADDER, DAC1_TO_X_MASK, 0xff);
+
+ /* if0_dld_r -> rx2 -> dac2 -> auxo2 */
+ update_widgets_link(LINK, widget_if0_dld_r, widget_rx2,
+ RX2, RXx_DATA_MASK, 0x04 << RXx_DATA_SHIFT);
+ update_widgets_link(LINK, widget_rx2, widget_dac2, 0, 0, 0);
+ update_widgets_link(LINK, widget_dac2, widget_auxo2,
+ AUXO2_ADDER, DAC2_TO_X_MASK, 0xff);
+
+}
+
+static inline void init_capture_route(void)
+{
+ /* mic bias - > mic2 inputs */
+ update_widgets_link(LINK, widget_micbias1, widget_mic2p2,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_micbias1, widget_mic2n2,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_micbias2, widget_mic2p2,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_micbias2, widget_mic2n2,
+ 0, 0, 0);
+
+
+ /* mic2 inputs -> mic2 */
+ update_widgets_link(LINK, widget_mic2p2, widget_mic2,
+ MIC2_INPUT_SELECT, MICxP2_SEL_MASK, 0xff);
+ update_widgets_link(LINK, widget_mic2n2, widget_mic2,
+ MIC2_INPUT_SELECT, MICxN2_SEL_MASK, 0xff);
+
+ /* mic2 -> adc2 -> tx2 */
+ update_widgets_link(LINK, widget_mic2, widget_adc2,
+ 0, 0, 0);
+ update_widgets_link(LINK, widget_adc2, widget_tx2,
+ TX2, TXx_MUX_MASK, 0);
+
+ /* tx2 -> if0_uld_l & if0_uld_r */
+ update_widgets_link(LINK, widget_tx2, widget_if0_uld_l,
+ INTERFACE0_ULD, I2Sx_ULD_L_MASK,
+ 0x02 << I2Sx_ULD_L_SHIFT);
+ update_widgets_link(LINK, widget_tx2, widget_if0_uld_r,
+ INTERFACE0_ULD, I2Sx_ULD_R_MASK,
+ 0x02 << I2Sx_ULD_R_SHIFT);
+
+ /* mic2 -> apga2 */
+ update_widgets_link(LINK, widget_mic2, widget_apga2,
+ ANALOG_LOOP_PGA2, APGAx_MUX_MIC2_MASK,
+ 1 << APGAx_MUX_MIC2_SHIFT);
+
+ /* apga2 -> auxo1 & auxo2 */
+ update_widgets_link(LINK, widget_apga2, widget_auxo1,
+ AUXO1_ADDER, APGA2_TO_X_MASK,
+ 1 << APGA2_TO_X_SHIFT);
+ update_widgets_link(LINK, widget_apga2, widget_auxo2,
+ AUXO2_ADDER, APGA2_TO_X_MASK,
+ 1 << APGA2_TO_X_SHIFT);
+}
+
+static inline void init_playback_gain(void)
+{
+ /* 0x43, 0x0C: pure gain values */
+ mask_set_reg(RX1_DPGA, RXx_DPGA_MASK,
+ 0x43 << RXx_DPGA_SHIFT);
+ mask_set_reg(RX2_DPGA, RXx_DPGA_MASK,
+ 0x43 << RXx_DPGA_SHIFT);
+ mask_set_reg(AUXO1, AUXOx_GAIN_MASK, 0x0C << AUXOx_GAIN_SHIFT);
+ mask_set_reg(AUXO2, AUXOx_GAIN_MASK, 0x0C << AUXOx_GAIN_SHIFT);
+}
+
+static inline void init_capture_gain(void)
+{
+ /* 0x06, 0x0f: pure gain values */
+ mask_set_reg(MIC1_GAIN, MICx_GAIN_MASK, 0x06 << MICx_GAIN_SHIFT);
+ mask_set_reg(TX_DPGA1, TX_DPGAx_MASK, 0x0f << TX_DPGAx_SHIFT);
+}
+
+static int __devinit ab5500_platform_probe(struct platform_device *pdev)
+{
+ int ret;
+ u8 reg;
+ struct ab5500_codec_dai_data *codec_drvdata;
+
+ ab5500_dev = &pdev->dev;
+ codec_drvdata = kzalloc(sizeof(struct ab5500_codec_dai_data),
+ GFP_KERNEL);
+ if (codec_drvdata == NULL)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, codec_drvdata);
+ ret = snd_soc_register_codec(ab5500_dev, &ab5500_codec_drv,
+ ab5500_dai_drv,
+ ARRAY_SIZE(ab5500_dai_drv));
+ if (ret < 0) {
+ dev_err(ab5500_dev, "%s: Failed to register codec. "
+ "Error %d.\n", __func__, ret);
+ snd_soc_unregister_codec(ab5500_dev);
+ kfree(codec_drvdata);
+ }
+ /* Initialize the codec registers */
+ for (reg = AB5500_FIRST_REG; reg <= AB5500_LAST_REG; reg++)
+ set_reg(reg, 0);
+
+ mask_set_reg(CLOCK, CLOCK_REF_SELECT_MASK | CLOCK_ENABLE_MASK,
+ 1 << CLOCK_REF_SELECT_SHIFT | 1 << CLOCK_ENABLE_SHIFT);
+ init_playback_route();
+ init_playback_gain();
+ init_capture_route();
+ init_capture_gain();
+ memset(&pm_stack, 0, sizeof(pm_stack));
+ return ret;
+}
+
+static int __devexit ab5500_platform_remove(struct platform_device *pdev)
+{
+ mask_set_reg(CLOCK, CLOCK_ENABLE_MASK, 0);
+ snd_soc_unregister_codec(ab5500_dev);
+ kfree(platform_get_drvdata(pdev));
+ ab5500_dev = NULL;
+ return 0;
+}
+
+static int ab5500_platform_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return 0;
+}
+
+static int ab5500_platform_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver ab5500_platform_driver = {
+ .driver = {
+ .name = "ab5500-codec",
+ .owner = THIS_MODULE,
+ },
+ .probe = ab5500_platform_probe,
+ .remove = ab5500_platform_remove,
+ .suspend = ab5500_platform_suspend,
+ .resume = ab5500_platform_resume,
+};
+
+static int __devinit ab5500_init(void)
+{
+ int ret;
+
+ /* Register codec platform driver. */
+ ret = platform_driver_register(&ab5500_platform_driver);
+ if (ret) {
+ pr_err("%s: Error %d: Failed to register codec platform "
+ "driver.\n", __func__, ret);
+ }
+ return ret;
+}
+
+static void __devexit ab5500_exit(void)
+{
+ platform_driver_unregister(&ab5500_platform_driver);
+}
+
+module_init(ab5500_init);
+module_exit(ab5500_exit);
+
+MODULE_DESCRIPTION("AB5500 Codec driver");
+MODULE_AUTHOR("Xie Xiaolei <xie.xiaolei@stericsson.com>");
+MODULE_LICENSE("GPL");